@@ -452,7 +470,11 @@ export function App({ {isSaveModalVisible && ( { @@ -465,13 +487,15 @@ export function App({ initialInput={initialInput} redirectTo={redirectTo} redirectToOrigin={redirectToOrigin} + initialContext={initialContext} returnToOriginSwitchLabel={ - getIsByValueMode() && initialInput + returnToOriginSwitchLabelForContext ?? + (getIsByValueMode() && initialInput ? i18n.translate('xpack.lens.app.updatePanel', { defaultMessage: 'Update panel on {originatingAppName}', values: { originatingAppName: getOriginatingAppName() }, }) - : undefined + : undefined) } /> )} @@ -494,7 +518,7 @@ export function App({ > {i18n.translate('xpack.lens.app.goBackModalMessage', { defaultMessage: - 'The changes you have made here are not backwards compatible with your original {contextOriginatingApp} visualization. Are you sure you want to discard these unsaved changes and return to {contextOriginatingApp}?', + 'The changes you have made here are not backwards compatible with your original {contextOriginatingApp}. Are you sure you want to discard these unsaved changes and return to {contextOriginatingApp}?', values: { contextOriginatingApp }, })} diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index 2e1b021e21470..1a3e1a72e1c4b 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -55,6 +55,7 @@ function getLensTopNavConfig(options: { savingToDashboardPermitted: boolean; contextOriginatingApp?: string; isSaveable: boolean; + initialContextAppId?: string; }): TopNavMenuData[] { const { actions, @@ -68,6 +69,7 @@ function getLensTopNavConfig(options: { tooltips, contextOriginatingApp, isSaveable, + initialContextAppId, } = options; const topNavMenu: TopNavMenuData[] = []; @@ -90,7 +92,7 @@ function getLensTopNavConfig(options: { defaultMessage: 'Save', }); - if (contextOriginatingApp) { + if (contextOriginatingApp && initialContextAppId !== 'dashboards') { topNavMenu.push({ label: i18n.translate('xpack.lens.app.goBackLabel', { defaultMessage: `Go back to {contextOriginatingApp}`, @@ -175,8 +177,10 @@ function getLensTopNavConfig(options: { topNavMenu.push({ label: saveButtonLabel, - iconType: !showSaveAndReturn ? 'save' : undefined, - emphasize: !showSaveAndReturn, + iconType: (initialContextAppId === 'dashboards' ? false : !showSaveAndReturn) + ? 'save' + : undefined, + emphasize: initialContextAppId === 'dashboards' ? false : !showSaveAndReturn, run: actions.showSaveModal, testId: 'lnsApp_saveButton', description: i18n.translate('xpack.lens.app.saveButtonAriaLabel', { @@ -200,6 +204,23 @@ function getLensTopNavConfig(options: { }), }); } + + if (initialContextAppId === 'dashboards') { + topNavMenu.push({ + label: i18n.translate('xpack.lens.app.saveAndReturn', { + defaultMessage: 'Replace in dashboard', + }), + emphasize: true, + iconType: 'checkInCircleFilled', + run: actions.saveAndReturn, + testId: 'lnsApp_replaceInDashboardButton', + disableButton: !isSaveable, + description: i18n.translate('xpack.lens.app.replaceInDashboardButtonAriaLabel', { + defaultMessage: + 'Replace legacy visualization with lens visualization and return to the dashboard', + }), + }); + } return topNavMenu; } @@ -452,11 +473,13 @@ export const LensTopNavMenu = ({ const topNavConfig = useMemo(() => { const baseMenuEntries = getLensTopNavConfig({ showSaveAndReturn: - Boolean( + initialContext?.originatingApp !== 'dashboards' && + (Boolean( isLinkedToOriginatingApp && // Temporarily required until the 'by value' paradigm is default. (dashboardFeatureFlag.allowByValueEmbeddables || Boolean(initialInput)) - ) || Boolean(initialContextIsEmbedded), + ) || + Boolean(initialContextIsEmbedded)), enableExportToCSV: Boolean(isSaveable && activeData && Object.keys(activeData).length), showOpenInDiscover: Boolean(layerMetaInfo?.isVisible), isByValueMode: getIsByValueMode(), @@ -466,6 +489,7 @@ export const LensTopNavMenu = ({ savingToDashboardPermitted, isSaveable, contextOriginatingApp, + initialContextAppId: initialContext?.originatingApp, tooltips: { showExportWarning: () => { if (activeData) { @@ -620,6 +644,7 @@ export const LensTopNavMenu = ({ isOnTextBasedMode, lensStore, theme$, + initialContext, ]); const onQuerySubmitWrapped = useCallback( diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index ef1933dbd5ca1..e0b5d714afd84 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -24,14 +24,14 @@ import { AnalyticsNoDataPage, } from '@kbn/shared-ux-page-analytics-no-data'; -import { ACTION_VISUALIZE_LENS_FIELD } from '@kbn/ui-actions-plugin/public'; +import { ACTION_VISUALIZE_LENS_FIELD, VisualizeFieldContext } from '@kbn/ui-actions-plugin/public'; import { ACTION_CONVERT_TO_LENS } from '@kbn/visualizations-plugin/public'; import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { EuiLoadingSpinner } from '@elastic/eui'; import { syncGlobalQueryStateWithUrl } from '@kbn/data-plugin/public'; import { App } from './app'; -import { EditorFrameStart, LensTopNavMenuEntryGenerator } from '../types'; +import { EditorFrameStart, LensTopNavMenuEntryGenerator, VisualizeEditorContext } from '../types'; import { addHelpMenuToAppChrome } from '../help_menu_util'; import { LensPluginStartDependencies } from '../plugin'; import { LENS_EMBEDDABLE_TYPE, LENS_EDIT_BY_VALUE, APP_ID } from '../../common'; @@ -56,7 +56,8 @@ import { getLensInspectorService } from '../lens_inspector_service'; export async function getLensServices( coreStart: CoreStart, startDependencies: LensPluginStartDependencies, - attributeService: LensAttributeService + attributeService: LensAttributeService, + initialContext?: VisualizeFieldContext | VisualizeEditorContext ): Promise { const { data, @@ -100,9 +101,9 @@ export async function getLensServices( dashboard: startDependencies.dashboard, charts: startDependencies.charts, getOriginatingAppName: () => { - return embeddableEditorIncomingState?.originatingApp - ? stateTransfer?.getAppNameFromId(embeddableEditorIncomingState.originatingApp) - : undefined; + const originatingApp = + embeddableEditorIncomingState?.originatingApp ?? initialContext?.originatingApp; + return originatingApp ? stateTransfer?.getAppNameFromId(originatingApp) : undefined; }, dataViews: startDependencies.dataViews, // Temporarily required until the 'by value' paradigm is default. @@ -136,7 +137,20 @@ export async function mountApp( ]); const historyLocationState = params.history.location.state as HistoryLocationState; - const lensServices = await getLensServices(coreStart, startDependencies, attributeService); + // get state from location, used for navigating from Visualize/Discover to Lens + const initialContext = + historyLocationState && + (historyLocationState.type === ACTION_VISUALIZE_LENS_FIELD || + historyLocationState.type === ACTION_CONVERT_TO_LENS) + ? historyLocationState.payload + : undefined; + + const lensServices = await getLensServices( + coreStart, + startDependencies, + attributeService, + initialContext + ); const { stateTransfer, data } = lensServices; @@ -206,13 +220,6 @@ export async function mountApp( }); } }; - // get state from location, used for navigating from Visualize/Discover to Lens - const initialContext = - historyLocationState && - (historyLocationState.type === ACTION_VISUALIZE_LENS_FIELD || - historyLocationState.type === ACTION_CONVERT_TO_LENS) - ? historyLocationState.payload - : undefined; if (historyLocationState && historyLocationState.type === ACTION_VISUALIZE_LENS_FIELD) { // remove originatingApp from context when visualizing a field in Lens diff --git a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx index 2dbc86e380bc9..656d498a95566 100644 --- a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx +++ b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx @@ -8,7 +8,7 @@ import React, { useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { isFilterPinned } from '@kbn/es-query'; - +import { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public'; import type { SavedObjectReference } from '@kbn/core/public'; import { SaveModal } from './save_modal'; import type { LensAppProps, LensAppServices } from './types'; @@ -18,6 +18,7 @@ import type { LensByReferenceInput, LensEmbeddableInput } from '../embeddable'; import { APP_ID, getFullPath, LENS_EMBEDDABLE_TYPE } from '../../common'; import type { LensAppState } from '../state_management'; import { getPersisted } from '../state_management/init_middleware/load_initial'; +import { VisualizeEditorContext } from '../types'; type ExtraProps = Pick & Partial>; @@ -33,6 +34,7 @@ export type SaveModalContainerProps = { isSaveable?: boolean; getAppNameFromId?: () => string | undefined; lensServices: LensAppServices; + initialContext?: VisualizeFieldContext | VisualizeEditorContext; } & ExtraProps; export function SaveModalContainer({ @@ -49,6 +51,7 @@ export function SaveModalContainer({ isSaveable = true, lastKnownDoc: initLastKnownDoc, lensServices, + initialContext, }: SaveModalContainerProps) { let title = ''; let description; @@ -60,6 +63,14 @@ export function SaveModalContainer({ savedObjectId = lastKnownDoc.savedObjectId; } + if (!lastKnownDoc?.title && initialContext && 'title' in initialContext && initialContext.title) { + title = + initialContext.title + + i18n.translate('xpack.lens.app.convertedLabel', { + defaultMessage: ' (converted)', + }); + } + const { attributeService, savedObjectsTagging, application, dashboardFeatureFlag } = lensServices; useEffect(() => { diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index c4d4cf9bfab9f..78c5614a691ed 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -24,7 +24,11 @@ import type { ExpressionsSetup, ExpressionsStart, } from '@kbn/expressions-plugin/public'; -import type { VisualizationsSetup, VisualizationsStart } from '@kbn/visualizations-plugin/public'; +import { + DASHBOARD_VISUALIZATION_PANEL_TRIGGER, + VisualizationsSetup, + VisualizationsStart, +} from '@kbn/visualizations-plugin/public'; import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; import type { UrlForwardingSetup } from '@kbn/url-forwarding-plugin/public'; import type { GlobalSearchPluginSetup } from '@kbn/global-search-plugin/public'; @@ -91,6 +95,7 @@ import { createOpenInDiscoverAction } from './trigger_actions/open_in_discover_a import { visualizeFieldAction } from './trigger_actions/visualize_field_actions'; import { visualizeTSVBAction } from './trigger_actions/visualize_tsvb_actions'; import { visualizeAggBasedVisAction } from './trigger_actions/visualize_agg_based_vis_actions'; +import { visualizeDashboardVisualizePanelction } from './trigger_actions/dashboard_visualize_panel_actions'; import type { LensEmbeddableInput } from './embeddable'; import { EmbeddableFactory, LensEmbeddableStartServices } from './embeddable/embeddable_factory'; @@ -491,6 +496,11 @@ export class LensPlugin { visualizeTSVBAction(core.application) ); + startDependencies.uiActions.addTriggerAction( + DASHBOARD_VISUALIZATION_PANEL_TRIGGER, + visualizeDashboardVisualizePanelction(core.application) + ); + startDependencies.uiActions.addTriggerAction( AGG_BASED_VISUALIZATION_TRIGGER, visualizeAggBasedVisAction(core.application) diff --git a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts index 69e93f7a763a3..7ca55e9447392 100644 --- a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts +++ b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { cloneDeep } from 'lodash'; import { MiddlewareAPI } from '@reduxjs/toolkit'; import { i18n } from '@kbn/i18n'; import { History } from 'history'; @@ -125,6 +126,15 @@ export function loadInitial( (attributeService.inputIsRefType(initialInput) && initialInput.savedObjectId === lens.persistedDoc?.savedObjectId) ) { + const newFilters = + initialContext && 'searchFilters' in initialContext && initialContext.searchFilters + ? cloneDeep(initialContext.searchFilters) + : undefined; + + if (newFilters) { + data.query.filterManager.setAppFilters(newFilters); + } + return initializeSources( { datasourceMap, diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index e8874fbcda822..b1c93656d5097 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -78,13 +78,20 @@ export const getPreloadedState = ({ // only if Lens was opened with the intention to visualize a field (e.g. coming from Discover) query: !initialContext ? data.query.queryString.getDefaultQuery() + : 'searchQuery' in initialContext && initialContext.searchQuery + ? initialContext.searchQuery : (data.query.queryString.getQuery() as Query), filters: !initialContext ? data.query.filterManager.getGlobalFilters() + : 'searchFilters' in initialContext && initialContext.searchFilters + ? initialContext.searchFilters : data.query.filterManager.getFilters(), searchSessionId: data.search.session.getSessionId(), resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter), - isLinkedToOriginatingApp: Boolean(embeddableEditorIncomingState?.originatingApp), + isLinkedToOriginatingApp: Boolean( + embeddableEditorIncomingState?.originatingApp || + initialContext?.originatingApp === 'dashboards' + ), activeDatasourceId: initialDatasourceId, datasourceStates, visualization: { diff --git a/x-pack/plugins/lens/public/trigger_actions/dashboard_visualize_panel_actions.ts b/x-pack/plugins/lens/public/trigger_actions/dashboard_visualize_panel_actions.ts new file mode 100644 index 0000000000000..943b656aba57e --- /dev/null +++ b/x-pack/plugins/lens/public/trigger_actions/dashboard_visualize_panel_actions.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { createAction } from '@kbn/ui-actions-plugin/public'; +import { + ACTION_CONVERT_DASHBOARD_PANEL_TO_LENS, + ACTION_CONVERT_TO_LENS, +} from '@kbn/visualizations-plugin/public'; +import type { ApplicationStart } from '@kbn/core/public'; +import type { VisualizeEditorContext } from '../types'; + +export const visualizeDashboardVisualizePanelction = (application: ApplicationStart) => + createAction<{ [key: string]: VisualizeEditorContext }>({ + type: ACTION_CONVERT_TO_LENS, + id: ACTION_CONVERT_DASHBOARD_PANEL_TO_LENS, + getDisplayName: () => + i18n.translate('xpack.lens.visualizeLegacyVisualizationChart', { + defaultMessage: 'Visualize legacy visualization chart', + }), + isCompatible: async () => !!application.capabilities.visualize.show, + execute: async (context: { [key: string]: VisualizeEditorContext }) => { + const table = Object.values(context.layers); + const payload = { + ...context, + layers: table, + isVisualizeAction: true, + }; + application.navigateToApp('lens', { + state: { + type: ACTION_CONVERT_TO_LENS, + payload, + originatingApp: i18n.translate('xpack.lens.dashboardLabel', { + defaultMessage: 'Dashboard', + }), + }, + }); + }, + }); diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 8540f3a87b49c..30c3c8302adcd 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -243,6 +243,9 @@ export type VisualizeEditorContext = { vizEditorOriginatingAppUrl?: string; originatingApp?: string; isVisualizeAction: boolean; + searchQuery: Query; + searchFilters: Filter[]; + title?: string; } & NavigateToLensContext; export interface GetDropPropsArgs { From 1b00b5e2364400a989b4a1068e49a2fa5b6ad547 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Mon, 28 Nov 2022 14:58:47 +0200 Subject: [PATCH 02/17] Fix CI --- .../visualizations/public/actions/edit_in_lens_action.tsx | 2 +- .../visualize_app/components/visualize_editor_common.test.tsx | 1 + .../application/settings/calendars/edit/new_calendar.test.js | 1 + .../application/settings/calendars/list/calendars_list.test.js | 1 + .../public/application/settings/calendars/list/header.test.js | 1 + .../settings/filter_lists/edit/edit_filter_list.test.js | 1 + .../application/settings/filter_lists/list/filter_lists.test.js | 1 + 7 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx index 94023dfcd5e75..d53627b8db1ec 100644 --- a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx +++ b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx @@ -25,7 +25,7 @@ export interface EditInLensContext { embeddable: IEmbeddable; } -const displayName = i18n.translate('visulizations.action.editInLens.displayName', { +const displayName = i18n.translate('visualizations.actions.editInLens.displayName', { defaultMessage: 'Convert to Lens', }); diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.test.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.test.tsx index 58727d9118c5b..08e83ab555f2d 100644 --- a/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.test.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.test.tsx @@ -39,6 +39,7 @@ jest.mock('@kbn/kibana-react-plugin/public', () => ({ }, })), withKibana: jest.fn((comp) => comp), + reactToUiComponent: jest.fn(), })); jest.mock('../../services', () => ({ diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.test.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.test.js index 299c1340e8599..dfe479f0d67cb 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.test.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.test.js @@ -65,6 +65,7 @@ jest.mock('@kbn/kibana-react-plugin/public', () => ({ withKibana: (comp) => { return comp; }, + reactToUiComponent: jest.fn(), })); import { shallowWithIntl, mountWithIntl } from '@kbn/test-jest-helpers'; diff --git a/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.test.js b/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.test.js index bbfdf7ef6b0e4..f8e08183fd914 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.test.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.test.js @@ -54,6 +54,7 @@ jest.mock('@kbn/kibana-react-plugin/public', () => ({ withKibana: (node) => { return node; }, + reactToUiComponent: jest.fn(), })); const testingState = { diff --git a/x-pack/plugins/ml/public/application/settings/calendars/list/header.test.js b/x-pack/plugins/ml/public/application/settings/calendars/list/header.test.js index ba4d49a31135c..31a8b9760482d 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/list/header.test.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/list/header.test.js @@ -14,6 +14,7 @@ jest.mock('@kbn/kibana-react-plugin/public', () => ({ withKibana: (comp) => { return comp; }, + reactToUiComponent: jest.fn(), })); describe('CalendarListsHeader', () => { diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.test.js b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.test.js index 45c43e04daa68..f30a25bbcb493 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.test.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.test.js @@ -49,6 +49,7 @@ jest.mock('@kbn/kibana-react-plugin/public', () => ({ withKibana: (node) => { return node; }, + reactToUiComponent: jest.fn(), })); import { shallowWithIntl } from '@kbn/test-jest-helpers'; diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/list/filter_lists.test.js b/x-pack/plugins/ml/public/application/settings/filter_lists/list/filter_lists.test.js index 5d0306564e312..b3cd815673aad 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/list/filter_lists.test.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/list/filter_lists.test.js @@ -30,6 +30,7 @@ jest.mock('@kbn/kibana-react-plugin/public', () => ({ withKibana: (node) => { return node; }, + reactToUiComponent: jest.fn(), })); // Mock the call for loading the list of filters. From 0fca07ebf138f97f8ca58064588ac7f19dd36bcf Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Mon, 28 Nov 2022 17:35:09 +0200 Subject: [PATCH 03/17] Fix more tests --- .../visualizations/public/actions/edit_in_lens_action.tsx | 2 +- x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx | 2 +- .../test/functional/apps/lens/open_in_lens/tsvb/dashboard.ts | 2 +- x-pack/test/functional/page_objects/lens_page.ts | 4 ++++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx index d53627b8db1ec..14da27cda9362 100644 --- a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx +++ b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx @@ -49,7 +49,7 @@ const UiMenuItem = reactToUiComponent(ReactMenuItem); export class EditInLensAction implements Action { public id = ACTION_EDIT_IN_LENS; public readonly type = ACTION_EDIT_IN_LENS; - public order = 80; + public order = 30; public showNotification = true; public currentAppId: string | undefined; diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index 1a3e1a72e1c4b..4084d0501cc7c 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -207,7 +207,7 @@ function getLensTopNavConfig(options: { if (initialContextAppId === 'dashboards') { topNavMenu.push({ - label: i18n.translate('xpack.lens.app.saveAndReturn', { + label: i18n.translate('xpack.lens.app.replaceInDashboard', { defaultMessage: 'Replace in dashboard', }), emphasize: true, diff --git a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/dashboard.ts b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/dashboard.ts index 72daa5ff5486b..f861b7b832e93 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/dashboard.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/dashboard.ts @@ -52,7 +52,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(await dimensions[1].getVisibleText()).to.be('Count of records'); }); - await lens.saveAndReturn(); + await lens.replaceInDashboard(); await retry.try(async () => { const embeddableCount = await canvas.getEmbeddableCount(); expect(embeddableCount).to.eql(originalEmbeddableCount); diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index 495135c1ece5c..a2d0faa34dc99 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -712,6 +712,10 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont await testSubjects.click('lnsApp_saveAndReturnButton'); }, + async replaceInDashboard() { + await testSubjects.click('lnsApp_replaceInDashboardButton'); + }, + async expectSaveAndReturnButtonDisabled() { const button = await testSubjects.find('lnsApp_saveAndReturnButton', 10000); const disabledAttr = await button.getAttribute('disabled'); From f78d79bd10ef18ce012e276fdcafc93f7ce074d6 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 29 Nov 2022 11:01:57 +0200 Subject: [PATCH 04/17] Fix test --- x-pack/test/functional/apps/lens/open_in_lens/tsvb/dashboard.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/dashboard.ts b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/dashboard.ts index f861b7b832e93..998d2df927cf2 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/dashboard.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/dashboard.ts @@ -80,7 +80,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(await dimensions[1].getVisibleText()).to.be('Count of records'); }); - await lens.saveAndReturn(); + await lens.replaceInDashboard(); await retry.try(async () => { const embeddableCount = await canvas.getEmbeddableCount(); expect(embeddableCount).to.eql(originalEmbeddableCount); From 8676538ed9712b4a8f49fcc9abef0faf29a98631 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 29 Nov 2022 13:22:40 +0200 Subject: [PATCH 05/17] Some improvments --- .../public/actions/edit_in_lens_action.tsx | 50 ++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx index 14da27cda9362..f44f6b3d18951 100644 --- a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx +++ b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx @@ -46,6 +46,10 @@ const ReactMenuItem: React.FC = () => { const UiMenuItem = reactToUiComponent(ReactMenuItem); +const isVisualizeEmbeddable = (embeddable: IEmbeddable): embeddable is VisualizeEmbeddable => { + return 'getVis' in embeddable; +}; + export class EditInLensAction implements Action { public id = ACTION_EDIT_IN_LENS; public readonly type = ACTION_EDIT_IN_LENS; @@ -63,21 +67,30 @@ export class EditInLensAction implements Action { .subscribe((appId: string | undefined) => (this.currentAppId = appId)); } const { embeddable } = context; - const vis = (embeddable as VisualizeEmbeddable).getVis(); - const navigateToLensConfig = await vis.type.navigateToLens?.(vis, this.timefilter); - const searchFilters = vis.data.searchSource?.getField('filter'); - const searchQuery = vis.data.searchSource?.getField('query'); - const updatedWithMeta = { - ...navigateToLensConfig, - title: embeddable.getOutput().title, - savedObjectId: vis.id, - embeddableId: embeddable.id, - originatingApp: this.currentAppId, - searchFilters, - searchQuery, - }; - if (navigateToLensConfig) { - getUiActions().getTrigger(DASHBOARD_VISUALIZATION_PANEL_TRIGGER).exec(updatedWithMeta); + if (isVisualizeEmbeddable(embeddable)) { + const vis = embeddable.getVis(); + const navigateToLensConfig = await vis.type.navigateToLens?.(vis, this.timefilter); + const searchFilters = vis.data.searchSource?.getField('filter'); + const searchQuery = vis.data.searchSource?.getField('query'); + const updatedWithMeta = { + ...navigateToLensConfig, + title: + embeddable.getOutput().title || + i18n.translate('visualizations.actions.editInLens.visulizationTitle', { + defaultMessage: '{type} visualization', + values: { + type: vis.type.title, + }, + }), + savedObjectId: vis.id, + embeddableId: embeddable.id, + originatingApp: this.currentAppId, + searchFilters, + searchQuery, + }; + if (navigateToLensConfig) { + getUiActions().getTrigger(DASHBOARD_VISUALIZATION_PANEL_TRIGGER).exec(updatedWithMeta); + } } } @@ -93,12 +106,15 @@ export class EditInLensAction implements Action { async isCompatible(context: ActionExecutionContext) { const { embeddable } = context; - const vis = (embeddable as VisualizeEmbeddable).getVis?.(); + if (!isVisualizeEmbeddable(embeddable)) { + return false; + } + const vis = embeddable.getVis(); if (!vis) { return false; } const canNavigateToLens = - (embeddable as VisualizeEmbeddable).getExpressionVariables?.()?.canNavigateToLens ?? + embeddable.getExpressionVariables?.()?.canNavigateToLens ?? (await vis.type.navigateToLens?.(vis, this.timefilter)); return Boolean(canNavigateToLens && embeddable.getInput().viewMode === ViewMode.EDIT); } From fe7c5cda9675c4126f59e1f6b8f22ed958c5e534 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 1 Dec 2022 13:32:13 +0200 Subject: [PATCH 06/17] Add functional tests --- .buildkite/ftr_configs.yml | 1 + .../public/actions/edit_in_lens_action.tsx | 6 +- .../functional/page_objects/dashboard_page.ts | 11 +++ .../services/dashboard/panel_actions.ts | 11 +++ .../lens/open_in_lens/dashboard/config.ts | 17 +++++ .../lens/open_in_lens/dashboard/dashboard.ts | 65 ++++++++++++++++ .../apps/lens/open_in_lens/dashboard/index.ts | 74 +++++++++++++++++++ 7 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 x-pack/test/functional/apps/lens/open_in_lens/dashboard/config.ts create mode 100644 x-pack/test/functional/apps/lens/open_in_lens/dashboard/dashboard.ts create mode 100644 x-pack/test/functional/apps/lens/open_in_lens/dashboard/index.ts diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index c0205e8af28f1..d41c5ac17d203 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -181,6 +181,7 @@ enabled: - x-pack/test/functional/apps/lens/group3/config.ts - x-pack/test/functional/apps/lens/open_in_lens/tsvb/config.ts - x-pack/test/functional/apps/lens/open_in_lens/agg_based/config.ts + - x-pack/test/functional/apps/lens/open_in_lens/dashboard/config.ts - x-pack/test/functional/apps/license_management/config.ts - x-pack/test/functional/apps/logstash/config.ts - x-pack/test/functional/apps/management/config.ts diff --git a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx index f44f6b3d18951..7eab241e576f6 100644 --- a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx +++ b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx @@ -17,7 +17,7 @@ import { IEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public'; import { Action } from '@kbn/ui-actions-plugin/public'; import { VisualizeEmbeddable } from '../embeddable'; import { DASHBOARD_VISUALIZATION_PANEL_TRIGGER } from '../triggers'; -import { getUiActions, getApplication } from '../services'; +import { getUiActions, getApplication, getEmbeddable } from '../services'; export const ACTION_EDIT_IN_LENS = 'ACTION_EDIT_IN_LENS'; @@ -65,6 +65,9 @@ export class EditInLensAction implements Action { application.currentAppId$ .pipe(take(1)) .subscribe((appId: string | undefined) => (this.currentAppId = appId)); + application.currentAppId$.subscribe(() => { + getEmbeddable().getStateTransfer().isTransferInProgress = false; + }); } const { embeddable } = context; if (isVisualizeEmbeddable(embeddable)) { @@ -89,6 +92,7 @@ export class EditInLensAction implements Action { searchQuery, }; if (navigateToLensConfig) { + getEmbeddable().getStateTransfer().isTransferInProgress = true; getUiActions().getTrigger(DASHBOARD_VISUALIZATION_PANEL_TRIGGER).exec(updatedWithMeta); } } diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index e3a1306eaaae6..c7fa5f3d241c4 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -789,4 +789,15 @@ export class DashboardPageObject extends FtrService { public async getPanelChartDebugState(panelIndex: number) { return await this.elasticChart.getChartDebugData(undefined, panelIndex); } + + public async isNotificationExists(panelIndex = 0) { + const panel = (await this.getDashboardPanels())[panelIndex]; + try { + const notification = await panel.findByClassName('embPanel__optionsMenuPopover-notification'); + return Boolean(notification); + } catch (e) { + // if not found then this is false + return false; + } + } } diff --git a/test/functional/services/dashboard/panel_actions.ts b/test/functional/services/dashboard/panel_actions.ts index 79370e8e6af6a..84618751de2d4 100644 --- a/test/functional/services/dashboard/panel_actions.ts +++ b/test/functional/services/dashboard/panel_actions.ts @@ -20,6 +20,7 @@ const OPEN_INSPECTOR_TEST_SUBJ = 'embeddablePanelAction-openInspector'; const COPY_PANEL_TO_DATA_TEST_SUBJ = 'embeddablePanelAction-copyToDashboard'; const SAVE_TO_LIBRARY_TEST_SUBJ = 'embeddablePanelAction-saveToLibrary'; const UNLINK_FROM_LIBRARY_TEST_SUBJ = 'embeddablePanelAction-unlinkFromLibrary'; +const CONVERT_TO_LENS_TEST_SUBJ = 'embeddablePanelAction-ACTION_EDIT_IN_LENS'; export class DashboardPanelActionsService extends FtrService { private readonly log = this.ctx.getService('log'); @@ -352,4 +353,14 @@ export class DashboardPanelActionsService extends FtrService { throw new Error(`No action matching text "${text}"`); } + + async convertToLens(parent?: WebElementWrapper) { + this.log.debug('convertToLens'); + await this.openContextMenu(parent); + const isActionVisible = await this.testSubjects.exists(CONVERT_TO_LENS_TEST_SUBJ); + if (!isActionVisible) await this.clickContextMenuMoreItem(); + const isPanelActionVisible = await this.testSubjects.exists(CONVERT_TO_LENS_TEST_SUBJ); + if (!isPanelActionVisible) await this.clickContextMenuMoreItem(); + await this.testSubjects.click(CONVERT_TO_LENS_TEST_SUBJ); + } } diff --git a/x-pack/test/functional/apps/lens/open_in_lens/dashboard/config.ts b/x-pack/test/functional/apps/lens/open_in_lens/dashboard/config.ts new file mode 100644 index 0000000000000..3bf1f38d29ca9 --- /dev/null +++ b/x-pack/test/functional/apps/lens/open_in_lens/dashboard/config.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../../../../config.base.js')); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('.')], + }; +} diff --git a/x-pack/test/functional/apps/lens/open_in_lens/dashboard/dashboard.ts b/x-pack/test/functional/apps/lens/open_in_lens/dashboard/dashboard.ts new file mode 100644 index 0000000000000..22feacc6f41ce --- /dev/null +++ b/x-pack/test/functional/apps/lens/open_in_lens/dashboard/dashboard.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const { lens, dashboard, canvas } = getPageObjects(['lens', 'dashboard', 'canvas']); + + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + const panelActions = getService('dashboardPanelActions'); + const dashboardAddPanel = getService('dashboardAddPanel'); + + describe('Convert to Lens action on dashboard', function describeIndexTests() { + before(async () => { + await dashboard.initTests(); + }); + + it('should show notification in context menu if visualization can be converted', async () => { + await dashboard.clickNewDashboard(); + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickAggBasedVisualizations(); + await dashboardAddPanel.clickVisType('area'); + await testSubjects.click('savedObjectTitlelogstash-*'); + await testSubjects.exists('visualizesaveAndReturnButton'); + await testSubjects.click('visualizesaveAndReturnButton'); + await dashboard.waitForRenderComplete(); + expect(await dashboard.isNotificationExists(0)).to.be(true); + }); + + it('should convert legacy visualization to lens by clicking "convert to lens" action', async () => { + const originalEmbeddableCount = await canvas.getEmbeddableCount(); + await panelActions.convertToLens(); + await lens.waitForVisualization('xyVisChart'); + const lastBreadcrumbdcrumb = await testSubjects.getVisibleText('breadcrumb last'); + expect(lastBreadcrumbdcrumb).to.be('Converting "Area visualization"'); + await lens.replaceInDashboard(); + + await retry.try(async () => { + const embeddableCount = await canvas.getEmbeddableCount(); + expect(embeddableCount).to.eql(originalEmbeddableCount); + }); + + const panel = await testSubjects.find(`embeddablePanelHeading-`); + const descendants = await testSubjects.findAllDescendant( + 'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION', + panel + ); + expect(descendants.length).to.equal(0); + expect(await dashboard.isNotificationExists(0)).to.be(false); + }); + }); +} diff --git a/x-pack/test/functional/apps/lens/open_in_lens/dashboard/index.ts b/x-pack/test/functional/apps/lens/open_in_lens/dashboard/index.ts new file mode 100644 index 0000000000000..3d9e8e53c6e4e --- /dev/null +++ b/x-pack/test/functional/apps/lens/open_in_lens/dashboard/index.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EsArchiver } from '@kbn/es-archiver'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile, getService, getPageObjects }: FtrProviderContext) { + const browser = getService('browser'); + const log = getService('log'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['timePicker']); + const config = getService('config'); + let remoteEsArchiver; + + describe('lens app - TSVB Open in Lens', () => { + const esArchive = 'x-pack/test/functional/es_archives/logstash_functional'; + const localIndexPatternString = 'logstash-*'; + const remoteIndexPatternString = 'ftr-remote:logstash-*'; + const localFixtures = { + lensBasic: 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json', + lensDefault: 'x-pack/test/functional/fixtures/kbn_archiver/lens/default', + }; + + const remoteFixtures = { + lensBasic: 'x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/lens_basic.json', + lensDefault: 'x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/default', + }; + let esNode: EsArchiver; + let fixtureDirs: { + lensBasic: string; + lensDefault: string; + }; + let indexPatternString: string; + before(async () => { + log.debug('Starting lens before method'); + await browser.setWindowSize(1280, 1200); + try { + config.get('esTestCluster.ccs'); + remoteEsArchiver = getService('remoteEsArchiver' as 'esArchiver'); + esNode = remoteEsArchiver; + fixtureDirs = remoteFixtures; + indexPatternString = remoteIndexPatternString; + } catch (error) { + esNode = esArchiver; + fixtureDirs = localFixtures; + indexPatternString = localIndexPatternString; + } + + await esNode.load(esArchive); + // changing the timepicker default here saves us from having to set it in Discover (~8s) + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.uiSettings.update({ + defaultIndex: indexPatternString, + 'dateFormat:tz': 'UTC', + }); + await kibanaServer.importExport.load(fixtureDirs.lensBasic); + await kibanaServer.importExport.load(fixtureDirs.lensDefault); + }); + + after(async () => { + await esArchiver.unload(esArchive); + await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.importExport.unload(fixtureDirs.lensBasic); + await kibanaServer.importExport.unload(fixtureDirs.lensDefault); + }); + + loadTestFile(require.resolve('./dashboard')); + }); +} From 15d3380502d11fe57b5b2253317a35f02c036cb5 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Mon, 5 Dec 2022 14:26:42 +0200 Subject: [PATCH 07/17] Fix notification --- .../panel/panel_header/panel_options_menu.tsx | 23 ++++++++++++++----- x-pack/plugins/lens/public/app_plugin/app.tsx | 7 +----- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_options_menu.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_options_menu.tsx index 55a14461c0934..794b38b5a2514 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_options_menu.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_options_menu.tsx @@ -34,6 +34,7 @@ interface State { actions: Action[]; }; isPopoverOpen: boolean; + showNotification: boolean; } export class PanelOptionsMenu extends React.Component { @@ -54,6 +55,7 @@ export class PanelOptionsMenu extends React.Component action.showNotification + ); if (this.mounted) { - this.setState({ actionContextMenuPanel }); + this.setState({ actionContextMenuPanel, showNotification }); + } + } + + public async componentDidUpdate() { + const actionContextMenuPanel = await this.props.getActionContextMenuPanel(); + const showNotification = actionContextMenuPanel.actions.some( + (action) => action.showNotification + ); + if (this.mounted && this.state.showNotification !== showNotification) { + this.setState({ showNotification }); } } @@ -100,15 +115,11 @@ export class PanelOptionsMenu extends React.Component ); - const showNotification = this.state.actionContextMenuPanel?.actions.some( - (action) => action.showNotification - ); - return ( Date: Mon, 5 Dec 2022 18:04:47 +0200 Subject: [PATCH 08/17] Fix some comments --- .../public/actions/edit_in_lens_action.tsx | 6 ++- .../services/dashboard/panel_actions.ts | 5 +++ .../plugins/lens/public/app_plugin/app.scss | 2 +- x-pack/plugins/lens/public/app_plugin/app.tsx | 2 +- .../lens/public/app_plugin/lens_top_nav.tsx | 39 ++++++++++++------- .../apps/dashboard_panel_options.ts | 2 +- 6 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx index 7eab241e576f6..b72e3eead2035 100644 --- a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx +++ b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx @@ -53,7 +53,7 @@ const isVisualizeEmbeddable = (embeddable: IEmbeddable): embeddable is Visualize export class EditInLensAction implements Action { public id = ACTION_EDIT_IN_LENS; public readonly type = ACTION_EDIT_IN_LENS; - public order = 30; + public order = 100; public showNotification = true; public currentAppId: string | undefined; @@ -78,6 +78,7 @@ export class EditInLensAction implements Action { const updatedWithMeta = { ...navigateToLensConfig, title: + vis.title || embeddable.getOutput().title || i18n.translate('visualizations.actions.editInLens.visulizationTitle', { defaultMessage: '{type} visualization', @@ -114,7 +115,8 @@ export class EditInLensAction implements Action { return false; } const vis = embeddable.getVis(); - if (!vis) { + const contextType = embeddable.getInput().executionContext?.type + if (!vis || contextType !== 'dashboard') { return false; } const canNavigateToLens = diff --git a/test/functional/services/dashboard/panel_actions.ts b/test/functional/services/dashboard/panel_actions.ts index 84618751de2d4..74bf34d3f364b 100644 --- a/test/functional/services/dashboard/panel_actions.ts +++ b/test/functional/services/dashboard/panel_actions.ts @@ -121,7 +121,12 @@ export class DashboardPanelActionsService extends FtrService { } async customizePanel(parent?: WebElementWrapper) { + this.log.debug('customizePanel'); await this.openContextMenu(parent); + const isActionVisible = await this.testSubjects.exists(CUSTOMIZE_PANEL_DATA_TEST_SUBJ); + if (!isActionVisible) await this.clickContextMenuMoreItem(); + const isPanelActionVisible = await this.testSubjects.exists(CUSTOMIZE_PANEL_DATA_TEST_SUBJ); + if (!isPanelActionVisible) await this.clickContextMenuMoreItem(); await this.testSubjects.click(CUSTOMIZE_PANEL_DATA_TEST_SUBJ); } diff --git a/x-pack/plugins/lens/public/app_plugin/app.scss b/x-pack/plugins/lens/public/app_plugin/app.scss index 3aa98ca1640b9..2bc7d7e1baaaa 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.scss +++ b/x-pack/plugins/lens/public/app_plugin/app.scss @@ -24,7 +24,7 @@ } // Less-than-ideal styles to add a vertical divider after this button. Consider restructuring markup for better semantics and styling options in the future. -.lnsNavItem__goBack { +.lnsNavItem__withDivider { @include euiBreakpoint('m', 'l', 'xl') { margin-right: $euiSizeM; position: relative; diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index 5ceaca2e0937f..585d72c491502 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -252,7 +252,7 @@ export function App({ } if (!persistedDoc?.title && initialContext && 'title' in initialContext) { currentDocTitle = i18n.translate('xpack.lens.breadcrumbsEditInLensFromDashboard', { - defaultMessage: 'Converting "{title}"', + defaultMessage: 'Converting {title}', values: { title: initialContext.title }, }); } diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index 4084d0501cc7c..b856bf7d77d48 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -41,6 +41,7 @@ import { } from '../utils'; import { combineQueryAndFilters, getLayerMetaInfo } from './show_underlying_data'; import { changeIndexPattern } from '../state_management/lens_slice'; +import { LensByReferenceInput } from '../embeddable'; function getLensTopNavConfig(options: { showSaveAndReturn: boolean; @@ -55,7 +56,7 @@ function getLensTopNavConfig(options: { savingToDashboardPermitted: boolean; contextOriginatingApp?: string; isSaveable: boolean; - initialContextAppId?: string; + showReplaceInDashbord: boolean; }): TopNavMenuData[] { const { actions, @@ -69,7 +70,7 @@ function getLensTopNavConfig(options: { tooltips, contextOriginatingApp, isSaveable, - initialContextAppId, + showReplaceInDashbord, } = options; const topNavMenu: TopNavMenuData[] = []; @@ -92,14 +93,14 @@ function getLensTopNavConfig(options: { defaultMessage: 'Save', }); - if (contextOriginatingApp && initialContextAppId !== 'dashboards') { + if (contextOriginatingApp && !showCancel) { topNavMenu.push({ label: i18n.translate('xpack.lens.app.goBackLabel', { defaultMessage: `Go back to {contextOriginatingApp}`, values: { contextOriginatingApp }, }), run: actions.goBack, - className: 'lnsNavItem__goBack', + className: 'lnsNavItem__withDivider', testId: 'lnsApp_goBackToAppButton', description: i18n.translate('xpack.lens.app.goBackLabel', { defaultMessage: `Go back to {contextOriginatingApp}`, @@ -118,6 +119,7 @@ function getLensTopNavConfig(options: { label: exploreDataInDiscoverLabel, run: () => {}, testId: 'lnsApp_openInDiscover', + className: 'lnsNavItem__withDivider', description: exploreDataInDiscoverLabel, disableButton: Boolean(tooltips.showUnderlyingDataWarning()), tooltip: tooltips.showUnderlyingDataWarning, @@ -156,6 +158,7 @@ function getLensTopNavConfig(options: { defaultMessage: 'Settings', }), run: actions.openSettings, + className: 'lnsNavItem__withDivider', testId: 'lnsApp_settingsButton', description: i18n.translate('xpack.lens.app.settingsAriaLabel', { defaultMessage: 'Open the Lens settings menu', @@ -177,10 +180,8 @@ function getLensTopNavConfig(options: { topNavMenu.push({ label: saveButtonLabel, - iconType: (initialContextAppId === 'dashboards' ? false : !showSaveAndReturn) - ? 'save' - : undefined, - emphasize: initialContextAppId === 'dashboards' ? false : !showSaveAndReturn, + iconType: (showReplaceInDashbord ? false : !showSaveAndReturn) ? 'save' : undefined, + emphasize: showReplaceInDashbord ? false : !showSaveAndReturn, run: actions.showSaveModal, testId: 'lnsApp_saveButton', description: i18n.translate('xpack.lens.app.saveButtonAriaLabel', { @@ -205,13 +206,13 @@ function getLensTopNavConfig(options: { }); } - if (initialContextAppId === 'dashboards') { + if (showReplaceInDashbord) { topNavMenu.push({ label: i18n.translate('xpack.lens.app.replaceInDashboard', { defaultMessage: 'Replace in dashboard', }), emphasize: true, - iconType: 'checkInCircleFilled', + iconType: 'merge', run: actions.saveAndReturn, testId: 'lnsApp_replaceInDashboardButton', disableButton: !isSaveable, @@ -471,9 +472,12 @@ export const LensTopNavMenu = ({ const lensStore = useStore(); const topNavConfig = useMemo(() => { + const showReplaceInDashbord = + initialContext?.originatingApp === 'dashboards' && + !(initialInput as LensByReferenceInput)?.savedObjectId; const baseMenuEntries = getLensTopNavConfig({ showSaveAndReturn: - initialContext?.originatingApp !== 'dashboards' && + !showReplaceInDashbord && (Boolean( isLinkedToOriginatingApp && // Temporarily required until the 'by value' paradigm is default. @@ -489,7 +493,7 @@ export const LensTopNavMenu = ({ savingToDashboardPermitted, isSaveable, contextOriginatingApp, - initialContextAppId: initialContext?.originatingApp, + showReplaceInDashbord, tooltips: { showExportWarning: () => { if (activeData) { @@ -549,7 +553,16 @@ export const LensTopNavMenu = ({ }); runSave( { - newTitle: title || '', + newTitle: + title || + (initialContext && 'title' in initialContext && initialContext.title + ? i18n.translate('xpack.lens.app.convertedLabel', { + defaultMessage: '{title} (converted)', + values: { + title: initialContext.title, + }, + }) + : ''), newCopyOnSave: false, isTitleDuplicateConfirmed: false, returnToOrigin: true, diff --git a/x-pack/test/accessibility/apps/dashboard_panel_options.ts b/x-pack/test/accessibility/apps/dashboard_panel_options.ts index 9cd49623cfae5..b56025291cf9b 100644 --- a/x-pack/test/accessibility/apps/dashboard_panel_options.ts +++ b/x-pack/test/accessibility/apps/dashboard_panel_options.ts @@ -112,7 +112,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('dashboard panel - edit panel title', async () => { await dashboardPanelActions.toggleContextMenu(header); - await testSubjects.click('embeddablePanelAction-ACTION_CUSTOMIZE_PANEL'); + await dashboardPanelActions.customizePanel(); await a11y.testAppSnapshot(); await testSubjects.click('customizePanelHideTitle'); await a11y.testAppSnapshot(); From c7fdc8edb098a1be4005238615595931ea83c648 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 5 Dec 2022 16:11:19 +0000 Subject: [PATCH 09/17] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../visualizations/public/actions/edit_in_lens_action.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx index b72e3eead2035..792b6b34c8b98 100644 --- a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx +++ b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx @@ -115,7 +115,7 @@ export class EditInLensAction implements Action { return false; } const vis = embeddable.getVis(); - const contextType = embeddable.getInput().executionContext?.type + const contextType = embeddable.getInput().executionContext?.type; if (!vis || contextType !== 'dashboard') { return false; } From b6f99e84a6e0fc7729fc060f81ad495089cbc337 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 6 Dec 2022 15:17:23 +0200 Subject: [PATCH 10/17] Fix some more nits --- .../lib/build_dashboard_container.ts | 8 +++-- .../public/actions/edit_in_lens_action.tsx | 5 ++- .../utils/get_top_nav_config.tsx | 1 - x-pack/plugins/lens/public/app_plugin/app.tsx | 11 +++++-- .../lens/public/app_plugin/lens_top_nav.tsx | 31 +++++++++++++++++-- .../app_plugin/save_modal_container.tsx | 11 ++++--- .../public/state_management/lens_slice.ts | 2 +- x-pack/plugins/lens/public/types.ts | 1 + .../lens/open_in_lens/dashboard/dashboard.ts | 2 +- 9 files changed, 52 insertions(+), 20 deletions(-) diff --git a/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts b/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts index 2d5304e002d54..966fe9dc8f85d 100644 --- a/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts +++ b/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts @@ -109,9 +109,11 @@ export const buildDashboardContainer = async ({ gridData: originalPanelState.gridData, type: incomingEmbeddable.type, explicitInput: { - ...(incomingEmbeddable.type === originalPanelState.type && { - ...originalPanelState.explicitInput, - }), + ...(incomingEmbeddable.type === originalPanelState.type + ? { + ...originalPanelState.explicitInput, + } + : { hidePanelTitles: originalPanelState.explicitInput.hidePanelTitles }), ...incomingEmbeddable.input, id: incomingEmbeddable.embeddableId, }, diff --git a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx index b72e3eead2035..6596816130aba 100644 --- a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx +++ b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx @@ -86,11 +86,11 @@ export class EditInLensAction implements Action { type: vis.type.title, }, }), - savedObjectId: vis.id, embeddableId: embeddable.id, originatingApp: this.currentAppId, searchFilters, searchQuery, + isEmbeddable: true, }; if (navigateToLensConfig) { getEmbeddable().getStateTransfer().isTransferInProgress = true; @@ -115,8 +115,7 @@ export class EditInLensAction implements Action { return false; } const vis = embeddable.getVis(); - const contextType = embeddable.getInput().executionContext?.type - if (!vis || contextType !== 'dashboard') { + if (!vis) { return false; } const canNavigateToLens = diff --git a/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx b/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx index 0c66dc9a66e6e..22d3529229fd7 100644 --- a/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx +++ b/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx @@ -310,7 +310,6 @@ export const getTopNavConfig = ( ); const updatedWithMeta = { ...navigateToLensConfig, - savedObjectId: visInstance.vis.id, embeddableId, vizEditorOriginatingAppUrl: getVizEditorOriginatingAppUrl(history), originatingApp, diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index 585d72c491502..e4c050f30e091 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -420,9 +420,14 @@ export function App({ }, []); const returnToOriginSwitchLabelForContext = - initialContext?.originatingApp === 'dashboards' && !persistedDoc + (initialContext?.originatingApp === 'dashboards' || + initialContext?.originatingApp === 'canvas') && + !persistedDoc ? i18n.translate('xpack.lens.app.replacePanel', { - defaultMessage: 'Replace panel on dashboard', + defaultMessage: 'Replace panel on {originatingApp}', + values: { + originatingApp: initialContext?.originatingApp, + }, }) : undefined; @@ -513,7 +518,7 @@ export function App({ > {i18n.translate('xpack.lens.app.goBackModalMessage', { defaultMessage: - 'The changes you have made here are not backwards compatible with your original {contextOriginatingApp}. Are you sure you want to discard these unsaved changes and return to {contextOriginatingApp}?', + 'The changes you have made here are not backwards compatible with your original {contextOriginatingApp} visualization. Are you sure you want to discard these unsaved changes and return to {contextOriginatingApp}?', values: { contextOriginatingApp }, })} diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index b856bf7d77d48..54bf81de464e4 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -57,6 +57,7 @@ function getLensTopNavConfig(options: { contextOriginatingApp?: string; isSaveable: boolean; showReplaceInDashbord: boolean; + showReplaceInCanvas: boolean; }): TopNavMenuData[] { const { actions, @@ -71,6 +72,7 @@ function getLensTopNavConfig(options: { contextOriginatingApp, isSaveable, showReplaceInDashbord, + showReplaceInCanvas, } = options; const topNavMenu: TopNavMenuData[] = []; @@ -180,8 +182,10 @@ function getLensTopNavConfig(options: { topNavMenu.push({ label: saveButtonLabel, - iconType: (showReplaceInDashbord ? false : !showSaveAndReturn) ? 'save' : undefined, - emphasize: showReplaceInDashbord ? false : !showSaveAndReturn, + iconType: (showReplaceInDashbord || showReplaceInCanvas ? false : !showSaveAndReturn) + ? 'save' + : undefined, + emphasize: showReplaceInDashbord || showReplaceInCanvas ? false : !showSaveAndReturn, run: actions.showSaveModal, testId: 'lnsApp_saveButton', description: i18n.translate('xpack.lens.app.saveButtonAriaLabel', { @@ -222,6 +226,23 @@ function getLensTopNavConfig(options: { }), }); } + + if (showReplaceInCanvas) { + topNavMenu.push({ + label: i18n.translate('xpack.lens.app.replaceInCanvas', { + defaultMessage: 'Replace in canvas', + }), + emphasize: true, + iconType: 'merge', + run: actions.saveAndReturn, + testId: 'lnsApp_replaceInCanvasButton', + disableButton: !isSaveable, + description: i18n.translate('xpack.lens.app.replaceInCanvasButtonAriaLabel', { + defaultMessage: + 'Replace legacy visualization with lens visualization and return to the canvas', + }), + }); + } return topNavMenu; } @@ -475,9 +496,12 @@ export const LensTopNavMenu = ({ const showReplaceInDashbord = initialContext?.originatingApp === 'dashboards' && !(initialInput as LensByReferenceInput)?.savedObjectId; + const showReplaceInCanvas = + initialContext?.originatingApp === 'canvas' && + !(initialInput as LensByReferenceInput)?.savedObjectId; const baseMenuEntries = getLensTopNavConfig({ showSaveAndReturn: - !showReplaceInDashbord && + !(showReplaceInDashbord || showReplaceInCanvas) && (Boolean( isLinkedToOriginatingApp && // Temporarily required until the 'by value' paradigm is default. @@ -494,6 +518,7 @@ export const LensTopNavMenu = ({ isSaveable, contextOriginatingApp, showReplaceInDashbord, + showReplaceInCanvas, tooltips: { showExportWarning: () => { if (activeData) { diff --git a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx index 656d498a95566..bfeeb6a767cb6 100644 --- a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx +++ b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx @@ -64,11 +64,12 @@ export function SaveModalContainer({ } if (!lastKnownDoc?.title && initialContext && 'title' in initialContext && initialContext.title) { - title = - initialContext.title + - i18n.translate('xpack.lens.app.convertedLabel', { - defaultMessage: ' (converted)', - }); + title = i18n.translate('xpack.lens.app.convertedLabel', { + defaultMessage: '{title} (converted)', + values: { + title: initialContext.title, + }, + }); } const { attributeService, savedObjectsTagging, application, dashboardFeatureFlag } = lensServices; diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index b1c93656d5097..e74e8c94edede 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -90,7 +90,7 @@ export const getPreloadedState = ({ resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter), isLinkedToOriginatingApp: Boolean( embeddableEditorIncomingState?.originatingApp || - initialContext?.originatingApp === 'dashboards' + (initialContext && 'isEmbeddable' in initialContext && initialContext?.isEmbeddable) ), activeDatasourceId: initialDatasourceId, datasourceStates, diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index b313ee886db02..efec1b2aab089 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -251,6 +251,7 @@ export type VisualizeEditorContext = { searchQuery: Query; searchFilters: Filter[]; title?: string; + isEmbeddable?: boolean; } & NavigateToLensContext; export interface GetDropPropsArgs { diff --git a/x-pack/test/functional/apps/lens/open_in_lens/dashboard/dashboard.ts b/x-pack/test/functional/apps/lens/open_in_lens/dashboard/dashboard.ts index 22feacc6f41ce..7b6e709cb681e 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/dashboard/dashboard.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/dashboard/dashboard.ts @@ -45,7 +45,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await panelActions.convertToLens(); await lens.waitForVisualization('xyVisChart'); const lastBreadcrumbdcrumb = await testSubjects.getVisibleText('breadcrumb last'); - expect(lastBreadcrumbdcrumb).to.be('Converting "Area visualization"'); + expect(lastBreadcrumbdcrumb).to.be('Converting Area visualization'); await lens.replaceInDashboard(); await retry.try(async () => { From 0a6c890b6db63ce32c0f937d48e07cdd4f9bd1f9 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 6 Dec 2022 17:29:05 +0200 Subject: [PATCH 11/17] Fix some tests --- .../apps/lens/open_in_lens/dashboard/dashboard.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/x-pack/test/functional/apps/lens/open_in_lens/dashboard/dashboard.ts b/x-pack/test/functional/apps/lens/open_in_lens/dashboard/dashboard.ts index 7b6e709cb681e..bc8ef372b3bff 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/dashboard/dashboard.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/dashboard/dashboard.ts @@ -53,12 +53,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(embeddableCount).to.eql(originalEmbeddableCount); }); - const panel = await testSubjects.find(`embeddablePanelHeading-`); - const descendants = await testSubjects.findAllDescendant( - 'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION', - panel - ); - expect(descendants.length).to.equal(0); expect(await dashboard.isNotificationExists(0)).to.be(false); }); }); From 91020f064104e8a7ac575e063517c2dc884c0d91 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Wed, 7 Dec 2022 15:36:42 +0200 Subject: [PATCH 12/17] Fix some new comments --- .../public/actions/edit_in_lens_action.tsx | 5 +++-- x-pack/plugins/lens/public/app_plugin/app.tsx | 5 +++-- .../lens/public/app_plugin/lens_top_nav.tsx | 16 ++++++++-------- x-pack/plugins/lens/public/types.ts | 4 ++-- .../lens/open_in_lens/dashboard/dashboard.ts | 14 ++++++++++++++ 5 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx index 6596816130aba..20984f4938f27 100644 --- a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx +++ b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx @@ -73,8 +73,9 @@ export class EditInLensAction implements Action { if (isVisualizeEmbeddable(embeddable)) { const vis = embeddable.getVis(); const navigateToLensConfig = await vis.type.navigateToLens?.(vis, this.timefilter); - const searchFilters = vis.data.searchSource?.getField('filter'); - const searchQuery = vis.data.searchSource?.getField('query'); + const parentSearchSource = vis.data.searchSource?.getParent(); + const searchFilters = parentSearchSource?.getField('filter'); + const searchQuery = parentSearchSource?.getField('query'); const updatedWithMeta = { ...navigateToLensConfig, title: diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index de4fb0b9e35c4..8b26463862d43 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -421,8 +421,9 @@ export function App({ }, []); const returnToOriginSwitchLabelForContext = - (initialContext?.originatingApp === 'dashboards' || - initialContext?.originatingApp === 'canvas') && + initialContext && + 'isEmbeddable' in initialContext && + initialContext.isEmbeddable && !persistedDoc ? i18n.translate('xpack.lens.app.replacePanel', { defaultMessage: 'Replace panel on {originatingApp}', diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index eb2d39dc57a53..6a883e6fdcf91 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -56,7 +56,7 @@ function getLensTopNavConfig(options: { savingToDashboardPermitted: boolean; contextOriginatingApp?: string; isSaveable: boolean; - showReplaceInDashbord: boolean; + showReplaceInDashboard: boolean; showReplaceInCanvas: boolean; }): TopNavMenuData[] { const { @@ -71,7 +71,7 @@ function getLensTopNavConfig(options: { tooltips, contextOriginatingApp, isSaveable, - showReplaceInDashbord, + showReplaceInDashboard, showReplaceInCanvas, } = options; const topNavMenu: TopNavMenuData[] = []; @@ -182,10 +182,10 @@ function getLensTopNavConfig(options: { topNavMenu.push({ label: saveButtonLabel, - iconType: (showReplaceInDashbord || showReplaceInCanvas ? false : !showSaveAndReturn) + iconType: (showReplaceInDashboard || showReplaceInCanvas ? false : !showSaveAndReturn) ? 'save' : undefined, - emphasize: showReplaceInDashbord || showReplaceInCanvas ? false : !showSaveAndReturn, + emphasize: showReplaceInDashboard || showReplaceInCanvas ? false : !showSaveAndReturn, run: actions.showSaveModal, testId: 'lnsApp_saveButton', description: i18n.translate('xpack.lens.app.saveButtonAriaLabel', { @@ -210,7 +210,7 @@ function getLensTopNavConfig(options: { }); } - if (showReplaceInDashbord) { + if (showReplaceInDashboard) { topNavMenu.push({ label: i18n.translate('xpack.lens.app.replaceInDashboard', { defaultMessage: 'Replace in dashboard', @@ -495,7 +495,7 @@ export const LensTopNavMenu = ({ const lensStore = useStore(); const topNavConfig = useMemo(() => { - const showReplaceInDashbord = + const showReplaceInDashboard = initialContext?.originatingApp === 'dashboards' && !(initialInput as LensByReferenceInput)?.savedObjectId; const showReplaceInCanvas = @@ -503,7 +503,7 @@ export const LensTopNavMenu = ({ !(initialInput as LensByReferenceInput)?.savedObjectId; const baseMenuEntries = getLensTopNavConfig({ showSaveAndReturn: - !(showReplaceInDashbord || showReplaceInCanvas) && + !(showReplaceInDashboard || showReplaceInCanvas) && (Boolean( isLinkedToOriginatingApp && // Temporarily required until the 'by value' paradigm is default. @@ -519,7 +519,7 @@ export const LensTopNavMenu = ({ savingToDashboardPermitted, isSaveable, contextOriginatingApp, - showReplaceInDashbord, + showReplaceInDashboard, showReplaceInCanvas, tooltips: { showExportWarning: () => { diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index efec1b2aab089..847e0df8374a6 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -248,8 +248,8 @@ export type VisualizeEditorContext = { vizEditorOriginatingAppUrl?: string; originatingApp?: string; isVisualizeAction: boolean; - searchQuery: Query; - searchFilters: Filter[]; + searchQuery?: Query; + searchFilters?: Filter[]; title?: string; isEmbeddable?: boolean; } & NavigateToLensContext; diff --git a/x-pack/test/functional/apps/lens/open_in_lens/dashboard/dashboard.ts b/x-pack/test/functional/apps/lens/open_in_lens/dashboard/dashboard.ts index bc8ef372b3bff..b6c94a4f32969 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/dashboard/dashboard.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/dashboard/dashboard.ts @@ -53,7 +53,21 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(embeddableCount).to.eql(originalEmbeddableCount); }); + const titles = await dashboard.getPanelTitles(); + + expect(titles[0]).to.be('Area visualization (converted)'); + expect(await dashboard.isNotificationExists(0)).to.be(false); }); + + it('should not show notification in context menu if visualization can not be converted', async () => { + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickAggBasedVisualizations(); + await dashboardAddPanel.clickVisType('timelion'); + await testSubjects.exists('visualizesaveAndReturnButton'); + await testSubjects.click('visualizesaveAndReturnButton'); + await dashboard.waitForRenderComplete(); + expect(await dashboard.isNotificationExists(1)).to.be(false); + }); }); } From 54161f86399939736fc7144cf45bc358ad910321 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 8 Dec 2022 16:19:52 +0200 Subject: [PATCH 13/17] Some fixes --- src/plugins/visualizations/kibana.json | 3 ++- .../public/actions/edit_in_lens_action.tsx | 12 ++++++++++-- src/plugins/visualizations/public/plugin.ts | 5 +++++ src/plugins/visualizations/public/services.ts | 4 ++++ .../hooks/workpad/use_incoming_embeddable.ts | 11 ++++++++++- 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/plugins/visualizations/kibana.json b/src/plugins/visualizations/kibana.json index f650b85dd71e1..d9f5e5a58e9c2 100644 --- a/src/plugins/visualizations/kibana.json +++ b/src/plugins/visualizations/kibana.json @@ -18,7 +18,8 @@ "presentationUtil", "dataViews", "dataViewEditor", - "unifiedSearch" + "unifiedSearch", + "usageCollection" ], "optionalPlugins": ["home", "share", "spaces", "savedObjectsTaggingOss"], "requiredBundles": ["kibanaUtils", "savedSearch", "kibanaReact", "charts"], diff --git a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx index 20984f4938f27..109f31540ad91 100644 --- a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx +++ b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { take } from 'rxjs/operators'; import { EuiFlexGroup, EuiFlexItem, EuiBadge } from '@elastic/eui'; +import { METRIC_TYPE } from '@kbn/analytics'; import { reactToUiComponent } from '@kbn/kibana-react-plugin/public'; import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; import { TimefilterContract } from '@kbn/data-plugin/public'; @@ -17,7 +18,7 @@ import { IEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public'; import { Action } from '@kbn/ui-actions-plugin/public'; import { VisualizeEmbeddable } from '../embeddable'; import { DASHBOARD_VISUALIZATION_PANEL_TRIGGER } from '../triggers'; -import { getUiActions, getApplication, getEmbeddable } from '../services'; +import { getUiActions, getApplication, getEmbeddable, getUsageCollection } from '../services'; export const ACTION_EDIT_IN_LENS = 'ACTION_EDIT_IN_LENS'; @@ -53,7 +54,7 @@ const isVisualizeEmbeddable = (embeddable: IEmbeddable): embeddable is Visualize export class EditInLensAction implements Action { public id = ACTION_EDIT_IN_LENS; public readonly type = ACTION_EDIT_IN_LENS; - public order = 100; + public order = 49; public showNotification = true; public currentAppId: string | undefined; @@ -94,6 +95,13 @@ export class EditInLensAction implements Action { isEmbeddable: true, }; if (navigateToLensConfig) { + if (this.currentAppId) { + getUsageCollection().reportUiCounter( + this.currentAppId, + METRIC_TYPE.CLICK, + ACTION_EDIT_IN_LENS + ); + } getEmbeddable().getStateTransfer().isTransferInProgress = true; getUiActions().getTrigger(DASHBOARD_VISUALIZATION_PANEL_TRIGGER).exec(updatedWithMeta); } diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts index 2f9ae5e3daf10..1e92e69086e4a 100644 --- a/src/plugins/visualizations/public/plugin.ts +++ b/src/plugins/visualizations/public/plugin.ts @@ -42,6 +42,7 @@ import type { Setup as InspectorSetup, Start as InspectorStart, } from '@kbn/inspector-plugin/public'; +import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { ExpressionsSetup, ExpressionsStart } from '@kbn/expressions-plugin/public'; @@ -95,6 +96,7 @@ import { setExecutionContext, setFieldFormats, setSavedObjectTagging, + setUsageCollection, } from './services'; import { VisualizeConstants } from '../common/constants'; import { EditInLensAction } from './actions/edit_in_lens_action'; @@ -143,6 +145,7 @@ export interface VisualizationsStartDeps { screenshotMode: ScreenshotModePluginStart; fieldFormats: FieldFormatsStart; unifiedSearch: UnifiedSearchPublicPluginStart; + usageCollection: UsageCollectionStart; } /** @@ -368,6 +371,7 @@ export class VisualizationsPlugin spaces, savedObjectsTaggingOss, fieldFormats, + usageCollection, }: VisualizationsStartDeps ): VisualizationsStart { const types = this.types.start(); @@ -387,6 +391,7 @@ export class VisualizationsPlugin setExecutionContext(core.executionContext); setChrome(core.chrome); setFieldFormats(fieldFormats); + setUsageCollection(usageCollection); if (spaces) { setSpaces(spaces); diff --git a/src/plugins/visualizations/public/services.ts b/src/plugins/visualizations/public/services.ts index 2474647917e1f..8227df9a52b20 100644 --- a/src/plugins/visualizations/public/services.ts +++ b/src/plugins/visualizations/public/services.ts @@ -26,6 +26,7 @@ import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import type { EmbeddableStart } from '@kbn/embeddable-plugin/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public'; +import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import type { TypesStart } from './vis_types'; export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); @@ -72,3 +73,6 @@ export const [getSpaces, setSpaces] = createGetterSetter('Spa export const [getSavedObjectTagging, setSavedObjectTagging] = createGetterSetter('SavedObjectTagging', false); + +export const [getUsageCollection, setUsageCollection] = + createGetterSetter('UsageCollection'); diff --git a/x-pack/plugins/canvas/public/components/hooks/workpad/use_incoming_embeddable.ts b/x-pack/plugins/canvas/public/components/hooks/workpad/use_incoming_embeddable.ts index 18a7ab9b3208c..9c7f83cc7ad73 100644 --- a/x-pack/plugins/canvas/public/components/hooks/workpad/use_incoming_embeddable.ts +++ b/x-pack/plugins/canvas/public/components/hooks/workpad/use_incoming_embeddable.ts @@ -53,11 +53,20 @@ export const useIncomingEmbeddable = (selectedPage: CanvasPage) => { originalAst.chain[functionIndex].arguments.config[0] as string ); + const originalType = originalAst.chain[functionIndex].arguments.type[0]; + // clear out resolved arg for old embeddable const argumentPath = [embeddableId, 'expressionRenderable']; dispatch(clearValue({ path: argumentPath })); - const updatedInput = { ...originalInput, ...incomingInput }; + let updatedInput; + + // if type was changed, we should not provide originalInput + if (originalType !== type) { + updatedInput = incomingInput; + } else { + updatedInput = { ...originalInput, ...incomingInput }; + } const expression = `embeddable config="${encode(updatedInput)}" type="${type}" From a5447b4e5c7241e519c2c4ced44fe39d9e2db54f Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 8 Dec 2022 16:38:52 +0200 Subject: [PATCH 14/17] Fix mocks --- src/plugins/visualizations/public/mocks.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/visualizations/public/mocks.ts b/src/plugins/visualizations/public/mocks.ts index e6d0e001f4572..d739c17383cef 100644 --- a/src/plugins/visualizations/public/mocks.ts +++ b/src/plugins/visualizations/public/mocks.ts @@ -77,6 +77,9 @@ const createInstance = async () => { screenshotMode: screenshotModePluginMock.createStartContract(), fieldFormats: fieldFormatsServiceMock.createStartContract(), unifiedSearch: unifiedSearchPluginMock.createStartContract(), + usageCollection: { + reportUiCounter: jest.fn(), + }, }); return { From 9b35aa548b22c1be67774a472eb571e0110a7c28 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 13 Dec 2022 12:19:56 +0200 Subject: [PATCH 15/17] Fix small nits --- .../lib/build_dashboard_container.ts | 3 +++ .../public/actions/edit_in_lens_action.tsx | 12 +++--------- x-pack/plugins/lens/public/app_plugin/app.tsx | 13 ++++++++++--- .../lens/public/app_plugin/lens_top_nav.tsx | 17 +++++++++++++---- x-pack/plugins/lens/public/types.ts | 1 + 5 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts b/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts index 966fe9dc8f85d..f71b19da6fd31 100644 --- a/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts +++ b/src/plugins/dashboard/public/application/lib/build_dashboard_container.ts @@ -109,6 +109,9 @@ export const buildDashboardContainer = async ({ gridData: originalPanelState.gridData, type: incomingEmbeddable.type, explicitInput: { + // even when we change embeddable type we should keep hidePanelTitles state + // this is temporary, and only required because the key is stored in explicitInput + // when it should be stored outside of it instead. ...(incomingEmbeddable.type === originalPanelState.type ? { ...originalPanelState.explicitInput, diff --git a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx index 109f31540ad91..2a5ae26b9fd68 100644 --- a/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx +++ b/src/plugins/visualizations/public/actions/edit_in_lens_action.tsx @@ -77,17 +77,11 @@ export class EditInLensAction implements Action { const parentSearchSource = vis.data.searchSource?.getParent(); const searchFilters = parentSearchSource?.getField('filter'); const searchQuery = parentSearchSource?.getField('query'); + const title = vis.title || embeddable.getOutput().title; const updatedWithMeta = { ...navigateToLensConfig, - title: - vis.title || - embeddable.getOutput().title || - i18n.translate('visualizations.actions.editInLens.visulizationTitle', { - defaultMessage: '{type} visualization', - values: { - type: vis.type.title, - }, - }), + title, + visTypeTitle: vis.type.title, embeddableId: embeddable.id, originatingApp: this.currentAppId, searchFilters, diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index 8b26463862d43..81cc45e0af432 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -250,10 +250,17 @@ export function App({ ? i18n.translate('xpack.lens.breadcrumbsByValue', { defaultMessage: 'Edit visualization' }) : persistedDoc.title; } - if (!persistedDoc?.title && initialContext && 'title' in initialContext) { + if ( + !persistedDoc?.title && + initialContext && + 'isEmbeddable' in initialContext && + initialContext.isEmbeddable + ) { currentDocTitle = i18n.translate('xpack.lens.breadcrumbsEditInLensFromDashboard', { - defaultMessage: 'Converting {title}', - values: { title: initialContext.title }, + defaultMessage: 'Converting {title} visualization', + values: { + title: initialContext.title ? `"${initialContext.title}"` : initialContext.visTypeTitle, + }, }); } breadcrumbs.push({ text: currentDocTitle }); diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index 6a883e6fdcf91..8d92182e66f7b 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -58,6 +58,7 @@ function getLensTopNavConfig(options: { isSaveable: boolean; showReplaceInDashboard: boolean; showReplaceInCanvas: boolean; + contextFromEmbeddable?: boolean; }): TopNavMenuData[] { const { actions, @@ -73,6 +74,7 @@ function getLensTopNavConfig(options: { isSaveable, showReplaceInDashboard, showReplaceInCanvas, + contextFromEmbeddable, } = options; const topNavMenu: TopNavMenuData[] = []; @@ -196,11 +198,15 @@ function getLensTopNavConfig(options: { if (showSaveAndReturn) { topNavMenu.push({ - label: i18n.translate('xpack.lens.app.saveAndReturn', { - defaultMessage: 'Save and return', - }), + label: contextFromEmbeddable + ? i18n.translate('xpack.lens.app.saveAndReplace', { + defaultMessage: 'Save and replace', + }) + : i18n.translate('xpack.lens.app.saveAndReturn', { + defaultMessage: 'Save and return', + }), emphasize: true, - iconType: 'checkInCircleFilled', + iconType: contextFromEmbeddable ? 'save' : 'checkInCircleFilled', run: actions.saveAndReturn, testId: 'lnsApp_saveAndReturnButton', disableButton: !isSaveable, @@ -501,6 +507,8 @@ export const LensTopNavMenu = ({ const showReplaceInCanvas = initialContext?.originatingApp === 'canvas' && !(initialInput as LensByReferenceInput)?.savedObjectId; + const contextFromEmbeddable = + initialContext && 'isEmbeddable' in initialContext && initialContext.isEmbeddable; const baseMenuEntries = getLensTopNavConfig({ showSaveAndReturn: !(showReplaceInDashboard || showReplaceInCanvas) && @@ -521,6 +529,7 @@ export const LensTopNavMenu = ({ contextOriginatingApp, showReplaceInDashboard, showReplaceInCanvas, + contextFromEmbeddable, tooltips: { showExportWarning: () => { if (activeData) { diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 79ae90899eb05..4cc0b4c4948fa 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -251,6 +251,7 @@ export type VisualizeEditorContext = { searchQuery?: Query; searchFilters?: Filter[]; title?: string; + visTypeTitle?: string; isEmbeddable?: boolean; } & NavigateToLensContext; From 1d260b1ef0101af4b42344c911f0eafbce6f01f5 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 13 Dec 2022 13:25:13 +0200 Subject: [PATCH 16/17] fix test --- .../lens/public/app_plugin/save_modal_container.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx index bfeeb6a767cb6..e45fee5545c4e 100644 --- a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx +++ b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx @@ -63,11 +63,16 @@ export function SaveModalContainer({ savedObjectId = lastKnownDoc.savedObjectId; } - if (!lastKnownDoc?.title && initialContext && 'title' in initialContext && initialContext.title) { + if ( + !lastKnownDoc?.title && + initialContext && + 'isEmbeddable' in initialContext && + initialContext.isEmbeddable + ) { title = i18n.translate('xpack.lens.app.convertedLabel', { defaultMessage: '{title} (converted)', values: { - title: initialContext.title, + title: initialContext.title || `${initialContext.visTypeTitle} visualization`, }, }); } From 705101fc5b859543ceb409ac08e8ad99c1aa3fb1 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 13 Dec 2022 14:30:59 +0200 Subject: [PATCH 17/17] Get correct title for replace action --- x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index 8d92182e66f7b..54bae8b037847 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -591,11 +591,12 @@ export const LensTopNavMenu = ({ { newTitle: title || - (initialContext && 'title' in initialContext && initialContext.title + (initialContext && 'isEmbeddable' in initialContext && initialContext.isEmbeddable ? i18n.translate('xpack.lens.app.convertedLabel', { defaultMessage: '{title} (converted)', values: { - title: initialContext.title, + title: + initialContext.title || `${initialContext.visTypeTitle} visualization`, }, }) : ''),