From ea2ee216f68af5e8b3d7618f3c6c1e3795379d18 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Wed, 19 Jun 2019 15:04:13 -0400 Subject: [PATCH] Final Embeddable API V2 PR --- .../containers/embeddable_child_panel.tsx | 4 +- .../build_eui_context_menu_panels.ts | 10 +- .../public/get_actions_for_trigger.ts | 3 +- .../triggers/execute_trigger_actions.ts | 1 + .../kibana/public/dashboard/_hacks.scss | 2 +- .../kibana/public/dashboard/_index.scss | 3 - .../dashboard/actions/embeddables.test.ts | 60 ---- .../public/dashboard/actions/embeddables.ts | 128 ------- .../kibana/public/dashboard/actions/index.ts | 23 -- .../public/dashboard/actions/metadata.ts | 43 --- .../kibana/public/dashboard/actions/panels.ts | 74 ---- .../kibana/public/dashboard/actions/view.ts | 114 ------ .../public/dashboard/dashboard_app.html | 5 +- .../kibana/public/dashboard/dashboard_app.tsx | 27 +- .../dashboard/dashboard_app_controller.tsx | 274 +++++++++----- .../public/dashboard/dashboard_constants.ts | 4 - .../public/dashboard/dashboard_state.test.ts | 72 +--- .../dashboard/dashboard_state_manager.ts | 333 +++++------------- .../public/dashboard/dashboard_strings.ts | 10 +- .../public/dashboard/dashboard_view_mode.ts | 23 -- .../__snapshots__/dashboard_grid.test.js.snap | 77 ---- .../dashboard/grid/_dashboard_grid.scss | 127 ------- .../kibana/public/dashboard/grid/_index.scss | 1 - .../dashboard/grid/dashboard_grid.test.js | 93 ----- .../public/dashboard/grid/dashboard_grid.tsx | 303 ---------------- .../grid/dashboard_grid_container.test.js | 144 -------- .../grid/dashboard_grid_container.ts | 54 --- ...embeddable_saved_object_converters.test.ts | 150 ++++++++ .../lib/embeddable_saved_object_converters.ts | 71 ++++ .../lib/get_app_state_defaults.test.ts | 60 ++++ .../dashboard/lib/get_app_state_defaults.ts | 106 ++++-- .../dashboard/lib/migrate_app_state.test.ts | 56 +++ .../public/dashboard/lib/migrate_app_state.ts | 33 +- .../public/dashboard/np_core.test.mocks.ts | 65 ++++ .../dashboard_panel.test.tsx.snap | 52 --- .../dashboard/panel/__tests__/panel_state.ts | 81 ----- .../dashboard/panel/_dashboard_panel.scss | 190 ---------- .../kibana/public/dashboard/panel/_index.scss | 2 - .../dashboard/panel/dashboard_panel.test.tsx | 93 ----- .../dashboard/panel/dashboard_panel.tsx | 199 ----------- .../panel/dashboard_panel_container.test.tsx | 84 ----- .../panel/dashboard_panel_container.ts | 125 ------- .../kibana/public/dashboard/panel/index.ts | 3 +- .../public/dashboard/panel/panel_error.tsx | 37 -- .../_panel_options_menu_form.scss | 3 - .../dashboard/panel/panel_header/index.ts | 20 -- .../get_customize_panel_action.tsx | 71 ---- .../panel_actions/get_edit_panel_action.tsx | 65 ---- .../get_inspector_panel_action.tsx | 87 ----- .../panel_actions/get_remove_panel_action.tsx | 50 --- .../get_toggle_expand_panel_action.tsx | 59 ---- .../panel/panel_header/panel_actions/index.ts | 24 -- .../panel/panel_header/panel_header.tsx | 86 ----- .../panel_header_container.test.tsx | 102 ------ .../panel_header/panel_header_container.ts | 69 ---- .../panel/panel_header/panel_options_menu.tsx | 83 ----- .../panel_options_menu_container.ts | 221 ------------ .../panel_header/panel_options_menu_form.tsx | 86 ----- .../dashboard/panel/panel_state.test.ts | 66 ---- .../public/dashboard/panel/panel_state.ts | 123 ------- .../dashboard/panel/panel_utils.test.js | 55 ++- .../public/dashboard/panel/panel_utils.ts | 184 +++++----- .../dashboard/reducers/embeddables.test.ts | 53 --- .../public/dashboard/reducers/embeddables.ts | 124 ------- .../kibana/public/dashboard/reducers/index.ts | 34 -- .../public/dashboard/reducers/metadata.ts | 57 --- .../public/dashboard/reducers/panels.test.ts | 91 ----- .../public/dashboard/reducers/panels.ts | 84 ----- .../public/dashboard/reducers/view.test.ts | 67 ---- .../kibana/public/dashboard/reducers/view.ts | 132 ------- .../public/dashboard/selectors/dashboard.ts | 151 -------- .../public/dashboard/selectors/types.ts | 70 ---- .../dashboard/store/panel_actions_store.ts | 38 -- .../__snapshots__/add_panel.test.js.snap | 62 ---- .../public/dashboard/top_nav/add_panel.tsx | 129 ------- .../dashboard/top_nav/get_top_nav_config.ts | 16 +- .../dashboard/top_nav/show_add_panel.tsx | 62 ---- .../kibana/public/dashboard/types.ts | 28 +- .../viewport/_dashboard_viewport.scss | 8 - .../public/dashboard/viewport/_index.scss | 1 - .../dashboard/viewport/dashboard_viewport.tsx | 59 ---- .../viewport/dashboard_viewport_container.js | 51 --- .../viewport/dashboard_viewport_provider.js | 36 -- .../public/discover/embeddable/_index.scss | 11 + .../embeddable}/index.ts | 5 +- .../discover/embeddable/search_embeddable.ts | 198 ++++++----- .../embeddable/search_embeddable_factory.ts | 111 ++++-- .../search_embeddable_factory_provider.ts | 36 -- .../embeddable/types.ts} | 63 ++-- .../core_plugins/kibana/public/reducers.ts | 30 -- .../public/selectors/dashboard_selectors.ts | 74 ---- .../kibana/public/selectors/types.ts | 38 -- .../core_plugins/kibana/public/store.ts | 49 --- .../visualize/embeddable/_embeddables.scss | 48 +++ .../public/visualize/embeddable/_index.scss | 1 + .../embeddable/constants.ts} | 4 +- .../embeddable/disabled_lab_embeddable.tsx | 22 +- .../grid => visualize/embeddable}/index.ts | 6 +- .../embeddable/visualize_embeddable.ts | 181 +++++----- .../visualize_embeddable_factory.ts | 118 ------- .../visualize_embeddable_factory.tsx | 189 ++++++++++ .../visualize_embeddable_factory_provider.ts | 41 --- .../build_eui_context_menu_panels.ts | 164 --------- .../context_menu_action.ts | 172 --------- .../context_menu_actions_registry.ts | 26 -- .../context_menu_panel.ts | 51 --- .../embeddable/context_menu_actions/index.ts | 24 -- .../embeddable/context_menu_actions/types.ts | 36 -- src/legacy/ui/public/embeddable/embeddable.ts | 85 ----- .../embeddable_factories_registry.ts | 29 -- .../public/embeddable/embeddable_factory.ts | 66 ---- src/legacy/ui/public/embeddable/index.ts | 24 -- src/legacy/ui/public/embeddable/types.ts | 73 ---- .../ui/public/saved_objects/saved_object.d.ts | 1 + .../loader/embedded_visualize_handler.test.ts | 4 + .../ui/public/visualize/loader/types.ts | 4 +- .../ui/ui_exports/ui_export_defaults.js | 4 +- .../kbn_tp_sample_panel_action/index.ts | 2 +- .../public/sample_panel_action.tsx | 39 +- .../public/sample_panel_link.ts | 36 +- x-pack/plugins/maps/index.js | 2 +- .../maps/public/embeddable/map_embeddable.js | 128 ++++--- .../embeddable/map_embeddable_factory.js | 67 +++- .../map_embeddable_factory_provider.js | 15 - x-pack/plugins/reporting/index.js | 4 +- .../panel_actions/get_csv_panel_action.tsx | 87 +++-- .../translations/translations/ja-JP.json | 19 - .../translations/translations/zh-CN.json | 15 - 128 files changed, 1731 insertions(+), 6897 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/actions/embeddables.test.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/actions/embeddables.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/actions/index.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/actions/metadata.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/actions/panels.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/actions/view.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/dashboard_view_mode.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/grid/__snapshots__/dashboard_grid.test.js.snap delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/grid/_dashboard_grid.scss delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/grid/_index.scss delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/grid/dashboard_grid.test.js delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/grid/dashboard_grid.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/grid/dashboard_grid_container.test.js delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/grid/dashboard_grid_container.ts create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.test.ts create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.ts create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/lib/get_app_state_defaults.test.ts create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.test.ts create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/np_core.test.mocks.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/__snapshots__/dashboard_panel.test.tsx.snap delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/__tests__/panel_state.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/_dashboard_panel.scss delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/_index.scss delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/dashboard_panel.test.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/dashboard_panel.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.test.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/panel_error.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/_panel_options_menu_form.scss delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/index.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_customize_panel_action.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_edit_panel_action.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_inspector_panel_action.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_remove_panel_action.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_toggle_expand_panel_action.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/index.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.test.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_form.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/panel_state.test.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/panel/panel_state.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/reducers/embeddables.test.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/reducers/embeddables.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/reducers/index.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/reducers/metadata.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/reducers/panels.test.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/reducers/panels.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/reducers/view.test.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/reducers/view.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/selectors/dashboard.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/selectors/types.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/store/panel_actions_store.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/add_panel.test.js.snap delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/top_nav/add_panel.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_add_panel.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/viewport/_dashboard_viewport.scss delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/viewport/_index.scss delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport_container.js delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport_provider.js create mode 100644 src/legacy/core_plugins/kibana/public/discover/embeddable/_index.scss rename src/legacy/core_plugins/kibana/public/{selectors => discover/embeddable}/index.ts (88%) delete mode 100644 src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory_provider.ts rename src/legacy/core_plugins/kibana/public/{dashboard/top_nav/add_panel.test.js => discover/embeddable/types.ts} (50%) delete mode 100644 src/legacy/core_plugins/kibana/public/reducers.ts delete mode 100644 src/legacy/core_plugins/kibana/public/selectors/dashboard_selectors.ts delete mode 100644 src/legacy/core_plugins/kibana/public/selectors/types.ts delete mode 100644 src/legacy/core_plugins/kibana/public/store.ts create mode 100644 src/legacy/core_plugins/kibana/public/visualize/embeddable/_embeddables.scss rename src/legacy/core_plugins/kibana/public/{dashboard/selectors/index.ts => visualize/embeddable/constants.ts} (93%) rename src/legacy/core_plugins/kibana/public/{dashboard/grid => visualize/embeddable}/index.ts (74%) delete mode 100644 src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.ts create mode 100644 src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory_provider.ts delete mode 100644 src/legacy/ui/public/embeddable/context_menu_actions/build_eui_context_menu_panels.ts delete mode 100644 src/legacy/ui/public/embeddable/context_menu_actions/context_menu_action.ts delete mode 100644 src/legacy/ui/public/embeddable/context_menu_actions/context_menu_actions_registry.ts delete mode 100644 src/legacy/ui/public/embeddable/context_menu_actions/context_menu_panel.ts delete mode 100644 src/legacy/ui/public/embeddable/context_menu_actions/index.ts delete mode 100644 src/legacy/ui/public/embeddable/context_menu_actions/types.ts delete mode 100644 src/legacy/ui/public/embeddable/embeddable.ts delete mode 100644 src/legacy/ui/public/embeddable/embeddable_factories_registry.ts delete mode 100644 src/legacy/ui/public/embeddable/embeddable_factory.ts delete mode 100644 src/legacy/ui/public/embeddable/index.ts delete mode 100644 src/legacy/ui/public/embeddable/types.ts delete mode 100644 x-pack/plugins/maps/public/embeddable/map_embeddable_factory_provider.js diff --git a/src/legacy/core_plugins/embeddable_api/public/containers/embeddable_child_panel.tsx b/src/legacy/core_plugins/embeddable_api/public/containers/embeddable_child_panel.tsx index 0a5366e4b0a351b..e6f69fb591c81cd 100644 --- a/src/legacy/core_plugins/embeddable_api/public/containers/embeddable_child_panel.tsx +++ b/src/legacy/core_plugins/embeddable_api/public/containers/embeddable_child_panel.tsx @@ -24,9 +24,9 @@ import React from 'react'; import { EuiLoadingChart } from '@elastic/eui'; import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import { ErrorEmbeddable, IEmbeddable } from 'plugins/embeddable_api'; - import { Subscription } from 'rxjs'; +import { ErrorEmbeddable, IEmbeddable } from '../embeddables'; + import { EmbeddablePanel } from '../panel'; import { IContainer } from './i_container'; diff --git a/src/legacy/core_plugins/embeddable_api/public/context_menu_actions/build_eui_context_menu_panels.ts b/src/legacy/core_plugins/embeddable_api/public/context_menu_actions/build_eui_context_menu_panels.ts index a22d22615cf95ac..283ca4cf13002b4 100644 --- a/src/legacy/core_plugins/embeddable_api/public/context_menu_actions/build_eui_context_menu_panels.ts +++ b/src/legacy/core_plugins/embeddable_api/public/context_menu_actions/build_eui_context_menu_panels.ts @@ -104,12 +104,10 @@ function convertPanelActionToContextMenuItem({ 'data-test-subj': `embeddablePanelAction-${action.id}`, }; - if (action.getHref(actionContext) === undefined) { - menuPanelItem.onClick = () => { - action.execute(actionContext); - closeMenu(); - }; - } + menuPanelItem.onClick = () => { + action.execute(actionContext); + closeMenu(); + }; if (action.getHref(actionContext)) { menuPanelItem.href = action.getHref(actionContext); diff --git a/src/legacy/core_plugins/embeddable_api/public/get_actions_for_trigger.ts b/src/legacy/core_plugins/embeddable_api/public/get_actions_for_trigger.ts index 6574e10d6002c6a..e454a282b4e1c7c 100644 --- a/src/legacy/core_plugins/embeddable_api/public/get_actions_for_trigger.ts +++ b/src/legacy/core_plugins/embeddable_api/public/get_actions_for_trigger.ts @@ -19,14 +19,13 @@ import { Action } from './actions'; import { IEmbeddable } from './embeddables'; -import { IContainer } from './containers'; import { IRegistry, Trigger } from './types'; export async function getActionsForTrigger( actionRegistry: IRegistry, triggerRegistry: IRegistry, triggerId: string, - context: { embeddable: IEmbeddable; container?: IContainer } + context: { embeddable: IEmbeddable; triggerContext?: { [key: string]: unknown } } ) { const trigger = triggerRegistry.get(triggerId); diff --git a/src/legacy/core_plugins/embeddable_api/public/triggers/execute_trigger_actions.ts b/src/legacy/core_plugins/embeddable_api/public/triggers/execute_trigger_actions.ts index f7dbe143645c900..78e87d577dd90e1 100644 --- a/src/legacy/core_plugins/embeddable_api/public/triggers/execute_trigger_actions.ts +++ b/src/legacy/core_plugins/embeddable_api/public/triggers/execute_trigger_actions.ts @@ -34,6 +34,7 @@ export async function executeTriggerActions( ) { const actions = await getActionsForTrigger(actionRegistry, triggerRegistry, triggerId, { embeddable, + triggerContext, }); if (actions.length > 1) { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/_hacks.scss b/src/legacy/core_plugins/kibana/public/dashboard/_hacks.scss index 474699d9cf25b8a..ec6272692cd2f4b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/_hacks.scss +++ b/src/legacy/core_plugins/kibana/public/dashboard/_hacks.scss @@ -3,7 +3,7 @@ /** * Needs to correspond with the react root nested inside angular. */ - dashboard-viewport-provider { + #dashboardViewport { flex: 1; display: flex; [data-reactroot] { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/_index.scss b/src/legacy/core_plugins/kibana/public/dashboard/_index.scss index a5037a64f067eca..35d4127365f025d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/_index.scss +++ b/src/legacy/core_plugins/kibana/public/dashboard/_index.scss @@ -16,6 +16,3 @@ // dshChart__legend-isLoading @import './dashboard_app'; -@import './grid/index'; -@import './panel/index'; -@import './viewport/index'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/actions/embeddables.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/actions/embeddables.test.ts deleted file mode 100644 index 1a2f0bbbcd2918c..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/actions/embeddables.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { store } from '../../store'; -import { - clearStagedFilters, - embeddableIsInitialized, - embeddableIsInitializing, - setStagedFilter, -} from '../actions'; - -import { getStagedFilters } from '../../selectors'; - -beforeAll(() => { - store.dispatch(embeddableIsInitializing('foo1')); - store.dispatch(embeddableIsInitializing('foo2')); - store.dispatch(embeddableIsInitialized({ panelId: 'foo1', metadata: {} })); - store.dispatch(embeddableIsInitialized({ panelId: 'foo2', metadata: {} })); -}); - -describe('staged filters', () => { - test('getStagedFilters initially is empty', () => { - const stagedFilters = getStagedFilters(store.getState()); - expect(stagedFilters.length).toBe(0); - }); - - test('can set a staged filter', () => { - store.dispatch(setStagedFilter({ stagedFilter: ['imafilter'], panelId: 'foo1' })); - const stagedFilters = getStagedFilters(store.getState()); - expect(stagedFilters.length).toBe(1); - }); - - test('getStagedFilters returns filters for all embeddables', () => { - store.dispatch(setStagedFilter({ stagedFilter: ['imafilter'], panelId: 'foo2' })); - const stagedFilters = getStagedFilters(store.getState()); - expect(stagedFilters.length).toBe(2); - }); - - test('clearStagedFilters clears all filters', () => { - store.dispatch(clearStagedFilters()); - const stagedFilters = getStagedFilters(store.getState()); - expect(stagedFilters.length).toBe(0); - }); -}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/actions/embeddables.ts b/src/legacy/core_plugins/kibana/public/dashboard/actions/embeddables.ts deleted file mode 100644 index f1ec47987750664..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/actions/embeddables.ts +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* eslint-disable @typescript-eslint/no-empty-interface */ - -import _ from 'lodash'; -import { createAction } from 'redux-actions'; -import { EmbeddableMetadata, EmbeddableState } from 'ui/embeddable'; -import { getEmbeddableCustomization, getPanel } from '../../selectors'; -import { PanelId } from '../selectors'; -import { updatePanel } from './panels'; -import { SavedDashboardPanel } from '../types'; - -import { KibanaAction, KibanaThunk } from '../../selectors/types'; - -export enum EmbeddableActionTypeKeys { - EMBEDDABLE_IS_INITIALIZING = 'EMBEDDABLE_IS_INITIALIZING', - EMBEDDABLE_IS_INITIALIZED = 'EMBEDDABLE_IS_INITIALIZED', - SET_STAGED_FILTER = 'SET_STAGED_FILTER', - CLEAR_STAGED_FILTERS = 'CLEAR_STAGED_FILTERS', - EMBEDDABLE_ERROR = 'EMBEDDABLE_ERROR', - REQUEST_RELOAD = 'REQUEST_RELOAD', -} - -export interface EmbeddableIsInitializingAction - extends KibanaAction {} - -export interface EmbeddableIsInitializedActionPayload { - panelId: PanelId; - metadata: EmbeddableMetadata; -} - -export interface EmbeddableIsInitializedAction - extends KibanaAction< - EmbeddableActionTypeKeys.EMBEDDABLE_IS_INITIALIZED, - EmbeddableIsInitializedActionPayload - > {} - -export interface SetStagedFilterActionPayload { - panelId: PanelId; - stagedFilter: object; -} - -export interface SetStagedFilterAction - extends KibanaAction {} - -export interface ClearStagedFiltersAction - extends KibanaAction {} -export interface RequestReload - extends KibanaAction {} - -export interface EmbeddableErrorActionPayload { - error: string | object; - panelId: PanelId; -} - -export interface EmbeddableErrorAction - extends KibanaAction {} - -export type EmbeddableActions = - | EmbeddableIsInitializingAction - | EmbeddableIsInitializedAction - | ClearStagedFiltersAction - | SetStagedFilterAction - | EmbeddableErrorAction; - -export const embeddableIsInitializing = createAction( - EmbeddableActionTypeKeys.EMBEDDABLE_IS_INITIALIZING -); -export const embeddableIsInitialized = createAction( - EmbeddableActionTypeKeys.EMBEDDABLE_IS_INITIALIZED -); -export const setStagedFilter = createAction( - EmbeddableActionTypeKeys.SET_STAGED_FILTER -); -export const clearStagedFilters = createAction(EmbeddableActionTypeKeys.CLEAR_STAGED_FILTERS); -export const embeddableError = createAction( - EmbeddableActionTypeKeys.EMBEDDABLE_ERROR -); - -export const requestReload = createAction(EmbeddableActionTypeKeys.REQUEST_RELOAD); - -/** - * The main point of communication from the embeddable to the dashboard. Any time state in the embeddable - * changes, this function will be called. The data is then extracted from EmbeddableState and stored in - * redux so the appropriate actions are taken and UI updated. - * - * @param changeData.panelId - the id of the panel whose state has changed. - * @param changeData.embeddableState - the new state of the embeddable. - */ -export function embeddableStateChanged(changeData: { - panelId: PanelId; - embeddableState: EmbeddableState; -}): KibanaThunk { - const { panelId, embeddableState } = changeData; - return (dispatch, getState) => { - // Translate embeddableState to things redux cares about. - const customization = getEmbeddableCustomization(getState(), panelId); - if (!_.isEqual(embeddableState.customization, customization)) { - const originalPanelState = getPanel(getState(), panelId); - const newPanelState: SavedDashboardPanel = { - ...originalPanelState, - embeddableConfig: _.cloneDeep(embeddableState.customization), - }; - dispatch(updatePanel(newPanelState)); - } - - if (embeddableState.stagedFilter) { - dispatch(setStagedFilter({ stagedFilter: embeddableState.stagedFilter, panelId })); - } - }; -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/actions/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/actions/index.ts deleted file mode 100644 index 7a5a8e209684f4f..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/actions/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export * from './view'; -export * from './panels'; -export * from './embeddables'; -export * from './metadata'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/actions/metadata.ts b/src/legacy/core_plugins/kibana/public/dashboard/actions/metadata.ts deleted file mode 100644 index eea16f02c9a4f6b..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/actions/metadata.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* eslint-disable @typescript-eslint/no-empty-interface */ - -import { createAction } from 'redux-actions'; -import { KibanaAction } from '../../selectors/types'; - -export enum MetadataActionTypeKeys { - UPDATE_DESCRIPTION = 'UPDATE_DESCRIPTION', - UPDATE_TITLE = 'UPDATE_TITLE', -} - -export type UpdateTitleActionPayload = string; - -export interface UpdateTitleAction - extends KibanaAction {} - -export type UpdateDescriptionActionPayload = string; - -export interface UpdateDescriptionAction - extends KibanaAction {} - -export type MetadataActions = UpdateDescriptionAction | UpdateTitleAction; - -export const updateDescription = createAction(MetadataActionTypeKeys.UPDATE_DESCRIPTION); -export const updateTitle = createAction(MetadataActionTypeKeys.UPDATE_TITLE); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/actions/panels.ts b/src/legacy/core_plugins/kibana/public/dashboard/actions/panels.ts deleted file mode 100644 index c4c41e53a545c20..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/actions/panels.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* eslint-disable @typescript-eslint/no-empty-interface */ - -import { createAction } from 'redux-actions'; -import { KibanaAction } from '../../selectors/types'; -import { PanelId } from '../selectors'; -import { SavedDashboardPanel, SavedDashboardPanelMap } from '../types'; - -export enum PanelActionTypeKeys { - DELETE_PANEL = 'DELETE_PANEL', - UPDATE_PANEL = 'UPDATE_PANEL', - RESET_PANEL_TITLE = 'RESET_PANEL_TITLE', - SET_PANEL_TITLE = 'SET_PANEL_TITLE', - UPDATE_PANELS = 'UPDATE_PANELS', - SET_PANELS = 'SET_PANELS', -} - -export interface DeletePanelAction - extends KibanaAction {} - -export interface UpdatePanelAction - extends KibanaAction {} - -export interface UpdatePanelsAction - extends KibanaAction {} - -export interface ResetPanelTitleAction - extends KibanaAction {} - -export interface SetPanelTitleActionPayload { - panelId: PanelId; - title?: string; -} - -export interface SetPanelTitleAction - extends KibanaAction {} - -export interface SetPanelsAction - extends KibanaAction {} - -export type PanelActions = - | DeletePanelAction - | UpdatePanelAction - | ResetPanelTitleAction - | UpdatePanelsAction - | SetPanelTitleAction - | SetPanelsAction; - -export const deletePanel = createAction(PanelActionTypeKeys.DELETE_PANEL); -export const updatePanel = createAction(PanelActionTypeKeys.UPDATE_PANEL); -export const resetPanelTitle = createAction(PanelActionTypeKeys.RESET_PANEL_TITLE); -export const setPanelTitle = createAction( - PanelActionTypeKeys.SET_PANEL_TITLE -); -export const updatePanels = createAction(PanelActionTypeKeys.UPDATE_PANELS); -export const setPanels = createAction(PanelActionTypeKeys.SET_PANELS); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/actions/view.ts b/src/legacy/core_plugins/kibana/public/dashboard/actions/view.ts deleted file mode 100644 index 205479032b36e50..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/actions/view.ts +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* eslint-disable @typescript-eslint/no-empty-interface */ - -import { createAction } from 'redux-actions'; -import { RefreshInterval } from 'ui/timefilter/timefilter'; -import { TimeRange } from 'ui/timefilter/time_history'; -import { Filter } from '@kbn/es-query'; -import { Query } from 'src/legacy/core_plugins/data/public'; -import { KibanaAction } from '../../selectors/types'; -import { DashboardViewMode } from '../dashboard_view_mode'; -import { PanelId } from '../selectors'; - -export enum ViewActionTypeKeys { - UPDATE_VIEW_MODE = 'UPDATE_VIEW_MODE', - SET_VISIBLE_CONTEXT_MENU_PANEL_ID = 'SET_VISIBLE_CONTEXT_MENU_PANEL_ID', - MAXIMIZE_PANEL = 'MAXIMIZE_PANEL', - MINIMIZE_PANEL = 'MINIMIZE_PANEL', - UPDATE_IS_FULL_SCREEN_MODE = 'UPDATE_IS_FULL_SCREEN_MODE', - UPDATE_USE_MARGINS = 'UPDATE_USE_MARGINS', - UPDATE_HIDE_PANEL_TITLES = 'UPDATE_HIDE_PANEL_TITLES', - UPDATE_TIME_RANGE = 'UPDATE_TIME_RANGE', - UPDATE_REFRESH_CONFIG = 'UPDATE_REFRESH_CONFIG', - UPDATE_FILTERS = 'UPDATE_FILTERS', - UPDATE_QUERY = 'UPDATE_QUERY', - CLOSE_CONTEXT_MENU = 'CLOSE_CONTEXT_MENU', -} - -export interface UpdateViewModeAction - extends KibanaAction {} - -export interface SetVisibleContextMenuPanelIdAction - extends KibanaAction {} - -export interface CloseContextMenuAction - extends KibanaAction {} - -export interface MaximizePanelAction - extends KibanaAction {} - -export interface MinimizePanelAction - extends KibanaAction {} - -export interface UpdateIsFullScreenModeAction - extends KibanaAction {} - -export interface UpdateUseMarginsAction - extends KibanaAction {} - -export interface UpdateHidePanelTitlesAction - extends KibanaAction {} - -export interface UpdateTimeRangeAction - extends KibanaAction {} - -export interface UpdateRefreshConfigAction - extends KibanaAction {} - -export interface UpdateFiltersAction - extends KibanaAction {} - -export interface UpdateQueryAction extends KibanaAction {} - -export type ViewActions = - | UpdateViewModeAction - | SetVisibleContextMenuPanelIdAction - | CloseContextMenuAction - | MaximizePanelAction - | MinimizePanelAction - | UpdateIsFullScreenModeAction - | UpdateUseMarginsAction - | UpdateHidePanelTitlesAction - | UpdateTimeRangeAction - | UpdateRefreshConfigAction - | UpdateFiltersAction - | UpdateQueryAction; - -export const updateViewMode = createAction(ViewActionTypeKeys.UPDATE_VIEW_MODE); -export const closeContextMenu = createAction(ViewActionTypeKeys.CLOSE_CONTEXT_MENU); -export const setVisibleContextMenuPanelId = createAction( - ViewActionTypeKeys.SET_VISIBLE_CONTEXT_MENU_PANEL_ID -); -export const maximizePanel = createAction(ViewActionTypeKeys.MAXIMIZE_PANEL); -export const minimizePanel = createAction(ViewActionTypeKeys.MINIMIZE_PANEL); -export const updateIsFullScreenMode = createAction( - ViewActionTypeKeys.UPDATE_IS_FULL_SCREEN_MODE -); -export const updateUseMargins = createAction(ViewActionTypeKeys.UPDATE_USE_MARGINS); -export const updateHidePanelTitles = createAction( - ViewActionTypeKeys.UPDATE_HIDE_PANEL_TITLES -); -export const updateTimeRange = createAction(ViewActionTypeKeys.UPDATE_TIME_RANGE); -export const updateRefreshConfig = createAction( - ViewActionTypeKeys.UPDATE_REFRESH_CONFIG -); -export const updateFilters = createAction(ViewActionTypeKeys.UPDATE_FILTERS); -export const updateQuery = createAction(ViewActionTypeKeys.UPDATE_QUERY); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html index d73e15287077c54..1e226ab804bdc04 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html @@ -118,9 +118,6 @@ - - +
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx index 473f2e0be4bb4f3..fbf663b8c37b856 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -22,7 +22,6 @@ import _ from 'lodash'; // @ts-ignore import { uiModules } from 'ui/modules'; import { IInjector } from 'ui/chrome'; -import { wrapInI18nContext } from 'ui/i18n'; // @ts-ignore import { ConfirmationButtonTypes } from 'ui/modals/confirm_modal'; @@ -38,7 +37,6 @@ import * as filterActions from 'plugins/kibana/discover/doc_table/actions/filter // @ts-ignore import { FilterManagerProvider } from 'ui/filter_manager'; -import { EmbeddableFactory } from 'ui/embeddable'; import { AppStateClass as TAppStateClass, @@ -50,16 +48,14 @@ import { Filter } from '@kbn/es-query'; import { TimeRange } from 'ui/timefilter/time_history'; import { IndexPattern } from 'ui/index_patterns'; import { IPrivate } from 'ui/private'; -import { StaticIndexPattern, Query } from 'src/legacy/core_plugins/data/public'; import moment from 'moment'; +import { StaticIndexPattern, Query } from '../../../data/public'; + +import { ViewMode } from '../../../embeddable_api/public'; import { SavedObjectDashboard } from './saved_dashboard/saved_dashboard'; import { DashboardAppState, SavedDashboardPanel, ConfirmModalFn, AddFilterFn } from './types'; -// @ts-ignore -- going away soon -import { DashboardViewportProvider } from './viewport/dashboard_viewport_provider'; - import { DashboardStateManager } from './dashboard_state_manager'; -import { DashboardViewMode } from './dashboard_view_mode'; import { DashboardAppController } from './dashboard_app_controller'; export interface DashboardAppScope extends ng.IScope { @@ -67,7 +63,7 @@ export interface DashboardAppScope extends ng.IScope { appState: TAppState; screenTitle: string; model: { - query: Query | string; + query: Query; filters: Filter[]; timeRestore: boolean; title: string; @@ -81,7 +77,7 @@ export interface DashboardAppScope extends ng.IScope { panels: SavedDashboardPanel[]; indexPatterns: StaticIndexPattern[]; $evalAsync: any; - dashboardViewMode: DashboardViewMode; + dashboardViewMode: ViewMode; expandedPanel?: string; getShouldShowEditHelp: () => boolean; getShouldShowViewHelp: () => boolean; @@ -99,9 +95,6 @@ export interface DashboardAppScope extends ng.IScope { kbnTopNav: any; enterEditMode: () => void; $listen: any; - getEmbeddableFactory: (type: string) => EmbeddableFactory; - getDashboardState: () => DashboardStateManager; - refresh: () => void; } const app = uiModules.get('app/dashboard', [ @@ -112,10 +105,6 @@ const app = uiModules.get('app/dashboard', [ 'kibana/config', ]); -app.directive('dashboardViewportProvider', function(reactDirective: any) { - return reactDirective(wrapInI18nContext(DashboardViewportProvider)); -}); - app.directive('dashboardApp', function($injector: IInjector) { const AppState = $injector.get>('AppState'); const kbnUrl = $injector.get('kbnUrl'); @@ -139,7 +128,6 @@ app.directive('dashboardApp', function($injector: IInjector) { controllerAs: 'dashboardApp', controller: ( $scope: DashboardAppScope, - $rootScope: ng.IRootScopeService, $route: any, $routeParams: { id?: string; @@ -150,11 +138,12 @@ app.directive('dashboardApp', function($injector: IInjector) { dashboardConfig: { getHideWriteControls: () => boolean; }, - localStorage: WindowLocalStorage + localStorage: { + get: (prop: string) => unknown; + } ) => new DashboardAppController({ $route, - $rootScope, $scope, $routeParams, getAppState, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 557c780f6192e5e..c1648f6492b18b0 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -37,9 +37,6 @@ import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; import { showShareContextMenu, ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; -import { EmbeddableFactoriesRegistryProvider } from 'ui/embeddable/embeddable_factories_registry'; -import { ContextMenuActionsRegistryProvider } from 'ui/embeddable'; -import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; import { timefilter } from 'ui/timefilter'; import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing/get_unhashable_states_provider'; @@ -55,17 +52,30 @@ import { IndexPattern } from 'ui/index_patterns'; import { IPrivate } from 'ui/private'; import { Query } from 'src/legacy/core_plugins/data/public'; import { SaveOptions } from 'ui/saved_objects/saved_object'; +import { Subscription } from 'rxjs'; +import { + DashboardContainer, + DASHBOARD_CONTAINER_TYPE, + DashboardContainerFactory, + DashboardContainerInput, + DashboardPanelState, +} from '../../../dashboard_embeddable_container/public'; +import { + isErrorEmbeddable, + embeddableFactories, + ErrorEmbeddable, + ViewMode, + openAddPanelFlyout, +} from '../../../embeddable_api/public'; import { DashboardAppState, - EmbeddableFactoryRegistry, NavAction, ConfirmModalFn, AddFilterFn, + SavedDashboardPanel, } from './types'; -import { showNewVisModal } from '../visualize/wizard'; import { showOptionsPopover } from './top_nav/show_options_popover'; -import { showAddPanel } from './top_nav/show_add_panel'; import { DashboardSaveModal } from './top_nav/save_modal'; import { showCloneModal } from './top_nav/show_clone_modal'; import { saveDashboard } from './lib'; @@ -73,10 +83,10 @@ import { DashboardStateManager } from './dashboard_state_manager'; import { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; import { getTopNavConfig } from './top_nav/get_top_nav_config'; import { TopNavIds } from './top_nav/top_nav_ids'; -import { DashboardViewMode } from './dashboard_view_mode'; import { getDashboardTitle } from './dashboard_strings'; -import { panelActionsStore } from './store/panel_actions_store'; import { DashboardAppScope } from './dashboard_app'; +import { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize/embeddable'; +import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters'; export class DashboardAppController { // Part of the exposed plugin API - do not remove without careful consideration. @@ -86,7 +96,6 @@ export class DashboardAppController { constructor({ $scope, - $rootScope, $route, $routeParams, getAppState, @@ -104,7 +113,6 @@ export class DashboardAppController { courier: { fetch: () => void }; $scope: DashboardAppScope; $route: any; - $rootScope: ng.IRootScopeService; $routeParams: any; getAppState: { previouslyStored: () => TAppState | undefined; @@ -113,7 +121,9 @@ export class DashboardAppController { getDefault: () => Promise; }; dashboardConfig: any; - localStorage: any; + localStorage: { + get: (prop: string) => unknown; + }; Private: IPrivate; kbnUrl: KbnUrl; AppStateClass: TAppStateClass; @@ -122,18 +132,10 @@ export class DashboardAppController { addFilter: AddFilterFn; }) { const queryFilter = Private(FilterBarQueryFilterProvider); - const embeddableFactories = Private( - EmbeddableFactoriesRegistryProvider - ) as EmbeddableFactoryRegistry; - const panelActionsRegistry = Private(ContextMenuActionsRegistryProvider); const getUnhashableStates = Private(getUnhashableStatesProvider); const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); - // @ts-ignore This code is going away shortly. - panelActionsStore.initializeFromRegistry(panelActionsRegistry); - - const visTypes = Private(VisTypesRegistryProvider); - $scope.getEmbeddableFactory = panelType => embeddableFactories.byName[panelType]; + let lastReloadRequestTime = 0; const dash = ($scope.dash = $route.current.locals.dash); if (dash.id) { @@ -147,7 +149,6 @@ export class DashboardAppController { addFilter, }); - $scope.getDashboardState = () => dashboardStateManager; $scope.appState = dashboardStateManager.getAppState(); // The 'previouslyStored' check is so we only update the time filter on dashboard open, not during @@ -156,6 +157,58 @@ export class DashboardAppController { dashboardStateManager.syncTimefilterWithDashboard(timefilter); } + const updateIndexPatterns = (container?: DashboardContainer) => { + if (!container || isErrorEmbeddable(container)) { + return; + } + const panelIndexPatterns = container.getPanelIndexPatterns(); + if (panelIndexPatterns && panelIndexPatterns.length > 0) { + $scope.$evalAsync(() => { + $scope.indexPatterns = panelIndexPatterns; + }); + } else { + indexPatterns.getDefault().then(defaultIndexPattern => { + $scope.$evalAsync(() => { + $scope.indexPatterns = [defaultIndexPattern]; + }); + }); + } + }; + + const getDashboardInput = (): DashboardContainerInput => { + const embeddablesMap: { + [key: string]: DashboardPanelState; + } = {}; + dashboardStateManager.getPanels().forEach((panel: SavedDashboardPanel) => { + embeddablesMap[panel.panelIndex] = convertSavedDashboardPanelToPanelState( + panel, + dashboardStateManager.getUseMargins() + ); + }); + let expandedPanelId; + if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) { + expandedPanelId = dashboardContainer.getInput().expandedPanelId; + } + return { + id: dashboardStateManager.savedDashboard.id || '', + filters: queryFilter.getFilters(), + hidePanelTitles: dashboardStateManager.getHidePanelTitles(), + query: $scope.model.query, + timeRange: { + ...timefilter.getTime(), + }, + refreshConfig: timefilter.getRefreshInterval(), + viewMode: dashboardStateManager.getViewMode(), + panels: embeddablesMap, + isFullScreenMode: dashboardStateManager.getFullScreenMode(), + useMargins: dashboardStateManager.getUseMargins(), + lastReloadRequestTime, + title: dashboardStateManager.getTitle(), + description: dashboardStateManager.getDescription(), + expandedPanelId, + }; + }; + const updateState = () => { // Following the "best practice" of always have a '.' in your ng-models – // https://github.com/angular/angular.js/wiki/Understanding-Scopes @@ -170,18 +223,74 @@ export class DashboardAppController { }; $scope.panels = dashboardStateManager.getPanels(); $scope.screenTitle = dashboardStateManager.getTitle(); + }; - const panelIndexPatterns = dashboardStateManager.getPanelIndexPatterns(); - if (panelIndexPatterns && panelIndexPatterns.length > 0) { - $scope.indexPatterns = panelIndexPatterns; - } else { - indexPatterns.getDefault().then(defaultIndexPattern => { - $scope.$evalAsync(() => { - $scope.indexPatterns = [defaultIndexPattern]; + updateState(); + + let dashboardContainer: DashboardContainer | undefined; + let inputSubscription: Subscription | undefined; + let outputSubscription: Subscription | undefined; + + const dashboardDom = document.getElementById('dashboardViewport'); + const dashboardFactory = embeddableFactories.get( + DASHBOARD_CONTAINER_TYPE + ) as DashboardContainerFactory; + dashboardFactory + .create(getDashboardInput()) + .then((container: DashboardContainer | ErrorEmbeddable) => { + if (!isErrorEmbeddable(container)) { + dashboardContainer = container; + + updateIndexPatterns(dashboardContainer); + + outputSubscription = dashboardContainer.getOutput$().subscribe(() => { + updateIndexPatterns(dashboardContainer); }); - }); - } - }; + + inputSubscription = dashboardContainer.getInput$().subscribe(async () => { + let dirty = false; + // This has to be first because handleDashboardContainerChanges cuases + // appState.save which will cause refreshDashboardContainer to be called. + + // Add filters modifies the object passed to it, hence the clone deep. + if (!_.isEqual(container.getInput().filters, queryFilter.getFilters())) { + await queryFilter.addFilters(_.cloneDeep(container.getInput().filters)); + + dashboardStateManager.applyFilters($scope.model.query, container.getInput().filters); + dirty = true; + } + + $scope.$evalAsync(() => { + dashboardStateManager.handleDashboardContainerChanges(container); + if (dirty) { + updateState(); + } + }); + }); + + dashboardStateManager.registerChangeListener(dirty => { + if (dirty) { + refreshDashboardContainer(); + } + }); + + // This code needs to be replaced with a better mechanism for adding new embeddables of + // any type from the add panel. Likely this will happen via creating a visualization "inline", + // without navigating away from the UX. + if ($routeParams[DashboardConstants.NEW_VISUALIZATION_ID_PARAM]) { + container.addSavedObjectEmbeddable( + VISUALIZE_EMBEDDABLE_TYPE, + $routeParams[DashboardConstants.NEW_VISUALIZATION_ID_PARAM] + ); + kbnUrl.removeParam(DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM); + kbnUrl.removeParam(DashboardConstants.NEW_VISUALIZATION_ID_PARAM); + } + } + + if (dashboardDom) { + container.render(dashboardDom); + } + }); // Part of the exposed plugin API - do not remove without careful consideration. this.appStatus = { @@ -205,15 +314,6 @@ export class DashboardAppController { timefilter.disableTimeRangeSelector(); timefilter.disableAutoRefreshSelector(); - updateState(); - - $scope.refresh = () => { - $rootScope.$broadcast('fetch'); - courier.fetch(); - }; - dashboardStateManager.handleTimeChange(timefilter.getTime()); - dashboardStateManager.handleRefreshConfigChange(timefilter.getRefreshInterval()); - const landingPageUrl = () => `#${DashboardConstants.LANDING_PAGE_PATH}`; const getDashTitle = () => @@ -248,6 +348,32 @@ export class DashboardAppController { dashboardStateManager.getIsViewMode() && !dashboardConfig.getHideWriteControls(); + const getChangesFromAppStateForContainerState = () => { + const appStateDashboardInput = getDashboardInput(); + if (!dashboardContainer || isErrorEmbeddable(dashboardContainer)) { + return appStateDashboardInput; + } + + const containerInput = dashboardContainer.getInput(); + const differences: Partial = {}; + Object.keys(containerInput).forEach(key => { + const containerValue = (containerInput as { [key: string]: unknown })[key]; + const appStateValue = (appStateDashboardInput as { [key: string]: unknown })[key]; + if (!_.isEqual(containerValue, appStateValue)) { + (differences as { [key: string]: unknown })[key] = appStateValue; + } + }); + + return Object.values(differences).length === 0 ? undefined : differences; + }; + + const refreshDashboardContainer = () => { + const changes = getChangesFromAppStateForContainerState(); + if (changes && dashboardContainer) { + dashboardContainer.updateInput(changes); + } + }; + $scope.updateQueryAndFetch = function({ query, dateRange }) { if (dateRange) { timefilter.setTime(dateRange); @@ -258,12 +384,12 @@ export class DashboardAppController { // The user can still request a reload in the query bar, even if the // query is the same, and in that case, we have to explicitly ask for // a reload, since no state changes will cause it. - dashboardStateManager.requestReload(); + lastReloadRequestTime = new Date().getTime(); + refreshDashboardContainer(); } else { $scope.model.query = query; dashboardStateManager.applyFilters($scope.model.query, $scope.model.filters); } - $scope.refresh(); }; $scope.onRefreshChange = function({ isPaused, refreshInterval }) { @@ -301,18 +427,20 @@ export class DashboardAppController { }); $scope.$listenAndDigestAsync(timefilter, 'fetch', () => { - dashboardStateManager.handleTimeChange(timefilter.getTime()); - // Currently discover relies on this logic to re-fetch. We need to refactor it to rely instead on the - // directly passed down time filter. Then we can get rid of this reliance on scope broadcasts. - $scope.refresh(); + // The only reason this is here is so that search embeddables work on a dashboard with + // a refresh interval turned on. This kicks off the search poller. It should be + // refactored so no embeddables need to listen to the timefilter directly but instead + // the container tells it when to reload. + courier.fetch(); }); + $scope.$listenAndDigestAsync(timefilter, 'refreshIntervalUpdate', () => { - dashboardStateManager.handleRefreshConfigChange(timefilter.getRefreshInterval()); updateState(); + refreshDashboardContainer(); }); $scope.$listenAndDigestAsync(timefilter, 'timeUpdate', updateState); - function updateViewMode(newMode: DashboardViewMode) { + function updateViewMode(newMode: ViewMode) { $scope.topNavMenu = getTopNavConfig( newMode, navActions, @@ -321,9 +449,9 @@ export class DashboardAppController { dashboardStateManager.switchViewMode(newMode); } - const onChangeViewMode = (newMode: DashboardViewMode) => { + const onChangeViewMode = (newMode: ViewMode) => { const isPageRefresh = newMode === dashboardStateManager.getViewMode(); - const isLeavingEditMode = !isPageRefresh && newMode === DashboardViewMode.VIEW; + const isLeavingEditMode = !isPageRefresh && newMode === ViewMode.VIEW; const willLoseChanges = isLeavingEditMode && dashboardStateManager.getIsDirty(timefilter); if (!willLoseChanges) { @@ -337,7 +465,7 @@ export class DashboardAppController { dash.id ? createDashboardEditUrl(dash.id) : DashboardConstants.CREATE_NEW_DASHBOARD_URL ); // This is only necessary for new dashboards, which will default to Edit mode. - updateViewMode(DashboardViewMode.VIEW); + updateViewMode(ViewMode.VIEW); // We need to do a hard reset of the timepicker. appState will not reload like // it does on 'open' because it's been saved to the url and the getAppState.previouslyStored() check on @@ -398,7 +526,7 @@ export class DashboardAppController { kbnUrl.change(createDashboardEditUrl(dash.id)); } else { docTitle.change(dash.lastSavedTitle); - updateViewMode(DashboardViewMode.VIEW); + updateViewMode(ViewMode.VIEW); } } return { id }; @@ -433,8 +561,8 @@ export class DashboardAppController { [key: string]: NavAction; } = {}; navActions[TopNavIds.FULL_SCREEN] = () => dashboardStateManager.setFullScreenMode(true); - navActions[TopNavIds.EXIT_EDIT_MODE] = () => onChangeViewMode(DashboardViewMode.VIEW); - navActions[TopNavIds.ENTER_EDIT_MODE] = () => onChangeViewMode(DashboardViewMode.EDIT); + navActions[TopNavIds.EXIT_EDIT_MODE] = () => onChangeViewMode(ViewMode.VIEW); + navActions[TopNavIds.ENTER_EDIT_MODE] = () => onChangeViewMode(ViewMode.EDIT); navActions[TopNavIds.SAVE] = () => { const currentTitle = dashboardStateManager.getTitle(); const currentDescription = dashboardStateManager.getDescription(); @@ -512,14 +640,11 @@ export class DashboardAppController { showCloneModal(onClone, currentTitle); }; navActions[TopNavIds.ADD] = () => { - const addNewVis = () => { - showNewVisModal(visTypes, { - editorParams: [DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM], - }); - }; - - showAddPanel(dashboardStateManager.addNewPanel, addNewVis, embeddableFactories); + if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) { + openAddPanelFlyout(dashboardContainer); + } }; + navActions[TopNavIds.OPTIONS] = (menuItem, navController, anchorElement) => { showOptionsPopover({ anchorElement, @@ -559,27 +684,18 @@ export class DashboardAppController { }, }); - // update data when filters fire fetch event - - const fetchSubscription = queryFilter.getFetches$().subscribe($scope.refresh); - $scope.$on('$destroy', () => { updateSubscription.unsubscribe(); - fetchSubscription.unsubscribe(); dashboardStateManager.destroy(); + if (inputSubscription) { + inputSubscription.unsubscribe(); + } + if (outputSubscription) { + outputSubscription.unsubscribe(); + } + if (dashboardContainer) { + dashboardContainer.destroy(); + } }); - - if ( - $route.current.params && - $route.current.params[DashboardConstants.NEW_VISUALIZATION_ID_PARAM] - ) { - dashboardStateManager.addNewPanel( - $route.current.params[DashboardConstants.NEW_VISUALIZATION_ID_PARAM], - 'visualization' - ); - - kbnUrl.removeParam(DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM); - kbnUrl.removeParam(DashboardConstants.NEW_VISUALIZATION_ID_PARAM); - } } } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_constants.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_constants.ts index 2d0995179860ea1..b76b3f309874ab4 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_constants.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_constants.ts @@ -23,10 +23,6 @@ export const DashboardConstants = { LANDING_PAGE_PATH: '/dashboards', CREATE_NEW_DASHBOARD_URL: '/dashboard', }; -export const DASHBOARD_GRID_COLUMN_COUNT = 48; -export const DASHBOARD_GRID_HEIGHT = 20; -export const DEFAULT_PANEL_WIDTH = DASHBOARD_GRID_COLUMN_COUNT / 2; -export const DEFAULT_PANEL_HEIGHT = 15; export function createDashboardEditUrl(id: string) { return `/dashboard/${id}`; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts index 88312629da5d929..e0ff192f0dffca1 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts @@ -17,17 +17,14 @@ * under the License. */ +import './np_core.test.mocks'; + import { DashboardStateManager } from './dashboard_state_manager'; -import { DashboardViewMode } from './dashboard_view_mode'; -import { embeddableIsInitialized, setPanels } from './actions'; import { getAppStateMock, getSavedDashboardMock } from './__tests__'; -import { store } from '../store'; import { AppStateClass } from 'ui/state_management/app_state'; import { DashboardAppState } from './types'; -import { IndexPattern } from 'ui/index_patterns'; import { Timefilter } from 'ui/timefilter'; - -jest.mock('ui/chrome', () => ({ getKibanaVersion: () => '6.0.0' }), { virtual: true }); +import { ViewMode } from '../../../embeddable_api/public'; describe('DashboardState', function() { let dashboardState: DashboardStateManager; @@ -48,8 +45,6 @@ describe('DashboardState', function() { off: jest.fn(), on: jest.fn(), }; - const mockIndexPattern: IndexPattern = { id: 'index1', fields: [], title: 'hi' }; - function initDashboardState() { dashboardState = new DashboardStateManager({ savedDashboard, @@ -112,72 +107,15 @@ describe('DashboardState', function() { }); test('getIsDirty is true if isDirty is true and editing', () => { - dashboardState.switchViewMode(DashboardViewMode.EDIT); + dashboardState.switchViewMode(ViewMode.EDIT); dashboardState.isDirty = true; expect(dashboardState.getIsDirty()).toBeTruthy(); }); test('getIsDirty is false if isDirty is true and editing', () => { - dashboardState.switchViewMode(DashboardViewMode.VIEW); + dashboardState.switchViewMode(ViewMode.VIEW); dashboardState.isDirty = true; expect(dashboardState.getIsDirty()).toBeFalsy(); }); }); - - describe('panelIndexPatternMapping', function() { - beforeAll(() => { - initDashboardState(); - }); - - function simulateNewEmbeddableWithIndexPatterns({ - panelId, - indexPatterns, - }: { - panelId: string; - indexPatterns?: IndexPattern[]; - }) { - store.dispatch( - setPanels({ - [panelId]: { - id: '123', - panelIndex: panelId, - version: '1', - type: 'hi', - embeddableConfig: {}, - gridData: { x: 1, y: 1, h: 1, w: 1, i: '1' }, - }, - }) - ); - const metadata = { title: 'my embeddable title', editUrl: 'editme', indexPatterns }; - store.dispatch(embeddableIsInitialized({ metadata, panelId })); - } - - test('initially has no index patterns', () => { - expect(dashboardState.getPanelIndexPatterns().length).toBe(0); - }); - - test('registers index pattern when an embeddable is initialized with one', async () => { - simulateNewEmbeddableWithIndexPatterns({ - panelId: 'foo1', - indexPatterns: [mockIndexPattern], - }); - await new Promise(resolve => process.nextTick(resolve)); - expect(dashboardState.getPanelIndexPatterns().length).toBe(1); - }); - - test('registers unique index patterns', async () => { - simulateNewEmbeddableWithIndexPatterns({ - panelId: 'foo2', - indexPatterns: [mockIndexPattern], - }); - await new Promise(resolve => process.nextTick(resolve)); - expect(dashboardState.getPanelIndexPatterns().length).toBe(1); - }); - - test('does not register undefined index pattern for panels with no index pattern', async () => { - simulateNewEmbeddableWithIndexPatterns({ panelId: 'foo2' }); - await new Promise(resolve => process.nextTick(resolve)); - expect(dashboardState.getPanelIndexPatterns().length).toBe(1); - }); - }); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts index 9aa909190d6b22e..1e0e3a396e795bf 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts @@ -20,61 +20,30 @@ import { i18n } from '@kbn/i18n'; import _ from 'lodash'; +import { Filter } from '@kbn/es-query'; import { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_monitor_factory'; -import { StaticIndexPattern } from 'ui/index_patterns'; -import { AppStateClass as TAppStateClass } from 'ui/state_management/app_state'; import { Timefilter } from 'ui/timefilter'; -import { RefreshInterval } from 'ui/timefilter/timefilter'; -import { Filter } from '@kbn/es-query'; -import moment from 'moment'; -import { Query } from 'src/legacy/core_plugins/data/public'; -import { TimeRange } from 'ui/timefilter/time_history'; -import { DashboardViewMode } from './dashboard_view_mode'; -import { FilterUtils } from './lib/filter_utils'; -import { PanelUtils } from './panel/panel_utils'; -import { store } from '../store'; +import { AppStateClass as TAppStateClass } from 'ui/state_management/app_state'; +import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; +import { Moment } from 'moment'; import { - updateViewMode, - setPanels, - updateUseMargins, - updateIsFullScreenMode, - minimizePanel, - updateTitle, - updateDescription, - updateHidePanelTitles, - updateTimeRange, - updateRefreshConfig, - clearStagedFilters, - updateFilters, - updateQuery, - closeContextMenu, - requestReload, -} from './actions'; -import { createPanelState } from './panel'; + DashboardContainer, + DashboardPanelState, +} from '../../../dashboard_embeddable_container/public'; +import { ViewMode } from '../../../embeddable_api/public'; +import { Query } from '../../../data/public'; + import { getAppStateDefaults, migrateAppState } from './lib'; -import { - getViewMode, - getFullScreenMode, - getPanels, - getPanel, - getTitle, - getDescription, - getUseMargins, - getHidePanelTitles, - getStagedFilters, - getEmbeddables, - getEmbeddableMetadata, - getQuery, - getFilters, -} from '../selectors'; +import { convertPanelStateToSavedDashboardPanel } from './lib/embeddable_saved_object_converters'; +import { FilterUtils } from './lib/filter_utils'; import { SavedObjectDashboard } from './saved_dashboard/saved_dashboard'; + import { - DashboardAppState, - SavedDashboardPanel, - SavedDashboardPanelMap, - DashboardAppStateParameters, AddFilterFn, + SavedDashboardPanel, + DashboardAppState, + DashboardAppStateDefaults, } from './types'; /** @@ -87,32 +56,28 @@ export class DashboardStateManager { public savedDashboard: SavedObjectDashboard; public appState: DashboardAppState; public lastSavedDashboardFilters: { - timeTo?: string | moment.Moment; - timeFrom?: string | moment.Moment; + timeTo?: string | Moment; + timeFrom?: string | Moment; filterBars: Filter[]; - query: Query | string; + query: Query; }; - private stateDefaults: DashboardAppStateParameters; + private stateDefaults: DashboardAppStateDefaults; private hideWriteControls: boolean; public isDirty: boolean; private changeListeners: Array<(status: { dirty: boolean }) => void>; - private stateMonitor: StateMonitor; - private panelIndexPatternMapping: { [key: string]: StaticIndexPattern[] } = {}; - private addFilter: AddFilterFn; - private unsubscribe: () => void; + private stateMonitor: StateMonitor; /** * - * @param savedDashboard - * @param AppState The AppState class to use when instantiating a new AppState instance. - * @param hideWriteControls true if write controls should be hidden. - * @param addFilter a function that can be used to add a filter bar filter + * @param {SavedDashboard} savedDashboard + * @param {AppState} AppState The AppState class to use when instantiating a new AppState instance. + * @param {boolean} hideWriteControls true if write controls should be hidden. + * @param {function} addFilter a function that can be used to add a filter bar filter */ constructor({ savedDashboard, AppStateClass, hideWriteControls, - addFilter, }: { savedDashboard: SavedObjectDashboard; AppStateClass: TAppStateClass; @@ -121,7 +86,6 @@ export class DashboardStateManager { }) { this.savedDashboard = savedDashboard; this.hideWriteControls = hideWriteControls; - this.addFilter = addFilter; this.stateDefaults = getAppStateDefaults(this.savedDashboard, this.hideWriteControls); @@ -141,15 +105,10 @@ export class DashboardStateManager { // in the 'lose changes' warning message. this.lastSavedDashboardFilters = this.getFilterState(); - // A mapping of panel index to the index pattern it uses. - this.panelIndexPatternMapping = {}; - - PanelUtils.initPanelIndexes(this.getPanels()); - /** * Creates a state monitor and saves it to this.stateMonitor. Used to track unsaved changes made to appState. */ - this.stateMonitor = stateMonitorFactory.create( + this.stateMonitor = stateMonitorFactory.create( this.appState, this.stateDefaults ); @@ -164,18 +123,10 @@ export class DashboardStateManager { this.isDirty = status.dirty; }); - store.dispatch(closeContextMenu()); - - // Always start out with all panels minimized when a dashboard is first loaded. - store.dispatch(minimizePanel()); - this.pushAppStateChangesToStore(); - this.changeListeners = []; - this.unsubscribe = store.subscribe(() => this.handleStoreChanges()); this.stateMonitor.onChange((status: { dirty: boolean }) => { this.changeListeners.forEach(listener => listener(status)); - this.pushAppStateChangesToStore(); }); } @@ -183,152 +134,68 @@ export class DashboardStateManager { this.changeListeners.push(callback); } - private areStoreAndAppStatePanelsEqual() { - const state = store.getState(); - const storePanels = getPanels(store.getState()); + public equalsAppStatePanels(panels: { [key: string]: DashboardPanelState }) { const appStatePanels = this.getPanels(); - if (Object.values(storePanels).length !== appStatePanels.length) { + if (Object.values(panels).length !== appStatePanels.length) { return false; } - return appStatePanels.every(appStatePanel => { - const storePanel = getPanel(state, appStatePanel.panelIndex); - return _.isEqual(appStatePanel, storePanel); + return appStatePanels.every((appStatePanel: SavedDashboardPanel) => { + const convertedPanel = convertPanelStateToSavedDashboardPanel( + panels[appStatePanel.panelIndex] + ); + return _.isEqual(appStatePanel, convertedPanel); }); } - /** - * Time is part of global state so we need to deal with it outside of pushAppStateChangesToStore. - */ - public handleTimeChange(newTimeRange: TimeRange) { - const from = FilterUtils.convertTimeToUTCString(newTimeRange.from); - const to = FilterUtils.convertTimeToUTCString(newTimeRange.to); - store.dispatch( - updateTimeRange({ - from: from ? from.toString() : '', - to: to ? to.toString() : '', - }) - ); - } - - public handleRefreshConfigChange(refreshInterval: RefreshInterval) { - store.dispatch(updateRefreshConfig(refreshInterval)); - } - - /** - * Changes made to app state outside of direct calls to this class will need to be propagated to the store. - * @private - */ - private pushAppStateChangesToStore() { - // We need these checks, or you can get into a loop where a change is triggered by the store, which updates - // AppState, which then dispatches the change here, which will end up triggering setState warnings. - if (!this.areStoreAndAppStatePanelsEqual()) { - // Translate appState panels data into the data expected by redux, copying the panel objects as we do so - // because the panels inside appState can be mutated, while redux state should never be mutated directly. - const panelsMap = this.getPanels().reduce((acc: SavedDashboardPanelMap, panel) => { - acc[panel.panelIndex] = _.cloneDeep(panel); - return acc; - }, {}); - store.dispatch(setPanels(panelsMap)); - } - - const state = store.getState(); - - if (getTitle(state) !== this.getTitle()) { - store.dispatch(updateTitle(this.getTitle())); - } - - if (getDescription(state) !== this.getDescription()) { - store.dispatch(updateDescription(this.getDescription())); - } - - if (getViewMode(state) !== this.getViewMode()) { - store.dispatch(updateViewMode(this.getViewMode())); - } - - if (getUseMargins(state) !== this.getUseMargins()) { - store.dispatch(updateUseMargins(this.getUseMargins())); - } - - if (getHidePanelTitles(state) !== this.getHidePanelTitles()) { - store.dispatch(updateHidePanelTitles(this.getHidePanelTitles())); - } - - if (getFullScreenMode(state) !== this.getFullScreenMode()) { - store.dispatch(updateIsFullScreenMode(this.getFullScreenMode())); - } - - if (getTitle(state) !== this.getTitle()) { - store.dispatch(updateTitle(this.getTitle())); - } - - if (getDescription(state) !== this.getDescription()) { - store.dispatch(updateDescription(this.getDescription())); - } + public handleDashboardContainerChanges(dashboardContainer: DashboardContainer) { + let dirty = false; - if (getQuery(state) !== this.getQuery()) { - store.dispatch(updateQuery(this.getQuery())); - } + const savedDashboardPanelMap: { [key: string]: SavedDashboardPanel } = {}; - this._pushFiltersToStore(); - } + const input = dashboardContainer.getInput(); + this.getPanels().forEach(savedDashboardPanel => { + if (input.panels[savedDashboardPanel.panelIndex] !== undefined) { + savedDashboardPanelMap[savedDashboardPanel.panelIndex] = savedDashboardPanel; + } else { + // A panel was deleted. + dirty = true; + } + }); - _pushFiltersToStore() { - const state = store.getState(); - const dashboardFilters = this.savedDashboard.getFilters(); - if ( - !_.isEqual( - FilterUtils.cleanFiltersForComparison(dashboardFilters), - FilterUtils.cleanFiltersForComparison(getFilters(state)) - ) - ) { - store.dispatch(updateFilters(dashboardFilters)); - } - } + const convertedPanelStateMap: { [key: string]: SavedDashboardPanel } = {}; - requestReload() { - store.dispatch(requestReload()); - } + Object.values(input.panels).forEach(panelState => { + if (savedDashboardPanelMap[panelState.explicitInput.id] === undefined) { + dirty = true; + } - private handleStoreChanges() { - let dirty = false; - if (!this.areStoreAndAppStatePanelsEqual()) { - const panels: SavedDashboardPanelMap = getPanels(store.getState()); - this.appState.panels = []; - this.panelIndexPatternMapping = {}; - Object.values(panels).map((panel: SavedDashboardPanel) => { - this.appState.panels.push(_.cloneDeep(panel)); - }); - dirty = true; - } + convertedPanelStateMap[panelState.explicitInput.id] = convertPanelStateToSavedDashboardPanel( + panelState + ); - _.forEach(getEmbeddables(store.getState()), (embeddable, panelId) => { if ( - panelId && - embeddable.initialized && - !this.panelIndexPatternMapping.hasOwnProperty(panelId) + !_.isEqual( + convertedPanelStateMap[panelState.explicitInput.id], + savedDashboardPanelMap[panelState.explicitInput.id] + ) ) { - const embeddableMetadata = getEmbeddableMetadata(store.getState(), panelId); - if (embeddableMetadata && embeddableMetadata.indexPatterns) { - this.panelIndexPatternMapping[panelId] = _.compact(embeddableMetadata.indexPatterns); - dirty = true; - } + // A panel was changed + dirty = true; } }); - const stagedFilters = getStagedFilters(store.getState()); - stagedFilters.forEach(filter => { - this.addFilter(filter, this.getAppState()); - }); - if (stagedFilters.length > 0) { - this.saveState(); - store.dispatch(clearStagedFilters()); + if (dirty) { + this.appState.panels = Object.values(convertedPanelStateMap); } - const fullScreen = getFullScreenMode(store.getState()); - if (fullScreen !== this.getFullScreenMode()) { - this.setFullScreenMode(fullScreen); + if (input.isFullScreenMode !== this.getFullScreenMode()) { + this.setFullScreenMode(input.isFullScreenMode); + } + + if (!_.isEqual(input.query, this.getQuery())) { + this.setQuery(input.query); } this.changeListeners.forEach(listener => listener({ dirty })); @@ -344,11 +211,6 @@ export class DashboardStateManager { this.saveState(); } - public getPanelIndexPatterns() { - const indexPatterns = _.flatten(Object.values(this.panelIndexPatternMapping)); - return _.uniq(indexPatterns, 'id'); - } - /** * Resets the state back to the last saved version of the dashboard. */ @@ -412,8 +274,8 @@ export class DashboardStateManager { return this.appState; } - public getQuery() { - return this.appState.query; + public getQuery(): Query { + return migrateLegacyQuery(this.appState.query); } public getUseMargins() { @@ -457,7 +319,7 @@ export class DashboardStateManager { return this.lastSavedDashboardFilters.filterBars; } - public getLastSavedQuery(): Query | string { + public getLastSavedQuery() { return this.lastSavedDashboardFilters.query; } @@ -469,10 +331,12 @@ export class DashboardStateManager { const currentQuery = this.appState.query; const lastSavedQuery = this.getLastSavedQuery(); + const query = migrateLegacyQuery(currentQuery); + const isLegacyStringQuery = _.isString(lastSavedQuery) && _.isPlainObject(currentQuery) && _.has(currentQuery, 'query'); if (isLegacyStringQuery) { - return (lastSavedQuery as string) !== (currentQuery as Query).query; + return lastSavedQuery !== query.query; } return !_.isEqual(currentQuery, lastSavedQuery); @@ -505,24 +369,24 @@ export class DashboardStateManager { /** * - * @returns {DashboardViewMode} + * @returns {ViewMode} */ public getViewMode() { - return this.hideWriteControls ? DashboardViewMode.VIEW : this.appState.viewMode; + return this.hideWriteControls ? ViewMode.VIEW : this.appState.viewMode; } /** * @returns {boolean} */ public getIsViewMode() { - return this.getViewMode() === DashboardViewMode.VIEW; + return this.getViewMode() === ViewMode.VIEW; } /** * @returns {boolean} */ public getIsEditMode() { - return this.getViewMode() === DashboardViewMode.EDIT; + return this.getViewMode() === ViewMode.EDIT; } /** @@ -549,30 +413,6 @@ export class DashboardStateManager { return foundPanel; } - /** - * Creates and initializes a basic panel, adding it to the state. - * @param {number} id - * @param {string} type - */ - public addNewPanel = (id: string, type: string) => { - const maxPanelIndex = PanelUtils.getMaxPanelIndex(this.getPanels()); - const newPanel = createPanelState(id, type, maxPanelIndex.toString(), this.getPanels()); - this.getPanels().push(newPanel); - this.saveState(); - }; - - public removePanel(panelIndex: string) { - _.remove(this.getPanels(), panel => { - if (panel.panelIndex === panelIndex) { - delete this.panelIndexPatternMapping[panelIndex]; - return true; - } else { - return false; - } - }); - this.saveState(); - } - /** * @param timeFilter * @returns {Array.} An array of user friendly strings indicating the filter types that have changed. @@ -592,7 +432,7 @@ export class DashboardStateManager { } /** - * @return True if filters (query, filter bar filters, and time picker if time is stored + * @return {boolean} True if filters (query, filter bar filters, and time picker if time is stored * with the dashboard) have changed since the last saved state (or if the dashboard hasn't been saved, * the default state). */ @@ -602,6 +442,9 @@ export class DashboardStateManager { /** * Updates timeFilter to match the time saved with the dashboard. + * @param {Object} timeFilter + * @param {func} timeFilter.setTime + * @param {func} timeFilter.setRefreshInterval */ public syncTimefilterWithDashboard(timeFilter: Timefilter) { if (!this.getIsTimeSavedWithDashboard()) { @@ -631,23 +474,26 @@ export class DashboardStateManager { this.appState.save(); } + public setQuery(query: Query) { + this.appState.query = query; + this.saveState(); + } + /** * Applies the current filter state to the dashboard. * @param filter {Array.} An array of filter bar filters. */ - public applyFilters(query: Query | string, filters: Filter[]) { + public applyFilters(query: Query, filters: Filter[]) { this.appState.query = query; this.savedDashboard.searchSource.setField('query', query); this.savedDashboard.searchSource.setField('filter', filters); this.saveState(); - // pinned filters go on global state, therefore are not propagated to store via app state and have to be pushed manually. - this._pushFiltersToStore(); } /** - * @param newMode {DashboardViewMode} + * @param newMode {ViewMode} */ - public switchViewMode(newMode: DashboardViewMode) { + public switchViewMode(newMode: ViewMode) { this.appState.viewMode = newMode; this.saveState(); } @@ -660,6 +506,5 @@ export class DashboardStateManager { this.stateMonitor.destroy(); } this.savedDashboard.destroy(); - this.unsubscribe(); } } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_strings.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_strings.ts index 3ea7cf593687cf2..7752177c16d4478 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_strings.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_strings.ts @@ -18,7 +18,7 @@ */ import { i18n } from '@kbn/i18n'; -import { DashboardViewMode } from './dashboard_view_mode'; +import { ViewMode } from '../../../embeddable_api/public'; /** * @param title {string} the current title of the dashboard @@ -27,12 +27,8 @@ import { DashboardViewMode } from './dashboard_view_mode'; * end of the title. * @returns {string} A title to display to the user based on the above parameters. */ -export function getDashboardTitle( - title: string, - viewMode: DashboardViewMode, - isDirty: boolean -): string { - const isEditMode = viewMode === DashboardViewMode.EDIT; +export function getDashboardTitle(title: string, viewMode: ViewMode, isDirty: boolean): string { + const isEditMode = viewMode === ViewMode.EDIT; let displayTitle: string; if (isEditMode && isDirty) { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_view_mode.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_view_mode.ts deleted file mode 100644 index e9f968249090b17..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_view_mode.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export enum DashboardViewMode { - EDIT = 'edit', - VIEW = 'view', -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/grid/__snapshots__/dashboard_grid.test.js.snap b/src/legacy/core_plugins/kibana/public/dashboard/grid/__snapshots__/dashboard_grid.test.js.snap deleted file mode 100644 index 806e11c557a0254..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/grid/__snapshots__/dashboard_grid.test.js.snap +++ /dev/null @@ -1,77 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders DashboardGrid 1`] = ` - -
- -
-
- -
-
-`; - -exports[`renders DashboardGrid with no visualizations 1`] = ` - -`; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/grid/_dashboard_grid.scss b/src/legacy/core_plugins/kibana/public/dashboard/grid/_dashboard_grid.scss deleted file mode 100644 index 7acdf1ce5993a91..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/grid/_dashboard_grid.scss +++ /dev/null @@ -1,127 +0,0 @@ -// SASSTODO: Can't find this selector, but could break something if removed -.react-grid-layout .gs-w { - z-index: auto; -} - -/** - * 1. Due to https://github.com/STRML/react-grid-layout/issues/240 we have to manually hide the resizable - * element. - */ -.dshLayout--viewing { - .react-resizable-handle { - display: none; /* 1 */ - } -} - -/** - * 1. If we don't give the resizable handler a larger z index value the layout will hide it. - */ -.dshLayout--editing { - .react-resizable-handle { - @include size($euiSizeL); - z-index: $euiZLevel1; /* 1 */ - right: 0; - bottom: 0; - padding-right: $euiSizeS; - padding-bottom: $euiSizeS; - } -} - -/** - * 1. Need to override the react grid layout height when a single panel is expanded. Important is required because - * otherwise the height is set inline. - */ - .dshLayout-isMaximizedPanel { - height: 100% !important; /* 1. */ - width: 100%; - position: absolute; -} - -/** - * .dshLayout-withoutMargins only affects the panel styles themselves, see ../panel - */ - -/** - * When a single panel is expanded, all the other panels are hidden in the grid. - */ -.dshDashboardGrid__item--hidden { - display: none; -} - -/** - * 1. We need to mark this as important because react grid layout sets the width and height of the panels inline. - */ -.dshDashboardGrid__item--expanded { - height: 100% !important; /* 1 */ - width: 100% !important; /* 1 */ - top: 0 !important; /* 1 */ - left: 0 !important; /* 1 */ - - // Altered panel styles can be found in ../panel -} - -// REACT-GRID - -.react-grid-item { - /** - * Disable transitions from the library on each grid element. - */ - transition: none; - /** - * Copy over and overwrite the fill color with EUI color mixin (for theming) - */ - > .react-resizable-handle { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='6' height='6' viewBox='0 0 6 6'%3E%3Cpolygon fill='#{hexToRGB($euiColorDarkShade)}' points='6 6 0 6 0 4.2 4 4.2 4.2 4.2 4.2 0 6 0' /%3E%3C/svg%3E%0A"); - - &::after { - border: none; - } - - &:hover, - &:focus { - background-color: $dshEditingModeHoverColor; - } - } - - /** - * Dragged/Resized panels in dashboard should always appear above other panels - * and above the placeholder - */ - &.resizing, - &.react-draggable-dragging { - z-index: $euiZLevel2 !important; - } - - &.react-draggable-dragging { - transition: box-shadow $euiAnimSpeedFast $euiAnimSlightResistance; - @include euiBottomShadowLarge; - border-radius: $euiBorderRadius; // keeps shadow within bounds - } - - /** - * Overwrites red coloring that comes from this library by default. - */ - &.react-grid-placeholder { - border-radius: $euiBorderRadius; - background: $euiColorWarning; - } -} - -// When in view-mode only, and on tiny mobile screens, just stack each of the grid-items - -@include euiBreakpoint('xs', 's') { - .dshLayout--viewing { - .react-grid-item { - position: static !important; - width: calc(100% - #{$euiSize}) !important; - margin: $euiSizeS; - } - - &.dshLayout-withoutMargins { - .react-grid-item { - width: 100% !important; - margin: 0; - } - } - } -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/grid/_index.scss b/src/legacy/core_plugins/kibana/public/dashboard/grid/_index.scss deleted file mode 100644 index eb393d7603b8af9..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/grid/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './dashboard_grid'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/grid/dashboard_grid.test.js b/src/legacy/core_plugins/kibana/public/dashboard/grid/dashboard_grid.test.js deleted file mode 100644 index 0d9b80763c136c0..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/grid/dashboard_grid.test.js +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import sizeMe from 'react-sizeme'; - -import { DashboardViewMode } from '../dashboard_view_mode'; -import { getEmbeddableFactoryMock } from '../__tests__'; - -import { DashboardGrid } from './dashboard_grid'; - -jest.mock('ui/chrome', () => ({ getKibanaVersion: () => '6.0.0' }), { virtual: true }); - -jest.mock('ui/notify', - () => ({ - toastNotifications: { - addDanger: () => {}, - } - }), { virtual: true }); - -function getProps(props = {}) { - const defaultTestProps = { - dashboardViewMode: DashboardViewMode.EDIT, - panels: { - '1': { - gridData: { x: 0, y: 0, w: 6, h: 6, i: 1 }, - panelIndex: '1', - type: 'visualization', - id: '123', - version: '7.0.0', - }, - '2': { - gridData: { x: 6, y: 6, w: 6, h: 6, i: 2 }, - panelIndex: '2', - type: 'visualization', - id: '456', - version: '7.0.0', - } - }, - getEmbeddableFactory: () => getEmbeddableFactoryMock(), - onPanelsUpdated: () => {}, - useMargins: true, - }; - return Object.assign(defaultTestProps, props); -} - -beforeAll(() => { - // sizeme detects the width to be 0 in our test environment. noPlaceholder will mean that the grid contents will - // get rendered even when width is 0, which will improve our tests. - sizeMe.noPlaceholders = true; -}); - -afterAll(() => { - sizeMe.noPlaceholders = false; -}); - -test('renders DashboardGrid', () => { - const component = shallowWithIntl(); - expect(component).toMatchSnapshot(); - const panelElements = component.find('Connect(InjectIntl(DashboardPanelUi))'); - expect(panelElements.length).toBe(2); -}); - -test('renders DashboardGrid with no visualizations', () => { - const component = shallowWithIntl(); - expect(component).toMatchSnapshot(); -}); - -test('adjusts z-index of focused panel to be higher than siblings', () => { - const component = shallowWithIntl(); - const panelElements = component.find('Connect(InjectIntl(DashboardPanelUi))'); - panelElements.first().prop('onPanelFocused')('1'); - const [gridItem1, gridItem2] = component.update().findWhere(el => el.key() === '1' || el.key() === '2'); - expect(gridItem1.props.style.zIndex).toEqual(2); - expect(gridItem2.props.style.zIndex).toEqual('auto'); -}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/grid/dashboard_grid.tsx b/src/legacy/core_plugins/kibana/public/dashboard/grid/dashboard_grid.tsx deleted file mode 100644 index 4376b936df76fff..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/grid/dashboard_grid.tsx +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { injectI18n } from '@kbn/i18n/react'; -import classNames from 'classnames'; -import _ from 'lodash'; -import React from 'react'; -import ReactGridLayout, { Layout } from 'react-grid-layout'; -import 'react-grid-layout/css/styles.css'; -import 'react-resizable/css/styles.css'; - -// @ts-ignore -import sizeMe from 'react-sizeme'; -import { EmbeddableFactory } from 'ui/embeddable'; -import { toastNotifications } from 'ui/notify'; -import { - DASHBOARD_GRID_COLUMN_COUNT, - DASHBOARD_GRID_HEIGHT, - DashboardConstants, -} from '../dashboard_constants'; -import { DashboardViewMode } from '../dashboard_view_mode'; -import { DashboardPanel } from '../panel'; -import { PanelUtils } from '../panel/panel_utils'; -import { - GridData, - SavedDashboardPanel, - Pre61SavedDashboardPanel, - SavedDashboardPanelMap, -} from '../types'; - -let lastValidGridSize = 0; - -/** - * This is a fix for a bug that stopped the browser window from automatically scrolling down when panels were made - * taller than the current grid. - * see https://github.com/elastic/kibana/issues/14710. - */ -function ensureWindowScrollsToBottom(event: { clientY: number; pageY: number }) { - // The buffer is to handle the case where the browser is maximized and it's impossible for the mouse to move below - // the screen, out of the window. see https://github.com/elastic/kibana/issues/14737 - const WINDOW_BUFFER = 10; - if (event.clientY > window.innerHeight - WINDOW_BUFFER) { - window.scrollTo(0, event.pageY + WINDOW_BUFFER - window.innerHeight); - } -} - -function ResponsiveGrid({ - size, - isViewMode, - layout, - onLayoutChange, - children, - maximizedPanelId, - useMargins, -}: { - size: { width: number }; - isViewMode: boolean; - layout: Layout[]; - onLayoutChange: () => void; - children: JSX.Element[]; - maximizedPanelId: string; - useMargins: boolean; -}) { - // This is to prevent a bug where view mode changes when the panel is expanded. View mode changes will trigger - // the grid to re-render, but when a panel is expanded, the size will be 0. Minimizing the panel won't cause the - // grid to re-render so it'll show a grid with a width of 0. - lastValidGridSize = size.width > 0 ? size.width : lastValidGridSize; - const classes = classNames({ - 'dshLayout--viewing': isViewMode, - 'dshLayout--editing': !isViewMode, - 'dshLayout-isMaximizedPanel': maximizedPanelId !== undefined, - 'dshLayout-withoutMargins': !useMargins, - }); - - const MARGINS = useMargins ? 8 : 0; - // We can't take advantage of isDraggable or isResizable due to performance concerns: - // https://github.com/STRML/react-grid-layout/issues/240 - return ( - ensureWindowScrollsToBottom(event)} - > - {children} - - ); -} - -// Using sizeMe sets up the grid to be re-rendered automatically not only when the window size changes, but also -// when the container size changes, so it works for Full Screen mode switches. -const config = { monitorWidth: true }; -const ResponsiveSizedGrid = sizeMe(config)(ResponsiveGrid); - -interface Props extends ReactIntl.InjectedIntlProps { - panels: SavedDashboardPanelMap; - getEmbeddableFactory: (panelType: string) => EmbeddableFactory; - dashboardViewMode: DashboardViewMode.EDIT | DashboardViewMode.VIEW; - onPanelsUpdated: (updatedPanels: SavedDashboardPanelMap) => void; - maximizedPanelId?: string; - useMargins: boolean; -} - -interface State { - focusedPanelIndex?: string; - isLayoutInvalid: boolean; - layout?: GridData[]; -} - -interface PanelLayout extends Layout { - i: string; -} - -class DashboardGridUi extends React.Component { - // A mapping of panelIndexes to grid items so we can set the zIndex appropriately on the last focused - // item. - private gridItems = {} as { [key: string]: HTMLDivElement | null }; - - // A mapping of panel type to embeddable handlers. Because this function reaches out of react and into angular, - // if done in the render method, it appears to be triggering a scope.apply, which appears to be trigging a setState - // call inside TSVB visualizations. Moving the function out of render appears to fix the issue. See - // https://github.com/elastic/kibana/issues/14802 for more info. - // This is probably a better implementation anyway so the handlers are cached. - // @type {Object.} - private embeddableFactoryMap: { [s: string]: EmbeddableFactory } = {}; - - constructor(props: Props) { - super(props); - - let isLayoutInvalid = false; - let layout; - try { - layout = this.buildLayoutFromPanels(); - } catch (error) { - isLayoutInvalid = true; - toastNotifications.addDanger({ - title: props.intl.formatMessage({ - id: 'kbn.dashboard.dashboardGrid.unableToLoadDashboardDangerMessage', - defaultMessage: 'Unable to load dashboard.', - }), - text: error.message, - }); - window.location.hash = DashboardConstants.LANDING_PAGE_PATH; - } - this.state = { - focusedPanelIndex: undefined, - layout, - isLayoutInvalid, - }; - } - - public buildLayoutFromPanels(): GridData[] { - return _.map(this.props.panels, panel => { - // panel version numbers added in 6.1. Any panel without version number is assumed to be 6.0.0 - const panelVersion = - 'version' in panel - ? PanelUtils.parseVersion(panel.version) - : PanelUtils.parseVersion('6.0.0'); - - if (panelVersion.major < 6 || (panelVersion.major === 6 && panelVersion.minor < 1)) { - panel = PanelUtils.convertPanelDataPre_6_1((panel as unknown) as Pre61SavedDashboardPanel); - } - - if (panelVersion.major < 6 || (panelVersion.major === 6 && panelVersion.minor < 3)) { - PanelUtils.convertPanelDataPre_6_3(panel as SavedDashboardPanel, this.props.useMargins); - } - - return (panel as SavedDashboardPanel).gridData; - }); - } - - public createEmbeddableFactoriesMap(panels: SavedDashboardPanelMap) { - Object.values(panels).map(panel => { - if (!this.embeddableFactoryMap[panel.type]) { - this.embeddableFactoryMap[panel.type] = this.props.getEmbeddableFactory(panel.type); - } - }); - } - - public componentWillMount() { - this.createEmbeddableFactoriesMap(this.props.panels); - } - - public componentWillReceiveProps(nextProps: Props) { - this.createEmbeddableFactoriesMap(nextProps.panels); - } - - public onLayoutChange = (layout: PanelLayout[]) => { - const { onPanelsUpdated, panels } = this.props; - const updatedPanels = layout.reduce((updatedPanelsAcc: SavedDashboardPanelMap, panelLayout) => { - updatedPanelsAcc[panelLayout.i] = { - ...panels[panelLayout.i], - panelIndex: panelLayout.i, - gridData: _.pick(panelLayout, ['x', 'y', 'w', 'h', 'i']), - }; - return updatedPanelsAcc; - }, {}); - onPanelsUpdated(updatedPanels); - }; - - public onPanelFocused = (focusedPanelIndex: string): void => { - this.setState({ focusedPanelIndex }); - }; - - public onPanelBlurred = (blurredPanelIndex: string): void => { - if (this.state.focusedPanelIndex === blurredPanelIndex) { - this.setState({ focusedPanelIndex: undefined }); - } - }; - - public renderDOM() { - const { panels, maximizedPanelId } = this.props; - const { focusedPanelIndex } = this.state; - - // Part of our unofficial API - need to render in a consistent order for plugins. - const panelsInOrder = Object.keys(panels).map( - (key: string) => panels[key] as SavedDashboardPanel - ); - panelsInOrder.sort((panelA, panelB) => { - if (panelA.gridData.y === panelB.gridData.y) { - return panelA.gridData.x - panelB.gridData.x; - } else { - return panelA.gridData.y - panelB.gridData.y; - } - }); - - return _.map(panelsInOrder, panel => { - const expandPanel = maximizedPanelId !== undefined && maximizedPanelId === panel.panelIndex; - const hidePanel = maximizedPanelId !== undefined && maximizedPanelId !== panel.panelIndex; - const classes = classNames({ - 'dshDashboardGrid__item--expanded': expandPanel, - 'dshDashboardGrid__item--hidden': hidePanel, - }); - return ( -
{ - this.gridItems[panel.panelIndex] = reactGridItem; - }} - > - -
- ); - }); - } - - public render() { - if (this.state.isLayoutInvalid) { - return null; - } - - const { dashboardViewMode, maximizedPanelId, useMargins } = this.props; - const isViewMode = dashboardViewMode === DashboardViewMode.VIEW; - return ( - - {this.renderDOM()} - - ); - } -} - -export const DashboardGrid = injectI18n(DashboardGridUi); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/grid/dashboard_grid_container.test.js b/src/legacy/core_plugins/kibana/public/dashboard/grid/dashboard_grid_container.test.js deleted file mode 100644 index 12d5d42c811ffd1..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/grid/dashboard_grid_container.test.js +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { Provider } from 'react-redux'; -import _ from 'lodash'; -import sizeMe from 'react-sizeme'; - -import { getEmbeddableFactoryMock } from '../__tests__'; -import { store } from '../../store'; -import { DashboardGridContainer } from './dashboard_grid_container'; -import { updatePanels, updateTimeRange, updateUseMargins } from '../actions'; - -jest.mock('ui/chrome', () => ({ getKibanaVersion: () => '6.3.0' }), { virtual: true }); - -jest.mock('ui/notify', - () => ({ - toastNotifications: { - addDanger: () => {}, - } - }), { virtual: true }); - -function getProps(props = {}) { - const defaultTestProps = { - hidden: false, - getEmbeddableFactory: () => getEmbeddableFactoryMock(), - }; - return Object.assign(defaultTestProps, props); -} - -function createOldPanelData(col, id, row, sizeX, sizeY, panelIndex) { - return { col, id, row, size_x: sizeX, size_y: sizeY, type: 'visualization', panelIndex }; -} - -const getSelection = window.getSelection; -beforeAll(() => { - // sizeme detects the width to be 0 in our test environment. noPlaceholder will mean that the grid contents will - // get rendered even when width is 0, which will improve our tests. - sizeMe.noPlaceholders = true; - - // react-grid-layout calls getSelection which isn't support by jsdom - // it's called regardless of whether we need to remove selection, - // and in this case we don't need to remove selection - window.getSelection = () => { - return { - removeAllRanges: () => {} - }; - }; - store.dispatch(updateTimeRange({ to: 'now', from: 'now-15m' })); -}); - -afterAll(() => { - sizeMe.noPlaceholders = false; - window.getSelection = getSelection; -}); - -test('loads old panel data in the right order', () => { - const panelData = [ - createOldPanelData(3, 'foo1', 1, 2, 2, 1), - createOldPanelData(5, 'foo2', 1, 2, 2, 2), - createOldPanelData(9, 'foo3', 1, 2, 2, 3), - createOldPanelData(11, 'foo4', 1, 2, 2, 4), - createOldPanelData(1, 'foo5', 1, 2, 2, 5), - createOldPanelData(7, 'foo6', 1, 2, 2, 6), - createOldPanelData(4, 'foo7', 6, 3, 2, 7), - createOldPanelData(1, 'foo8', 8, 3, 2, 8), - createOldPanelData(10, 'foo9', 8, 3, 2, 9), - createOldPanelData(10, 'foo10', 6, 3, 2, 10), - createOldPanelData(4, 'foo11', 8, 3, 2, 11), - createOldPanelData(7, 'foo12', 8, 3, 2, 12), - createOldPanelData(1, 'foo13', 6, 3, 2, 13), - createOldPanelData(7, 'foo14', 6, 3, 2, 14), - createOldPanelData(5, 'foo15', 3, 6, 3, 15), - createOldPanelData(1, 'foo17', 3, 4, 3, 16) - ]; - - store.dispatch(updatePanels(panelData)); - store.dispatch(updateUseMargins(false)); - - const grid = mountWithIntl(); - - const panels = store.getState().dashboard.panels; - expect(Object.keys(panels).length).toBe(16); - - const foo8Panel = _.find(panels, panel => panel.id === 'foo8'); - expect(foo8Panel.row).toBe(undefined); - expect(foo8Panel.gridData.y).toBe(35); - expect(foo8Panel.gridData.x).toBe(0); - - grid.unmount(); -}); - -test('loads old panel data in the right order with margins', () => { - const panelData = [ - createOldPanelData(3, 'foo1', 1, 2, 2, 1), - createOldPanelData(5, 'foo2', 1, 2, 2, 2), - createOldPanelData(9, 'foo3', 1, 2, 2, 3), - createOldPanelData(11, 'foo4', 1, 2, 2, 4), - createOldPanelData(1, 'foo5', 1, 2, 2, 5), - createOldPanelData(7, 'foo6', 1, 2, 2, 6), - createOldPanelData(4, 'foo7', 6, 3, 2, 7), - createOldPanelData(1, 'foo8', 8, 3, 2, 8), - createOldPanelData(10, 'foo9', 8, 3, 2, 9), - createOldPanelData(10, 'foo10', 6, 3, 2, 10), - createOldPanelData(4, 'foo11', 8, 3, 2, 11), - createOldPanelData(7, 'foo12', 8, 3, 2, 12), - createOldPanelData(1, 'foo13', 6, 3, 2, 13), - createOldPanelData(7, 'foo14', 6, 3, 2, 14), - createOldPanelData(5, 'foo15', 3, 6, 3, 15), - createOldPanelData(1, 'foo17', 3, 4, 3, 16) - ]; - - store.dispatch(updatePanels(panelData)); - store.dispatch(updateUseMargins(true)); - - const grid = mountWithIntl(); - - const panels = store.getState().dashboard.panels; - expect(Object.keys(panels).length).toBe(16); - - const foo8Panel = _.find(panels, panel => panel.id === 'foo8'); - expect(foo8Panel.row).toBe(undefined); - expect(foo8Panel.gridData.y).toBe(28); - expect(foo8Panel.gridData.x).toBe(0); - - grid.unmount(); -}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/grid/dashboard_grid_container.ts b/src/legacy/core_plugins/kibana/public/dashboard/grid/dashboard_grid_container.ts deleted file mode 100644 index aaf994376759d25..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/grid/dashboard_grid_container.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; -import { updatePanels } from '../actions'; -import { getPanels, getUseMargins, getViewMode } from '../selectors'; -import { DashboardViewMode } from '../selectors/types'; -import { DashboardGrid } from './dashboard_grid'; -import { SavedDashboardPanelMap } from '../types'; - -interface DashboardGridContainerStateProps { - panels: SavedDashboardPanelMap; - dashboardViewMode: DashboardViewMode; - useMargins: boolean; -} - -interface DashboardGridContainerDispatchProps { - onPanelsUpdated(updatedPanels: SavedDashboardPanelMap): void; -} - -const mapStateToProps = ({ dashboard }: any): any => ({ - panels: getPanels(dashboard), - dashboardViewMode: getViewMode(dashboard), - useMargins: getUseMargins(dashboard), -}); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - onPanelsUpdated: (updatedPanels: SavedDashboardPanelMap) => dispatch(updatePanels(updatedPanels)), -}); - -export const DashboardGridContainer = connect< - DashboardGridContainerStateProps, - DashboardGridContainerDispatchProps ->( - mapStateToProps, - mapDispatchToProps -)(DashboardGrid); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.test.ts new file mode 100644 index 000000000000000..669c5d8e7a8e9b9 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.test.ts @@ -0,0 +1,150 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import '../np_core.test.mocks'; + +import { + convertSavedDashboardPanelToPanelState, + convertPanelStateToSavedDashboardPanel, +} from './embeddable_saved_object_converters'; +import { SavedDashboardPanel, Pre61SavedDashboardPanel } from '../types'; +import { DashboardPanelState } from '../../../../dashboard_embeddable_container/public'; +import { EmbeddableInput } from '../../../../embeddable_api/public'; + +interface CustomInput extends EmbeddableInput { + something: string; +} + +test('convertSavedDashboardPanelToPanelState', () => { + const savedDashboardPanel: SavedDashboardPanel = { + type: 'search', + embeddableConfig: { + something: 'hi!', + }, + id: 'savedObjectId', + panelIndex: '123', + gridData: { + x: 0, + y: 0, + h: 15, + w: 15, + i: '123', + }, + version: '7.0.0', + }; + + expect(convertSavedDashboardPanelToPanelState(savedDashboardPanel, true)).toEqual({ + gridData: { + x: 0, + y: 0, + h: 15, + w: 15, + i: '123', + }, + explicitInput: { + something: 'hi!', + id: '123', + }, + savedObjectId: 'savedObjectId', + type: 'search', + }); +}); + +test('convertPanelStateToSavedDashboardPanel', () => { + const dashboardPanel: DashboardPanelState = { + gridData: { + x: 0, + y: 0, + h: 15, + w: 15, + i: '123', + }, + savedObjectId: 'savedObjectId', + explicitInput: { + something: 'hi!', + id: '123', + }, + type: 'search', + }; + + expect(convertPanelStateToSavedDashboardPanel(dashboardPanel)).toEqual({ + type: 'search', + embeddableConfig: { + something: 'hi!', + }, + id: 'savedObjectId', + panelIndex: '123', + gridData: { + x: 0, + y: 0, + h: 15, + w: 15, + i: '123', + }, + version: '6.3.0', + }); +}); + +test('convertPanelStateToSavedDashboardPanel does not include undefined savedObjectId', () => { + const dashboardPanel: DashboardPanelState = { + gridData: { + x: 0, + y: 0, + h: 15, + w: 15, + i: '123', + }, + explicitInput: { + id: '123', + something: 'hi!', + }, + type: 'search', + }; + + expect(convertPanelStateToSavedDashboardPanel(dashboardPanel)).toEqual({ + type: 'search', + embeddableConfig: { + something: 'hi!', + }, + panelIndex: '123', + gridData: { + x: 0, + y: 0, + h: 15, + w: 15, + i: '123', + }, + version: '6.3.0', + }); +}); + +test('convert 6.0 panel state', () => { + const oldPanelState: Pre61SavedDashboardPanel = { + col: 1, + id: 'Visualization-MetricChart', + panelIndex: 1, + row: 1, + size_x: 6, + size_y: 3, + type: 'visualization', + }; + const converted = convertSavedDashboardPanelToPanelState(oldPanelState, false); + + expect((converted as DashboardPanelState).gridData.w).toBe(24); + expect((converted as DashboardPanelState).gridData.h).toBe(15); +}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.ts new file mode 100644 index 000000000000000..232ab22070e4051 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { omit } from 'lodash'; +import { DashboardPanelState } from 'plugins/dashboard_embeddable_container'; +import chrome from 'ui/chrome'; +import { SavedDashboardPanel, Pre61SavedDashboardPanel } from '../types'; +import { parseVersion, convertPanelDataPre63, convertPanelDataPre61 } from '../panel/panel_utils'; + +export function convertSavedDashboardPanelToPanelState( + savedDashboardPanel: SavedDashboardPanel | Pre61SavedDashboardPanel, + useMargins: boolean +): DashboardPanelState { + // panel version numbers added in 6.1. Any panel without version number is assumed to be 6.0.0 + const panelVersion = + 'version' in savedDashboardPanel + ? parseVersion(savedDashboardPanel.version) + : parseVersion('6.0.0'); + + let panel: SavedDashboardPanel; + if (panelVersion.major < 6 || (panelVersion.major === 6 && panelVersion.minor < 1)) { + panel = convertPanelDataPre61(savedDashboardPanel as Pre61SavedDashboardPanel, useMargins); + } else if (panelVersion.major < 6 || (panelVersion.major === 6 && panelVersion.minor < 3)) { + panel = convertPanelDataPre63(savedDashboardPanel as SavedDashboardPanel, useMargins); + } else { + panel = savedDashboardPanel as SavedDashboardPanel; + } + + return { + type: savedDashboardPanel.type, + gridData: panel.gridData, + ...(savedDashboardPanel.id !== undefined && { savedObjectId: savedDashboardPanel.id }), + explicitInput: { + id: savedDashboardPanel.panelIndex, + ...(panel.title !== undefined && { title: panel.title }), + ...panel.embeddableConfig, + }, + }; +} + +export function convertPanelStateToSavedDashboardPanel( + panelState: DashboardPanelState +): SavedDashboardPanel { + const customTitle: string | undefined = panelState.explicitInput.title + ? (panelState.explicitInput.title as string) + : undefined; + return { + version: chrome.getKibanaVersion(), + type: panelState.type, + gridData: panelState.gridData, + panelIndex: panelState.explicitInput.id, + embeddableConfig: omit(panelState.explicitInput, 'id'), + ...(customTitle && { title: customTitle }), + ...(panelState.savedObjectId !== undefined && { id: panelState.savedObjectId }), + }; +} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/get_app_state_defaults.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/get_app_state_defaults.test.ts new file mode 100644 index 000000000000000..ab44e6002ef248e --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/get_app_state_defaults.test.ts @@ -0,0 +1,60 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import '../np_core.test.mocks'; + +import { getSavedDashboardMock } from '../__tests__'; +import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard'; +import { getAppStateDefaults } from './get_app_state_defaults'; + +test('getAppStateDefaults migrates old uiState into embeddableConfig', () => { + const savedDashboard: SavedObjectDashboard = getSavedDashboardMock({ + panelsJSON: '[{ "panelIndex": "1", "col": 0, "row": 0}]', + uiStateJSON: '{ "P-1": {"hi": "bye"}}', + }); + + const defaults = getAppStateDefaults(savedDashboard, false); + const panel = defaults.panels.find(p => p.panelIndex === '1'); + + expect(panel).toBeDefined(); + if (panel) { + expect(panel.embeddableConfig.hi).toBe('bye'); + } + expect(savedDashboard.uiStateJSON).toBeUndefined(); +}); + +test('getAppStateDefaults migrates columns and sort into embeddableConfig', () => { + const savedDashboard: SavedObjectDashboard = getSavedDashboardMock({ + panelsJSON: '[{ "panelIndex": "1", "columns": ["hi"], "sort": "hi", "version": "6.3.1." }]', + uiStateJSON: '{ "P-1": {"see": "shells"}}', + }); + + const defaults = getAppStateDefaults(savedDashboard, false); + const panel = defaults.panels.find(p => p.panelIndex === '1'); + + expect(panel).toBeDefined(); + if (panel) { + expect(panel.embeddableConfig.see).toBe('shells'); + expect(panel.embeddableConfig.columns).toEqual(['hi']); + expect(panel.embeddableConfig.sort).toEqual('hi'); + // @ts-ignore + expect(panel.sort).toBeUndefined(); + // @ts-ignore + expect(panel.columns).toBeUndefined(); + } +}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/get_app_state_defaults.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/get_app_state_defaults.ts index 7a38d8d6d1d2935..c20812cfeb876a2 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/get_app_state_defaults.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/get_app_state_defaults.ts @@ -17,54 +17,88 @@ * under the License. */ -import { DashboardViewMode } from '../dashboard_view_mode'; -import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard'; +import { ViewMode } from '../../../../embeddable_api/public'; import { + DashboardAppStateDefaults, + SavedDashboardPanel, Pre61SavedDashboardPanel, Pre64SavedDashboardPanel, - DashboardAppStateParameters, } from '../types'; +import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard'; +import { parseVersion, convertPanelDataPre61, convertPanelDataPre63 } from '../panel/panel_utils'; +/** + * Does not actually convert the panels into the latest format + * @param savedDashboard + * @param hideWriteControls + */ export function getAppStateDefaults( savedDashboard: SavedObjectDashboard, hideWriteControls: boolean -): DashboardAppStateParameters { - const appState = { - fullScreenMode: false, - title: savedDashboard.title, - description: savedDashboard.description || '', - timeRestore: savedDashboard.timeRestore, - panels: savedDashboard.panelsJSON ? JSON.parse(savedDashboard.panelsJSON) : [], - options: savedDashboard.optionsJSON ? JSON.parse(savedDashboard.optionsJSON) : {}, - query: savedDashboard.getQuery(), - filters: savedDashboard.getFilters(), - viewMode: - savedDashboard.id || hideWriteControls ? DashboardViewMode.VIEW : DashboardViewMode.EDIT, - }; +): DashboardAppStateDefaults { + const options: { + useMargins: boolean; + hidePanelTitles: boolean; + } = savedDashboard.optionsJSON + ? JSON.parse(savedDashboard.optionsJSON) + : { useMargins: true, hidePanelTitles: false }; + + const panels: Array< + SavedDashboardPanel | Pre61SavedDashboardPanel | Pre64SavedDashboardPanel + > = savedDashboard.panelsJSON ? JSON.parse(savedDashboard.panelsJSON) : []; + + const convertedPanels: SavedDashboardPanel[] = []; - // For BWC in pre 6.1 versions where uiState was stored at the dashboard level, not at the panel level. - // TODO: introduce a migration for this - if (savedDashboard.uiStateJSON) { - const uiState = JSON.parse(savedDashboard.uiStateJSON); - appState.panels.forEach((panel: Pre61SavedDashboardPanel) => { - panel.embeddableConfig = uiState[`P-${panel.panelIndex}`]; - }); - delete savedDashboard.uiStateJSON; - } + const uiState = savedDashboard.uiStateJSON ? JSON.parse(savedDashboard.uiStateJSON) : {}; - // For BWC of pre 6.4 where search embeddables stored state directly on the panel and not under embeddableConfig. - // TODO: introduce a migration for this - appState.panels.forEach((panel: Pre64SavedDashboardPanel) => { - if (panel.columns || panel.sort) { - panel.embeddableConfig = { - ...panel.embeddableConfig, - columns: panel.columns, - sort: panel.sort, + panels.forEach(panel => { + // For BWC in pre 6.1 versions where uiState was stored at the dashboard level, not at the panel level. + // TODO: introduce a migration for this + const embeddableConfig = savedDashboard.uiStateJSON + ? uiState[`P-${panel.panelIndex}`] + : (panel as SavedDashboardPanel).embeddableConfig; + + // For BWC of pre 6.4 where search embeddables stored state directly on the panel and not under embeddableConfig. + // TODO: introduce a migration for this + const pre64Panel = panel as Pre64SavedDashboardPanel; + if (pre64Panel.columns || pre64Panel.sort) { + embeddableConfig.columns = pre64Panel.columns; + embeddableConfig.sort = pre64Panel.sort; + delete pre64Panel.sort; + delete pre64Panel.columns; + } + + // Panel version numbers added in 6.1. Any panel without version number is assumed to be 6.0.0 + const panelVersion = 'version' in panel ? parseVersion(panel.version) : parseVersion('6.0.0'); + + let latestPanel: SavedDashboardPanel; + if (panelVersion.major < 6 || (panelVersion.major === 6 && panelVersion.minor < 1)) { + latestPanel = { + ...convertPanelDataPre61(panel as Pre61SavedDashboardPanel, options.useMargins), + embeddableConfig, }; - delete panel.columns; - delete panel.sort; + } else if (panelVersion.major < 6 || (panelVersion.major === 6 && panelVersion.minor < 3)) { + latestPanel = convertPanelDataPre63( + { ...panel, embeddableConfig } as SavedDashboardPanel, + options.useMargins + ); + } else { + latestPanel = { ...panel, embeddableConfig } as SavedDashboardPanel; } + + convertedPanels.push(latestPanel); }); - return appState; + delete savedDashboard.uiStateJSON; + return { + fullScreenMode: false, + title: savedDashboard.title, + description: savedDashboard.description, + timeRestore: savedDashboard.timeRestore, + panels: convertedPanels, + options, + query: savedDashboard.getQuery(), + filters: savedDashboard.getFilters(), + viewMode: savedDashboard.id || hideWriteControls ? ViewMode.VIEW : ViewMode.EDIT, + }; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.test.ts new file mode 100644 index 000000000000000..2702d44a78a9565 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.test.ts @@ -0,0 +1,56 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import '../np_core.test.mocks'; + +import { SavedDashboardPanel } from '../types'; +import { migrateAppState } from './migrate_app_state'; + +test('migrate app state from 6.0', async () => { + const mockSave = jest.fn(); + const appState = { + uiState: { + 'P-1': { vis: { defaultColors: { '0+-+100': 'rgb(0,104,55)' } } }, + }, + panels: [ + { + col: 1, + id: 'Visualization-MetricChart', + panelIndex: 1, + row: 1, + size_x: 6, + size_y: 3, + type: 'visualization', + }, + ], + translateHashToRison: () => 'a', + getQueryParamName: () => 'a', + save: mockSave, + }; + migrateAppState(appState); + expect(appState.uiState).toBeUndefined(); + expect(((appState.panels[0] as unknown) as SavedDashboardPanel).gridData.w).toBe(24); + expect(((appState.panels[0] as unknown) as SavedDashboardPanel).gridData.h).toBe(15); + expect( + ((appState.panels[0] as unknown) as SavedDashboardPanel).embeddableConfig.vis.defaultColors[ + '0+-+100' + ] + ).toBe('rgb(0,104,55)'); + expect(mockSave).toBeCalledTimes(1); +}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.ts index 1ff2596a546b03c..4326411e1d9871a 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.ts @@ -17,20 +17,43 @@ * under the License. */ -import { SavedDashboardPanel, DashboardAppState } from '../types'; +import { AppState } from 'ui/state_management/app_state'; +import { convertPanelDataPre61, convertPanelDataPre63, parseVersion } from '../panel/panel_utils'; +import { Pre61SavedDashboardPanel, SavedDashboardPanel } from '../types'; /** * Creates a new instance of AppState based of the saved dashboard. * * @param appState {AppState} AppState class to instantiate */ -export function migrateAppState(appState: DashboardAppState) { +export function migrateAppState(appState: AppState) { // For BWC in pre 6.1 versions where uiState was stored at the dashboard level, not at the panel level. if (appState.uiState) { - appState.panels.forEach((panel: SavedDashboardPanel) => { - panel.embeddableConfig = appState.uiState[`P-${panel.panelIndex}`]; - }); + appState.panels = appState.panels.map( + (panel: SavedDashboardPanel | Pre61SavedDashboardPanel) => { + // Panel version numbers added in 6.1. Any panel without version number is assumed to be 6.0.0 + const panelVersion = + 'version' in panel ? parseVersion(panel.version) : parseVersion('6.0.0'); + + if (panelVersion.major < 6 || (panelVersion.major === 6 && panelVersion.minor < 1)) { + return { + ...convertPanelDataPre61(panel as Pre61SavedDashboardPanel, appState.useMargins), + embeddableConfig: appState.uiState[`P-${panel.panelIndex}`], + }; + } + + if (panelVersion.major < 6 || (panelVersion.major === 6 && panelVersion.minor < 3)) { + return { + ...convertPanelDataPre63(panel as SavedDashboardPanel, appState.useMargins), + embeddableConfig: appState.uiState[`P-${panel.panelIndex}`], + }; + } + + return panel; + } + ); delete appState.uiState; + appState.save(); } } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_core.test.mocks.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_core.test.mocks.ts new file mode 100644 index 000000000000000..fff5aeab599eac1 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_core.test.mocks.ts @@ -0,0 +1,65 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { fatalErrorsServiceMock, notificationServiceMock } from '../../../../../core/public/mocks'; + +let modalContents: React.Component; + +export const getModalContents = () => modalContents; + +jest.doMock('ui/new_platform', () => { + return { + npStart: { + core: { + overlays: { + openFlyout: jest.fn(), + openModal: (component: React.Component) => { + modalContents = component; + return { + close: jest.fn(), + }; + }, + }, + }, + }, + npSetup: { + core: { + fatalErrors: fatalErrorsServiceMock.createSetupContract(), + notifications: notificationServiceMock.createSetupContract(), + }, + }, + }; +}); + +jest.doMock('ui/metadata', () => ({ + metadata: { + branch: 'my-metadata-branch', + version: 'my-metadata-version', + }, +})); + +jest.doMock('ui/capabilities', () => ({ + uiCapabilities: { + visualize: { + save: true, + }, + }, +})); + +jest.doMock('ui/chrome', () => ({ getKibanaVersion: () => '6.3.0', setVisible: () => {} })); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/__snapshots__/dashboard_panel.test.tsx.snap b/src/legacy/core_plugins/kibana/public/dashboard/panel/__snapshots__/dashboard_panel.test.tsx.snap deleted file mode 100644 index 107bda0aea08bfe..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/__snapshots__/dashboard_panel.test.tsx.snap +++ /dev/null @@ -1,52 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DashboardPanel matches snapshot 1`] = ` -
-
-
- my embeddable title -
-
-
- -
-
-
-
-
-`; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/__tests__/panel_state.ts b/src/legacy/core_plugins/kibana/public/dashboard/panel/__tests__/panel_state.ts deleted file mode 100644 index 57a3d336c3dec53..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/__tests__/panel_state.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { createPanelState } from '../panel_state'; -import { SavedDashboardPanel } from '../../types'; - -function createPanelWithDimensions( - x: number, - y: number, - w: number, - h: number -): SavedDashboardPanel { - return { - id: 'foo', - version: '6.3.0', - type: 'bar', - panelIndex: 'test', - title: 'test title', - gridData: { - x, - y, - w, - h, - i: 'an id', - }, - embeddableConfig: {}, - }; -} - -describe('Panel state', () => { - it('finds a spot on the right', () => { - // Default setup after a single panel, of default size, is on the grid - const panels = [createPanelWithDimensions(0, 0, 24, 30)]; - - const panel = createPanelState('1', 'a type', '1', panels); - expect(panel.gridData.x).to.equal(24); - expect(panel.gridData.y).to.equal(0); - }); - - it('finds a spot on the right when the panel is taller than any other panel on the grid', () => { - // Should be a little empty spot on the right. - const panels = [ - createPanelWithDimensions(0, 0, 24, 45), - createPanelWithDimensions(24, 0, 24, 30), - ]; - - const panel = createPanelState('1', 'a type', '1', panels); - expect(panel.gridData.x).to.equal(24); - expect(panel.gridData.y).to.equal(30); - }); - - it('finds an empty spot in the middle of the grid', () => { - const panels = [ - createPanelWithDimensions(0, 0, 48, 5), - createPanelWithDimensions(0, 5, 4, 30), - createPanelWithDimensions(40, 5, 4, 30), - createPanelWithDimensions(0, 55, 48, 5), - ]; - - const panel = createPanelState('1', 'a type', '1', panels); - expect(panel.gridData.x).to.equal(4); - expect(panel.gridData.y).to.equal(5); - }); -}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/_dashboard_panel.scss b/src/legacy/core_plugins/kibana/public/dashboard/panel/_dashboard_panel.scss deleted file mode 100644 index 8f860151478f096..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/_dashboard_panel.scss +++ /dev/null @@ -1,190 +0,0 @@ -/** - * EDITING MODE - * Use .dshLayout--editing to target editing state because - * .dshPanel--editing doesn't get updating without a hard refresh - */ - -.dshPanel { - z-index: auto; - flex: 1; - display: flex; - flex-direction: column; - height: 100%; - position: relative; - - // SASSTODO: The inheritence factor stemming from embeddables makes this class hard to change - .panel-content { - display: flex; - flex: 1 1 100%; - height: auto; - z-index: 1; - min-height: 0; // Absolute must for Firefox to scroll contents - } - - // SASSTODO: Pretty sure this doesn't do anything since the flex-basis 100%, - // but it MIGHT be fixing IE - .panel-content--fullWidth { - width: 100%; - } - - .panel-content-isLoading { - // completely center the loading indicator - justify-content: center; - align-items: center; - } - - /** - * 1. We want the kbnDocTable__container to scroll only when embedded in a dashboard panel - * 2. Fix overflow of vis's specifically for inside dashboard panels, lets the panel decide the overflow - * 3. Force a better looking scrollbar - */ - .kbnDocTable__container { - @include euiScrollBar; /* 3 */ - flex: 1 1 0; /* 1 */ - overflow: auto; /* 1 */ - } - - .visualization { - @include euiScrollBar; /* 3 */ - } - - .visualization .visChart__container { - overflow: visible; /* 2 */ - } - - .visLegend__toggle { - border-bottom-right-radius: 0; - border-top-left-radius: 0; - } -} - -.dshLayout--editing .dshPanel { - border-style: dashed; - border-color: $euiColorMediumShade; - transition: all $euiAnimSpeedFast $euiAnimSlightResistance; - - &:hover, - &:focus { - @include euiSlightShadowHover; - } -} - -// LAYOUT MODES - -// Adjust borders/etc... for non-spaced out and expanded panels -.dshLayout-withoutMargins, -.dshDashboardGrid__item--expanded { - .dshPanel { - box-shadow: none; - border-radius: 0; - } -} - -// Remove border color unless in editing mode -.dshLayout-withoutMargins:not(.dshLayout--editing), -.dshDashboardGrid__item--expanded { - .dshPanel { - border-color: transparent; - } -} - -// HEADER - -.dshPanel__header { - flex: 0 0 auto; - display: flex; - // ensure menu button is on the right even if the title doesn't exist - justify-content: flex-end; -} - -.dshPanel__title { - @include euiTextTruncate; - @include euiTitle('xxxs'); - line-height: 1.5; - flex-grow: 1; - - &:not(:empty) { - padding: ($euiSizeXS * 1.5) $euiSizeS 0; - } -} - -.dshLayout--editing { - .dshPanel__dragger { - transition: background-color $euiAnimSpeedFast $euiAnimSlightResistance; - } - - .dshPanel__dragger:hover { - background-color: $dshEditingModeHoverColor; - cursor: move; - } -} - -.dshPanel__dragger:not(.dshPanel__title) { - flex-grow: 1; -} - -.dshPanel__header--floater { - position: absolute; - right: 0; - top: 0; - left: 0; - z-index: $euiZLevel1; -} - -// OPTIONS MENU - -/** - * 1. Use opacity to make this element accessible to screen readers and keyboard. - * 2. Show on focus to enable keyboard accessibility. - * 3. Always show in editing mode - */ - -.dshPanel_optionsMenuButton { - background-color: transparentize($euiColorDarkestShade, .9); - border-bottom-right-radius: 0; - border-top-left-radius: 0; - - &:focus { - background-color: $euiFocusBackgroundColor; - } -} - -.dshPanel .visLegend__toggle, -.dshPanel_optionsMenuButton { - opacity: 0; /* 1 */ - - &:focus { - opacity: 1; /* 2 */ - } -} - -.dshPanel_optionsMenuPopover[class*="-isOpen"], -.dshPanel:hover { - .dshPanel_optionsMenuButton, - .visLegend__toggle { - opacity: 1; - } -} - -.dshLayout--editing { - .dshPanel_optionsMenuButton, - .dshPanel .visLegend__toggle { - opacity: 1; /* 3 */ - } -} - - -// ERROR - -.dshPanel__error { - text-align: center; - justify-content: center; - flex-direction: column; - overflow: auto; - text-align: center; - - .fa-exclamation-triangle { - font-size: $euiFontSizeXL; - color: $euiColorDanger; - } -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/_index.scss b/src/legacy/core_plugins/kibana/public/dashboard/panel/_index.scss deleted file mode 100644 index 7fed11fe9db9d7c..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import "./dashboard_panel"; -@import 'panel_header/panel_options_menu_form'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/dashboard_panel.test.tsx b/src/legacy/core_plugins/kibana/public/dashboard/panel/dashboard_panel.test.tsx deleted file mode 100644 index e6e0cf292e65543..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/dashboard_panel.test.tsx +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// TODO: remove this when EUI supports types for this. -// @ts-ignore: implicit any for JS file -import { takeMountedSnapshot } from '@elastic/eui/lib/test'; -import _ from 'lodash'; -import React from 'react'; -import { Provider } from 'react-redux'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { store } from '../../store'; -// @ts-ignore: implicit any for JS file -import { getEmbeddableFactoryMock } from '../__tests__/get_embeddable_factories_mock'; -import { embeddableIsInitialized, setPanels, updateTimeRange, updateViewMode } from '../actions'; -import { DashboardViewMode } from '../dashboard_view_mode'; -import { DashboardPanel, DashboardPanelUiProps } from './dashboard_panel'; - -import { PanelError } from './panel_error'; - -function getProps(props = {}): DashboardPanelUiProps { - const defaultTestProps = { - panel: { panelIndex: 'foo1' }, - viewOnlyMode: false, - initialized: true, - lastReloadRequestTime: 0, - embeddableFactory: getEmbeddableFactoryMock(), - }; - return _.defaultsDeep(props, defaultTestProps); -} - -beforeAll(() => { - store.dispatch(updateTimeRange({ to: 'now', from: 'now-15m' })); - store.dispatch(updateViewMode(DashboardViewMode.EDIT)); - store.dispatch( - setPanels({ - foo1: { - panelIndex: 'foo1', - id: 'hi', - version: '123', - type: 'viz', - embeddableConfig: {}, - gridData: { - x: 1, - y: 1, - w: 1, - h: 1, - i: 'hi', - }, - }, - }) - ); - const metadata = { title: 'my embeddable title', editUrl: 'editme' }; - store.dispatch(embeddableIsInitialized({ metadata, panelId: 'foo1' })); -}); - -test('DashboardPanel matches snapshot', () => { - const component = mountWithIntl( - - - - ); - expect(takeMountedSnapshot(component)).toMatchSnapshot(); -}); - -test('renders an error when error prop is passed', () => { - const props = getProps({ - error: 'Simulated error', - }); - - const component = mountWithIntl( - - - - ); - const panelError = component.find(PanelError); - expect(panelError.length).toBe(1); -}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/dashboard_panel.tsx b/src/legacy/core_plugins/kibana/public/dashboard/panel/dashboard_panel.tsx deleted file mode 100644 index 0d30494b1dab8f5..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/dashboard_panel.tsx +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { EuiLoadingChart, EuiPanel } from '@elastic/eui'; -import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import classNames from 'classnames'; -import _ from 'lodash'; -import React from 'react'; -import { - ContainerState, - Embeddable, - EmbeddableFactory, - EmbeddableMetadata, - EmbeddableState, -} from 'ui/embeddable'; -import { EmbeddableErrorAction } from '../actions'; -import { PanelId } from '../selectors'; -import { PanelError } from './panel_error'; -import { PanelHeader } from './panel_header'; -import { SavedDashboardPanel } from '../types'; - -export interface DashboardPanelProps { - viewOnlyMode: boolean; - onPanelFocused?: (panelIndex: PanelId) => void; - onPanelBlurred?: (panelIndex: PanelId) => void; - error?: string | object; - destroy: () => void; - containerState: ContainerState; - embeddableFactory: EmbeddableFactory; - lastReloadRequestTime?: number; - embeddableStateChanged: (embeddableStateChanges: EmbeddableState) => void; - embeddableIsInitialized: (embeddableIsInitializing: EmbeddableMetadata) => void; - embeddableError: (errorMessage: EmbeddableErrorAction) => void; - embeddableIsInitializing: () => void; - initialized: boolean; - panel: SavedDashboardPanel; - className?: string; -} - -export interface DashboardPanelUiProps extends DashboardPanelProps { - intl: InjectedIntl; -} - -interface State { - error: string | null; -} - -class DashboardPanelUi extends React.Component { - [panel: string]: any; - public mounted: boolean; - public embeddable!: Embeddable; - constructor(props: DashboardPanelUiProps) { - super(props); - this.state = { - error: props.embeddableFactory - ? null - : props.intl.formatMessage({ - id: 'kbn.dashboard.panel.noEmbeddableFactoryErrorMessage', - defaultMessage: 'The feature to render this panel is missing.', - }), - }; - - this.mounted = false; - } - - public async componentDidMount() { - this.mounted = true; - const { - initialized, - embeddableFactory, - embeddableIsInitializing, - panel, - embeddableStateChanged, - embeddableIsInitialized, - embeddableError, - } = this.props; - - if (!initialized) { - embeddableIsInitializing(); - embeddableFactory - .create(panel, embeddableStateChanged) - .then((embeddable: Embeddable) => { - if (this.mounted) { - this.embeddable = embeddable; - embeddableIsInitialized(embeddable.metadata); - this.embeddable.render(this.panelElement, this.props.containerState); - } else { - embeddable.destroy(); - } - }) - .catch((error: { message: EmbeddableErrorAction }) => { - if (this.mounted) { - embeddableError(error.message); - } - }); - } - } - - public componentWillUnmount() { - this.props.destroy(); - this.mounted = false; - if (this.embeddable) { - this.embeddable.destroy(); - } - } - - public onFocus = () => { - const { onPanelFocused, panel } = this.props; - if (onPanelFocused) { - onPanelFocused(panel.panelIndex); - } - }; - - public onBlur = () => { - const { onPanelBlurred, panel } = this.props; - if (onPanelBlurred) { - onPanelBlurred(panel.panelIndex); - } - }; - - public renderEmbeddableViewport() { - const classes = classNames('panel-content', { - 'panel-content-isLoading': !this.props.initialized, - }); - - return ( -
(this.panelElement = panelElement)} - > - {!this.props.initialized && } -
- ); - } - - public shouldComponentUpdate(nextProps: DashboardPanelUiProps) { - if (this.embeddable && !_.isEqual(nextProps.containerState, this.props.containerState)) { - this.embeddable.onContainerStateChanged(nextProps.containerState); - } - - if (this.embeddable && nextProps.lastReloadRequestTime !== this.props.lastReloadRequestTime) { - this.embeddable.reload(); - } - - return nextProps.error !== this.props.error || nextProps.initialized !== this.props.initialized; - } - - public renderEmbeddedError() { - return ; - } - - public renderContent() { - const { error } = this.props; - if (error) { - return this.renderEmbeddedError(); - } else { - return this.renderEmbeddableViewport(); - } - } - - public render() { - const { viewOnlyMode, panel } = this.props; - const classes = classNames('dshPanel', this.props.className, { - 'dshPanel--editing': !viewOnlyMode, - }); - return ( - - - - {this.renderContent()} - - ); - } -} - -export const DashboardPanel = injectI18n(DashboardPanelUi); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.test.tsx b/src/legacy/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.test.tsx deleted file mode 100644 index 939bf4c04ae0e14..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.test.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import React from 'react'; -import { Provider } from 'react-redux'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { store } from '../../store'; -// @ts-ignore: implicit for any JS file -import { getEmbeddableFactoryMock } from '../__tests__/get_embeddable_factories_mock'; -import { setPanels, updateTimeRange, updateViewMode } from '../actions'; -import { DashboardViewMode } from '../dashboard_view_mode'; -import { PanelError } from '../panel/panel_error'; -import { - DashboardPanelContainer, - DashboardPanelContainerOwnProps, -} from './dashboard_panel_container'; - -function getProps(props = {}): DashboardPanelContainerOwnProps { - const defaultTestProps = { - panelId: 'foo1', - embeddableFactory: getEmbeddableFactoryMock(), - }; - return _.defaultsDeep(props, defaultTestProps); -} - -beforeAll(() => { - store.dispatch(updateViewMode(DashboardViewMode.EDIT)); - store.dispatch(updateTimeRange({ to: 'now', from: 'now-15m' })); - store.dispatch( - setPanels({ - foo1: { - panelIndex: 'foo1', - id: 'hi', - version: '123', - type: 'viz', - embeddableConfig: {}, - gridData: { - x: 1, - y: 1, - w: 1, - h: 1, - i: 'hi', - }, - }, - }) - ); -}); - -test('renders an error when embeddableFactory.create throws an error', done => { - const props = getProps(); - props.embeddableFactory.create = () => { - return new Promise(() => { - throw new Error('simulated error'); - }); - }; - const component = mountWithIntl( - - - - ); - setTimeout(() => { - component.update(); - const panelError = component.find(PanelError); - expect(panelError.length).toBe(1); - done(); - }, 0); -}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.ts b/src/legacy/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.ts deleted file mode 100644 index 100d7ba2806b7cd..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.ts +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import { connect } from 'react-redux'; -import { Action } from 'redux-actions'; -import { ThunkDispatch } from 'redux-thunk'; -import { - ContainerState, - EmbeddableFactory, - EmbeddableMetadata, - EmbeddableState, -} from 'ui/embeddable'; -import { CoreKibanaState } from '../../selectors'; -import { - deletePanel, - embeddableError, - EmbeddableErrorAction, - embeddableIsInitialized, - embeddableIsInitializing, - embeddableStateChanged, -} from '../actions'; -import { DashboardViewMode } from '../dashboard_view_mode'; -import { - getContainerState, - getEmbeddable, - getEmbeddableError, - getEmbeddableInitialized, - getFullScreenMode, - getPanel, - getPanelType, - getViewMode, - PanelId, -} from '../selectors'; -import { DashboardPanel } from './dashboard_panel'; -import { SavedDashboardPanel } from '../types'; - -export interface DashboardPanelContainerOwnProps { - panelId: PanelId; - embeddableFactory: EmbeddableFactory; -} - -interface DashboardPanelContainerStateProps { - error?: string | object; - viewOnlyMode: boolean; - containerState: ContainerState; - initialized: boolean; - panel: SavedDashboardPanel; - lastReloadRequestTime?: number; -} - -export interface DashboardPanelContainerDispatchProps { - destroy: () => void; - embeddableIsInitializing: () => void; - embeddableIsInitialized: (metadata: EmbeddableMetadata) => void; - embeddableStateChanged: (embeddableState: EmbeddableState) => void; - embeddableError: (errorMessage: EmbeddableErrorAction) => void; -} - -const mapStateToProps = ( - { dashboard }: CoreKibanaState, - { embeddableFactory, panelId }: DashboardPanelContainerOwnProps -) => { - const embeddable = getEmbeddable(dashboard, panelId); - let error = null; - if (!embeddableFactory) { - const panelType = getPanelType(dashboard, panelId); - error = i18n.translate('kbn.dashboard.panel.noFoundEmbeddableFactoryErrorMessage', { - defaultMessage: 'No embeddable factory found for panel type {panelType}', - values: { panelType }, - }); - } else { - error = (embeddable && getEmbeddableError(dashboard, panelId)) || ''; - } - const lastReloadRequestTime = embeddable ? embeddable.lastReloadRequestTime : 0; - const initialized = embeddable ? getEmbeddableInitialized(dashboard, panelId) : false; - return { - error, - viewOnlyMode: getFullScreenMode(dashboard) || getViewMode(dashboard) === DashboardViewMode.VIEW, - containerState: getContainerState(dashboard, panelId), - initialized, - panel: getPanel(dashboard, panelId), - lastReloadRequestTime, - }; -}; - -const mapDispatchToProps = ( - dispatch: ThunkDispatch>, - { panelId }: DashboardPanelContainerOwnProps -): DashboardPanelContainerDispatchProps => ({ - destroy: () => dispatch(deletePanel(panelId)), - embeddableIsInitializing: () => dispatch(embeddableIsInitializing(panelId)), - embeddableIsInitialized: (metadata: EmbeddableMetadata) => - dispatch(embeddableIsInitialized({ panelId, metadata })), - embeddableStateChanged: (embeddableState: EmbeddableState) => - dispatch(embeddableStateChanged({ panelId, embeddableState })), - embeddableError: (errorMessage: EmbeddableErrorAction) => - dispatch(embeddableError({ panelId, error: errorMessage })), -}); - -export const DashboardPanelContainer = connect< - DashboardPanelContainerStateProps, - DashboardPanelContainerDispatchProps, - DashboardPanelContainerOwnProps, - CoreKibanaState ->( - mapStateToProps, - mapDispatchToProps -)(DashboardPanel); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/panel/index.ts index e5a5ad18a94c27f..ebc556ff1c8a682 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/index.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/panel/index.ts @@ -17,5 +17,4 @@ * under the License. */ -export { DashboardPanelContainer as DashboardPanel } from './dashboard_panel_container'; -export { createPanelState } from './panel_state'; +export { convertPanelDataPre61, convertPanelDataPre63, parseVersion } from './panel_utils'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_error.tsx b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_error.tsx deleted file mode 100644 index 9345bb2c6322430..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_error.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui'; -import React from 'react'; - -export interface PanelErrorProps { - error: string | React.ReactNode; -} - -export function PanelError({ error }: PanelErrorProps) { - return ( -
- - - - {error} - -
- ); -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/_panel_options_menu_form.scss b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/_panel_options_menu_form.scss deleted file mode 100644 index e7fc3c59c3889de..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/_panel_options_menu_form.scss +++ /dev/null @@ -1,3 +0,0 @@ -.dshPanel__optionsMenuForm { - padding: $euiSize; -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/index.ts deleted file mode 100644 index 1ba52de585d1f55..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { PanelHeaderContainer as PanelHeader } from './panel_header_container'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_customize_panel_action.tsx b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_customize_panel_action.tsx deleted file mode 100644 index 065f073b0177d27..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_customize_panel_action.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { EuiIcon } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { ContextMenuAction, ContextMenuPanel } from 'ui/embeddable'; -import { DashboardViewMode } from '../../../dashboard_view_mode'; -import { PanelOptionsMenuForm } from '../panel_options_menu_form'; - -export function getCustomizePanelAction({ - onResetPanelTitle, - onUpdatePanelTitle, - closeContextMenu, - title, -}: { - onResetPanelTitle: () => void; - onUpdatePanelTitle: (title: string) => void; - closeContextMenu: () => void; - title?: string; -}): ContextMenuAction { - return new ContextMenuAction( - { - id: 'customizePanel', - parentPanelId: 'mainMenu', - }, - { - childContextMenuPanel: new ContextMenuPanel( - { - id: 'panelSubOptionsMenu', - title: i18n.translate('kbn.dashboard.panel.customizePanelTitle', { - defaultMessage: 'Customize panel', - }), - }, - { - getContent: () => ( - - ), - } - ), - icon: , - isVisible: ({ containerState }) => containerState.viewMode === DashboardViewMode.EDIT, - getDisplayName: () => { - return i18n.translate('kbn.dashboard.panel.customizePanel.displayName', { - defaultMessage: 'Customize panel', - }); - }, - } - ); -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_edit_panel_action.tsx b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_edit_panel_action.tsx deleted file mode 100644 index 4441b4101e961f3..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_edit_panel_action.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; - -import { EuiIcon } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { ContextMenuAction } from 'ui/embeddable'; -import { DashboardViewMode } from '../../../dashboard_view_mode'; - -/** - * - * @return {ContextMenuAction} - */ -export function getEditPanelAction() { - return new ContextMenuAction( - { - id: 'editPanel', - parentPanelId: 'mainMenu', - }, - { - icon: , - isDisabled: ({ embeddable }) => - !embeddable || !embeddable.metadata || !embeddable.metadata.editUrl, - isVisible: ({ containerState, embeddable }) => { - const canEditEmbeddable = Boolean( - embeddable && embeddable.metadata && embeddable.metadata.editable - ); - const inDashboardEditMode = containerState.viewMode === DashboardViewMode.EDIT; - return canEditEmbeddable && inDashboardEditMode; - }, - getHref: ({ embeddable }) => { - if (embeddable && embeddable.metadata.editUrl) { - return embeddable.metadata.editUrl; - } - }, - getDisplayName: ({ embeddable }) => { - if (embeddable && embeddable.metadata.editLabel) { - return embeddable.metadata.editLabel; - } - - return i18n.translate('kbn.dashboard.panel.editPanel.defaultDisplayName', { - defaultMessage: 'Edit', - }); - }, - } - ); -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_inspector_panel_action.tsx b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_inspector_panel_action.tsx deleted file mode 100644 index af8918fe9960950..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_inspector_panel_action.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { EuiIcon } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { ContextMenuAction } from 'ui/embeddable'; -import { Inspector } from 'ui/inspector'; - -/** - * Returns the dashboard panel action for opening an inspector for a specific panel. - * This will check if the embeddable inside the panel actually exposes inspector adapters - * via its embeddable.getInspectorAdapters() method. If so - and if an inspector - * could be shown for those adapters - the inspector icon will be visible. - * @return {ContextMenuAction} - */ -export function getInspectorPanelAction({ - closeContextMenu, - panelTitle, -}: { - closeContextMenu: () => void; - panelTitle?: string; -}) { - return new ContextMenuAction( - { - id: 'openInspector', - parentPanelId: 'mainMenu', - }, - { - getDisplayName: () => { - return i18n.translate('kbn.dashboard.panel.inspectorPanel.displayName', { - defaultMessage: 'Inspect', - }); - }, - icon: , - isVisible: ({ embeddable }) => { - if (!embeddable) { - return false; - } - return Inspector.isAvailable(embeddable.getInspectorAdapters()); - }, - onClick: ({ embeddable }) => { - if (!embeddable) { - return; - } - closeContextMenu(); - const adapters = embeddable.getInspectorAdapters(); - if (!adapters) { - return; - } - - const session = Inspector.open(adapters, { - title: panelTitle, - }); - // Overwrite the embeddables.destroy() function to close the inspector - // before calling the original destroy method - const originalDestroy = embeddable.destroy; - embeddable.destroy = () => { - session.close(); - if (originalDestroy) { - originalDestroy.call(embeddable); - } - }; - // In case the inspector gets closed (otherwise), restore the original destroy function - session.onClose.finally(() => { - embeddable.destroy = originalDestroy; - }); - }, - } - ); -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_remove_panel_action.tsx b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_remove_panel_action.tsx deleted file mode 100644 index 113079aaeb103d7..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_remove_panel_action.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { EuiIcon } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; - -import { ContextMenuAction } from 'ui/embeddable'; -import { DashboardViewMode } from '../../../dashboard_view_mode'; - -/** - * - * @param {function} onDeletePanel - * @return {ContextMenuAction} - */ -export function getRemovePanelAction(onDeletePanel: () => void) { - return new ContextMenuAction( - { - id: 'deletePanel', - parentPanelId: 'mainMenu', - }, - { - getDisplayName: () => { - return i18n.translate('kbn.dashboard.panel.removePanel.displayName', { - defaultMessage: 'Delete from dashboard', - }); - }, - icon: , - isVisible: ({ containerState }) => - containerState.viewMode === DashboardViewMode.EDIT && !containerState.isPanelExpanded, - onClick: onDeletePanel, - } - ); -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_toggle_expand_panel_action.tsx b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_toggle_expand_panel_action.tsx deleted file mode 100644 index 71dd5598cad263c..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_toggle_expand_panel_action.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { EuiIcon } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; - -import { ContextMenuAction } from 'ui/embeddable'; - -/** - * Returns an action that toggles the panel into maximized or minimized state. - * @param {boolean} isExpanded - * @param {function} toggleExpandedPanel - * @return {ContextMenuAction} - */ -export function getToggleExpandPanelAction({ - isExpanded, - toggleExpandedPanel, -}: { - isExpanded: boolean; - toggleExpandedPanel: () => void; -}) { - return new ContextMenuAction( - { - id: 'togglePanel', - parentPanelId: 'mainMenu', - }, - { - getDisplayName: () => { - return isExpanded - ? i18n.translate('kbn.dashboard.panel.toggleExpandPanel.expandedDisplayName', { - defaultMessage: 'Minimize', - }) - : i18n.translate('kbn.dashboard.panel.toggleExpandPanel.notExpandedDisplayName', { - defaultMessage: 'Full screen', - }); - }, - // TODO: Update to minimize icon when https://github.com/elastic/eui/issues/837 is complete. - icon: , - onClick: toggleExpandedPanel, - } - ); -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/index.ts deleted file mode 100644 index b4cc5ea82e94847..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { getEditPanelAction } from './get_edit_panel_action'; -export { getRemovePanelAction } from './get_remove_panel_action'; -export { getCustomizePanelAction } from './get_customize_panel_action'; -export { getToggleExpandPanelAction } from './get_toggle_expand_panel_action'; -export { getInspectorPanelAction } from './get_inspector_panel_action'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header.tsx b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header.tsx deleted file mode 100644 index 9f6d9bb309da090..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import classNames from 'classnames'; -import React from 'react'; -import { Embeddable } from 'ui/embeddable'; -import { PanelId } from '../../selectors'; -import { PanelOptionsMenuContainer } from './panel_options_menu_container'; - -export interface PanelHeaderProps { - title?: string; - panelId: PanelId; - embeddable?: Embeddable; - isViewOnlyMode: boolean; - hidePanelTitles: boolean; -} - -interface PanelHeaderUiProps extends PanelHeaderProps { - intl: InjectedIntl; -} - -function PanelHeaderUi({ - title, - panelId, - embeddable, - isViewOnlyMode, - hidePanelTitles, - intl, -}: PanelHeaderUiProps) { - const classes = classNames('dshPanel__header', { - 'dshPanel__header--floater': !title || hidePanelTitles, - }); - - if (isViewOnlyMode && (!title || hidePanelTitles)) { - return ( -
- -
- ); - } - - return ( -
-
- {hidePanelTitles ? '' : title} -
- - -
- ); -} - -export const PanelHeader = injectI18n(PanelHeaderUi); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.test.tsx b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.test.tsx deleted file mode 100644 index f395b17207be5e2..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.test.tsx +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { ReactWrapper } from 'enzyme'; -import _ from 'lodash'; -import React from 'react'; -import { Provider } from 'react-redux'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; - -// TODO: remove this when EUI supports types for this. -// @ts-ignore: implicit any for JS file -import { findTestSubject } from '@elastic/eui/lib/test'; - -import { store } from '../../../store'; -import { - embeddableIsInitialized, - resetPanelTitle, - setPanels, - setPanelTitle, - updateTimeRange, - updateViewMode, -} from '../../actions'; -import { DashboardViewMode } from '../../dashboard_view_mode'; -import { PanelHeaderContainer, PanelHeaderContainerOwnProps } from './panel_header_container'; - -function getProps(props = {}): PanelHeaderContainerOwnProps { - const defaultTestProps = { - panelId: 'foo1', - }; - return _.defaultsDeep(props, defaultTestProps); -} - -let component: ReactWrapper; - -beforeAll(() => { - store.dispatch(updateTimeRange({ to: 'now', from: 'now-15m' })); - store.dispatch(updateViewMode(DashboardViewMode.EDIT)); - store.dispatch( - setPanels({ - foo1: { - panelIndex: 'foo1', - id: 'hi', - version: '123', - type: 'viz', - embeddableConfig: {}, - gridData: { - x: 1, - y: 1, - w: 1, - h: 1, - i: 'hi', - }, - }, - }) - ); - const metadata = { title: 'my embeddable title', editUrl: 'editme' }; - store.dispatch(embeddableIsInitialized({ metadata, panelId: 'foo1' })); -}); - -afterAll(() => { - component.unmount(); -}); - -test('Panel header shows embeddable title when nothing is set on the panel', () => { - component = mountWithIntl( - - - - ); - expect(findTestSubject(component, 'dashboardPanelTitle').text()).toBe('my embeddable title'); -}); - -test('Panel header shows panel title when it is set on the panel', () => { - store.dispatch(setPanelTitle({ title: 'my custom panel title', panelId: 'foo1' })); - expect(findTestSubject(component, 'dashboardPanelTitle').text()).toBe('my custom panel title'); -}); - -test('Panel header shows no panel title when it is set to an empty string on the panel', () => { - store.dispatch(setPanelTitle({ title: '', panelId: 'foo1' })); - expect(findTestSubject(component, 'dashboardPanelTitle').text()).toBe(''); -}); - -test('Panel header shows embeddable title when the panel title is reset', () => { - store.dispatch(resetPanelTitle('foo1')); - expect(findTestSubject(component, 'dashboardPanelTitle').text()).toBe('my embeddable title'); -}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.ts b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.ts deleted file mode 100644 index ba25db6acf8e178..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { connect } from 'react-redux'; - -import { Embeddable } from 'ui/embeddable'; -import { DashboardViewMode } from '../../dashboard_view_mode'; -import { PanelHeader } from './panel_header'; - -import { CoreKibanaState } from '../../../selectors'; -import { - getEmbeddableTitle, - getFullScreenMode, - getHidePanelTitles, - getMaximizedPanelId, - getPanel, - getViewMode, - PanelId, -} from '../../selectors'; - -export interface PanelHeaderContainerOwnProps { - panelId: PanelId; - embeddable?: Embeddable; -} - -interface PanelHeaderContainerStateProps { - title?: string; - isExpanded: boolean; - isViewOnlyMode: boolean; - hidePanelTitles: boolean; -} - -const mapStateToProps = ( - { dashboard }: CoreKibanaState, - { panelId }: PanelHeaderContainerOwnProps -) => { - const panel = getPanel(dashboard, panelId); - const embeddableTitle = getEmbeddableTitle(dashboard, panelId); - return { - title: panel.title === undefined ? embeddableTitle : panel.title, - isExpanded: getMaximizedPanelId(dashboard) === panelId, - isViewOnlyMode: - getFullScreenMode(dashboard) || getViewMode(dashboard) === DashboardViewMode.VIEW, - hidePanelTitles: getHidePanelTitles(dashboard), - }; -}; - -export const PanelHeaderContainer = connect< - PanelHeaderContainerStateProps, - {}, - PanelHeaderContainerOwnProps, - CoreKibanaState ->(mapStateToProps)(PanelHeader); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu.tsx b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu.tsx deleted file mode 100644 index a9ebf3ce42a51ff..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import React from 'react'; - -import { - EuiButtonIcon, - EuiContextMenu, - EuiContextMenuPanelDescriptor, - EuiPopover, -} from '@elastic/eui'; - -export interface PanelOptionsMenuProps { - toggleContextMenu: () => void; - isPopoverOpen: boolean; - closeContextMenu: () => void; - panels: EuiContextMenuPanelDescriptor[]; - isViewMode: boolean; -} - -interface PanelOptionsMenuUiProps extends PanelOptionsMenuProps { - intl: InjectedIntl; -} - -function PanelOptionsMenuUi({ - toggleContextMenu, - isPopoverOpen, - closeContextMenu, - panels, - isViewMode, - intl, -}: PanelOptionsMenuUiProps) { - const button = ( - - ); - - return ( - - - - ); -} - -export const PanelOptionsMenu = injectI18n(PanelOptionsMenuUi); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.ts b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.ts deleted file mode 100644 index 6b537a2a2c405a9..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.ts +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { EuiContextMenuPanelDescriptor } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { connect } from 'react-redux'; -import { - buildEuiContextMenuPanels, - ContainerState, - ContextMenuPanel, - Embeddable, -} from 'ui/embeddable'; -import { Dispatch } from 'redux'; -import { panelActionsStore } from '../../store/panel_actions_store'; -import { - getCustomizePanelAction, - getEditPanelAction, - getInspectorPanelAction, - getRemovePanelAction, - getToggleExpandPanelAction, -} from './panel_actions'; -import { PanelOptionsMenu, PanelOptionsMenuProps } from './panel_options_menu'; - -import { - closeContextMenu, - deletePanel, - maximizePanel, - minimizePanel, - resetPanelTitle, - setPanelTitle, - setVisibleContextMenuPanelId, -} from '../../actions'; - -import { CoreKibanaState } from '../../../selectors'; -import { DashboardViewMode } from '../../dashboard_view_mode'; -import { - getContainerState, - getEmbeddable, - getEmbeddableEditUrl, - getEmbeddableTitle, - getMaximizedPanelId, - getPanel, - getViewMode, - getVisibleContextMenuPanelId, - PanelId, -} from '../../selectors'; - -interface PanelOptionsMenuContainerDispatchProps { - onDeletePanel: () => void; - onCloseContextMenu: () => void; - openContextMenu: () => void; - onMaximizePanel: () => void; - onMinimizePanel: () => void; - onResetPanelTitle: () => void; - onUpdatePanelTitle: (title: string) => void; -} - -interface PanelOptionsMenuContainerOwnProps { - panelId: PanelId; - embeddable?: Embeddable; -} - -interface PanelOptionsMenuContainerStateProps { - panelTitle?: string; - editUrl: string | null | undefined; - isExpanded: boolean; - containerState: ContainerState; - visibleContextMenuPanelId: PanelId | undefined; - isViewMode: boolean; -} - -const mapStateToProps = ( - { dashboard }: CoreKibanaState, - { panelId }: PanelOptionsMenuContainerOwnProps -) => { - const embeddable = getEmbeddable(dashboard, panelId); - const panel = getPanel(dashboard, panelId); - const embeddableTitle = getEmbeddableTitle(dashboard, panelId); - const containerState = getContainerState(dashboard, panelId); - const visibleContextMenuPanelId = getVisibleContextMenuPanelId(dashboard); - const viewMode = getViewMode(dashboard); - return { - panelTitle: panel.title === undefined ? embeddableTitle : panel.title, - editUrl: embeddable ? getEmbeddableEditUrl(dashboard, panelId) : null, - isExpanded: getMaximizedPanelId(dashboard) === panelId, - containerState, - visibleContextMenuPanelId, - isViewMode: viewMode === DashboardViewMode.VIEW, - }; -}; - -/** - * @param dispatch {Function} - * @param embeddableFactory {EmbeddableFactory} - * @param panelId {string} - */ -const mapDispatchToProps = ( - dispatch: Dispatch, - { panelId }: PanelOptionsMenuContainerOwnProps -) => ({ - onDeletePanel: () => { - dispatch(deletePanel(panelId)); - }, - onCloseContextMenu: () => dispatch(closeContextMenu()), - openContextMenu: () => dispatch(setVisibleContextMenuPanelId(panelId)), - onMaximizePanel: () => dispatch(maximizePanel(panelId)), - onMinimizePanel: () => dispatch(minimizePanel()), - onResetPanelTitle: () => dispatch(resetPanelTitle(panelId)), - onUpdatePanelTitle: (newTitle: string) => dispatch(setPanelTitle({ title: newTitle, panelId })), -}); - -const mergeProps = ( - stateProps: PanelOptionsMenuContainerStateProps, - dispatchProps: PanelOptionsMenuContainerDispatchProps, - ownProps: PanelOptionsMenuContainerOwnProps -) => { - const { - isExpanded, - panelTitle, - containerState, - visibleContextMenuPanelId, - isViewMode, - } = stateProps; - const isPopoverOpen = visibleContextMenuPanelId === ownProps.panelId; - const { - onMaximizePanel, - onMinimizePanel, - onDeletePanel, - onResetPanelTitle, - onUpdatePanelTitle, - onCloseContextMenu, - openContextMenu, - } = dispatchProps; - const toggleContextMenu = () => (isPopoverOpen ? onCloseContextMenu() : openContextMenu()); - - // Outside click handlers will trigger for every closed context menu, we only want to react to clicks external to - // the currently opened menu. - const closeMyContextMenuPanel = () => { - if (isPopoverOpen) { - onCloseContextMenu(); - } - }; - - const toggleExpandedPanel = () => { - // eslint-disable-next-line no-unused-expressions - isExpanded ? onMinimizePanel() : onMaximizePanel(); - closeMyContextMenuPanel(); - }; - - let panels: EuiContextMenuPanelDescriptor[] = []; - - // Don't build the panels if the pop over is not open, or this gets expensive - this function is called once for - // every panel, every time any state changes. - if (isPopoverOpen) { - const contextMenuPanel = new ContextMenuPanel({ - title: i18n.translate('kbn.dashboard.panel.optionsMenu.optionsContextMenuTitle', { - defaultMessage: 'Options', - }), - id: 'mainMenu', - }); - - const actions = [ - getInspectorPanelAction({ - closeContextMenu: closeMyContextMenuPanel, - panelTitle, - }), - getEditPanelAction(), - getCustomizePanelAction({ - onResetPanelTitle, - onUpdatePanelTitle, - title: panelTitle, - closeContextMenu: closeMyContextMenuPanel, - }), - getToggleExpandPanelAction({ isExpanded, toggleExpandedPanel }), - getRemovePanelAction(onDeletePanel), - ].concat(panelActionsStore.actions); - - panels = buildEuiContextMenuPanels({ - contextMenuPanel, - actions, - embeddable: ownProps.embeddable, - containerState, - }); - } - - return { - panels, - toggleContextMenu, - closeContextMenu: closeMyContextMenuPanel, - isPopoverOpen, - isViewMode, - }; -}; - -export const PanelOptionsMenuContainer = connect< - PanelOptionsMenuContainerStateProps, - PanelOptionsMenuContainerDispatchProps, - PanelOptionsMenuContainerOwnProps, - PanelOptionsMenuProps, - CoreKibanaState ->( - mapStateToProps, - mapDispatchToProps, - mergeProps -)(PanelOptionsMenu); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_form.tsx b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_form.tsx deleted file mode 100644 index c80ab00a46b771e..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_form.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React, { ChangeEvent, KeyboardEvent } from 'react'; - -import { EuiButtonEmpty, EuiFieldText, EuiFormRow, keyCodes } from '@elastic/eui'; -import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; - -export interface PanelOptionsMenuFormProps { - title?: string; - onReset: () => void; - onUpdatePanelTitle: (newPanelTitle: string) => void; - onClose: () => void; -} - -interface PanelOptionsMenuFormUiProps extends PanelOptionsMenuFormProps { - intl: InjectedIntl; -} - -function PanelOptionsMenuFormUi({ - title, - onReset, - onUpdatePanelTitle, - onClose, - intl, -}: PanelOptionsMenuFormUiProps) { - function onInputChange(event: ChangeEvent) { - onUpdatePanelTitle(event.target.value); - } - - function onKeyDown(event: KeyboardEvent) { - if (event.keyCode === keyCodes.ENTER) { - onClose(); - } - } - - return ( -
- - - - - - - -
- ); -} - -export const PanelOptionsMenuForm = injectI18n(PanelOptionsMenuFormUi); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_state.test.ts deleted file mode 100644 index 3b821b005d07f20..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_state.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -jest.mock('ui/chrome', () => ({ getKibanaVersion: () => '6.0.0' }), { virtual: true }); - -import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../dashboard_constants'; -import { SavedDashboardPanel } from '../types'; -import { createPanelState } from './panel_state'; - -const panels: SavedDashboardPanel[] = []; - -test('createPanelState adds a new panel state in 0,0 position', () => { - const panelState = createPanelState('id', 'type', '1', panels); - expect(panelState.type).toBe('type'); - expect(panelState.gridData.x).toBe(0); - expect(panelState.gridData.y).toBe(0); - expect(panelState.gridData.h).toBe(DEFAULT_PANEL_HEIGHT); - expect(panelState.gridData.w).toBe(DEFAULT_PANEL_WIDTH); - - panels.push(panelState); -}); - -test('createPanelState adds a second new panel state', () => { - const panelState = createPanelState('id2', 'type', '2', panels); - expect(panelState.gridData.x).toBe(DEFAULT_PANEL_WIDTH); - expect(panelState.gridData.y).toBe(0); - expect(panelState.gridData.h).toBe(DEFAULT_PANEL_HEIGHT); - expect(panelState.gridData.w).toBe(DEFAULT_PANEL_WIDTH); - - panels.push(panelState); -}); - -test('createPanelState adds a third new panel state', () => { - const panelState = createPanelState('id3', 'type', '3', panels); - expect(panelState.gridData.x).toBe(0); - expect(panelState.gridData.y).toBe(DEFAULT_PANEL_HEIGHT); - expect(panelState.gridData.h).toBe(DEFAULT_PANEL_HEIGHT); - expect(panelState.gridData.w).toBe(DEFAULT_PANEL_WIDTH); - - panels.push(panelState); -}); - -test('createPanelState adds a new panel state in the top most position', () => { - const panelsWithEmptySpace = panels.filter(panel => panel.gridData.x === 0); - const panelState = createPanelState('id3', 'type', '3', panelsWithEmptySpace); - expect(panelState.gridData.x).toBe(DEFAULT_PANEL_WIDTH); - expect(panelState.gridData.y).toBe(0); - expect(panelState.gridData.h).toBe(DEFAULT_PANEL_HEIGHT); - expect(panelState.gridData.w).toBe(DEFAULT_PANEL_WIDTH); -}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_state.ts b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_state.ts deleted file mode 100644 index 105ae2ebf1fcb43..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_state.ts +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import chrome from 'ui/chrome'; -import { - DASHBOARD_GRID_COLUMN_COUNT, - DEFAULT_PANEL_HEIGHT, - DEFAULT_PANEL_WIDTH, -} from '../dashboard_constants'; -import { SavedDashboardPanel } from '../types'; - -// Look for the smallest y and x value where the default panel will fit. -function findTopLeftMostOpenSpace( - width: number, - height: number, - currentPanels: SavedDashboardPanel[] -) { - let maxY = -1; - - currentPanels.forEach(panel => { - maxY = Math.max(panel.gridData.y + panel.gridData.h, maxY); - }); - - // Handle case of empty grid. - if (maxY < 0) { - return { x: 0, y: 0 }; - } - - const grid = new Array(maxY); - for (let y = 0; y < maxY; y++) { - grid[y] = new Array(DASHBOARD_GRID_COLUMN_COUNT).fill(0); - } - - currentPanels.forEach(panel => { - for (let x = panel.gridData.x; x < panel.gridData.x + panel.gridData.w; x++) { - for (let y = panel.gridData.y; y < panel.gridData.y + panel.gridData.h; y++) { - const row = grid[y]; - if (row === undefined) { - throw new Error( - `Attempted to access a row that doesn't exist at ${y} for panel ${JSON.stringify( - panel - )}` - ); - } - grid[y][x] = 1; - } - } - }); - - for (let y = 0; y < maxY; y++) { - for (let x = 0; x < DASHBOARD_GRID_COLUMN_COUNT; x++) { - if (grid[y][x] === 1) { - // Space is filled - continue; - } else { - for (let h = y; h < Math.min(y + height, maxY); h++) { - for (let w = x; w < Math.min(x + width, DASHBOARD_GRID_COLUMN_COUNT); w++) { - const spaceIsEmpty = grid[h][w] === 0; - const fitsPanelWidth = w === x + width - 1; - // If the panel is taller than any other panel in the current grid, it can still fit in the space, hence - // we check the minimum of maxY and the panel height. - const fitsPanelHeight = h === Math.min(y + height - 1, maxY - 1); - - if (spaceIsEmpty && fitsPanelWidth && fitsPanelHeight) { - // Found space - return { x, y }; - } else if (grid[h][w] === 1) { - // x, y spot doesn't work, break. - break; - } - } - } - } - } - } - return { x: 0, y: maxY }; -} - -/** - * Creates and initializes a basic panel state. - */ -export function createPanelState( - id: string, - type: string, - panelIndex: string, - currentPanels: SavedDashboardPanel[] -) { - const { x, y } = findTopLeftMostOpenSpace( - DEFAULT_PANEL_WIDTH, - DEFAULT_PANEL_HEIGHT, - currentPanels - ); - return { - gridData: { - w: DEFAULT_PANEL_WIDTH, - h: DEFAULT_PANEL_HEIGHT, - x, - y, - i: panelIndex.toString(), - }, - version: chrome.getKibanaVersion(), - panelIndex: panelIndex.toString(), - type, - id, - embeddableConfig: {}, - }; -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_utils.test.js b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_utils.test.js index 9f410ffbedcd6e5..c3dade7120a1cde 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_utils.test.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_utils.test.js @@ -16,35 +16,33 @@ * specific language governing permissions and limitations * under the License. */ - -jest.mock( - 'ui/chrome', - () => ({ - getKibanaVersion: () => '6.3.0', - }), - { virtual: true } -); - -import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../dashboard_constants'; -import { PanelUtils } from './panel_utils'; -import { createPanelState } from './panel_state'; +import '../np_core.test.mocks'; +import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../../../../dashboard_embeddable_container/public'; +import { parseVersion, convertPanelDataPre61, convertPanelDataPre63 } from './panel_utils'; test('parseVersion', () => { - const { major, minor } = PanelUtils.parseVersion('6.2.0'); + const { major, minor } = parseVersion('6.2.0'); expect(major).toBe(6); expect(minor).toBe(2); }); -test('convertPanelDataPre_6_1 gives supplies width and height when missing', () => { - const panelData = [ +test('convertPanelDataPre61 gives supplies width and height when missing', () => { + const panelData = { col: 3, id: 'foo1', row: 1, type: 'visualization', panelIndex: 1, - gridData: createPanelState, - }, + }; + const newPanelData = convertPanelDataPre61(panelData, false); + expect(newPanelData.gridData.w).toBe(DEFAULT_PANEL_WIDTH); + expect(newPanelData.gridData.h).toBe(DEFAULT_PANEL_HEIGHT); + expect(newPanelData.version).toBe('6.3.0'); +}); + +test('convertPanelDataPre61 scales width and height', () => { + const panelData = { col: 3, id: 'foo2', @@ -53,20 +51,15 @@ test('convertPanelDataPre_6_1 gives supplies width and height when missing', () size_y: 2, type: 'visualization', panelIndex: 2, - gridData: createPanelState, - }, - ]; - panelData.forEach(oldPanel => PanelUtils.convertPanelDataPre_6_1(oldPanel)); - expect(panelData[0].gridData.w).toBe(DEFAULT_PANEL_WIDTH); - expect(panelData[0].gridData.h).toBe(DEFAULT_PANEL_HEIGHT); - expect(panelData[0].version).toBe('6.3.0'); - - expect(panelData[1].gridData.w).toBe(3); - expect(panelData[1].gridData.h).toBe(2); - expect(panelData[1].version).toBe('6.3.0'); + }; + const newPanelData = convertPanelDataPre61(panelData, false); + expect(newPanelData.gridData.w).toBe(12); + expect(newPanelData.gridData.h).toBe(10); + expect(newPanelData.version).toBe('6.3.0'); }); -test('convertPanelDataPre_6_3 scales panel dimensions', () => { + +test('convertPanelDataPre63 scales panel dimensions', () => { const oldPanel = { gridData: { h: 3, @@ -76,7 +69,7 @@ test('convertPanelDataPre_6_3 scales panel dimensions', () => { }, version: '6.2.0', }; - const updatedPanel = PanelUtils.convertPanelDataPre_6_3(oldPanel, false); + const updatedPanel = convertPanelDataPre63(oldPanel, false); expect(updatedPanel.gridData.w).toBe(28); expect(updatedPanel.gridData.h).toBe(15); expect(updatedPanel.gridData.x).toBe(8); @@ -94,7 +87,7 @@ test('convertPanelDataPre_6_3 with margins scales panel dimensions', () => { }, version: '6.2.0', }; - const updatedPanel = PanelUtils.convertPanelDataPre_6_3(oldPanel, true); + const updatedPanel = convertPanelDataPre63(oldPanel, true); expect(updatedPanel.gridData.w).toBe(28); expect(updatedPanel.gridData.h).toBe(12); expect(updatedPanel.gridData.x).toBe(8); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_utils.ts b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_utils.ts index 51d0baee2b1594a..ddd2d6149347a3b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_utils.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/panel/panel_utils.ts @@ -20,8 +20,11 @@ import { i18n } from '@kbn/i18n'; import _ from 'lodash'; import chrome from 'ui/chrome'; -import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../dashboard_constants'; -import { GridData, SavedDashboardPanel } from '../types'; +import { + DEFAULT_PANEL_HEIGHT, + DEFAULT_PANEL_WIDTH, +} from '../../../../dashboard_embeddable_container/public'; +import { GridData, Pre61SavedDashboardPanel, SavedDashboardPanel } from '../types'; const PANEL_HEIGHT_SCALE_FACTOR = 5; const PANEL_HEIGHT_SCALE_FACTOR_WITH_MARGINS = 4; @@ -32,115 +35,92 @@ export interface SemanticVersion { minor: number; } -export class PanelUtils { - // 6.1 switched from gridster to react grid. React grid uses different variables for tracking layout - // eslint-disable-next-line @typescript-eslint/camelcase - public static convertPanelDataPre_6_1(panel: any): SavedDashboardPanel { - ['col', 'row'].forEach(key => { - if (!_.has(panel, key)) { - throw new Error( - i18n.translate('kbn.dashboard.panel.unableToMigratePanelDataForSixOneZeroErrorMessage', { - defaultMessage: - 'Unable to migrate panel data for "6.1.0" backwards compatibility, panel does not contain expected field: {key}', - values: { key }, - }) - ); - } - }); - - panel.gridData = { - x: panel.col - 1, - y: panel.row - 1, - w: panel.size_x || DEFAULT_PANEL_WIDTH, - h: panel.size_y || DEFAULT_PANEL_HEIGHT, +// 6.1 switched from gridster to react grid. React grid uses different variables for tracking layout +export function convertPanelDataPre61( + panel: Pre61SavedDashboardPanel, + useMargins: boolean +): SavedDashboardPanel { + ['col', 'row'].forEach(key => { + if (!_.has(panel, key)) { + throw new Error( + i18n.translate('kbn.dashboard.panel.unableToMigratePanelDataForSixOneZeroErrorMessage', { + defaultMessage: + 'Unable to migrate panel data for "6.1.0" backwards compatibility, panel does not contain expected field: {key}', + values: { key }, + }) + ); + } + }); + const heightScaleFactor = useMargins + ? PANEL_HEIGHT_SCALE_FACTOR_WITH_MARGINS + : PANEL_HEIGHT_SCALE_FACTOR; + return { + gridData: { + w: panel.size_x ? panel.size_x * PANEL_WIDTH_SCALE_FACTOR : DEFAULT_PANEL_WIDTH, + x: (panel.col - 1) * PANEL_WIDTH_SCALE_FACTOR, + h: panel.size_y ? panel.size_y * heightScaleFactor : DEFAULT_PANEL_HEIGHT, + y: (panel.row - 1) * heightScaleFactor, i: panel.panelIndex.toString(), - }; - panel.version = chrome.getKibanaVersion(); - panel.panelIndex = panel.panelIndex.toString(); - delete panel.size_x; - delete panel.size_y; - delete panel.row; - delete panel.col; - - return panel; - } - - // 6.3 changed the panel dimensions to allow finer control over sizing - // 1) decrease column height from 100 to 20. - // 2) increase rows from 12 to 48 - // Need to scale pre 6.3 panels so they maintain the same layout - // eslint-disable-next-line @typescript-eslint/camelcase - public static convertPanelDataPre_6_3( - panel: { - gridData: GridData; - version: string; }, - useMargins: boolean - ) { - ['w', 'x', 'h', 'y'].forEach(key => { - if (!_.has(panel.gridData, key)) { - throw new Error( - i18n.translate( - 'kbn.dashboard.panel.unableToMigratePanelDataForSixThreeZeroErrorMessage', - { - defaultMessage: - 'Unable to migrate panel data for "6.3.0" backwards compatibility, panel does not contain expected field: {key}', - values: { key }, - } - ) - ); - } - }); - - // see https://github.com/elastic/kibana/issues/20635 on why the scale factor changes when margins are being used - const heightScaleFactor = useMargins - ? PANEL_HEIGHT_SCALE_FACTOR_WITH_MARGINS - : PANEL_HEIGHT_SCALE_FACTOR; - - panel.gridData.w = panel.gridData.w * PANEL_WIDTH_SCALE_FACTOR; - panel.gridData.x = panel.gridData.x * PANEL_WIDTH_SCALE_FACTOR; - panel.gridData.h = panel.gridData.h * heightScaleFactor; - panel.gridData.y = panel.gridData.y * heightScaleFactor; - panel.version = chrome.getKibanaVersion(); - - return panel; - } + version: chrome.getKibanaVersion(), + panelIndex: panel.panelIndex.toString(), + id: panel.id, + type: panel.type, + embeddableConfig: {}, + }; +} - public static parseVersion(version = '6.0.0'): SemanticVersion { - const versionSplit = version.split('.'); - if (versionSplit.length < 3) { +// 6.3 changed the panel dimensions to allow finer control over sizing +// 1) decrease column height from 100 to 20. +// 2) increase rows from 12 to 48 +// Need to scale pre 6.3 panels so they maintain the same layout +export function convertPanelDataPre63(panel: SavedDashboardPanel, useMargins: boolean) { + ['w', 'x', 'h', 'y'].forEach(key => { + if (!_.has(panel.gridData, key)) { throw new Error( - i18n.translate('kbn.dashboard.panel.invalidVersionErrorMessage', { - defaultMessage: 'Invalid version, {version}, expected {semver}', - values: { - version, - semver: '..', - }, + i18n.translate('kbn.dashboard.panel.unableToMigratePanelDataForSixThreeZeroErrorMessage', { + defaultMessage: + 'Unable to migrate panel data for "6.3.0" backwards compatibility, panel does not contain expected field: {key}', + values: { key }, }) ); } - return { - major: parseInt(versionSplit[0], 10), - minor: parseInt(versionSplit[1], 10), - }; - } + }); - public static initPanelIndexes(panels: SavedDashboardPanel[]): void { - // find the largest panelIndex in all the panels - let maxIndex = this.getMaxPanelIndex(panels); + // see https://github.com/elastic/kibana/issues/20635 on why the scale factor changes when margins are being used + const heightScaleFactor = useMargins + ? PANEL_HEIGHT_SCALE_FACTOR_WITH_MARGINS + : PANEL_HEIGHT_SCALE_FACTOR; + const gridData: GridData = { + w: panel.gridData.w * PANEL_WIDTH_SCALE_FACTOR, + x: panel.gridData.x * PANEL_WIDTH_SCALE_FACTOR, + h: panel.gridData.h * heightScaleFactor, + y: panel.gridData.y * heightScaleFactor, + i: panel.gridData.i, + }; - // ensure that all panels have a panelIndex - panels.forEach(panel => { - if (!panel.panelIndex) { - panel.panelIndex = (maxIndex++).toString(); - } - }); - } + return { + ...panel, + gridData, + version: chrome.getKibanaVersion(), + }; +} - public static getMaxPanelIndex(panels: SavedDashboardPanel[]): number { - let maxId = panels.reduce((id, panel) => { - return Math.max(id, Number(panel.panelIndex || id)); - }, 0); - return ++maxId; +export function parseVersion(version = '6.0.0'): SemanticVersion { + const versionSplit = version.split('.'); + if (versionSplit.length < 3) { + throw new Error( + i18n.translate('kbn.dashboard.panel.invalidVersionErrorMessage', { + defaultMessage: 'Invalid version, {version}, expected {semver}', + values: { + version, + semver: '..', + }, + }) + ); } + return { + major: parseInt(versionSplit[0], 10), + minor: parseInt(versionSplit[1], 10), + }; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/reducers/embeddables.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/reducers/embeddables.test.ts deleted file mode 100644 index aa29740fa9d3776..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/reducers/embeddables.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { getEmbeddableError, getEmbeddableInitialized } from '../../selectors'; -import { store } from '../../store'; -import { embeddableIsInitializing, setPanels } from '../actions'; - -beforeAll(() => { - const panelData = { - embeddableConfig: {}, - gridData: { - h: 0, - i: '0', - w: 0, - x: 0, - y: 0, - }, - id: '123', - panelIndex: 'foo1', - type: 'mySpecialType', - version: '123', - }; - store.dispatch(setPanels({ foo1: panelData })); -}); - -describe('embeddableIsInitializing', () => { - test('clears the error', () => { - store.dispatch(embeddableIsInitializing('foo1')); - const initialized = getEmbeddableInitialized(store.getState(), 'foo1'); - expect(initialized).toEqual(false); - }); - - test('and clears the error', () => { - const error = getEmbeddableError(store.getState(), 'foo1'); - expect(error).toEqual(undefined); - }); -}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/reducers/embeddables.ts b/src/legacy/core_plugins/kibana/public/dashboard/reducers/embeddables.ts deleted file mode 100644 index f85e7649d8f9a84..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/reducers/embeddables.ts +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { Reducer } from 'redux'; - -import { - EmbeddableActionTypeKeys, - EmbeddableErrorActionPayload, - EmbeddableIsInitializedActionPayload, - PanelActionTypeKeys, - SetStagedFilterActionPayload, -} from '../actions'; -import { EmbeddableReduxState, EmbeddablesMap, PanelId } from '../selectors/types'; - -const embeddableIsInitializing = ( - embeddables: EmbeddablesMap, - panelId: PanelId -): EmbeddablesMap => ({ - ...embeddables, - [panelId]: { - error: undefined, - initialized: false, - metadata: {}, - stagedFilter: undefined, - lastReloadRequestTime: 0, - }, -}); - -const embeddableIsInitialized = ( - embeddables: EmbeddablesMap, - { panelId, metadata }: EmbeddableIsInitializedActionPayload -): EmbeddablesMap => ({ - ...embeddables, - [panelId]: { - ...embeddables[panelId], - initialized: true, - metadata: { ...metadata }, - }, -}); - -const setStagedFilter = ( - embeddables: EmbeddablesMap, - { panelId, stagedFilter }: SetStagedFilterActionPayload -): EmbeddablesMap => ({ - ...embeddables, - [panelId]: { - ...embeddables[panelId], - stagedFilter, - }, -}); - -const embeddableError = ( - embeddables: EmbeddablesMap, - payload: EmbeddableErrorActionPayload -): EmbeddablesMap => ({ - ...embeddables, - [payload.panelId]: { - ...embeddables[payload.panelId], - error: payload.error, - }, -}); - -const clearStagedFilters = (embeddables: EmbeddablesMap): EmbeddablesMap => { - const omitStagedFilters = (embeddable: EmbeddableReduxState): EmbeddablesMap => - _.omit({ ...embeddable }, ['stagedFilter']); - return _.mapValues(embeddables, omitStagedFilters); -}; - -const deleteEmbeddable = (embeddables: EmbeddablesMap, panelId: PanelId): EmbeddablesMap => { - const embeddablesCopy = { ...embeddables }; - delete embeddablesCopy[panelId]; - return embeddablesCopy; -}; - -const setReloadRequestTime = ( - embeddables: EmbeddablesMap, - lastReloadRequestTime: number -): EmbeddablesMap => { - return _.mapValues(embeddables, embeddable => ({ - ...embeddable, - lastReloadRequestTime, - })); -}; - -export const embeddablesReducer: Reducer = ( - embeddables = {}, - action -): EmbeddablesMap => { - switch (action.type as EmbeddableActionTypeKeys | PanelActionTypeKeys.DELETE_PANEL) { - case EmbeddableActionTypeKeys.EMBEDDABLE_IS_INITIALIZING: - return embeddableIsInitializing(embeddables, action.payload); - case EmbeddableActionTypeKeys.EMBEDDABLE_IS_INITIALIZED: - return embeddableIsInitialized(embeddables, action.payload); - case EmbeddableActionTypeKeys.SET_STAGED_FILTER: - return setStagedFilter(embeddables, action.payload); - case EmbeddableActionTypeKeys.CLEAR_STAGED_FILTERS: - return clearStagedFilters(embeddables); - case EmbeddableActionTypeKeys.EMBEDDABLE_ERROR: - return embeddableError(embeddables, action.payload); - case PanelActionTypeKeys.DELETE_PANEL: - return deleteEmbeddable(embeddables, action.payload); - case EmbeddableActionTypeKeys.REQUEST_RELOAD: - return setReloadRequestTime(embeddables, new Date().getTime()); - default: - return embeddables; - } -}; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/reducers/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/reducers/index.ts deleted file mode 100644 index 1f4a26d255d33b5..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/reducers/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { combineReducers } from 'redux'; -import { embeddablesReducer } from './embeddables'; - -import { panelsReducer } from './panels'; - -import { viewReducer } from './view'; - -import { metadataReducer } from './metadata'; - -export const dashboard = combineReducers({ - embeddables: embeddablesReducer, - metadata: metadataReducer, - panels: panelsReducer, - view: viewReducer, -}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/reducers/metadata.ts b/src/legacy/core_plugins/kibana/public/dashboard/reducers/metadata.ts deleted file mode 100644 index 3a2b9c8e1bae3ff..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/reducers/metadata.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Reducer } from 'redux'; -import { - MetadataActions, - MetadataActionTypeKeys, - UpdateDescriptionActionPayload, - UpdateTitleActionPayload, -} from '../actions'; -import { DashboardMetadata } from '../selectors'; - -const updateTitle = (metadata: DashboardMetadata, title: UpdateTitleActionPayload) => ({ - ...metadata, - title, -}); - -const updateDescription = ( - metadata: DashboardMetadata, - description: UpdateDescriptionActionPayload -) => ({ - ...metadata, - description, -}); - -export const metadataReducer: Reducer = ( - metadata = { - description: '', - title: '', - }, - action -): DashboardMetadata => { - switch ((action as MetadataActions).type) { - case MetadataActionTypeKeys.UPDATE_TITLE: - return updateTitle(metadata, action.payload); - case MetadataActionTypeKeys.UPDATE_DESCRIPTION: - return updateDescription(metadata, action.payload); - default: - return metadata; - } -}; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/reducers/panels.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/reducers/panels.test.ts deleted file mode 100644 index aa16dc2d4bd6743..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/reducers/panels.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { getPanel, getPanelType } from '../../selectors'; -import { store } from '../../store'; -import { updatePanel, updatePanels } from '../actions'; - -const originalPanelData = { - embeddableConfig: {}, - gridData: { - h: 0, - i: '0', - w: 0, - x: 0, - y: 0, - }, - id: '123', - panelIndex: '1', - type: 'mySpecialType', - version: '123', -}; - -beforeEach(() => { - // init store - store.dispatch(updatePanels({ '1': originalPanelData })); -}); - -describe('UpdatePanel', () => { - test('updates a panel', () => { - const newPanelData = { - ...originalPanelData, - gridData: { - h: 1, - i: '1', - w: 10, - x: 1, - y: 5, - }, - }; - store.dispatch(updatePanel(newPanelData)); - - const panel = getPanel(store.getState(), '1'); - expect(panel.gridData.x).toBe(1); - expect(panel.gridData.y).toBe(5); - expect(panel.gridData.w).toBe(10); - expect(panel.gridData.h).toBe(1); - expect(panel.gridData.i).toBe('1'); - }); - - test('should allow updating an array that contains fewer values', () => { - const panelData = { - ...originalPanelData, - embeddableConfig: { - columns: ['field1', 'field2', 'field3'], - }, - }; - store.dispatch(updatePanels({ '1': panelData })); - const newPanelData = { - ...originalPanelData, - embeddableConfig: { - columns: ['field2', 'field3'], - }, - }; - store.dispatch(updatePanel(newPanelData)); - - const panel = getPanel(store.getState(), '1'); - expect(panel.embeddableConfig.columns.length).toBe(2); - expect(panel.embeddableConfig.columns[0]).toBe('field2'); - expect(panel.embeddableConfig.columns[1]).toBe('field3'); - }); -}); - -test('getPanelType', () => { - expect(getPanelType(store.getState(), '1')).toBe('mySpecialType'); -}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/reducers/panels.ts b/src/legacy/core_plugins/kibana/public/dashboard/reducers/panels.ts deleted file mode 100644 index 735f636d8af3bee..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/reducers/panels.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { Reducer } from 'redux'; -import { PanelActions, PanelActionTypeKeys, SetPanelTitleActionPayload } from '../actions'; -import { PanelId } from '../selectors'; -import { SavedDashboardPanel } from '../types'; - -interface PanelStateMap { - [key: string]: SavedDashboardPanel; -} - -const deletePanel = (panels: PanelStateMap, panelId: PanelId): PanelStateMap => { - const panelsCopy = { ...panels }; - delete panelsCopy[panelId]; - return panelsCopy; -}; - -const updatePanel = (panels: PanelStateMap, panelState: SavedDashboardPanel): PanelStateMap => ({ - ...panels, - [panelState.panelIndex]: panelState, -}); - -const updatePanels = (panels: PanelStateMap, updatedPanels: PanelStateMap): PanelStateMap => { - const panelsCopy = { ...panels }; - Object.values(updatedPanels).forEach(panel => { - panelsCopy[panel.panelIndex] = panel; - }); - return panelsCopy; -}; - -const resetPanelTitle = (panels: PanelStateMap, panelId: PanelId) => ({ - ...panels, - [panelId]: { - ...panels[panelId], - title: undefined, - }, -}); - -const setPanelTitle = (panels: PanelStateMap, payload: SetPanelTitleActionPayload) => ({ - ...panels, - [payload.panelId]: { - ...panels[payload.panelId], - title: payload.title, - }, -}); - -const setPanels = ({}, newPanels: PanelStateMap) => _.cloneDeep(newPanels); - -export const panelsReducer: Reducer = (panels = {}, action): PanelStateMap => { - switch ((action as PanelActions).type) { - case PanelActionTypeKeys.DELETE_PANEL: - return deletePanel(panels, action.payload); - case PanelActionTypeKeys.UPDATE_PANEL: - return updatePanel(panels, action.payload); - case PanelActionTypeKeys.UPDATE_PANELS: - return updatePanels(panels, action.payload); - case PanelActionTypeKeys.RESET_PANEL_TITLE: - return resetPanelTitle(panels, action.payload); - case PanelActionTypeKeys.SET_PANEL_TITLE: - return setPanelTitle(panels, action.payload); - case PanelActionTypeKeys.SET_PANELS: - return setPanels(panels, action.payload); - default: - return panels; - } -}; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/reducers/view.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/reducers/view.test.ts deleted file mode 100644 index 481e6c9a8555ab5..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/reducers/view.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { store } from '../../store'; -import { maximizePanel, minimizePanel, updateIsFullScreenMode, updateViewMode } from '../actions'; - -import { getFullScreenMode, getMaximizedPanelId, getViewMode } from '../../selectors'; - -import { DashboardViewMode } from '../dashboard_view_mode'; - -describe('isFullScreenMode', () => { - test('updates to true', () => { - store.dispatch(updateIsFullScreenMode(true)); - const fullScreenMode = getFullScreenMode(store.getState()); - expect(fullScreenMode).toBe(true); - }); - - test('updates to false', () => { - store.dispatch(updateIsFullScreenMode(false)); - const fullScreenMode = getFullScreenMode(store.getState()); - expect(fullScreenMode).toBe(false); - }); -}); - -describe('viewMode', () => { - test('updates to EDIT', () => { - store.dispatch(updateViewMode(DashboardViewMode.EDIT)); - const viewMode = getViewMode(store.getState()); - expect(viewMode).toBe(DashboardViewMode.EDIT); - }); - - test('updates to VIEW', () => { - store.dispatch(updateViewMode(DashboardViewMode.VIEW)); - const viewMode = getViewMode(store.getState()); - expect(viewMode).toBe(DashboardViewMode.VIEW); - }); -}); - -describe('maximizedPanelId', () => { - test('updates to an id when maximized', () => { - store.dispatch(maximizePanel('1')); - const maximizedId = getMaximizedPanelId(store.getState()); - expect(maximizedId).toBe('1'); - }); - - test('updates to an id when minimized', () => { - store.dispatch(minimizePanel()); - const maximizedId = getMaximizedPanelId(store.getState()); - expect(maximizedId).toBe(undefined); - }); -}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/reducers/view.ts b/src/legacy/core_plugins/kibana/public/dashboard/reducers/view.ts deleted file mode 100644 index 8c1a1585caab7eb..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/reducers/view.ts +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { cloneDeep } from 'lodash'; -import { Reducer } from 'redux'; - -import { RefreshInterval } from 'ui/timefilter/timefilter'; -import { TimeRange } from 'ui/timefilter/time_history'; -import { Filter } from '@kbn/es-query'; -import { Query } from 'src/legacy/core_plugins/data/public'; -import { ViewActions, ViewActionTypeKeys } from '../actions'; -import { DashboardViewMode } from '../dashboard_view_mode'; -import { PanelId, ViewState } from '../selectors'; - -const closeContextMenu = (view: ViewState) => ({ - ...view, - visibleContextMenuPanelId: undefined, -}); - -const setVisibleContextMenuPanelId = (view: ViewState, panelId: PanelId) => ({ - ...view, - visibleContextMenuPanelId: panelId, -}); - -const updateHidePanelTitles = (view: ViewState, hidePanelTitles: boolean) => ({ - ...view, - hidePanelTitles, -}); - -const minimizePanel = (view: ViewState) => ({ - ...view, - maximizedPanelId: undefined, -}); - -const maximizePanel = (view: ViewState, panelId: PanelId) => ({ - ...view, - maximizedPanelId: panelId, -}); - -const updateIsFullScreenMode = (view: ViewState, isFullScreenMode: boolean) => ({ - ...view, - isFullScreenMode, -}); - -const updateTimeRange = (view: ViewState, timeRange: TimeRange) => ({ - ...view, - timeRange, -}); - -const updateRefreshConfig = (view: ViewState, refreshConfig: RefreshInterval) => ({ - ...view, - refreshConfig, -}); - -const updateFilters = (view: ViewState, filters: Filter[]) => ({ - ...view, - filters: cloneDeep(filters), -}); - -const updateQuery = (view: ViewState, query: Query) => ({ - ...view, - query, -}); - -const updateUseMargins = (view: ViewState, useMargins: boolean) => ({ - ...view, - useMargins, -}); - -const updateViewMode = (view: ViewState, viewMode: DashboardViewMode) => ({ - ...view, - viewMode, -}); - -export const viewReducer: Reducer = ( - view = { - filters: [], - hidePanelTitles: false, - isFullScreenMode: false, - query: { language: 'lucene', query: '' }, - timeRange: { to: 'now', from: 'now-15m' }, - refreshConfig: { pause: true, value: 0 }, - useMargins: true, - viewMode: DashboardViewMode.VIEW, - }, - action -): ViewState => { - switch ((action as ViewActions).type) { - case ViewActionTypeKeys.MINIMIZE_PANEL: - return minimizePanel(view); - case ViewActionTypeKeys.MAXIMIZE_PANEL: - return maximizePanel(view, action.payload); - case ViewActionTypeKeys.SET_VISIBLE_CONTEXT_MENU_PANEL_ID: - return setVisibleContextMenuPanelId(view, action.payload); - case ViewActionTypeKeys.CLOSE_CONTEXT_MENU: - return closeContextMenu(view); - case ViewActionTypeKeys.UPDATE_HIDE_PANEL_TITLES: - return updateHidePanelTitles(view, action.payload); - case ViewActionTypeKeys.UPDATE_TIME_RANGE: - return updateTimeRange(view, action.payload); - case ViewActionTypeKeys.UPDATE_REFRESH_CONFIG: - return updateRefreshConfig(view, action.payload); - case ViewActionTypeKeys.UPDATE_USE_MARGINS: - return updateUseMargins(view, action.payload); - case ViewActionTypeKeys.UPDATE_VIEW_MODE: - return updateViewMode(view, action.payload); - case ViewActionTypeKeys.UPDATE_IS_FULL_SCREEN_MODE: - return updateIsFullScreenMode(view, action.payload); - case ViewActionTypeKeys.UPDATE_FILTERS: - return updateFilters(view, action.payload); - case ViewActionTypeKeys.UPDATE_QUERY: - return updateQuery(view, action.payload); - default: - return view; - } -}; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/selectors/dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/selectors/dashboard.ts deleted file mode 100644 index 47d771de41522b3..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/selectors/dashboard.ts +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { ContainerState, EmbeddableMetadata } from 'ui/embeddable'; -import { EmbeddableCustomization } from 'ui/embeddable/types'; -import { Filter } from '@kbn/es-query'; -import { RefreshInterval } from 'ui/timefilter/timefilter'; -import { Query } from 'src/legacy/core_plugins/data/public'; -import { TimeRange } from 'ui/timefilter/time_history'; -import { DashboardViewMode } from '../dashboard_view_mode'; -import { - DashboardMetadata, - DashboardState, - EmbeddableReduxState, - EmbeddablesMap, - PanelId, -} from './types'; -import { SavedDashboardPanel, SavedDashboardPanelMap, StagedFilter } from '../types'; - -export const getPanels = (dashboard: DashboardState): Readonly => - dashboard.panels; - -export const getPanel = (dashboard: DashboardState, panelId: PanelId): SavedDashboardPanel => - getPanels(dashboard)[panelId] as SavedDashboardPanel; - -export const getPanelType = (dashboard: DashboardState, panelId: PanelId): string => - getPanel(dashboard, panelId).type; - -export const getEmbeddables = (dashboard: DashboardState): EmbeddablesMap => dashboard.embeddables; - -// TODO: rename panel.embeddableConfig to embeddableCustomization. Because it's on the panel that's stored on a -// dashboard, renaming this will require a migration step. -export const getEmbeddableCustomization = ( - dashboard: DashboardState, - panelId: PanelId -): EmbeddableCustomization => getPanel(dashboard, panelId).embeddableConfig; - -export const getEmbeddable = (dashboard: DashboardState, panelId: PanelId): EmbeddableReduxState => - dashboard.embeddables[panelId]; - -export const getEmbeddableError = ( - dashboard: DashboardState, - panelId: PanelId -): string | object | undefined => getEmbeddable(dashboard, panelId).error; - -export const getEmbeddableTitle = ( - dashboard: DashboardState, - panelId: PanelId -): string | undefined => { - const embeddable = getEmbeddable(dashboard, panelId); - return embeddable && embeddable.initialized && embeddable.metadata - ? embeddable.metadata.title - : ''; -}; - -export const getEmbeddableInitialized = (dashboard: DashboardState, panelId: PanelId): boolean => - getEmbeddable(dashboard, panelId).initialized; - -export const getEmbeddableStagedFilter = ( - dashboard: DashboardState, - panelId: PanelId -): object | undefined => getEmbeddable(dashboard, panelId).stagedFilter; - -export const getEmbeddableMetadata = ( - dashboard: DashboardState, - panelId: PanelId -): EmbeddableMetadata | undefined => getEmbeddable(dashboard, panelId).metadata; - -export const getEmbeddableEditUrl = ( - dashboard: DashboardState, - panelId: PanelId -): string | undefined => { - const embeddable = getEmbeddable(dashboard, panelId); - return embeddable && embeddable.initialized && embeddable.metadata - ? embeddable.metadata.editUrl - : ''; -}; - -export const getVisibleContextMenuPanelId = (dashboard: DashboardState): PanelId | undefined => - dashboard.view.visibleContextMenuPanelId; - -export const getUseMargins = (dashboard: DashboardState): boolean => dashboard.view.useMargins; - -export const getViewMode = (dashboard: DashboardState): DashboardViewMode => - dashboard.view.viewMode; - -export const getFullScreenMode = (dashboard: DashboardState): boolean => - dashboard.view.isFullScreenMode; - -export const getHidePanelTitles = (dashboard: DashboardState): boolean => - dashboard.view.hidePanelTitles; - -export const getMaximizedPanelId = (dashboard: DashboardState): PanelId | undefined => - dashboard.view.maximizedPanelId; - -export const getTimeRange = (dashboard: DashboardState): TimeRange => dashboard.view.timeRange; - -export const getRefreshConfig = (dashboard: DashboardState): RefreshInterval => - dashboard.view.refreshConfig; - -export const getFilters = (dashboard: DashboardState): Filter[] => dashboard.view.filters; - -export const getQuery = (dashboard: DashboardState): Query => dashboard.view.query; - -export const getMetadata = (dashboard: DashboardState): DashboardMetadata => dashboard.metadata; - -export const getTitle = (dashboard: DashboardState): string => dashboard.metadata.title; - -export const getDescription = (dashboard: DashboardState): string | undefined => - dashboard.metadata.description; - -export const getContainerState = (dashboard: DashboardState, panelId: PanelId): ContainerState => { - const time = getTimeRange(dashboard); - return { - customTitle: getPanel(dashboard, panelId).title, - embeddableCustomization: _.cloneDeep(getEmbeddableCustomization(dashboard, panelId) || {}), - filters: getFilters(dashboard), - hidePanelTitles: getHidePanelTitles(dashboard), - isPanelExpanded: getMaximizedPanelId(dashboard) === panelId, - query: getQuery(dashboard), - timeRange: { - from: time.from, - to: time.to, - }, - refreshConfig: getRefreshConfig(dashboard), - viewMode: getViewMode(dashboard), - }; -}; - -/** - * @return an array of filters any embeddables wish dashboard to apply - */ -export const getStagedFilters = (dashboard: DashboardState): StagedFilter[] => - _.compact(_.map(dashboard.embeddables, 'stagedFilter')); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/selectors/types.ts b/src/legacy/core_plugins/kibana/public/dashboard/selectors/types.ts deleted file mode 100644 index befb6f693610598..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/selectors/types.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { EmbeddableMetadata } from 'ui/embeddable'; -import { Filter } from '@kbn/es-query'; -import { RefreshInterval } from 'ui/timefilter/timefilter'; -import { TimeRange } from 'ui/timefilter/time_history'; -import { Query } from 'src/legacy/core_plugins/data/public'; -import { DashboardViewMode } from '../dashboard_view_mode'; -import { SavedDashboardPanelMap } from '../types'; - -export type DashboardViewMode = DashboardViewMode; -export interface ViewState { - readonly viewMode: DashboardViewMode; - readonly isFullScreenMode: boolean; - readonly maximizedPanelId?: string; - readonly visibleContextMenuPanelId?: string; - readonly timeRange: TimeRange; - readonly refreshConfig: RefreshInterval; - readonly hidePanelTitles: boolean; - readonly useMargins: boolean; - readonly query: Query; - readonly filters: Filter[]; -} - -export type PanelId = string; -export type SavedObjectId = string; - -export interface EmbeddableReduxState { - readonly metadata?: EmbeddableMetadata; - readonly error?: string | object; - readonly initialized: boolean; - readonly stagedFilter?: object; - /** - * Timestamp of the last time this embeddable was requested to reload. - */ - readonly lastReloadRequestTime: number; -} - -export interface EmbeddablesMap { - readonly [panelId: string]: EmbeddableReduxState; -} - -export interface DashboardMetadata { - readonly title: string; - readonly description?: string; -} - -export interface DashboardState { - readonly view: ViewState; - readonly panels: SavedDashboardPanelMap; - readonly embeddables: EmbeddablesMap; - readonly metadata: DashboardMetadata; -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/store/panel_actions_store.ts b/src/legacy/core_plugins/kibana/public/dashboard/store/panel_actions_store.ts deleted file mode 100644 index 69a9a93b1828a1e..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/store/panel_actions_store.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { ContextMenuAction } from 'ui/embeddable'; - -class PanelActionsStore { - public actions: ContextMenuAction[] = []; - - /** - * - * @type {IndexedArray} panelActionsRegistry - */ - public initializeFromRegistry(panelActionsRegistry: ContextMenuAction[]) { - panelActionsRegistry.forEach(panelAction => { - if (!this.actions.includes(panelAction)) { - this.actions.push(panelAction); - } - }); - } -} - -export const panelActionsStore = new PanelActionsStore(); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/add_panel.test.js.snap b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/add_panel.test.js.snap deleted file mode 100644 index 493f4f65df77614..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/add_panel.test.js.snap +++ /dev/null @@ -1,62 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`render 1`] = ` - - - -

- -

-
-
- - - - - - - - - - - - -
-`; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/add_panel.tsx b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/add_panel.tsx deleted file mode 100644 index 27595e2ecd548f6..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/add_panel.tsx +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { capabilities } from 'ui/capabilities'; -import { toastNotifications, Toast } from 'ui/notify'; -import { - SavedObjectFinder, - SavedObjectMetaData, -} from 'ui/saved_objects/components/saved_object_finder'; - -import { - EuiFlexGroup, - EuiFlexItem, - EuiFlyout, - EuiFlyoutHeader, - EuiFlyoutFooter, - EuiFlyoutBody, - EuiButton, - EuiTitle, -} from '@elastic/eui'; -import { SavedObjectAttributes } from 'src/core/server/saved_objects'; -import { EmbeddableFactoryRegistry } from '../types'; - -interface Props { - onClose: () => void; - addNewPanel: (id: string, type: string) => void; - addNewVis: () => void; - embeddableFactories: EmbeddableFactoryRegistry; -} - -export class DashboardAddPanel extends React.Component { - private lastToast?: Toast; - - onAddPanel = (id: string, type: string, name: string) => { - this.props.addNewPanel(id, type); - - // To avoid the clutter of having toast messages cover flyout - // close previous toast message before creating a new one - if (this.lastToast) { - toastNotifications.remove(this.lastToast); - } - - this.lastToast = toastNotifications.addSuccess({ - title: i18n.translate( - 'kbn.dashboard.topNav.addPanel.savedObjectAddedToDashboardSuccessMessageTitle', - { - defaultMessage: '{savedObjectName} was added to your dashboard', - values: { - savedObjectName: name, - }, - } - ), - 'data-test-subj': 'addObjectToDashboardSuccess', - }); - }; - - render() { - return ( - - - -

- -

-
-
- - Boolean(embeddableFactory.savedObjectMetaData)) - .map(({ savedObjectMetaData }) => savedObjectMetaData) as Array< - SavedObjectMetaData - > - } - showFilter={true} - noItemsMessage={i18n.translate( - 'kbn.dashboard.topNav.addPanel.noMatchingObjectsMessage', - { - defaultMessage: 'No matching objects found.', - } - )} - /> - - {capabilities.get().visualize.save ? ( - - - - - - - - - - ) : null} -
- ); - } -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/get_top_nav_config.ts b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/get_top_nav_config.ts index 32775101b8b344d..955868f1f7ca87c 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/get_top_nav_config.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/get_top_nav_config.ts @@ -18,25 +18,23 @@ */ import { i18n } from '@kbn/i18n'; -import { DashboardViewMode } from '../dashboard_view_mode'; +import { ViewMode } from '../../../../embeddable_api/public'; import { TopNavIds } from './top_nav_ids'; import { NavAction } from '../types'; /** - * @param {DashboardMode} dashboardMode. - * @param actions {Object} - A mapping of TopNavIds to an action function that should run when the + * @param actions - A mapping of TopNavIds to an action function that should run when the * corresponding top nav is clicked. - * @param hideWriteControls {boolean} if true, does not include any controls that allow editing or creating objects. - * @return {Array} - Returns an array of objects for a top nav configuration, based on the - * mode. + * @param hideWriteControls if true, does not include any controls that allow editing or creating objects. + * @return an array of objects for a top nav configuration, based on the mode. */ export function getTopNavConfig( - dashboardMode: DashboardViewMode, + dashboardMode: ViewMode, actions: { [key: string]: NavAction }, hideWriteControls: boolean ) { switch (dashboardMode) { - case DashboardViewMode.VIEW: + case ViewMode.VIEW: return hideWriteControls ? [ getFullScreenConfig(actions[TopNavIds.FULL_SCREEN]), @@ -48,7 +46,7 @@ export function getTopNavConfig( getCloneConfig(actions[TopNavIds.CLONE]), getEditConfig(actions[TopNavIds.ENTER_EDIT_MODE]), ]; - case DashboardViewMode.EDIT: + case ViewMode.EDIT: return [ getSaveConfig(actions[TopNavIds.SAVE]), getViewConfig(actions[TopNavIds.EXIT_EDIT_MODE]), diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_add_panel.tsx b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_add_panel.tsx deleted file mode 100644 index 76df56acd1749b9..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_add_panel.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { I18nContext } from 'ui/i18n'; -import React from 'react'; -import ReactDOM from 'react-dom'; -import { DashboardAddPanel } from './add_panel'; -import { EmbeddableFactoryRegistry } from '../types'; - -let isOpen = false; - -export function showAddPanel( - addNewPanel: (id: string, type: string) => void, - addNewVis: () => void, - embeddableFactories: EmbeddableFactoryRegistry -) { - if (isOpen) { - return; - } - - isOpen = true; - const container = document.createElement('div'); - const onClose = () => { - ReactDOM.unmountComponentAtNode(container); - document.body.removeChild(container); - isOpen = false; - }; - - const addNewVisWithCleanup = () => { - onClose(); - addNewVis(); - }; - - document.body.appendChild(container); - const element = ( - - - - ); - ReactDOM.render(element, container); -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/types.ts b/src/legacy/core_plugins/kibana/public/dashboard/types.ts index 0202785ec9a5124..8f681e9fa8eb263 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/types.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/types.ts @@ -17,17 +17,11 @@ * under the License. */ -import { EmbeddableFactory } from 'ui/embeddable'; import { AppState } from 'ui/state_management/app_state'; -import { UIRegistry } from 'ui/registry/_registry'; import { Filter } from '@kbn/es-query'; import { Query } from 'src/legacy/core_plugins/data/public'; import { AppState as TAppState } from 'ui/state_management/app_state'; -import { DashboardViewMode } from './dashboard_view_mode'; - -export interface EmbeddableFactoryRegistry extends UIRegistry { - byName: { [key: string]: EmbeddableFactory }; -} +import { ViewMode } from '../../../embeddable_api/public'; export type NavAction = (menuItem: any, navController: any, anchorElement: any) => void; @@ -40,9 +34,9 @@ export interface GridData { } export interface SavedDashboardPanel { - // TODO: Make id optional when embeddable API V2 is merged. At that point, it's okay to store panels - // that aren't backed by saved object ids. - readonly id: string; + // Id is the saved object id. It is optional - not all embeddables are backed by saved objects but + // created on the fly. + readonly id?: string; readonly version: string; readonly type: string; @@ -60,7 +54,7 @@ export interface Pre61SavedDashboardPanel { readonly panelIndex: number | string; // earlier versions allowed this to be number or string readonly id: string; readonly type: string; - embeddableConfig: any; + embeddableConfig?: { [key: string]: unknown }; } export interface Pre64SavedDashboardPanel { @@ -75,7 +69,11 @@ export interface Pre64SavedDashboardPanel { embeddableConfig: any; } -export interface DashboardAppStateDefaults { +interface IndexSignature { + [key: string]: unknown; +} + +export interface DashboardAppStateDefaults extends IndexSignature { panels: SavedDashboardPanel[]; fullScreenMode: boolean; title: string; @@ -87,10 +85,10 @@ export interface DashboardAppStateDefaults { }; query: Query; filters: Filter[]; - viewMode: DashboardViewMode; + viewMode: ViewMode; } -export interface DashboardAppStateParameters { +export interface DashboardAppStateParameters extends IndexSignature { panels: SavedDashboardPanel[]; fullScreenMode: boolean; title: string; @@ -102,7 +100,7 @@ export interface DashboardAppStateParameters { }; query: Query | string; filters: Filter[]; - viewMode: DashboardViewMode; + viewMode: ViewMode; } // This could probably be improved if we flesh out AppState more... though AppState will be going away diff --git a/src/legacy/core_plugins/kibana/public/dashboard/viewport/_dashboard_viewport.scss b/src/legacy/core_plugins/kibana/public/dashboard/viewport/_dashboard_viewport.scss deleted file mode 100644 index 7cbe1351158776b..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/viewport/_dashboard_viewport.scss +++ /dev/null @@ -1,8 +0,0 @@ -.dshDashboardViewport { - width: 100%; - background-color: $euiColorEmptyShade; -} - -.dshDashboardViewport-withMargins { - width: 100%; -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/viewport/_index.scss b/src/legacy/core_plugins/kibana/public/dashboard/viewport/_index.scss deleted file mode 100644 index 56483d9d101957a..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/viewport/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './dashboard_viewport'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport.tsx b/src/legacy/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport.tsx deleted file mode 100644 index 8e34b50a0383d51..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { EmbeddableFactory } from 'ui/embeddable'; -import { ExitFullScreenButton } from 'ui/exit_full_screen'; -import { DashboardGrid } from '../grid'; - -export function DashboardViewport({ - maximizedPanelId, - getEmbeddableFactory, - panelCount, - title, - description, - useMargins, - isFullScreenMode, - onExitFullScreenMode, -}: { - maximizedPanelId: string; - getEmbeddableFactory: (panelType: string) => EmbeddableFactory; - panelCount: number; - title: string; - description: string; - useMargins: boolean; - isFullScreenMode: boolean; - onExitFullScreenMode: () => void; -}) { - return ( -
- {isFullScreenMode && } - -
- ); -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport_container.js b/src/legacy/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport_container.js deleted file mode 100644 index 9c48241a390c171..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport_container.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { connect } from 'react-redux'; -import { DashboardViewport } from './dashboard_viewport'; -import { updateIsFullScreenMode } from '../actions'; -import { - getMaximizedPanelId, - getPanels, - getTitle, - getDescription, - getUseMargins, - getFullScreenMode, -} from '../selectors'; - -const mapStateToProps = ({ dashboard }) => { - const maximizedPanelId = getMaximizedPanelId(dashboard); - return { - maximizedPanelId, - panelCount: Object.keys(getPanels(dashboard)).length, - description: getDescription(dashboard), - title: getTitle(dashboard), - useMargins: getUseMargins(dashboard), - isFullScreenMode: getFullScreenMode(dashboard), - }; -}; - -const mapDispatchToProps = (dispatch) => ({ - onExitFullScreenMode: () => dispatch(updateIsFullScreenMode(false)), -}); - -export const DashboardViewportContainer = connect( - mapStateToProps, - mapDispatchToProps, -)(DashboardViewport); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport_provider.js b/src/legacy/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport_provider.js deleted file mode 100644 index a219f763dac28cd..000000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport_provider.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { store } from '../../store'; -import { Provider } from 'react-redux'; -import { DashboardViewportContainer } from './dashboard_viewport_container'; - -export function DashboardViewportProvider(props) { - return ( - - - - ); -} - -DashboardViewportProvider.propTypes = { - getEmbeddableFactory: PropTypes.func.isRequired, -}; diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/_index.scss b/src/legacy/core_plugins/kibana/public/discover/embeddable/_index.scss new file mode 100644 index 000000000000000..f3c72e80b81b338 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/_index.scss @@ -0,0 +1,11 @@ + /** + * 1. We want the kbnDocTable__container to scroll only when embedded in an embeddable panel + * 2. Force a better looking scrollbar + */ +.embPanel { + .kbnDocTable__container { + @include euiScrollBar; /* 2 */ + flex: 1 1 0; /* 1 */ + overflow: auto; /* 1 */ + } +} \ No newline at end of file diff --git a/src/legacy/core_plugins/kibana/public/selectors/index.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts similarity index 88% rename from src/legacy/core_plugins/kibana/public/selectors/index.ts rename to src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts index 6c72362d160c402..3138008f3e3a009 100644 --- a/src/legacy/core_plugins/kibana/public/selectors/index.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts @@ -17,5 +17,6 @@ * under the License. */ -export * from './dashboard_selectors'; -export { CoreKibanaState } from './types'; +export * from './types'; +export * from './search_embeddable_factory'; +export * from './search_embeddable'; diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts index 6976036b9b7423d..6c89e6be602e780 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts @@ -19,23 +19,24 @@ import angular from 'angular'; import _ from 'lodash'; -import { i18n } from '@kbn/i18n'; import { SearchSource } from 'ui/courier'; -import { - ContainerState, - Embeddable, - EmbeddableState, - OnEmbeddableStateChanged, -} from 'ui/embeddable'; +import { StaticIndexPattern } from 'ui/index_patterns'; import { RequestAdapter } from 'ui/inspector/adapters'; import { Adapters } from 'ui/inspector/types'; import { getTime } from 'ui/timefilter/get_time'; -import { TimeRange } from 'ui/timefilter/time_history'; -import { Filter } from '@kbn/es-query'; -import { Query } from 'src/legacy/core_plugins/data/public'; +import { Subscription } from 'rxjs'; +import * as Rx from 'rxjs'; +import { Filter, FilterStateStore } from '@kbn/es-query'; +import { + APPLY_FILTER_TRIGGER, + Embeddable, + executeTriggerActions, + Container, +} from '../../../../embeddable_api/public'; import * as columnActions from '../doc_table/actions/columns'; import { SavedSearch } from '../types'; import searchTemplate from './search_template.html'; +import { ISearchEmbeddable, SearchInput, SearchOutput } from './types'; interface SearchScope extends ng.IScope { columns?: string[]; @@ -48,89 +49,93 @@ interface SearchScope extends ng.IScope { removeColumn?: (column: string) => void; addColumn?: (column: string) => void; moveColumn?: (column: string, index: number) => void; - filter?: (field: string, value: string, operator: string) => void; + filter?: (field: { name: string; scripted: boolean }, value: string[], operator: string) => void; } -interface SearchEmbeddableCustomization { - sort?: string[]; - columns?: string[]; +export interface FilterManager { + generate: ( + field: { + name: string; + scripted: boolean; + }, + values: string | string[], + operation: string, + index: number + ) => Filter[]; } interface SearchEmbeddableConfig { - onEmbeddableStateChanged: OnEmbeddableStateChanged; + $rootScope: ng.IRootScopeService; + $compile: ng.ICompileService; + courier: any; savedSearch: SavedSearch; editUrl: string; + filterManager: FilterManager; + indexPatterns?: StaticIndexPattern[]; editable: boolean; - $rootScope: ng.IRootScopeService; - $compile: ng.ICompileService; } -export class SearchEmbeddable extends Embeddable { - private readonly onEmbeddableStateChanged: OnEmbeddableStateChanged; +export const SEARCH_EMBEDDABLE_TYPE = 'search'; + +export class SearchEmbeddable extends Embeddable + implements ISearchEmbeddable { private readonly savedSearch: SavedSearch; private $rootScope: ng.IRootScopeService; private $compile: ng.ICompileService; - private customization: SearchEmbeddableCustomization; private inspectorAdaptors: Adapters; private searchScope?: SearchScope; private panelTitle: string = ''; private filtersSearchSource: SearchSource; - private timeRange?: TimeRange; - private filters?: Filter[]; - private query?: Query; private searchInstance?: JQLite; + private courier: any; + private subscription?: Subscription; + private filterManager: FilterManager; + public readonly type = SEARCH_EMBEDDABLE_TYPE; - constructor({ - onEmbeddableStateChanged, - savedSearch, - editable, - editUrl, - $rootScope, - $compile, - }: SearchEmbeddableConfig) { - super({ - title: savedSearch.title, + constructor( + { + $rootScope, + $compile, + courier, + savedSearch, editUrl, - editLabel: i18n.translate('kbn.embeddable.search.editLabel', { - defaultMessage: 'Edit saved search', - }), + indexPatterns, editable, - indexPatterns: _.compact([savedSearch.searchSource.getField('index')]), - }); - this.onEmbeddableStateChanged = onEmbeddableStateChanged; + filterManager, + }: SearchEmbeddableConfig, + initialInput: SearchInput, + parent?: Container + ) { + super( + initialInput, + { defaultTitle: savedSearch.title, editUrl, indexPatterns, editable }, + parent + ); + + this.filterManager = filterManager; + this.courier = courier; this.savedSearch = savedSearch; this.$rootScope = $rootScope; this.$compile = $compile; - this.customization = {}; this.inspectorAdaptors = { requests: new RequestAdapter(), }; + + this.subscription = Rx.merge(this.getOutput$(), this.getInput$()).subscribe(() => { + this.panelTitle = this.output.title || ''; + + if (this.searchScope) { + this.pushContainerStateParamsToScope(this.searchScope); + } + }); } public getInspectorAdapters() { return this.inspectorAdaptors; } - public getPanelTitle() { - return this.panelTitle; - } - - public onContainerStateChanged(containerState: ContainerState) { - this.customization = containerState.embeddableCustomization || {}; - this.filters = containerState.filters; - this.query = containerState.query; - this.timeRange = containerState.timeRange; - this.panelTitle = ''; - if (!containerState.hidePanelTitles) { - this.panelTitle = - containerState.customTitle !== undefined - ? containerState.customTitle - : this.savedSearch.title; - } - - if (this.searchScope) { - this.pushContainerStateParamsToScope(this.searchScope); - } + public getSavedSearch() { + return this.savedSearch; } /** @@ -138,8 +143,7 @@ export class SearchEmbeddable extends Embeddable { * @param {Element} domNode * @param {ContainerState} containerState */ - public render(domNode: HTMLElement, containerState: ContainerState) { - this.onContainerStateChanged(containerState); + public render(domNode: HTMLElement) { this.initializeSearchScope(); if (!this.searchScope) { throw new Error('Search scope not defined'); @@ -148,9 +152,12 @@ export class SearchEmbeddable extends Embeddable { this.searchInstance = this.$compile(searchTemplate)(this.searchScope); const rootNode = angular.element(domNode); rootNode.append(this.searchInstance); + + this.pushContainerStateParamsToScope(this.searchScope); } public destroy() { + super.destroy(); this.savedSearch.destroy(); if (this.searchInstance) { this.searchInstance.remove(); @@ -159,6 +166,9 @@ export class SearchEmbeddable extends Embeddable { this.searchScope.$destroy(); delete this.searchScope; } + if (this.subscription) { + this.subscription.unsubscribe(); + } } private initializeSearchScope() { @@ -170,10 +180,10 @@ export class SearchEmbeddable extends Embeddable { const timeRangeSearchSource = searchScope.searchSource.create(); timeRangeSearchSource.setField('filter', () => { - if (!this.searchScope || !this.timeRange) { + if (!this.searchScope || !this.input.timeRange) { return; } - return getTime(this.searchScope.searchSource.getField('index'), this.timeRange); + return getTime(this.searchScope.searchSource.getField('index'), this.input.timeRange); }); this.filtersSearchSource = searchScope.searchSource.create(); @@ -184,8 +194,8 @@ export class SearchEmbeddable extends Embeddable { this.pushContainerStateParamsToScope(searchScope); searchScope.setSortOrder = (columnName, direction) => { - searchScope.sort = this.customization.sort = [columnName, direction]; - this.emitEmbeddableStateChange(this.getEmbeddableState()); + searchScope.sort = [columnName, direction]; + this.updateInput({ sort: searchScope.sort }); }; searchScope.addColumn = (columnName: string) => { @@ -194,8 +204,8 @@ export class SearchEmbeddable extends Embeddable { } this.savedSearch.searchSource.getField('index').popularizeField(columnName, 1); columnActions.addColumn(searchScope.columns, columnName); - searchScope.columns = this.customization.columns = searchScope.columns; - this.emitEmbeddableStateChange(this.getEmbeddableState()); + searchScope.columns = searchScope.columns; + this.updateInput({ columns: searchScope.columns }); }; searchScope.removeColumn = (columnName: string) => { @@ -204,8 +214,8 @@ export class SearchEmbeddable extends Embeddable { } this.savedSearch.searchSource.getField('index').popularizeField(columnName, 1); columnActions.removeColumn(searchScope.columns, columnName); - this.customization.columns = searchScope.columns; - this.emitEmbeddableStateChange(this.getEmbeddableState()); + + this.updateInput({ columns: searchScope.columns }); }; searchScope.moveColumn = (columnName, newIndex: number) => { @@ -213,46 +223,44 @@ export class SearchEmbeddable extends Embeddable { return; } columnActions.moveColumn(searchScope.columns, columnName, newIndex); - this.customization.columns = searchScope.columns; - this.emitEmbeddableStateChange(this.getEmbeddableState()); + this.updateInput({ columns: searchScope.columns }); }; - searchScope.filter = (field, value, operator) => { + searchScope.filter = async (field, value, operator) => { const index = this.savedSearch.searchSource.getField('index').id; - const stagedFilter = { - field, - value, - operator, - index, - }; - this.emitEmbeddableStateChange({ - ...this.getEmbeddableState(), - stagedFilter, + + let filters = this.filterManager.generate(field, value, operator, index); + filters = filters.map(filter => ({ + ...filter, + $state: { store: FilterStateStore.APP_STATE }, + })); + + await executeTriggerActions(APPLY_FILTER_TRIGGER, { + embeddable: this, + triggerContext: { + filters, + }, }); }; this.searchScope = searchScope; } - private emitEmbeddableStateChange(embeddableState: EmbeddableState) { - this.onEmbeddableStateChanged(embeddableState); - } - - private getEmbeddableState(): EmbeddableState { - return { - customization: this.customization, - }; + public reload() { + this.courier.fetch(); } private pushContainerStateParamsToScope(searchScope: SearchScope) { // If there is column or sort data on the panel, that means the original columns or sort settings have // been overridden in a dashboard. - - searchScope.columns = this.customization.columns || this.savedSearch.columns; - searchScope.sort = this.customization.sort || this.savedSearch.sort; + searchScope.columns = this.input.columns || this.savedSearch.columns; + searchScope.sort = this.input.sort || this.savedSearch.sort; searchScope.sharedItemTitle = this.panelTitle; - this.filtersSearchSource.setField('filter', this.filters); - this.filtersSearchSource.setField('query', this.query); + this.filtersSearchSource.setField('filter', this.input.filters); + this.filtersSearchSource.setField('query', this.input.query); + + // Sadly this is neccessary to tell the angular component to refetch the data. + this.courier.fetch(); } } diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts index 1f041fc09dfc367..7a3421448e5f432 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts @@ -20,22 +20,30 @@ import '../doc_table'; import { capabilities } from 'ui/capabilities'; import { i18n } from '@kbn/i18n'; -import { EmbeddableFactory } from 'ui/embeddable'; +import chrome from 'ui/chrome'; +import { IPrivate } from 'ui/private'; +// @ts-ignore +import { FilterManagerProvider } from 'ui/filter_manager'; +import { TimeRange } from 'ui/timefilter/time_history'; import { - EmbeddableInstanceConfiguration, - OnEmbeddableStateChanged, -} from 'ui/embeddable/embeddable_factory'; + embeddableFactories, + EmbeddableFactory, + ErrorEmbeddable, + Container, +} from '../../../../embeddable_api/public/index'; import { SavedSearchLoader } from '../types'; -import { SearchEmbeddable } from './search_embeddable'; +import { SearchEmbeddable, FilterManager, SEARCH_EMBEDDABLE_TYPE } from './search_embeddable'; +import { SearchInput, SearchOutput } from './types'; -export class SearchEmbeddableFactory extends EmbeddableFactory { - constructor( - private $compile: ng.ICompileService, - private $rootScope: ng.IRootScopeService, - private searchLoader: SavedSearchLoader - ) { +export class SearchEmbeddableFactory extends EmbeddableFactory< + SearchInput, + SearchOutput, + SearchEmbeddable +> { + public readonly type = SEARCH_EMBEDDABLE_TYPE; + + constructor() { super({ - name: 'search', savedObjectMetaData: { name: i18n.translate('kbn.discover.savedSearch.savedObjectName', { defaultMessage: 'Saved search', @@ -46,35 +54,64 @@ export class SearchEmbeddableFactory extends EmbeddableFactory { }); } - public getEditPath(panelId: string) { - return this.searchLoader.urlFor(panelId); + public isEditable() { + return capabilities.get().discover.save as boolean; + } + + public canCreateNew() { + return false; + } + + public getDisplayName() { + return i18n.translate('kbn.embeddable.search.displayName', { + defaultMessage: 'search', + }); } - /** - * - * @param {Object} panelMetadata. Currently just passing in panelState but it's more than we need, so we should - * decouple this to only include data given to us from the embeddable when it's added to the dashboard. Generally - * will be just the object id, but could be anything depending on the plugin. - * @param onEmbeddableStateChanged - * @return {Promise.} - */ - public create( - { id }: EmbeddableInstanceConfiguration, - onEmbeddableStateChanged: OnEmbeddableStateChanged - ) { - const editUrl = this.getEditPath(id); - const editable = capabilities.get().discover.save as boolean; + public async createFromSavedObject( + savedObjectId: string, + input: Partial & { id: string; timeRange: TimeRange }, + parent?: Container + ): Promise { + const $injector = await chrome.dangerouslyGetActiveInjector(); + + const $compile = $injector.get('$compile'); + const $rootScope = $injector.get('$rootScope'); + const courier = $injector.get('courier'); + const searchLoader = $injector.get('savedSearches'); + const editUrl = chrome.addBasePath(`/app/kibana${searchLoader.urlFor(savedObjectId)}`); + + const Private = $injector.get('Private'); + const filterManager = Private(FilterManagerProvider); // can't change this to be async / awayt, because an Anglular promise is expected to be returned. - return this.searchLoader.get(id).then(savedObject => { - return new SearchEmbeddable({ - onEmbeddableStateChanged, - savedSearch: savedObject, - editUrl, - editable, - $rootScope: this.$rootScope, - $compile: this.$compile, + return searchLoader + .get(savedObjectId) + .then(savedObject => { + return new SearchEmbeddable( + { + courier, + savedSearch: savedObject, + $rootScope, + $compile, + editUrl, + filterManager, + editable: capabilities.get().discover.save as boolean, + indexPatterns: _.compact([savedObject.searchSource.getField('index')]), + }, + input, + parent + ); + }) + .catch((e: Error) => { + console.error(e); // eslint-disable-line no-console + return new ErrorEmbeddable(e, input, parent); }); - }); + } + + public async create(input: SearchInput) { + return new ErrorEmbeddable('Saved searches can only be created from a saved object', input); } } + +embeddableFactories.set(SEARCH_EMBEDDABLE_TYPE, new SearchEmbeddableFactory()); diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory_provider.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory_provider.ts deleted file mode 100644 index 717fdc717f1f9b5..000000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory_provider.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { EmbeddableFactoriesRegistryProvider } from 'ui/embeddable/embeddable_factories_registry'; -import { IPrivate } from 'ui/private'; -import { SavedSearchLoader } from '../types'; -import { SearchEmbeddableFactory } from './search_embeddable_factory'; - -export function searchEmbeddableFactoryProvider(Private: IPrivate) { - const SearchEmbeddableFactoryProvider = ( - $compile: ng.ICompileService, - $rootScope: ng.IRootScopeService, - savedSearches: SavedSearchLoader - ) => { - return new SearchEmbeddableFactory($compile, $rootScope, savedSearches); - }; - return Private(SearchEmbeddableFactoryProvider); -} - -EmbeddableFactoriesRegistryProvider.register(searchEmbeddableFactoryProvider); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/add_panel.test.js b/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts similarity index 50% rename from src/legacy/core_plugins/kibana/public/dashboard/top_nav/add_panel.test.js rename to src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts index 96147b385202059..c75b58343666ecc 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/add_panel.test.js +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts @@ -17,45 +17,32 @@ * under the License. */ -import React from 'react'; -import sinon from 'sinon'; -import { shallow } from 'enzyme'; - +import { StaticIndexPattern } from 'ui/index_patterns'; +import { TimeRange } from 'ui/timefilter/time_history'; +import { Query } from 'src/legacy/core_plugins/data/public'; +import { Filter } from '@kbn/es-query'; +import { SavedSearch } from '../types'; import { - DashboardAddPanel, -} from './add_panel'; - -jest.mock('ui/capabilities', - () => ({ - capabilities: { - get: () => ({ - visualize: { - show: true, - save: true - } - }) - } - }), { virtual: true }); + EmbeddableInput, + EmbeddableOutput, + IEmbeddable, +} from '../../../../embeddable_api/public/index'; -jest.mock('ui/notify', - () => ({ - toastNotifications: { - addDanger: () => {}, - } - }), { virtual: true }); +export interface SearchInput extends EmbeddableInput { + timeRange: TimeRange; + query?: Query; + filters?: Filter[]; + hidePanelTitles?: boolean; + columns?: string[]; + sort?: string[]; +} -let onClose; -beforeEach(() => { - onClose = sinon.spy(); -}); +export interface SearchOutput extends EmbeddableOutput { + editUrl: string; + indexPatterns?: StaticIndexPattern[]; + editable: boolean; +} -test('render', () => { - const component = shallow( {}} - addNewVis={() => {}} - embeddableFactories={[]} - />); - expect(component).toMatchSnapshot(); -}); +export interface ISearchEmbeddable extends IEmbeddable { + getSavedSearch(): SavedSearch; +} diff --git a/src/legacy/core_plugins/kibana/public/reducers.ts b/src/legacy/core_plugins/kibana/public/reducers.ts deleted file mode 100644 index 5097134087d6117..000000000000000 --- a/src/legacy/core_plugins/kibana/public/reducers.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { combineReducers } from 'redux'; -import { dashboard } from './dashboard/reducers'; -import { CoreKibanaState } from './selectors'; - -/** - * Only a single reducer now, but eventually there should be one for each sub app that is part of the - * core kibana plugins. - */ -export const reducers = combineReducers({ - dashboard, -}); diff --git a/src/legacy/core_plugins/kibana/public/selectors/dashboard_selectors.ts b/src/legacy/core_plugins/kibana/public/selectors/dashboard_selectors.ts deleted file mode 100644 index 85a702cb93296ad..000000000000000 --- a/src/legacy/core_plugins/kibana/public/selectors/dashboard_selectors.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Filter } from '@kbn/es-query'; -import { TimeRange } from 'ui/timefilter/time_history'; -import { Query } from 'src/legacy/core_plugins/data/public'; -import { DashboardViewMode } from '../dashboard/dashboard_view_mode'; -import * as DashboardSelectors from '../dashboard/selectors'; -import { PanelId } from '../dashboard/selectors/types'; -import { CoreKibanaState } from './types'; -import { StagedFilter } from '../dashboard/types'; - -export const getDashboard = (state: CoreKibanaState): DashboardSelectors.DashboardState => - state.dashboard; - -export const getPanels = (state: CoreKibanaState) => - DashboardSelectors.getPanels(getDashboard(state)); -export const getPanel = (state: CoreKibanaState, panelId: PanelId) => - DashboardSelectors.getPanel(getDashboard(state), panelId); -export const getPanelType = (state: CoreKibanaState, panelId: PanelId) => - DashboardSelectors.getPanelType(getDashboard(state), panelId); - -export const getEmbeddables = (state: CoreKibanaState) => - DashboardSelectors.getEmbeddables(getDashboard(state)); -export const getEmbeddableError = (state: CoreKibanaState, panelId: PanelId) => - DashboardSelectors.getEmbeddableError(getDashboard(state), panelId); -export const getEmbeddableInitialized = (state: CoreKibanaState, panelId: PanelId) => - DashboardSelectors.getEmbeddableInitialized(getDashboard(state), panelId); -export const getEmbeddableCustomization = (state: CoreKibanaState, panelId: PanelId) => - DashboardSelectors.getEmbeddableCustomization(getDashboard(state), panelId); -export const getEmbeddableStagedFilter = (state: CoreKibanaState, panelId: PanelId) => - DashboardSelectors.getEmbeddableStagedFilter(getDashboard(state), panelId); -export const getEmbeddableMetadata = (state: CoreKibanaState, panelId: PanelId) => - DashboardSelectors.getEmbeddableMetadata(getDashboard(state), panelId); - -export const getStagedFilters = (state: CoreKibanaState): StagedFilter[] => - DashboardSelectors.getStagedFilters(getDashboard(state)); -export const getViewMode = (state: CoreKibanaState): DashboardViewMode => - DashboardSelectors.getViewMode(getDashboard(state)); -export const getFullScreenMode = (state: CoreKibanaState): boolean => - DashboardSelectors.getFullScreenMode(getDashboard(state)); -export const getMaximizedPanelId = (state: CoreKibanaState): PanelId | undefined => - DashboardSelectors.getMaximizedPanelId(getDashboard(state)); -export const getUseMargins = (state: CoreKibanaState): boolean => - DashboardSelectors.getUseMargins(getDashboard(state)); -export const getHidePanelTitles = (state: CoreKibanaState): boolean => - DashboardSelectors.getHidePanelTitles(getDashboard(state)); -export const getTimeRange = (state: CoreKibanaState): TimeRange => - DashboardSelectors.getTimeRange(getDashboard(state)); -export const getFilters = (state: CoreKibanaState): Filter[] => - DashboardSelectors.getFilters(getDashboard(state)); -export const getQuery = (state: CoreKibanaState): Query => - DashboardSelectors.getQuery(getDashboard(state)); - -export const getTitle = (state: CoreKibanaState): string => - DashboardSelectors.getTitle(getDashboard(state)); -export const getDescription = (state: CoreKibanaState): string | undefined => - DashboardSelectors.getDescription(getDashboard(state)); diff --git a/src/legacy/core_plugins/kibana/public/selectors/types.ts b/src/legacy/core_plugins/kibana/public/selectors/types.ts deleted file mode 100644 index 61ae8da5ed4bbad..000000000000000 --- a/src/legacy/core_plugins/kibana/public/selectors/types.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Action } from 'redux'; -import { ThunkAction } from 'redux-thunk'; -import { DashboardState } from '../dashboard/selectors'; - -export interface CoreKibanaState { - readonly dashboard: DashboardState; -} - -export interface KibanaAction extends Action { - readonly type: T; - readonly payload: P; -} - -export type KibanaThunk< - R = Action | Promise | void, - S = CoreKibanaState, - E = any, - A extends Action = Action -> = ThunkAction; diff --git a/src/legacy/core_plugins/kibana/public/store.ts b/src/legacy/core_plugins/kibana/public/store.ts deleted file mode 100644 index 91aa57d8035f9e1..000000000000000 --- a/src/legacy/core_plugins/kibana/public/store.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { applyMiddleware, compose, createStore } from 'redux'; -import thunk from 'redux-thunk'; - -import { DashboardViewMode } from './dashboard/dashboard_view_mode'; -import { reducers } from './reducers'; - -const enhancers = [applyMiddleware(thunk)]; - -export const store = createStore( - reducers, - { - dashboard: { - embeddables: {}, - metadata: { - title: 'New Dashboard', - }, - panels: {}, - view: { - filters: [], - hidePanelTitles: false, - isFullScreenMode: false, - query: { language: 'lucene', query: '' }, - timeRange: { from: 'now-15m', to: 'now' }, - useMargins: true, - viewMode: DashboardViewMode.VIEW, - }, - }, - }, - compose(...enhancers) -); diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/_embeddables.scss b/src/legacy/core_plugins/kibana/public/visualize/embeddable/_embeddables.scss new file mode 100644 index 000000000000000..23d3e189767d7af --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/_embeddables.scss @@ -0,0 +1,48 @@ +/* +* 1. Fix overflow of vis's specifically for inside embeddable panels, lets the panel decide the overflow +* 2. Force a better looking scrollbar +*/ + +.embPanel { + .visualization { + @include euiScrollBar; /* 2 */ + } + + .visualization .visChart__container { + overflow: visible; /* 1 */ + } + + .visLegend__toggle { + border-bottom-right-radius: 0; + border-top-left-radius: 0; + } +} + +// OPTIONS MENU + +/** + * 1. Use opacity to make this element accessible to screen readers and keyboard. + * 2. Show on focus to enable keyboard accessibility. + * 3. Always show in editing mode + */ + +.embPanel_optionsMenuPopover[class*="-isOpen"], +.embPanel:hover { + .visLegend__toggle { + opacity: 1; + } +} + +.embPanel .visLegend__toggle { + opacity: 0; /* 1 */ + + &:focus { + opacity: 1; /* 2 */ + } +} + +.embPanel--editing { + .visLegend__toggle { + opacity: 1; /* 3 */ + } +} \ No newline at end of file diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/_index.scss b/src/legacy/core_plugins/kibana/public/visualize/embeddable/_index.scss index 1e273d18714d69a..6b31803e7c8c52c 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/_index.scss +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/_index.scss @@ -1 +1,2 @@ @import './visualize_lab_disabled'; +@import './embeddables'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/selectors/index.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/constants.ts similarity index 93% rename from src/legacy/core_plugins/kibana/public/dashboard/selectors/index.ts rename to src/legacy/core_plugins/kibana/public/visualize/embeddable/constants.ts index c3d90748976d46e..b7967db28956710 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/selectors/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/constants.ts @@ -17,6 +17,4 @@ * under the License. */ -export * from './types'; - -export * from './dashboard'; +export const VISUALIZE_EMBEDDABLE_TYPE = 'visualization'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx index e119f7cfcf25386..a9dd3b6be5a2d60 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx @@ -19,26 +19,24 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { Embeddable } from 'ui/embeddable'; -import { I18nContext } from 'ui/i18n'; +import { Embeddable, EmbeddableOutput } from 'plugins/embeddable_api'; import { DisabledLabVisualization } from './disabled_lab_visualization'; +import { VisualizeInput } from './visualize_embeddable'; +import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; -export class DisabledLabEmbeddable extends Embeddable { +export class DisabledLabEmbeddable extends Embeddable { private domNode?: HTMLElement; + public readonly type = VISUALIZE_EMBEDDABLE_TYPE; - constructor(title: string) { - super({ title }); + constructor(private readonly title: string, initialInput: VisualizeInput) { + super(initialInput, { title }); } + public reload() {} public render(domNode: HTMLElement) { - if (this.metadata.title) { + if (this.title) { this.domNode = domNode; - ReactDOM.render( - - - , - domNode - ); + ReactDOM.render(, domNode); } } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/grid/index.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/index.ts similarity index 74% rename from src/legacy/core_plugins/kibana/public/dashboard/grid/index.ts rename to src/legacy/core_plugins/kibana/public/visualize/embeddable/index.ts index b226168a31a6af7..a1cd31eebef2051 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/grid/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/index.ts @@ -16,5 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - -export { DashboardGridContainer as DashboardGrid } from './dashboard_grid_container'; +export { DisabledLabEmbeddable } from './disabled_lab_embeddable'; +export { VisualizeEmbeddable, VisualizeInput } from './visualize_embeddable'; +export { VisualizeEmbeddableFactory } from './visualize_embeddable_factory'; +export { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts index 9c3a2cbe7e4076d..258327da7d5de6a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts @@ -18,8 +18,14 @@ */ import _ from 'lodash'; -import { ContainerState, Embeddable } from 'ui/embeddable'; -import { OnEmbeddableStateChanged } from 'ui/embeddable/embeddable_factory'; +import { + APPLY_FILTER_TRIGGER, + Embeddable, + EmbeddableInput, + EmbeddableOutput, + Trigger, + Container, +} from 'plugins/embeddable_api'; import { StaticIndexPattern } from 'ui/index_patterns'; import { PersistedState } from 'ui/persisted_state'; import { VisualizeLoader } from 'ui/visualize/loader'; @@ -29,50 +35,72 @@ import { VisualizeLoaderParams, VisualizeUpdateParams, } from 'ui/visualize/loader/types'; -import { i18n } from '@kbn/i18n'; +import { Subscription } from 'rxjs'; +import * as Rx from 'rxjs'; import { TimeRange } from 'ui/timefilter/time_history'; import { Query } from 'src/legacy/core_plugins/data/public'; import { Filter } from '@kbn/es-query'; +import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; + +const getKeys = (o: T): Array => Object.keys(o) as Array; export interface VisualizeEmbeddableConfiguration { - onEmbeddableStateChanged: OnEmbeddableStateChanged; savedVisualization: VisSavedObject; indexPatterns?: StaticIndexPattern[]; - editUrl?: string; - editable: boolean; + editUrl: string; loader: VisualizeLoader; + editable: boolean; +} + +export interface VisualizeInput extends EmbeddableInput { + timeRange?: TimeRange; + query?: Query; + filters?: Filter[]; + vis?: { + colors?: { [key: string]: string }; + }; } -export class VisualizeEmbeddable extends Embeddable { - private onEmbeddableStateChanged: OnEmbeddableStateChanged; +export interface VisualizeOutput extends EmbeddableOutput { + editUrl: string; + indexPatterns?: StaticIndexPattern[]; + savedObjectId: string; +} + +export class VisualizeEmbeddable extends Embeddable { private savedVisualization: VisSavedObject; private loader: VisualizeLoader; private uiState: PersistedState; private handler?: EmbeddedVisualizeHandler; - private customization?: object; - private panelTitle?: string; private timeRange?: TimeRange; private query?: Query; + private title?: string; private filters?: Filter[]; + private subscription: Subscription; + public readonly type = VISUALIZE_EMBEDDABLE_TYPE; - constructor({ - onEmbeddableStateChanged, - savedVisualization, - indexPatterns, - editUrl, - editable, - loader, - }: VisualizeEmbeddableConfiguration) { - super({ - title: savedVisualization.title, + constructor( + { + savedVisualization, + loader, editUrl, - editLabel: i18n.translate('kbn.embeddable.visualize.editLabel', { - defaultMessage: 'Edit visualization', - }), - editable, indexPatterns, - }); - this.onEmbeddableStateChanged = onEmbeddableStateChanged; + editable, + }: VisualizeEmbeddableConfiguration, + initialInput: VisualizeInput, + parent?: Container + ) { + super( + initialInput, + { + defaultTitle: savedVisualization.title, + editUrl, + indexPatterns, + editable, + savedObjectId: savedVisualization.id!, + }, + parent + ); this.savedVisualization = savedVisualization; this.loader = loader; @@ -82,6 +110,11 @@ export class VisualizeEmbeddable extends Embeddable { this.uiState = new PersistedState(parsedUiState); this.uiState.on('change', this.uiStateChangeHandler); + + this.subscription = Rx.merge(this.getOutput$(), this.getInput$()).subscribe(() => { + this.reload(); + this.handleChanges(); + }); } public getInspectorAdapters() { @@ -91,62 +124,61 @@ export class VisualizeEmbeddable extends Embeddable { return this.handler.inspectorAdapters; } - public getEmbeddableState() { - return { - customization: this.customization, - }; + public supportsTrigger(trigger: Trigger) { + return trigger.id !== APPLY_FILTER_TRIGGER; } /** - * Transfers all changes in the containerState.embeddableCustomization into + * Transfers all changes in the containerState.customization into * the uiState of this visualization. */ - public transferCustomizationsToUiState(containerState: ContainerState) { + public transferCustomizationsToUiState() { // Check for changes that need to be forwarded to the uiState // Since the vis has an own listener on the uiState we don't need to // pass anything from here to the handler.update method - const customization = containerState.embeddableCustomization; - if (customization && !_.isEqual(this.customization, customization)) { + const visCustomizations = this.input.vis; + if (visCustomizations) { // Turn this off or the uiStateChangeHandler will fire for every modification. this.uiState.off('change', this.uiStateChangeHandler); this.uiState.clearAllKeys(); - Object.getOwnPropertyNames(customization).forEach(key => { - this.uiState.set(key, customization[key]); + this.uiState.set('vis', visCustomizations); + getKeys(visCustomizations).forEach(key => { + this.uiState.set(key, visCustomizations[key]); }); - this.customization = customization; this.uiState.on('change', this.uiStateChangeHandler); + } else { + this.uiState.clearAllKeys(); } } - public onContainerStateChanged(containerState: ContainerState) { - this.transferCustomizationsToUiState(containerState); + public handleChanges() { + this.transferCustomizationsToUiState(); const updatedParams: VisualizeUpdateParams = {}; // Check if timerange has changed - if (containerState.timeRange !== this.timeRange) { - updatedParams.timeRange = containerState.timeRange; - this.timeRange = containerState.timeRange; + if (this.input.timeRange !== this.timeRange) { + updatedParams.timeRange = this.input.timeRange; + this.timeRange = this.input.timeRange; } // Check if filters has changed - if (containerState.filters !== this.filters) { - updatedParams.filters = containerState.filters; - this.filters = containerState.filters; + if (this.input.filters !== this.filters) { + updatedParams.filters = this.input.filters; + this.filters = this.input.filters; } // Check if query has changed - if (containerState.query !== this.query) { - updatedParams.query = containerState.query; - this.query = containerState.query; + if (this.input.query !== this.query) { + updatedParams.query = this.input.query; + this.query = this.input.query; } - const derivedPanelTitle = this.getPanelTitle(containerState); - if (this.panelTitle !== derivedPanelTitle) { + if (this.output.title !== this.title) { + this.title = this.output.title; updatedParams.dataAttrs = { - title: derivedPanelTitle, + title: this.title || '', }; - this.panelTitle = derivedPanelTitle; } if (this.handler && !_.isEmpty(updatedParams)) { @@ -159,17 +191,16 @@ export class VisualizeEmbeddable extends Embeddable { * @param {Element} domNode * @param {ContainerState} containerState */ - public render(domNode: HTMLElement, containerState: ContainerState) { - this.panelTitle = this.getPanelTitle(containerState); - this.timeRange = containerState.timeRange; - this.query = containerState.query; - this.filters = containerState.filters; + public render(domNode: HTMLElement) { + this.timeRange = this.input.timeRange; + this.query = this.input.query; + this.filters = this.input.filters; - this.transferCustomizationsToUiState(containerState); + this.transferCustomizationsToUiState(); const dataAttrs: { [key: string]: string } = { 'shared-item': '', - title: this.panelTitle, + title: this.output.title || '', }; if (this.savedVisualization.description) { dataAttrs.description = this.savedVisualization.description; @@ -179,10 +210,10 @@ export class VisualizeEmbeddable extends Embeddable { uiState: this.uiState, // Append visualization to container instead of replacing its content append: true, - timeRange: containerState.timeRange, - query: containerState.query, - filters: containerState.filters, - cssClass: `embPanel__content embPanel__content--fullWidth`, + timeRange: this.input.timeRange, + query: this.input.query, + filters: this.input.filters, + cssClass: `panel-content panel-content--fullWidth`, dataAttrs, }; @@ -194,6 +225,10 @@ export class VisualizeEmbeddable extends Embeddable { } public destroy() { + super.destroy(); + if (this.subscription) { + this.subscription.unsubscribe(); + } this.uiState.off('change', this.uiStateChangeHandler); this.savedVisualization.destroy(); if (this.handler) { @@ -208,23 +243,9 @@ export class VisualizeEmbeddable extends Embeddable { } } - /** - * Retrieve the panel title for this panel from the container state. - * This will either return the overwritten panel title or the visualization title. - */ - private getPanelTitle(containerState: ContainerState) { - let derivedPanelTitle = ''; - if (!containerState.hidePanelTitles) { - derivedPanelTitle = - containerState.customTitle !== undefined - ? containerState.customTitle - : this.savedVisualization.title; - } - return derivedPanelTitle; - } - private uiStateChangeHandler = () => { - this.customization = this.uiState.toJSON(); - this.onEmbeddableStateChanged(this.getEmbeddableState()); + this.updateInput({ + ...this.uiState.toJSON(), + }); }; } diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.ts deleted file mode 100644 index 954c0962ba7ef58..000000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.ts +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import chrome from 'ui/chrome'; -import { EmbeddableFactory } from 'ui/embeddable'; -import { getVisualizeLoader } from 'ui/visualize/loader'; - -import { Legacy } from 'kibana'; -import { capabilities } from 'ui/capabilities'; -import { - EmbeddableInstanceConfiguration, - OnEmbeddableStateChanged, -} from 'ui/embeddable/embeddable_factory'; -import { VisTypesRegistry } from 'ui/registry/vis_types'; -import { SavedObjectAttributes } from 'src/core/server'; -import { VisualizeEmbeddable } from './visualize_embeddable'; -import { SavedVisualizations } from '../types'; -import { DisabledLabEmbeddable } from './disabled_lab_embeddable'; -import { getIndexPattern } from './get_index_pattern'; - -export interface VisualizationAttributes extends SavedObjectAttributes { - visState: string; -} - -export class VisualizeEmbeddableFactory extends EmbeddableFactory { - private savedVisualizations: SavedVisualizations; - private config: Legacy.KibanaConfig; - - constructor( - savedVisualizations: SavedVisualizations, - config: Legacy.KibanaConfig, - visTypes: VisTypesRegistry - ) { - super({ - name: 'visualization', - savedObjectMetaData: { - name: i18n.translate('kbn.visualize.savedObjectName', { defaultMessage: 'Visualization' }), - type: 'visualization', - getIconForSavedObject: savedObject => { - return ( - visTypes.byName[JSON.parse(savedObject.attributes.visState).type].icon || 'visualizeApp' - ); - }, - getTooltipForSavedObject: savedObject => { - const visType = visTypes.byName[JSON.parse(savedObject.attributes.visState).type].title; - return `${savedObject.attributes.title} (${visType})`; - }, - showSavedObject: savedObject => { - if (chrome.getUiSettingsClient().get('visualize:enableLabs')) { - return true; - } - const typeName: string = JSON.parse(savedObject.attributes.visState).type; - const visType = visTypes.byName[typeName]; - return visType.stage !== 'experimental'; - }, - }, - }); - this.config = config; - this.savedVisualizations = savedVisualizations; - } - - public getEditPath(panelId: string) { - return this.savedVisualizations.urlFor(panelId); - } - - /** - * - * @param {Object} panelMetadata. Currently just passing in panelState but it's more than we need, so we should - * decouple this to only include data given to us from the embeddable when it's added to the dashboard. Generally - * will be just the object id, but could be anything depending on the plugin. - * @param {function} onEmbeddableStateChanged - * @return {Promise.<{ metadata, onContainerStateChanged, render, destroy }>} - */ - public async create( - panelMetadata: EmbeddableInstanceConfiguration, - onEmbeddableStateChanged: OnEmbeddableStateChanged - ) { - const visId = panelMetadata.id; - const editUrl = this.getEditPath(visId); - const editable: boolean = capabilities.get().visualize.save as boolean; - - const loader = await getVisualizeLoader(); - const savedObject = await this.savedVisualizations.get(visId); - const isLabsEnabled = this.config.get('visualize:enableLabs'); - - if (!isLabsEnabled && savedObject.vis.type.stage === 'experimental') { - return new DisabledLabEmbeddable(savedObject.title); - } - - const indexPattern = await getIndexPattern(savedObject); - const indexPatterns = indexPattern ? [indexPattern] : []; - return new VisualizeEmbeddable({ - onEmbeddableStateChanged, - savedVisualization: savedObject, - editUrl, - editable, - loader, - indexPatterns, - }); - } -} diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx new file mode 100644 index 000000000000000..cc510659fcf45ad --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -0,0 +1,189 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import 'ui/registry/field_formats'; +import 'uiExports/autocompleteProviders'; +import 'uiExports/contextMenuActions'; +import 'uiExports/devTools'; +import 'uiExports/docViews'; +import 'uiExports/embeddableFactories'; +import 'uiExports/fieldFormatEditors'; +import 'uiExports/fieldFormats'; +import 'uiExports/home'; +import 'uiExports/indexManagement'; +import 'uiExports/inspectorViews'; +import 'uiExports/savedObjectTypes'; +import 'uiExports/search'; +import 'uiExports/shareContextMenuExtensions'; +import 'uiExports/visEditorTypes'; +import 'uiExports/visRequestHandlers'; +import 'uiExports/visResponseHandlers'; +import 'uiExports/visTypes'; +import 'uiExports/visualize'; + +import { i18n } from '@kbn/i18n'; + +import { capabilities } from 'ui/capabilities'; +// @ts-ignore +import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; +// @ts-ignore +import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; + +import { + embeddableFactories, + EmbeddableFactory, + ErrorEmbeddable, + Container, + EmbeddableOutput, +} from 'plugins/embeddable_api'; +import chrome from 'ui/chrome'; +import { getVisualizeLoader } from 'ui/visualize/loader'; + +import { Legacy } from 'kibana'; +import { VisTypesRegistry, VisTypesRegistryProvider } from 'ui/registry/vis_types'; + +import { IPrivate } from 'ui/private'; +import { SavedObjectAttributes } from 'kibana/server'; +import { showNewVisModal } from '../wizard'; +import { SavedVisualizations } from '../types'; +import { DisabledLabEmbeddable } from './disabled_lab_embeddable'; +import { getIndexPattern } from './get_index_pattern'; +import { VisualizeEmbeddable, VisualizeInput, VisualizeOutput } from './visualize_embeddable'; +import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; + +interface VisualizationAttributes extends SavedObjectAttributes { + visState: string; +} + +export class VisualizeEmbeddableFactory extends EmbeddableFactory< + VisualizeInput, + VisualizeOutput | EmbeddableOutput, + VisualizeEmbeddable | DisabledLabEmbeddable, + VisualizationAttributes +> { + private visTypes?: VisTypesRegistry; + public readonly type = VISUALIZE_EMBEDDABLE_TYPE; + + constructor() { + super({ + savedObjectMetaData: { + name: i18n.translate('kbn.visualize.savedObjectName', { defaultMessage: 'Visualization' }), + type: 'visualization', + getIconForSavedObject: savedObject => { + if (!this.visTypes) { + return 'visualizeApp'; + } + return ( + this.visTypes.byName[JSON.parse(savedObject.attributes.visState).type].icon || + 'visualizeApp' + ); + }, + getTooltipForSavedObject: savedObject => { + if (!this.visTypes) { + return ''; + } + const visType = this.visTypes.byName[JSON.parse(savedObject.attributes.visState).type] + .title; + return `${savedObject.attributes.title} (${visType})`; + }, + showSavedObject: savedObject => { + if (chrome.getUiSettingsClient().get('visualize:enableLabs')) { + return true; + } + if (!this.visTypes) { + return false; + } + const typeName: string = JSON.parse(savedObject.attributes.visState).type; + const visType = this.visTypes.byName[typeName]; + return visType.stage !== 'experimental'; + }, + }, + }); + this.initializeVisTypes(); + } + + public isEditable() { + return capabilities.get().visualize.save as boolean; + } + + public getDisplayName() { + return i18n.translate('kbn.embeddable.visualizations.displayName', { + defaultMessage: 'visualization', + }); + } + + public async initializeVisTypes() { + const $injector = await chrome.dangerouslyGetActiveInjector(); + const Private = $injector.get('Private'); + this.visTypes = Private(VisTypesRegistryProvider); + } + + public async createFromSavedObject( + savedObjectId: string, + input: Partial & { id: string }, + parent?: Container + ): Promise { + const $injector = await chrome.dangerouslyGetActiveInjector(); + const config = $injector.get('config'); + const savedVisualizations = $injector.get('savedVisualizations'); + + try { + const visId = savedObjectId; + + const editUrl = chrome.addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`); + const loader = await getVisualizeLoader(); + const savedObject = await savedVisualizations.get(visId); + const isLabsEnabled = config.get('visualize:enableLabs'); + + if (!isLabsEnabled && savedObject.vis.type.stage === 'experimental') { + return new DisabledLabEmbeddable(savedObject.title, input); + } + + const indexPattern = await getIndexPattern(savedObject); + const indexPatterns = indexPattern ? [indexPattern] : []; + return new VisualizeEmbeddable( + { + savedVisualization: savedObject, + loader, + indexPatterns, + editUrl, + editable: this.isEditable(), + }, + input, + parent + ); + } catch (e) { + console.error(e); // eslint-disable-line no-console + return new ErrorEmbeddable(e, input, parent); + } + } + + public async create() { + // TODO: This is a bit of a hack to preserve the original functionality. Ideally we will clean this up + // to allow for in place creation of visualizations without having to navigate away to a new URL. + if (this.visTypes) { + showNewVisModal(this.visTypes, { + editorParams: ['addToDashboard'], + }); + } + return undefined; + } +} + +embeddableFactories.set(VISUALIZE_EMBEDDABLE_TYPE, new VisualizeEmbeddableFactory()); diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory_provider.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory_provider.ts deleted file mode 100644 index dcdf58a52d918e7..000000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory_provider.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Legacy } from 'kibana'; -import { EmbeddableFactoriesRegistryProvider } from 'ui/embeddable/embeddable_factories_registry'; -import { IPrivate } from 'ui/private'; -import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; -import { SavedVisualizations } from '../types'; -import { VisualizeEmbeddableFactory } from './visualize_embeddable_factory'; - -export function visualizeEmbeddableFactoryProvider(Private: IPrivate) { - const VisualizeEmbeddableFactoryProvider = ( - savedVisualizations: SavedVisualizations, - config: Legacy.KibanaConfig - ) => { - return new VisualizeEmbeddableFactory( - savedVisualizations, - config, - Private(VisTypesRegistryProvider) - ); - }; - return Private(VisualizeEmbeddableFactoryProvider); -} - -EmbeddableFactoriesRegistryProvider.register(visualizeEmbeddableFactoryProvider); diff --git a/src/legacy/ui/public/embeddable/context_menu_actions/build_eui_context_menu_panels.ts b/src/legacy/ui/public/embeddable/context_menu_actions/build_eui_context_menu_panels.ts deleted file mode 100644 index 87c3d13c8de931a..000000000000000 --- a/src/legacy/ui/public/embeddable/context_menu_actions/build_eui_context_menu_panels.ts +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; -import _ from 'lodash'; -import { ContainerState, ContextMenuAction, ContextMenuPanel, Embeddable } from 'ui/embeddable'; - -/** - * Loops through allActions and extracts those that belong on the given contextMenuPanelId - * @param {string} contextMenuPanelId - * @param {Array.} allActions - */ -function getActionsForPanel(contextMenuPanelId: string, allActions: ContextMenuAction[]) { - return allActions.filter(action => action.parentPanelId === contextMenuPanelId); -} - -/** - * @param {String} contextMenuPanelId - * @param {Array.} actions - * @param {Embeddable} embeddable - * @param {ContainerState} containerState - * @return {{ - * Array. items - panel actions converted into the items expected to be on an - * EuiContextMenuPanel, - * Array. childPanels - extracted child panels, if any actions also open a panel. They - * need to be moved to the top level for EUI. - * }} - */ -function buildEuiContextMenuPanelItemsAndChildPanels({ - contextMenuPanelId, - actions, - embeddable, - containerState, -}: { - contextMenuPanelId: string; - actions: ContextMenuAction[]; - embeddable?: Embeddable; - containerState: ContainerState; -}) { - const items: EuiContextMenuPanelItemDescriptor[] = []; - const childPanels: EuiContextMenuPanelDescriptor[] = []; - const actionsForPanel = getActionsForPanel(contextMenuPanelId, actions); - actionsForPanel.forEach(action => { - const isVisible = action.isVisible({ embeddable, containerState }); - if (!isVisible) { - return; - } - - if (action.childContextMenuPanel) { - childPanels.push( - ...buildEuiContextMenuPanels({ - contextMenuPanel: action.childContextMenuPanel, - actions, - embeddable, - containerState, - }) - ); - } - - items.push( - convertPanelActionToContextMenuItem({ - action, - containerState, - embeddable, - }) - ); - }); - - return { items, childPanels }; -} - -/** - * Transforms a DashboardContextMenuPanel to the shape EuiContextMenuPanel expects, inserting any registered pluggable - * panel actions. - * @param {ContextMenuPanel} contextMenuPanel - * @param {Array.} actions to build the context menu with - * @param {Embeddable} embeddable - * @param {ContainerState} containerState - * @return {EuiContextMenuPanelDescriptor[]} An array of context menu panels to be used in the eui react component. - */ -export function buildEuiContextMenuPanels({ - contextMenuPanel, - actions, - embeddable, - containerState, -}: { - contextMenuPanel: ContextMenuPanel; - actions: ContextMenuAction[]; - embeddable?: Embeddable; - containerState: ContainerState; -}): EuiContextMenuPanelDescriptor[] { - const euiContextMenuPanel: EuiContextMenuPanelDescriptor = { - id: contextMenuPanel.id, - title: contextMenuPanel.title, - items: [], - content: contextMenuPanel.getContent({ embeddable, containerState }), - }; - const contextMenuPanels = [euiContextMenuPanel]; - - const { items, childPanels } = buildEuiContextMenuPanelItemsAndChildPanels({ - contextMenuPanelId: contextMenuPanel.id, - actions, - embeddable, - containerState, - }); - - euiContextMenuPanel.items = items; - return contextMenuPanels.concat(childPanels); -} - -/** - * - * @param {ContextMenuAction} action - * @param {ContainerState} containerState - * @param {Embeddable} embeddable - * @return {EuiContextMenuPanelItemDescriptor} - */ -function convertPanelActionToContextMenuItem({ - action, - containerState, - embeddable, -}: { - action: ContextMenuAction; - containerState: ContainerState; - embeddable?: Embeddable; -}): EuiContextMenuPanelItemDescriptor { - const menuPanelItem: EuiContextMenuPanelItemDescriptor = { - name: action.getDisplayName({ embeddable, containerState }), - icon: action.icon, - panel: _.get(action, 'childContextMenuPanel.id'), - disabled: action.isDisabled({ embeddable, containerState }), - 'data-test-subj': `dashboardPanelAction-${action.id}`, - }; - - if (action.onClick) { - menuPanelItem.onClick = () => { - if (action.onClick) { - action.onClick({ embeddable, containerState }); - } - }; - } - - if (action.getHref) { - menuPanelItem.href = action.getHref({ embeddable, containerState }); - } - - return menuPanelItem; -} diff --git a/src/legacy/ui/public/embeddable/context_menu_actions/context_menu_action.ts b/src/legacy/ui/public/embeddable/context_menu_actions/context_menu_action.ts deleted file mode 100644 index 019bbce42690feb..000000000000000 --- a/src/legacy/ui/public/embeddable/context_menu_actions/context_menu_action.ts +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { EuiContextMenuItemIcon } from '@elastic/eui'; -import { ContextMenuPanel } from './context_menu_panel'; -import { PanelActionAPI } from './types'; - -interface ContextMenuActionOptions { - /** - * An optional child context menu to display when the action is clicked. - */ - childContextMenuPanel?: ContextMenuPanel; - - /** - * Whether this action should be disabled based on the parameters given. - * @param {PanelActionAPI} panelActionAPI - * @return {boolean} - */ - isDisabled?: (actionAPI: PanelActionAPI) => boolean; - - /** - * Whether this action should be visible based on the parameters given. - * @param {PanelActionAPI} panelActionAPI - * @return {boolean} - */ - isVisible?: (panelActionAPI: PanelActionAPI) => boolean; - - /** - * Determines which ContextMenuPanel this action is displayed on. - */ - parentPanelId?: string; - - /** - * Optional icon to display to the left of the action. - */ - icon?: EuiContextMenuItemIcon; - - /** - * Return display name of the action in the menu - */ - getDisplayName: (actionAPI: PanelActionAPI) => string; -} - -interface ContextMenuButtonOptions extends ContextMenuActionOptions { - /** - * An optional action to take when the action is clicked on. Either this or childContextMenuPanel should be - * given. - */ - onClick?: (actionAPI: PanelActionAPI) => void; -} - -interface ContextMenuLinkOptions extends ContextMenuActionOptions { - /** - * An optional href to use as navigation when the action is clicked on. - */ - getHref?: (actionAPI: PanelActionAPI) => string; -} - -interface ContextMenuActionsConfig { - id: string; - - /** - * Determines which ContextMenuPanel this action is displayed on. - */ - parentPanelId: string; -} - -export class ContextMenuAction { - public readonly id: string; - - /** - * Optional icon to display to the left of the action. - */ - public readonly icon?: EuiContextMenuItemIcon; - - /** - * Optional child context menu to open when the action is clicked. - */ - public readonly childContextMenuPanel?: ContextMenuPanel; - - /** - * Determines which ContextMenuPanel this action is displayed on. - */ - public readonly parentPanelId: string; - - /** - * @param {PanelActionAPI} panelActionAPI - */ - public readonly onClick?: (panelActionAPI: PanelActionAPI) => void; - - /** - * @param {PanelActionAPI} panelActionAPI - */ - public readonly getHref?: (panelActionAPI: PanelActionAPI) => string; - - /** - * @param {PanelActionAPI} panelActionAPI - */ - public readonly getDisplayName: (panelActionAPI: PanelActionAPI) => string; - - /** - * - * @param {string} config.id - * @param {string} config.parentPanelId - set if this action belongs on a nested child panel - * @param {function} options.onClick - * @param {ContextMenuPanel} options.childContextMenuPanel - optional child panel to open when clicked. - * @param {function} options.isDisabled - optionally set a custom disabled function - * @param {function} options.isVisible - optionally set a custom isVisible function - * @param {function} options.getHref - * @param {function} options.getDisplayName - * @param {Element} options.icon - */ - public constructor( - config: ContextMenuActionsConfig, - options: ContextMenuButtonOptions | ContextMenuLinkOptions - ) { - this.id = config.id; - this.parentPanelId = config.parentPanelId; - - this.icon = options.icon; - this.childContextMenuPanel = options.childContextMenuPanel; - this.getDisplayName = options.getDisplayName; - - if ('onClick' in options) { - this.onClick = options.onClick; - } - - if (options.isDisabled) { - this.isDisabled = options.isDisabled; - } - - if (options.isVisible) { - this.isVisible = options.isVisible; - } - - if ('getHref' in options) { - this.getHref = options.getHref; - } - } - - /** - * Whether this action should be visible based on the parameters given. Defaults to always visible. - * @param {PanelActionAPI} panelActionAPI - * @return {boolean} - */ - public isVisible(panelActionAPI: PanelActionAPI): boolean { - return true; - } - - /** - * Whether this action should be disabled based on the parameters given. Defaults to always enabled. - * @param {PanelActionAPI} panelActionAPI - */ - public isDisabled(panelActionAPI: PanelActionAPI): boolean { - return false; - } -} diff --git a/src/legacy/ui/public/embeddable/context_menu_actions/context_menu_actions_registry.ts b/src/legacy/ui/public/embeddable/context_menu_actions/context_menu_actions_registry.ts deleted file mode 100644 index fe9782e2000eb64..000000000000000 --- a/src/legacy/ui/public/embeddable/context_menu_actions/context_menu_actions_registry.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// @ts-ignore: implicit any for JS file -import { uiRegistry } from 'ui/registry/_registry'; - -export const ContextMenuActionsRegistryProvider = uiRegistry({ - index: ['name'], - name: 'ContextMenuActions', -}); diff --git a/src/legacy/ui/public/embeddable/context_menu_actions/context_menu_panel.ts b/src/legacy/ui/public/embeddable/context_menu_actions/context_menu_panel.ts deleted file mode 100644 index 900125e9a58837f..000000000000000 --- a/src/legacy/ui/public/embeddable/context_menu_actions/context_menu_panel.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { ReactElement } from 'react'; -import { PanelActionAPI } from './types'; - -interface ContextMenuPanelOptions { - getContent?: (panelActionAPI: PanelActionAPI) => ReactElement | HTMLElement | undefined; -} - -interface ContextMenuPanelConfig { - id: string; - title: string; -} - -export class ContextMenuPanel { - public readonly id: string; - public readonly title: string; - - constructor(config: ContextMenuPanelConfig, options: ContextMenuPanelOptions = {}) { - this.id = config.id; - this.title = config.title; - - if (options.getContent) { - this.getContent = options.getContent; - } - } - - /** - * Optional, could be composed of actions instead of content. - */ - public getContent(panelActionAPI: PanelActionAPI): ReactElement | HTMLElement | undefined { - return; - } -} diff --git a/src/legacy/ui/public/embeddable/context_menu_actions/index.ts b/src/legacy/ui/public/embeddable/context_menu_actions/index.ts deleted file mode 100644 index d43f1d16aefd1a8..000000000000000 --- a/src/legacy/ui/public/embeddable/context_menu_actions/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { ContextMenuPanel } from './context_menu_panel'; -export { ContextMenuAction } from './context_menu_action'; -export { ContextMenuActionsRegistryProvider } from './context_menu_actions_registry'; -export { buildEuiContextMenuPanels } from './build_eui_context_menu_panels'; -export { PanelActionAPI } from './types'; diff --git a/src/legacy/ui/public/embeddable/context_menu_actions/types.ts b/src/legacy/ui/public/embeddable/context_menu_actions/types.ts deleted file mode 100644 index a35c1f5b4480ee7..000000000000000 --- a/src/legacy/ui/public/embeddable/context_menu_actions/types.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { ContainerState, Embeddable } from 'ui/embeddable'; - -/** - * Exposes information about the current state of the panel and the embeddable rendered internally. - */ -export interface PanelActionAPI { - /** - * The embeddable that resides inside this action. It's possible it's undefined if the embeddable has not been returned from - * the EmbeddableFactory yet. - */ - embeddable?: Embeddable; - - /** - * Information about the current state of the panel and dashboard. - */ - containerState: ContainerState; -} diff --git a/src/legacy/ui/public/embeddable/embeddable.ts b/src/legacy/ui/public/embeddable/embeddable.ts deleted file mode 100644 index 55ef4f3a6b15acd..000000000000000 --- a/src/legacy/ui/public/embeddable/embeddable.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Adapters } from 'ui/inspector'; -import { StaticIndexPattern } from 'ui/index_patterns'; -import { ContainerState } from './types'; - -export interface EmbeddableMetadata { - /** - * Should specify any index pattern the embeddable uses. This will be used by the container to list out - * available fields to filter on. - */ - indexPatterns?: StaticIndexPattern[]; - - /** - * The title, or name, of the embeddable. - */ - title?: string; - - /** - * A url to direct the user for managing the embeddable instance. We may want to eventually make this optional - * for non-instanced panels that can only be created and deleted but not edited. We also wish to eventually support - * in-place editing on the dashboard itself, so another option could be to supply an element, or fly out panel, to - * offer for editing directly on the dashboard. - */ - editUrl?: string; - - editLabel?: string; - - /** - * A flag indicating if this embeddable can be edited. - */ - editable?: boolean; -} - -export abstract class Embeddable { - public readonly metadata: EmbeddableMetadata = {}; - - // TODO: Make title and editUrl required and move out of options parameter. - constructor(metadata: EmbeddableMetadata = {}) { - this.metadata = metadata || {}; - } - - public onContainerStateChanged(containerState: ContainerState): void { - return; - } - - /** - * Embeddable should render itself at the given domNode. - */ - public abstract render(domNode: HTMLElement, containerState: ContainerState): void; - - /** - * An embeddable can return inspector adapters if it want the inspector to be - * available via the context menu of that panel. - * @return Inspector adapters that will be used to open an inspector for. - */ - public getInspectorAdapters(): Adapters | undefined { - return undefined; - } - - public destroy(): void { - return; - } - - public reload(): void { - return; - } -} diff --git a/src/legacy/ui/public/embeddable/embeddable_factories_registry.ts b/src/legacy/ui/public/embeddable/embeddable_factories_registry.ts deleted file mode 100644 index 8b227081ec6f938..000000000000000 --- a/src/legacy/ui/public/embeddable/embeddable_factories_registry.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// @ts-ignore: implicit any for JS file -import { uiRegistry } from '../registry/_registry'; - -/** - * Registry of functions (EmbeddableFactoryProviders) which return an EmbeddableFactory. - */ -export const EmbeddableFactoriesRegistryProvider = uiRegistry({ - index: ['name'], - name: 'embeddableFactories', -}); diff --git a/src/legacy/ui/public/embeddable/embeddable_factory.ts b/src/legacy/ui/public/embeddable/embeddable_factory.ts deleted file mode 100644 index 782b8053498a9fc..000000000000000 --- a/src/legacy/ui/public/embeddable/embeddable_factory.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { SavedObjectAttributes } from 'src/core/server'; -import { SavedObjectMetaData } from '../saved_objects/components/saved_object_finder'; -import { Embeddable } from './embeddable'; -import { EmbeddableState } from './types'; -export interface EmbeddableInstanceConfiguration { - id: string; -} - -export type OnEmbeddableStateChanged = (embeddableStateChanges: EmbeddableState) => void; - -/** - * The EmbeddableFactory creates and initializes an embeddable instance - */ -export abstract class EmbeddableFactory { - public readonly name: string; - public readonly savedObjectMetaData?: SavedObjectMetaData; - - /** - * - * @param name - a unique identified for this factory, which will be used to map an embeddable spec to - * a factory that can generate an instance of it. - */ - constructor({ - name, - savedObjectMetaData, - }: { - name: string; - savedObjectMetaData?: SavedObjectMetaData; - }) { - this.name = name; - this.savedObjectMetaData = savedObjectMetaData; - } - - /** - * - * @param {{ id: string }} containerMetadata. Currently just passing in panelState but it's more than we need, so we should - * decouple this to only include data given to us from the embeddable when it's added to the dashboard. Generally - * will be just the object id, but could be anything depending on the plugin. - * @param {onEmbeddableStateChanged} onEmbeddableStateChanged - embeddable should call this function with updated - * state whenever something changes that the dashboard should know about. - * @return {Promise.} - */ - public abstract create( - containerMetadata: { id: string }, - onEmbeddableStateChanged: OnEmbeddableStateChanged - ): Promise; -} diff --git a/src/legacy/ui/public/embeddable/index.ts b/src/legacy/ui/public/embeddable/index.ts deleted file mode 100644 index a4d3b08bb1093f5..000000000000000 --- a/src/legacy/ui/public/embeddable/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { EmbeddableFactory, OnEmbeddableStateChanged } from './embeddable_factory'; -export * from './embeddable'; -export * from './context_menu_actions'; -export { EmbeddableFactoriesRegistryProvider } from './embeddable_factories_registry'; -export { ContainerState, EmbeddableState } from './types'; diff --git a/src/legacy/ui/public/embeddable/types.ts b/src/legacy/ui/public/embeddable/types.ts deleted file mode 100644 index 0f98b070dda78d1..000000000000000 --- a/src/legacy/ui/public/embeddable/types.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Filter } from '@kbn/es-query'; -import { RefreshInterval } from 'ui/timefilter/timefilter'; -import { TimeRange } from 'ui/timefilter/time_history'; -import { Query } from 'src/legacy/core_plugins/data/public'; - -export interface EmbeddableCustomization { - [key: string]: object | string; -} - -export interface ContainerState { - // 'view' or 'edit'. Should probably be an enum but I'm undecided where to define it, here or in dashboard code. - viewMode: string; - - timeRange: TimeRange; - - filters: Filter[]; - - refreshConfig: RefreshInterval; - - query: Query; - - // The shape will be up to the embeddable type. - embeddableCustomization?: EmbeddableCustomization; - - /** - * Whether or not panel titles are hidden. It is not the embeddable's responsibility to hide the title (the container - * handles that). This information is currently only used to determine the title for reporting (data-sharing-title - * attribute). If we move that out of the embeddables and push it to the container (as we probably should), then - * we shouldn't need to expose this information. - */ - hidePanelTitles: boolean; - - /** - * Is the current panel in expanded mode - */ - isPanelExpanded: boolean; - - /** - * A way to override the underlying embeddable title and supply a title at the panel level. - */ - customTitle?: string; -} - -export interface EmbeddableState { - /** - * Any customization data that should be stored at the panel level. For - * example, pie slice colors, or custom per panel sort order or columns. - */ - customization?: object; - /** - * A possible filter the embeddable wishes dashboard to apply. - */ - stagedFilter?: object; -} diff --git a/src/legacy/ui/public/saved_objects/saved_object.d.ts b/src/legacy/ui/public/saved_objects/saved_object.d.ts index 762cb9b6ee9c7f6..dc6496eacfcbed7 100644 --- a/src/legacy/ui/public/saved_objects/saved_object.d.ts +++ b/src/legacy/ui/public/saved_objects/saved_object.d.ts @@ -26,4 +26,5 @@ export interface SaveOptions { export interface SavedObject { save: (saveOptions: SaveOptions) => Promise; copyOnSave: boolean; + id?: string; } diff --git a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts index e5a4eb122dc8882..36891f0fbb5da0b 100644 --- a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts +++ b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts @@ -66,6 +66,8 @@ describe('EmbeddedVisualizeHandler', () => { title: 'My Vis', searchSource: undefined, destroy: () => ({}), + copyOnSave: false, + save: () => Promise.resolve('123'), }, { autoFetch: true, @@ -96,6 +98,8 @@ describe('EmbeddedVisualizeHandler', () => { title: 'My Vis', searchSource: undefined, destroy: () => ({}), + copyOnSave: false, + save: () => Promise.resolve('123'), }, { autoFetch: false, diff --git a/src/legacy/ui/public/visualize/loader/types.ts b/src/legacy/ui/public/visualize/loader/types.ts index 33aea5151762e32..dd7d0c663e481d2 100644 --- a/src/legacy/ui/public/visualize/loader/types.ts +++ b/src/legacy/ui/public/visualize/loader/types.ts @@ -20,12 +20,14 @@ import { TimeRange } from 'ui/timefilter/time_history'; import { Filter } from '@kbn/es-query'; import { Query } from 'src/legacy/core_plugins/data/public'; +import { SavedObject } from 'ui/saved_objects/saved_object'; + import { SearchSource } from '../../courier'; import { PersistedState } from '../../persisted_state'; import { AppState } from '../../state_management/app_state'; import { Vis } from '../../vis'; -export interface VisSavedObject { +export interface VisSavedObject extends SavedObject { vis: Vis; description?: string; searchSource: SearchSource; diff --git a/src/legacy/ui/ui_exports/ui_export_defaults.js b/src/legacy/ui/ui_exports/ui_export_defaults.js index 3ebcd6a254c902f..7795f8388c9583d 100644 --- a/src/legacy/ui/ui_exports/ui_export_defaults.js +++ b/src/legacy/ui/ui_exports/ui_export_defaults.js @@ -54,8 +54,8 @@ export const UI_EXPORT_DEFAULTS = { 'ui/vis/editors/default/default', ], embeddableFactories: [ - 'plugins/kibana/visualize/embeddable/visualize_embeddable_factory_provider', - 'plugins/kibana/discover/embeddable/search_embeddable_factory_provider', + 'plugins/kibana/visualize/embeddable/visualize_embeddable_factory', + 'plugins/kibana/discover/embeddable/search_embeddable_factory', ], search: [ 'ui/courier/search_strategy/default_search_strategy', diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/index.ts b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/index.ts index dd069010e0742ba..13b7f8fe52fa071 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/index.ts +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/index.ts @@ -27,7 +27,7 @@ function samplePanelAction(kibana: KibanaPlugin) { return new kibana.Plugin({ publicDir: resolve(__dirname, './public'), uiExports: { - contextMenuActions: [ + embeddableActions: [ 'plugins/kbn_tp_sample_panel_action/sample_panel_action', 'plugins/kbn_tp_sample_panel_action/sample_panel_link', ], diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx index d8832a8ae46e20c..b7d7daf8252aacd 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx @@ -21,26 +21,26 @@ import React from 'react'; import { npStart } from 'ui/new_platform'; import { - ContextMenuAction, - ContextMenuActionsRegistryProvider, - PanelActionAPI, -} from 'ui/embeddable'; + ActionContext, + actionRegistry, + Action, + triggerRegistry, + attachAction, + CONTEXT_MENU_TRIGGER, +} from 'plugins/embeddable_api'; + +class SamplePanelAction extends Action { + public readonly type = 'samplePanelAction'; -class SamplePanelAction extends ContextMenuAction { constructor() { - super( - { - id: 'samplePanelAction', - parentPanelId: 'mainMenu', - }, - { - getDisplayName: () => { - return 'Sample Panel Action'; - }, - } - ); + super('samplePanelAction'); + } + + public getDisplayName() { + return 'Sample Panel Action'; } - public onClick = ({ embeddable }: PanelActionAPI) => { + + public execute = ({ embeddable }: ActionContext) => { if (!embeddable) { return; } @@ -48,7 +48,7 @@ class SamplePanelAction extends ContextMenuAction { -

{embeddable.metadata.title}

+

{embeddable.getTitle()}

@@ -62,4 +62,5 @@ class SamplePanelAction extends ContextMenuAction { }; } -ContextMenuActionsRegistryProvider.register(() => new SamplePanelAction()); +actionRegistry.set('samplePanelAction', new SamplePanelAction()); +attachAction(triggerRegistry, { triggerId: CONTEXT_MENU_TRIGGER, actionId: 'samplePanelAction' }); diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_link.ts b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_link.ts index ea754dc269d0317..4e59bbd8968143d 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_link.ts +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_link.ts @@ -16,21 +16,27 @@ * specific language governing permissions and limitations * under the License. */ -import { ContextMenuAction, ContextMenuActionsRegistryProvider } from 'ui/embeddable'; +import { + Action, + actionRegistry, + triggerRegistry, + CONTEXT_MENU_TRIGGER, + attachAction, +} from 'plugins/embeddable_api'; + +class SamplePanelLink extends Action { + public readonly type = 'samplePanelLink'; -class SamplePanelLink extends ContextMenuAction { constructor() { - super( - { - id: 'samplePanelLink', - parentPanelId: 'mainMenu', - }, - { - getDisplayName: () => { - return 'Sample Panel Link'; - }, - } - ); + super('samplePanelLink'); + } + + public getDisplayName() { + return 'Sample panel Link'; + } + + public execute() { + return; } public getHref = () => { @@ -38,4 +44,6 @@ class SamplePanelLink extends ContextMenuAction { }; } -ContextMenuActionsRegistryProvider.register(() => new SamplePanelLink()); +actionRegistry.set('samplePanelLink', new SamplePanelLink()); + +attachAction(triggerRegistry, { triggerId: CONTEXT_MENU_TRIGGER, actionId: 'samplePanelLink' }); diff --git a/x-pack/plugins/maps/index.js b/x-pack/plugins/maps/index.js index 89ec5476416503a..f90fb651df1f8dd 100644 --- a/x-pack/plugins/maps/index.js +++ b/x-pack/plugins/maps/index.js @@ -54,7 +54,7 @@ export function maps(kibana) { }; }, embeddableFactories: [ - 'plugins/maps/embeddable/map_embeddable_factory_provider' + 'plugins/maps/embeddable/map_embeddable_factory' ], inspectorViews: [ 'plugins/maps/inspector/views/register_views', diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.js b/x-pack/plugins/maps/public/embeddable/map_embeddable.js index c088338030adcfe..fb09b5dd9c55cba 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.js +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.js @@ -10,7 +10,7 @@ import { Provider } from 'react-redux'; import { render, unmountComponentAtNode } from 'react-dom'; import 'mapbox-gl/dist/mapbox-gl.css'; -import { Embeddable } from 'ui/embeddable'; +import { Embeddable } from '../../../../../src/legacy/core_plugins/embeddable_api/public/index'; import { I18nContext } from 'ui/i18n'; import { GisMap } from '../components/gis_map'; @@ -34,31 +34,26 @@ import { } from '../store/ui'; import { getInspectorAdapters } from '../store/non_serializable_instances'; import { getMapCenter, getMapZoom } from '../selectors/map_selectors'; -import { i18n } from '@kbn/i18n'; +import { MAP_SAVED_OBJECT_TYPE } from '../../common/constants'; export class MapEmbeddable extends Embeddable { - - constructor({ - onEmbeddableStateChanged, - embeddableConfig, - savedMap, - editUrl, - editable, - indexPatterns = [] - }) { - super({ - title: savedMap.title, - editUrl, - editLabel: i18n.translate('xpack.maps.embeddable.editLabel', { - defaultMessage: 'Edit map', - }), - editable, - indexPatterns }); - - this._onEmbeddableStateChanged = onEmbeddableStateChanged; - this._embeddableConfig = _.cloneDeep(embeddableConfig); - this._savedMap = savedMap; + type = MAP_SAVED_OBJECT_TYPE; + + constructor(config, initialInput, parent) { + super( + initialInput, + { + editUrl: config.editUrl, + indexPatterns: config.indexPatterns, + editable: config.editable, + defaultTitle: config.savedMap.title + }, + parent); + + this._savedMap = config.savedMap; this._store = createMapStore(); + + this._subscription = this.getInput$().subscribe((input) => this.onContainerStateChanged(input)); } getInspectorAdapters() { @@ -72,11 +67,7 @@ export class MapEmbeddable extends Embeddable { this._dispatchSetQuery(containerState); } - const refreshConfig = { - isPaused: containerState.refreshConfig.pause, - interval: containerState.refreshConfig.value - }; - if (!_.isEqual(refreshConfig, this._prevRefreshConfig)) { + if (!_.isEqual(containerState.refreshConfig, this._prevRefreshConfig)) { this._dispatchSetRefreshConfig(containerState); } } @@ -93,12 +84,11 @@ export class MapEmbeddable extends Embeddable { } _dispatchSetRefreshConfig({ refreshConfig }) { - const internalRefreshConfig = { + this._prevRefreshConfig = refreshConfig; + this._store.dispatch(setRefreshConfig({ isPaused: refreshConfig.pause, - interval: refreshConfig.value - }; - this._prevRefreshConfig = internalRefreshConfig; - this._store.dispatch(setRefreshConfig(internalRefreshConfig)); + interval: refreshConfig.value, + })); } /** @@ -106,30 +96,30 @@ export class MapEmbeddable extends Embeddable { * @param {HTMLElement} domNode * @param {ContainerState} containerState */ - render(domNode, containerState) { + render(domNode) { this._store.dispatch(setReadOnly(true)); this._store.dispatch(setFilterable(true)); this._store.dispatch(disableScrollZoom()); - if (_.has(this._embeddableConfig, 'isLayerTOCOpen')) { - this._store.dispatch(setIsLayerTOCOpen(this._embeddableConfig.isLayerTOCOpen)); + if (_.has(this.input, 'isLayerTOCOpen')) { + this._store.dispatch(setIsLayerTOCOpen(this.input.isLayerTOCOpen)); } else if (this._savedMap.uiStateJSON) { const uiState = JSON.parse(this._savedMap.uiStateJSON); this._store.dispatch(setIsLayerTOCOpen(_.get(uiState, 'isLayerTOCOpen', DEFAULT_IS_LAYER_TOC_OPEN))); } - if (_.has(this._embeddableConfig, 'openTOCDetails')) { - this._store.dispatch(setOpenTOCDetails(this._embeddableConfig.openTOCDetails)); + if (_.has(this.input, 'openTOCDetails')) { + this._store.dispatch(setOpenTOCDetails(this.input.openTOCDetails)); } else if (this._savedMap.uiStateJSON) { const uiState = JSON.parse(this._savedMap.uiStateJSON); this._store.dispatch(setOpenTOCDetails(_.get(uiState, 'openTOCDetails', []))); } - if (this._embeddableConfig.mapCenter) { + if (this.input.mapCenter) { this._store.dispatch(setGotoWithCenter({ - lat: this._embeddableConfig.mapCenter.lat, - lon: this._embeddableConfig.mapCenter.lon, - zoom: this._embeddableConfig.mapCenter.zoom, + lat: this.input.mapCenter.lat, + lon: this.input.mapCenter.lon, + zoom: this.input.mapCenter.zoom, })); } else if (this._savedMap.mapStateJSON) { const mapState = JSON.parse(this._savedMap.mapStateJSON); @@ -141,8 +131,8 @@ export class MapEmbeddable extends Embeddable { } const layerList = getInitialLayers(this._savedMap.layerListJSON); this._store.dispatch(replaceLayerList(layerList)); - this._dispatchSetQuery(containerState); - this._dispatchSetRefreshConfig(containerState); + this._dispatchSetQuery(this.input); + this._dispatchSetRefreshConfig(this.input); render( @@ -159,6 +149,7 @@ export class MapEmbeddable extends Embeddable { } destroy() { + super.destroy(); if (this._unsubscribeFromStore) { this._unsubscribeFromStore(); } @@ -166,6 +157,10 @@ export class MapEmbeddable extends Embeddable { if (this._domNode) { unmountComponentAtNode(this._domNode); } + + if (this._subscription) { + this._subscription.unsubscribe(); + } } reload() { @@ -177,37 +172,38 @@ export class MapEmbeddable extends Embeddable { } _handleStoreChanges() { - let embeddableConfigChanged = false; const center = getMapCenter(this._store.getState()); const zoom = getMapZoom(this._store.getState()); - if (!this._embeddableConfig.mapCenter - || this._embeddableConfig.mapCenter.lat !== center.lat - || this._embeddableConfig.mapCenter.lon !== center.lon - || this._embeddableConfig.mapCenter.zoom !== zoom) { - embeddableConfigChanged = true; - this._embeddableConfig.mapCenter = { - lat: center.lat, - lon: center.lon, - zoom: zoom, - }; + + + const mapCenter = this.input.mapCenter || {}; + if (!mapCenter + || mapCenter.lat !== center.lat + || mapCenter.lon !== center.lon + || mapCenter.zoom !== zoom) { + this.updateInput({ + mapCenter: { + lat: center.lat, + lon: center.lon, + zoom: zoom, + } + }); } const isLayerTOCOpen = getIsLayerTOCOpen(this._store.getState()); - if (this._embeddableConfig.isLayerTOCOpen !== isLayerTOCOpen) { - embeddableConfigChanged = true; - this._embeddableConfig.isLayerTOCOpen = isLayerTOCOpen; - } - const openTOCDetails = getOpenTOCDetails(this._store.getState()); - if (!_.isEqual(this._embeddableConfig.openTOCDetails, openTOCDetails)) { - embeddableConfigChanged = true; - this._embeddableConfig.openTOCDetails = openTOCDetails; + if (!this.input.isLayerTOCOpen + || this.input.isLayerTOCOpen !== isLayerTOCOpen) { + this.updateInput({ + isLayerTOCOpen + }); } - if (embeddableConfigChanged) { - this._onEmbeddableStateChanged({ - customization: this._embeddableConfig + const openTOCDetails = getOpenTOCDetails(this._store.getState()); + if (!_.isEqual(this.input.openTOCDetails, openTOCDetails)) { + this.updateInput({ + openTOCDetails }); } } diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.js b/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.js index 7074d83db79541d..961dc8a4890d2c7 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.js +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.js @@ -6,30 +6,48 @@ import _ from 'lodash'; import chrome from 'ui/chrome'; -import { EmbeddableFactory } from 'ui/embeddable'; +import { capabilities } from 'ui/capabilities'; +import { i18n } from '@kbn/i18n'; +import { + EmbeddableFactory, + embeddableFactories, + ErrorEmbeddable +} from '../../../../../src/legacy/core_plugins/embeddable_api/public/index'; import { MapEmbeddable } from './map_embeddable'; import { indexPatternService } from '../kibana_services'; -import { i18n } from '@kbn/i18n'; + import { createMapPath, MAP_SAVED_OBJECT_TYPE, APP_ICON } from '../../common/constants'; import { createMapStore } from '../store/store'; import { addLayerWithoutDataSync } from '../actions/store_actions'; import { getQueryableUniqueIndexPatternIds } from '../selectors/map_selectors'; -import { capabilities } from 'ui/capabilities'; +import '../angular/services/gis_map_saved_object_loader'; +import 'ui/vis/map/service_settings'; export class MapEmbeddableFactory extends EmbeddableFactory { + type = MAP_SAVED_OBJECT_TYPE; - constructor(gisMapSavedObjectLoader) { + constructor() { super({ - name: 'map', savedObjectMetaData: { name: i18n.translate('xpack.maps.mapSavedObjectLabel', { defaultMessage: 'Map', }), type: MAP_SAVED_OBJECT_TYPE, - getIconForSavedObject: () => APP_ICON + getIconForSavedObject: () => APP_ICON, }, }); - this._savedObjectLoader = gisMapSavedObjectLoader; + } + isEditable() { + return capabilities.get().maps.save; + } + + // Not supported yet for maps types. + canCreateNew() { return false; } + + getDisplayName() { + return i18n.translate('xpack.maps.embeddableDisplayName', { + defaultMessage: 'map', + }); } async _getIndexPatterns(layerListJSON) { @@ -59,20 +77,33 @@ export class MapEmbeddableFactory extends EmbeddableFactory { return _.compact(indexPatterns); } - async create(panelMetadata, onEmbeddableStateChanged) { - const savedMap = await this._savedObjectLoader.get(panelMetadata.id); + async createFromSavedObject( + savedObjectId, + input, + parent + ) { + const $injector = await chrome.dangerouslyGetActiveInjector(); + const savedObjectLoader = $injector.get('gisMapSavedObjectLoader'); + const savedMap = await savedObjectLoader.get(savedObjectId); const indexPatterns = await this._getIndexPatterns(savedMap.layerListJSON); - const editable = capabilities.get().maps.save; + return new MapEmbeddable( + { + savedMap, + editUrl: chrome.addBasePath(createMapPath(savedObjectId)), + indexPatterns, + editable: this.isEditable(), + }, + input, + parent + ); + } - return new MapEmbeddable({ - onEmbeddableStateChanged, - embeddableConfig: panelMetadata.embeddableConfig, - savedMap, - editUrl: chrome.addBasePath(createMapPath(panelMetadata.id)), - editable, - indexPatterns, - }); + async create(input) { + window.location.href = chrome.addBasePath(createMapPath('')); + return new ErrorEmbeddable('Maps can only be created from a saved object', input); } } + +embeddableFactories.set(MAP_SAVED_OBJECT_TYPE, new MapEmbeddableFactory()); diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable_factory_provider.js b/x-pack/plugins/maps/public/embeddable/map_embeddable_factory_provider.js deleted file mode 100644 index 38be62a658f0f9e..000000000000000 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable_factory_provider.js +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EmbeddableFactoriesRegistryProvider } from 'ui/embeddable/embeddable_factories_registry'; -import { MapEmbeddableFactory } from './map_embeddable_factory'; -import '../angular/services/gis_map_saved_object_loader'; - -function mapEmbeddableFactoryProvider(gisMapSavedObjectLoader) { - return new MapEmbeddableFactory(gisMapSavedObjectLoader); -} - -EmbeddableFactoriesRegistryProvider.register(mapEmbeddableFactoryProvider); diff --git a/x-pack/plugins/reporting/index.js b/x-pack/plugins/reporting/index.js index ea659993d15aff1..bb319df5828af2a 100644 --- a/x-pack/plugins/reporting/index.js +++ b/x-pack/plugins/reporting/index.js @@ -36,7 +36,7 @@ export const reporting = (kibana) => { 'plugins/reporting/share_context_menu/register_csv_reporting', 'plugins/reporting/share_context_menu/register_reporting', ], - contextMenuActions: [ + embeddableActions: [ 'plugins/reporting/panel_actions/get_csv_panel_action', ], hacks: ['plugins/reporting/hacks/job_completion_notifier'], @@ -127,7 +127,7 @@ export const reporting = (kibana) => { }).default() }).default(), csv: Joi.object({ - enablePanelActionDownload: Joi.boolean().default(false), + enablePanelActionDownload: Joi.boolean().default(true), maxSizeBytes: Joi.number().integer().default(1024 * 1024 * 10), // bytes in a kB * kB in a mB * 10 scroll: Joi.object({ duration: Joi.string().regex(/^[0-9]+(d|h|m|s|ms|micros|nanos)$/, { name: 'DurationString' }).default('30s'), diff --git a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx index 939aef9586cc16e..de28e5f536d3545 100644 --- a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx +++ b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx @@ -3,40 +3,62 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import React from 'react'; import dateMath from '@elastic/datemath'; import { i18n } from '@kbn/i18n'; import moment from 'moment-timezone'; -import { ContextMenuAction, ContextMenuActionsRegistryProvider } from 'ui/embeddable'; -import { PanelActionAPI } from 'ui/embeddable/context_menu_actions/types'; import { kfetch } from 'ui/kfetch'; import { toastNotifications } from 'ui/notify'; import chrome from 'ui/chrome'; +import { EuiIcon } from '@elastic/eui'; +import { + ISearchEmbeddable, + SEARCH_EMBEDDABLE_TYPE, +} from '../../../../../src/legacy/core_plugins/kibana/public/discover/embeddable'; + +import { + Action, + actionRegistry, + ActionContext, + ViewMode, + IncompatibleActionError, + IEmbeddable, + triggerRegistry, + attachAction, + CONTEXT_MENU_TRIGGER, +} from '../../../../../src/legacy/core_plugins/embeddable_api/public'; import { API_BASE_URL_V1 } from '../../common/constants'; const API_BASE_URL = `${API_BASE_URL_V1}/generate/immediate/csv/saved-object`; -class GetCsvReportPanelAction extends ContextMenuAction { +const CSV_REPORTING_ACTION = 'downloadCsvReport'; + +function isSavedSearchEmbeddable( + embeddable: IEmbeddable | ISearchEmbeddable +): embeddable is ISearchEmbeddable { + return embeddable.type === SEARCH_EMBEDDABLE_TYPE; +} +class GetCsvReportPanelAction extends Action { private isDownloading: boolean; + public readonly type = CSV_REPORTING_ACTION; constructor() { - super( - { - id: 'downloadCsvReport', - parentPanelId: 'mainMenu', - }, - { - icon: 'document', - getDisplayName: () => - i18n.translate('xpack.reporting.dashboard.downloadCsvPanelTitle', { - defaultMessage: 'Download CSV', - }), - } - ); + super(CSV_REPORTING_ACTION); this.isDownloading = false; } + public getIcon() { + return ; + } + + public getDisplayName() { + return i18n.translate('xpack.reporting.dashboard.downloadCsvPanelTitle', { + defaultMessage: 'Download CSV', + }); + } + public async getSearchRequestBody({ searchEmbeddable }: { searchEmbeddable: any }) { const adapters = searchEmbeddable.getInspectorAdapters(); if (!adapters) { @@ -50,37 +72,40 @@ class GetCsvReportPanelAction extends ContextMenuAction { return searchEmbeddable.searchScope.searchSource.getSearchRequestBody(); } - public isVisible = (panelActionAPI: PanelActionAPI): boolean => { + public isCompatible = async (context: ActionContext) => { const enablePanelActionDownload = chrome.getInjected('enablePanelActionDownload'); if (!enablePanelActionDownload) { return false; } - const { embeddable, containerState } = panelActionAPI; + const { embeddable } = context; - return ( - containerState.viewMode !== 'edit' && !!embeddable && embeddable.hasOwnProperty('savedSearch') - ); + return embeddable.getInput().viewMode !== ViewMode.EDIT && embeddable.type === 'search'; }; - public onClick = async (panelActionAPI: PanelActionAPI) => { - const { embeddable } = panelActionAPI as any; - const { - timeRange: { from, to }, - } = embeddable; + public execute = async (context: ActionContext) => { + const { embeddable } = context; + + if (!isSavedSearchEmbeddable(embeddable)) { + throw new IncompatibleActionError(); + } - if (!embeddable || this.isDownloading) { + if (this.isDownloading) { return; } + const { + timeRange: { to, from }, + } = embeddable.getInput(); + const searchEmbeddable = embeddable; const searchRequestBody = await this.getSearchRequestBody({ searchEmbeddable }); const state = _.pick(searchRequestBody, ['sort', 'docvalue_fields', 'query']); const kibanaTimezone = chrome.getUiSettingsClient().get('dateFormat:tz'); - const id = `search:${embeddable.savedSearch.id}`; - const filename = embeddable.getPanelTitle(); + const id = `search:${embeddable.getSavedSearch().id}`; + const filename = embeddable.getTitle(); const timezone = kibanaTimezone === 'Browser' ? moment.tz.guess() : kibanaTimezone; const fromTime = dateMath.parse(from); const toTime = dateMath.parse(to); @@ -151,4 +176,6 @@ class GetCsvReportPanelAction extends ContextMenuAction { } } -ContextMenuActionsRegistryProvider.register(() => new GetCsvReportPanelAction()); +actionRegistry.set(CSV_REPORTING_ACTION, new GetCsvReportPanelAction()); + +attachAction(triggerRegistry, { triggerId: CONTEXT_MENU_TRIGGER, actionId: CSV_REPORTING_ACTION }); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d3b037c8c8f9b6f..b67165ea21ce282 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1306,7 +1306,6 @@ "kbn.dashboard.changeViewModeConfirmModal.discardChangesTitle": "ダッシュボードへの変更を破棄しますか?", "kbn.dashboard.dashboardAppBreadcrumbsTitle": "ダッシュボード", "kbn.dashboard.dashboardBreadcrumbsTitle": "ダッシュボード", - "kbn.dashboard.dashboardGrid.unableToLoadDashboardDangerMessage": "ダッシュボードが読み込めません。", "kbn.dashboard.dashboardWasNotSavedDangerMessage": "ダッシュボード「{dashTitle}」は保存されませんでした。エラー: {errorMessage}", "kbn.dashboard.dashboardWasSavedSuccessMessage": "ダッシュボード「{dashTitle}」が保存されました。", "kbn.dashboard.featureCatalogue.dashboardDescription": "ビジュアライゼーションと保存された検索のコレクションの表示と共有を行います。", @@ -1326,21 +1325,7 @@ "kbn.dashboard.listing.table.entityName": "ダッシュボード", "kbn.dashboard.listing.table.entityNamePlural": "ダッシュボード", "kbn.dashboard.listing.table.titleColumnName": "タイトル", - "kbn.dashboard.panel.customizePanel.displayName": "パネルをカスタマイズ", - "kbn.dashboard.panel.customizePanelTitle": "パネルをカスタマイズ", - "kbn.dashboard.panel.dashboardPanelAriaLabel": "ダッシュボードパネル: {title}", - "kbn.dashboard.panel.inspectorPanel.displayName": "検査", "kbn.dashboard.panel.invalidVersionErrorMessage": "無効なバージョン {version}、{semver} が必要です", - "kbn.dashboard.panel.noEmbeddableFactoryErrorMessage": "このパネルを並べ替える機能が欠けています。", - "kbn.dashboard.panel.noFoundEmbeddableFactoryErrorMessage": "パネルタイプ {panelType} をレンダリングする機能がありません", - "kbn.dashboard.panel.optionsMenu.optionsContextMenuTitle": "オプション", - "kbn.dashboard.panel.optionsMenu.panelOptionsButtonAriaLabel": "パネルオプション", - "kbn.dashboard.panel.optionsMenuForm.panelTitleFormRowLabel": "パネルタイトル", - "kbn.dashboard.panel.optionsMenuForm.panelTitleInputAriaLabel": "このインプットへの変更は直ちに適用されます。Enter を押して閉じます。", - "kbn.dashboard.panel.optionsMenuForm.resetCustomDashboardButtonLabel": "タイトルをリセット", - "kbn.dashboard.panel.removePanel.displayName": "ダッシュボードから削除", - "kbn.dashboard.panel.toggleExpandPanel.expandedDisplayName": "最小化", - "kbn.dashboard.panel.toggleExpandPanel.notExpandedDisplayName": "全画面", "kbn.dashboard.panel.unableToMigratePanelDataForSixOneZeroErrorMessage": "「6.1.0」のダッシュボードの互換性のため、パネルデータを移行できませんでした。パネルに必要なフィールドがありません: {key}", "kbn.dashboard.panel.unableToMigratePanelDataForSixThreeZeroErrorMessage": "「6.3.0」のダッシュボードの互換性のため、パネルデータを移行できませんでした。パネルに必要なフィールドがありません: {key}", "kbn.dashboard.savedDashboard.newDashboardTitle": "新規ダッシュボード", @@ -1348,10 +1333,6 @@ "kbn.dashboard.stateManager.timeNotSavedWithDashboardErrorMessage": "このダッシュボードに時刻が保存されていないため、同期できません。", "kbn.dashboard.strings.dashboardEditTitle": "{title} を編集中", "kbn.dashboard.strings.dashboardUnsavedEditTitle": "{title} を編集中 (未保存)", - "kbn.dashboard.topNav.addPanel.createNewVisualizationButtonLabel": "新規ビジュアライゼーションを追加", - "kbn.dashboard.topNav.addPanel.noMatchingObjectsMessage": "一致するオブジェクトが見つかりません。", - "kbn.dashboard.topNav.addPanel.savedObjectAddedToDashboardSuccessMessageTitle": "ダッシュボードに {savedObjectName} が追加されました", - "kbn.dashboard.topNav.addPanelsTitle": "パネルの追加", "kbn.dashboard.topNav.cloneModal.cancelButtonLabel": "キャンセル", "kbn.dashboard.topNav.cloneModal.cloneDashboardModalHeaderTitle": "ダッシュボードを閉じる", "kbn.dashboard.topNav.cloneModal.confirmButtonLabel": "クローンの確定します", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 63e6d0f93bca8c7..82e666d2eea0cbc 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1055,7 +1055,6 @@ "kbn.dashboard.changeViewModeConfirmModal.discardChangesTitle": "放弃对仪表板的更改?", "kbn.dashboard.dashboardAppBreadcrumbsTitle": "仪表板", "kbn.dashboard.dashboardBreadcrumbsTitle": "仪表板", - "kbn.dashboard.dashboardGrid.unableToLoadDashboardDangerMessage": "无法加载仪表板。", "kbn.dashboard.dashboardWasNotSavedDangerMessage": "仪表板 “{dashTitle}” 未保存。错误:{errorMessage}", "kbn.dashboard.dashboardWasSavedSuccessMessage": "仪表板 “{dashTitle}” 已保存", "kbn.dashboard.featureCatalogue.dashboardDescription": "显示和共享可视化和已保存搜索的集合。", @@ -1072,20 +1071,7 @@ "kbn.dashboard.listing.dashboardsTitle": "仪表板", "kbn.dashboard.listing.table.descriptionColumnName": "描述", "kbn.dashboard.listing.table.titleColumnName": "标题", - "kbn.dashboard.panel.customizePanel.displayName": "定制面板", - "kbn.dashboard.panel.customizePanelTitle": "定制面板", - "kbn.dashboard.panel.dashboardPanelAriaLabel": "仪表板面板:{title}", - "kbn.dashboard.panel.inspectorPanel.displayName": "检查", "kbn.dashboard.panel.invalidVersionErrorMessage": "版本 {version} 无效,应为 {semver}", - "kbn.dashboard.panel.noFoundEmbeddableFactoryErrorMessage": "未找到面板类型 {panelType} 的 Embeddable 工厂", - "kbn.dashboard.panel.optionsMenu.optionsContextMenuTitle": "选项", - "kbn.dashboard.panel.optionsMenu.panelOptionsButtonAriaLabel": "面板选项", - "kbn.dashboard.panel.optionsMenuForm.panelTitleFormRowLabel": "面板标题", - "kbn.dashboard.panel.optionsMenuForm.panelTitleInputAriaLabel": "对此输入的更改将立即应用。按 enter 键可退出。", - "kbn.dashboard.panel.optionsMenuForm.resetCustomDashboardButtonLabel": "重置标题", - "kbn.dashboard.panel.removePanel.displayName": "从仪表板删除", - "kbn.dashboard.panel.toggleExpandPanel.expandedDisplayName": "最小化", - "kbn.dashboard.panel.toggleExpandPanel.notExpandedDisplayName": "全屏", "kbn.dashboard.panel.unableToMigratePanelDataForSixOneZeroErrorMessage": "无法迁移用于“6.1.0”向后兼容的面板数据,面板不包含预期字段:{key}", "kbn.dashboard.panel.unableToMigratePanelDataForSixThreeZeroErrorMessage": "无法迁移用于“6.3.0”向后兼容的面板数据,面板不包含预期字段:{key}", "kbn.dashboard.savedDashboard.newDashboardTitle": "新建仪表板", @@ -1093,7 +1079,6 @@ "kbn.dashboard.stateManager.timeNotSavedWithDashboardErrorMessage": "时间未随此仪表板保存,因此无法同步。", "kbn.dashboard.strings.dashboardEditTitle": "编辑 {title}", "kbn.dashboard.strings.dashboardUnsavedEditTitle": "编辑 {title}(未保存)", - "kbn.dashboard.topNav.addPanelsTitle": "添加面板", "kbn.dashboard.topNav.cloneModal.cancelButtonLabel": "取消", "kbn.dashboard.topNav.cloneModal.cloneDashboardModalHeaderTitle": "克隆面板", "kbn.dashboard.topNav.cloneModal.confirmButtonLabel": "确认克隆",