From 3495720e42a44aae9d5f175bdc14acf87a0e5d10 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 7 Jul 2023 04:15:23 -0400 Subject: [PATCH 1/3] skip failing test suite (#161324) --- x-pack/test/api_integration/apis/ml/jobs/update_groups.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/api_integration/apis/ml/jobs/update_groups.ts b/x-pack/test/api_integration/apis/ml/jobs/update_groups.ts index 1702388eef4c6..20ebb75a219e8 100644 --- a/x-pack/test/api_integration/apis/ml/jobs/update_groups.ts +++ b/x-pack/test/api_integration/apis/ml/jobs/update_groups.ts @@ -57,7 +57,8 @@ export default ({ getService }: FtrProviderContext) => { { id: 'new_group', jobIds: [SINGLE_METRIC_JOB_CONFIG.job_id], calendarIds: [] }, ]; - describe('update groups', function () { + // Failing: See https://github.com/elastic/kibana/issues/161324 + describe.skip('update groups', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); await ml.testResources.setKibanaTimeZoneToUTC(); From fde953907cab24a5a119807b6a37ff28164067cf Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 7 Jul 2023 11:20:45 +0300 Subject: [PATCH 2/3] [TextBased] Allow inline editing from dashboards (#161146) ## Summary Part of https://github.com/elastic/kibana/issues/158802 For panels created from Discover with text based languages, not navigate to Lens but open a flyout instead. ![sql](https://github.com/elastic/kibana/assets/17003240/489402ac-dcdb-468b-89b9-a84f4c4f2ca5) Follow up PR: - Remove the SQL option from Lens dataview picker and move the FTs in Discover/Dashboard Note: - Changing the query on the dashboard level is going to be added in 8.11 ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- packages/kbn-optimizer/limits.yml | 2 +- .../filters_notification_popover.tsx | 44 ++++---- .../filters_notification_popover_contents.tsx | 7 +- .../chart/hooks/use_chart_config_panel.tsx | 5 +- .../chart/utils/get_lens_attributes.test.ts | 37 +++++++ .../public/chart/utils/get_lens_attributes.ts | 7 ++ .../get_edit_lens_configuration.tsx | 55 +++++---- .../lens_configuration_flyout.test.tsx | 10 +- .../lens_configuration_flyout.tsx | 9 +- x-pack/plugins/lens/public/async_services.ts | 1 + .../lens/public/embeddable/embeddable.tsx | 70 +++++++++++- x-pack/plugins/lens/public/plugin.ts | 8 ++ .../public/state_management/lens_slice.ts | 24 +++- .../open_in_discover_helpers.ts | 7 +- .../open_lens_config/action.test.tsx | 104 ++++++++++++++++++ .../open_lens_config/action.tsx | 59 ++++++++++ .../open_lens_config/helpers.ts | 70 ++++++++++++ .../lens/public/trigger_actions/utils.ts | 13 +++ x-pack/plugins/lens/tsconfig.json | 4 +- 19 files changed, 467 insertions(+), 69 deletions(-) create mode 100644 x-pack/plugins/lens/public/trigger_actions/open_lens_config/action.test.tsx create mode 100644 x-pack/plugins/lens/public/trigger_actions/open_lens_config/action.tsx create mode 100644 x-pack/plugins/lens/public/trigger_actions/open_lens_config/helpers.ts create mode 100644 x-pack/plugins/lens/public/trigger_actions/utils.ts diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index cd654512f332b..3a1a8387feaa7 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -83,7 +83,7 @@ pageLoadAssetSize: kibanaUsageCollection: 16463 kibanaUtils: 79713 kubernetesSecurity: 77234 - lens: 37000 + lens: 38000 licenseManagement: 41817 licensing: 29004 lists: 22900 diff --git a/src/plugins/dashboard/public/dashboard_actions/filters_notification_popover.tsx b/src/plugins/dashboard/public/dashboard_actions/filters_notification_popover.tsx index 636c88d563470..eab6c89c38844 100644 --- a/src/plugins/dashboard/public/dashboard_actions/filters_notification_popover.tsx +++ b/src/plugins/dashboard/public/dashboard_actions/filters_notification_popover.tsx @@ -40,6 +40,7 @@ export function FiltersNotificationPopover({ }: FiltersNotificationProps) { const { embeddable } = context; const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [disableEditbutton, setDisableEditButton] = useState(false); return ( {displayName} - + - - - editPanelAction.execute({ embeddable })} - > - {dashboardFilterNotificationActionStrings.getEditButtonTitle()} - - - + {!disableEditbutton && ( + + + editPanelAction.execute({ embeddable })} + > + {dashboardFilterNotificationActionStrings.getEditButtonTitle()} + + + + )} ); diff --git a/src/plugins/dashboard/public/dashboard_actions/filters_notification_popover_contents.tsx b/src/plugins/dashboard/public/dashboard_actions/filters_notification_popover_contents.tsx index 9c21642aadeaa..9e2e5d3e2fa8c 100644 --- a/src/plugins/dashboard/public/dashboard_actions/filters_notification_popover_contents.tsx +++ b/src/plugins/dashboard/public/dashboard_actions/filters_notification_popover_contents.tsx @@ -26,9 +26,13 @@ import { DashboardContainer } from '../dashboard_container/embeddable/dashboard_ export interface FiltersNotificationProps { context: FiltersNotificationActionContext; + setDisableEditButton: (flag: boolean) => void; } -export function FiltersNotificationPopoverContents({ context }: FiltersNotificationProps) { +export function FiltersNotificationPopoverContents({ + context, + setDisableEditButton, +}: FiltersNotificationProps) { const { embeddable } = context; const [isLoading, setIsLoading] = useState(true); const [filters, setFilters] = useState([]); @@ -53,6 +57,7 @@ export function FiltersNotificationPopoverContents({ context }: FiltersNotificat const language = getAggregateQueryMode(embeddableQuery); setQueryLanguage(language); setQueryString(embeddableQuery[language as keyof AggregateQuery]); + setDisableEditButton(true); } } setIsLoading(false); diff --git a/src/plugins/unified_histogram/public/chart/hooks/use_chart_config_panel.tsx b/src/plugins/unified_histogram/public/chart/hooks/use_chart_config_panel.tsx index 99fb8fbb8fe31..c8b70c043fcdc 100644 --- a/src/plugins/unified_histogram/public/chart/hooks/use_chart_config_panel.tsx +++ b/src/plugins/unified_histogram/public/chart/hooks/use_chart_config_panel.tsx @@ -67,7 +67,10 @@ export function useChartConfigPanel({ dataView={dataView} adaptersTables={lensTablesAdapter} updateAll={updateSuggestion} - setIsFlyoutVisible={setIsFlyoutVisible} + closeFlyout={() => { + setIsFlyoutVisible(false); + }} + wrapInFlyout datasourceId="textBased" /> ); diff --git a/src/plugins/unified_histogram/public/chart/utils/get_lens_attributes.test.ts b/src/plugins/unified_histogram/public/chart/utils/get_lens_attributes.test.ts index b19c6db64c0e0..915c820ef7095 100644 --- a/src/plugins/unified_histogram/public/chart/utils/get_lens_attributes.test.ts +++ b/src/plugins/unified_histogram/public/chart/utils/get_lens_attributes.test.ts @@ -740,6 +740,43 @@ describe('getLensAttributes', () => { `); }); + it('should return correct attributes for text based languages with adhoc dataview', () => { + const adHocDataview = { + ...dataView, + isPersisted: () => false, + } as DataView; + const lensAttrs = getLensAttributes({ + title: 'test', + filters, + query, + dataView: adHocDataview, + timeInterval, + breakdownField: undefined, + suggestion: currentSuggestionMock, + }); + expect(lensAttrs.attributes).toEqual({ + state: expect.objectContaining({ + adHocDataViews: { + 'index-pattern-with-timefield-id': {}, + }, + }), + references: [ + { + id: 'index-pattern-with-timefield-id', + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: 'index-pattern-with-timefield-id', + name: 'indexpattern-datasource-layer-unifiedHistogram', + type: 'index-pattern', + }, + ], + title: 'test', + visualizationType: 'lnsHeatmap', + }); + }); + it('should return suggestion title if no title is given', () => { expect( getLensAttributes({ diff --git a/src/plugins/unified_histogram/public/chart/utils/get_lens_attributes.ts b/src/plugins/unified_histogram/public/chart/utils/get_lens_attributes.ts index 32c2d19a3a251..1e9b58a8e53c2 100644 --- a/src/plugins/unified_histogram/public/chart/utils/get_lens_attributes.ts +++ b/src/plugins/unified_histogram/public/chart/utils/get_lens_attributes.ts @@ -205,6 +205,13 @@ export const getLensAttributes = ({ filters, query, visualization, + ...(dataView && + dataView.id && + !dataView.isPersisted() && { + adHocDataViews: { + [dataView.id]: dataView.toSpec(false), + }, + }), }, visualizationType: suggestion ? suggestion.visualizationId : 'lnsXY', } as TypedLensByValueInput['attributes']; diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx index 0eb71d6f6e1d7..65ad607a91da5 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx @@ -51,7 +51,8 @@ export function getEditLensConfiguration( attributes, dataView, updateAll, - setIsFlyoutVisible, + closeFlyout, + wrapInFlyout, datasourceId, adaptersTables, }: EditLensConfigurationProps) => { @@ -88,15 +89,38 @@ export function getEditLensConfiguration( const lensStore: LensRootStore = makeConfigureStore(storeDeps, { lens: getPreloadedState(storeDeps) as LensAppState, } as unknown as PreloadedState); - const closeFlyout = () => { - setIsFlyoutVisible?.(false); + + const getWrapper = (children: JSX.Element) => { + if (wrapInFlyout) { + return ( + { + closeFlyout?.(); + }} + aria-labelledby={i18n.translate('xpack.lens.config.editLabel', { + defaultMessage: 'Edit configuration', + })} + size="s" + hideCloseButton + css={css` + background: none; + `} + > + {children} + + ); + } else { + return children; + } }; const configPanelProps = { attributes, dataView, updateAll, - setIsFlyoutVisible, + closeFlyout, datasourceId, adaptersTables, coreStart, @@ -105,25 +129,10 @@ export function getEditLensConfiguration( datasourceMap, }; - return ( - - - - - + return getWrapper( + + + ); }; } diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.test.tsx b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.test.tsx index dc05ff1577382..3a14b26f5d9cc 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.test.tsx @@ -120,22 +120,22 @@ describe('LensEditConfigurationFlyout', () => { startDependencies, visualizationMap, datasourceMap, - setIsFlyoutVisible: jest.fn(), + closeFlyout: jest.fn(), datasourceId: 'testDatasource', } as unknown as EditConfigPanelProps; } - it('should call the setIsFlyout callback if collapse button is clicked', async () => { - const setIsFlyoutVisibleSpy = jest.fn(); + it('should call the closeFlyout callback if collapse button is clicked', async () => { + const closeFlyoutSpy = jest.fn(); const props = getDefaultProps(); const newProps = { ...props, - setIsFlyoutVisible: setIsFlyoutVisibleSpy, + closeFlyout: closeFlyoutSpy, }; const { instance } = await prepareAndMountComponent(newProps); expect(instance.find(EuiFlyoutBody).exists()).toBe(true); instance.find('[data-test-subj="collapseFlyoutButton"]').at(1).simulate('click'); - expect(setIsFlyoutVisibleSpy).toHaveBeenCalled(); + expect(closeFlyoutSpy).toHaveBeenCalled(); }); it('should compute the frame public api correctly', async () => { diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx index 9fe486c58048a..98add346d114d 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx @@ -43,7 +43,8 @@ export interface EditConfigPanelProps { startDependencies: LensPluginStartDependencies; visualizationMap: VisualizationMap; datasourceMap: DatasourceMap; - setIsFlyoutVisible?: (flag: boolean) => void; + closeFlyout?: () => void; + wrapInFlyout?: boolean; datasourceId: 'formBased' | 'textBased'; adaptersTables?: Record; } @@ -57,7 +58,7 @@ export function LensEditConfigurationFlyout({ datasourceMap, datasourceId, updateAll, - setIsFlyoutVisible, + closeFlyout, adaptersTables, }: EditConfigPanelProps) { const currentDataViewId = dataView.id ?? ''; @@ -112,10 +113,6 @@ export function LensEditConfigurationFlyout({ }; }, [activeData, dataViews, datasourceLayers, dateRange]); - const closeFlyout = () => { - setIsFlyoutVisible?.(false); - }; - const layerPanelsProps = { framePublicAPI, datasourceMap, diff --git a/x-pack/plugins/lens/public/async_services.ts b/x-pack/plugins/lens/public/async_services.ts index d4c6fe5be8dcd..7bbfaf415db03 100644 --- a/x-pack/plugins/lens/public/async_services.ts +++ b/x-pack/plugins/lens/public/async_services.ts @@ -50,3 +50,4 @@ export * from './app_plugin/save_modal_container'; export * from './chart_info_api'; export * from './trigger_actions/open_in_discover_helpers'; +export * from './trigger_actions/open_lens_config/helpers'; diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index a5b1dbc957ef9..c690f875debf5 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -18,6 +18,7 @@ import { AggregateQuery, TimeRange, isOfQueryType, + getAggregateQueryMode, } from '@kbn/es-query'; import type { PaletteOutput } from '@kbn/coloring'; import { @@ -127,8 +128,11 @@ import { } from '../app_plugin/get_application_user_messages'; import { MessageList } from '../editor_frame_service/editor_frame/workspace_panel/message_list'; import type { DocumentToExpressionReturnType } from '../editor_frame_service/editor_frame'; +import type { TypedLensByValueInput } from './embeddable_component'; +import type { LensPluginStartDependencies } from '../plugin'; import { EmbeddableFeatureBadge } from './embeddable_info_badges'; import { getDatasourceLayers } from '../state_management/utils'; +import type { EditLensConfigurationProps } from '../app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration'; export type LensSavedObjectAttributes = Omit; @@ -718,6 +722,70 @@ export class Embeddable return this.fullAttributes; } + public isTextBasedLanguage() { + if (!this.savedVis) { + return; + } + const query = this.savedVis.state.query; + return !isOfQueryType(query); + } + + public getTextBasedLanguage(): string | undefined { + if (!this.isTextBasedLanguage() || !this.savedVis?.state.query) { + return; + } + const query = this.savedVis?.state.query as unknown as AggregateQuery; + const language = getAggregateQueryMode(query); + return String(language).toUpperCase(); + } + + async updateVisualization(datasourceState: unknown, visualizationState: unknown) { + const viz = this.savedVis; + const datasourceId = (this.activeDatasourceId ?? + 'formBased') as EditLensConfigurationProps['datasourceId']; + if (viz?.state) { + const attrs = { + ...viz, + state: { + ...viz.state, + visualization: visualizationState, + datasourceStates: { + ...viz.state.datasourceStates, + [datasourceId]: datasourceState, + }, + }, + }; + this.updateInput({ attributes: attrs }); + } + } + + async openConfingPanel(startDependencies: LensPluginStartDependencies) { + const { getEditLensConfiguration } = await import('../async_services'); + const Component = getEditLensConfiguration( + this.deps.coreStart, + startDependencies, + this.deps.visualizationMap, + this.deps.datasourceMap + ); + + const datasourceId = (this.activeDatasourceId ?? + 'formBased') as EditLensConfigurationProps['datasourceId']; + const attributes = this.savedVis as TypedLensByValueInput['attributes']; + const dataView = this.dataViews[0]; + if (attributes) { + return ( + + ); + } + return null; + } + async initializeSavedVis(input: LensEmbeddableInput) { const unwrapResult: LensUnwrapResult | false = await this.deps.attributeService .unwrapAttributes(input) @@ -1346,7 +1414,7 @@ export class Embeddable this.updateOutput({ defaultTitle: this.savedVis.title, defaultDescription: this.savedVis.description, - editable: this.getIsEditable(), + editable: this.getIsEditable() && !this.isTextBasedLanguage(), title, description, editPath: getEditPath(savedObjectId), diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 1a195183142c3..3f2559ce634be 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -104,6 +104,7 @@ import type { } from './types'; import { getLensAliasConfig } from './vis_type_alias'; import { createOpenInDiscoverAction } from './trigger_actions/open_in_discover_action'; +import { ConfigureInLensPanelAction } from './trigger_actions/open_lens_config/action'; 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'; @@ -589,6 +590,13 @@ export class LensPlugin { visualizeAggBasedVisAction(core.application) ); + const editInLensAction = new ConfigureInLensPanelAction( + startDependencies, + core.overlays, + core.theme + ); + startDependencies.uiActions.addTriggerAction('CONTEXT_MENU_TRIGGER', editInLensAction); + const discoverLocator = startDependencies.share?.url.locators.get('DISCOVER_APP_LOCATOR'); if (discoverLocator) { startDependencies.uiActions.addTriggerAction( 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 cb35f16f0e8c5..60d15f532d3d4 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -30,6 +30,20 @@ import { getVisualizeFieldSuggestions } from '../editor_frame_service/editor_fra import type { FramePublicAPI, LensEditContextMapping, LensEditEvent } from '../types'; import { selectDataViews, selectFramePublicAPI } from './selectors'; import { onDropForVisualization } from '../editor_frame_service/editor_frame/config_panel/buttons/drop_targets_utils'; +import type { LensAppServices } from '../app_plugin/types'; + +const getQueryFromContext = ( + context: VisualizeFieldContext | VisualizeEditorContext, + data: LensAppServices['data'] +) => { + if ('searchQuery' in context && context.searchQuery) { + return context.searchQuery; + } + if ('query' in context && context.query) { + return context.query; + } + return data.query.queryString.getQuery(); +}; export const initialState: LensAppState = { persistedDoc: undefined, @@ -93,16 +107,16 @@ export const getPreloadedState = ({ }; } + const query = !initialContext + ? data.query.queryString.getDefaultQuery() + : getQueryFromContext(initialContext, data); + const state = { ...initialState, isLoading: true, // Do not use app-specific filters from previous app, // 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), + query: query as Query, filters: !initialContext ? data.query.filterManager.getGlobalFilters() : 'searchFilters' in initialContext && initialContext.searchFilters diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_helpers.ts b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_helpers.ts index 15773ccbbd1f8..bd3a10536d343 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_helpers.ts +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_helpers.ts @@ -10,8 +10,7 @@ import type { IEmbeddable } from '@kbn/embeddable-plugin/public'; import type { DataViewsService } from '@kbn/data-views-plugin/public'; import type { LocatorPublic } from '@kbn/share-plugin/public'; import type { SerializableRecord } from '@kbn/utility-types'; -import type { Embeddable } from '../embeddable'; -import { DOC_TYPE } from '../../common/constants'; +import { isLensEmbeddable } from './utils'; interface DiscoverAppLocatorParams extends SerializableRecord { timeRange?: TimeRange; @@ -33,10 +32,6 @@ interface Context { timeFieldName?: string; } -export function isLensEmbeddable(embeddable: IEmbeddable): embeddable is Embeddable { - return embeddable.type === DOC_TYPE; -} - export async function isCompatible({ hasDiscoverAccess, embeddable }: Context) { if (!hasDiscoverAccess) return false; try { diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/action.test.tsx b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/action.test.tsx new file mode 100644 index 0000000000000..6c3d98fb3637d --- /dev/null +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/action.test.tsx @@ -0,0 +1,104 @@ +/* + * 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 React from 'react'; +import type { IEmbeddable } from '@kbn/embeddable-plugin/public'; +import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; +import { overlayServiceMock } from '@kbn/core-overlays-browser-mocks'; +import { themeServiceMock } from '@kbn/core-theme-browser-mocks'; +import type { LensPluginStartDependencies } from '../../plugin'; +import { createMockStartDependencies } from '../../editor_frame_service/mocks'; +import { DOC_TYPE } from '../../../common/constants'; +import { ConfigureInLensPanelAction } from './action'; + +describe('open config panel action', () => { + const overlays = overlayServiceMock.createStartContract(); + const theme = themeServiceMock.createStartContract(); + const mockStartDependencies = + createMockStartDependencies() as unknown as LensPluginStartDependencies; + describe('compatibility check', () => { + it('is incompatible with non-lens embeddables', async () => { + const embeddable = { + type: 'NOT_LENS', + isTextBasedLanguage: () => true, + } as unknown as IEmbeddable; + const configurablePanelAction = new ConfigureInLensPanelAction( + mockStartDependencies, + overlays, + theme + ); + + const isCompatible = await configurablePanelAction.isCompatible({ + embeddable, + } as ActionExecutionContext<{ embeddable: IEmbeddable }>); + + expect(isCompatible).toBeFalsy(); + }); + + it('is incompatible with non text based language embeddable', async () => { + const embeddable = { + type: DOC_TYPE, + isTextBasedLanguage: () => false, + } as unknown as IEmbeddable; + const configurablePanelAction = new ConfigureInLensPanelAction( + mockStartDependencies, + overlays, + theme + ); + + const isCompatible = await configurablePanelAction.isCompatible({ + embeddable, + } as ActionExecutionContext<{ embeddable: IEmbeddable }>); + + expect(isCompatible).toBeFalsy(); + }); + + it('is compatible with text based language embeddable', async () => { + const embeddable = { + type: DOC_TYPE, + isTextBasedLanguage: () => true, + } as unknown as IEmbeddable; + const configurablePanelAction = new ConfigureInLensPanelAction( + mockStartDependencies, + overlays, + theme + ); + + const isCompatible = await configurablePanelAction.isCompatible({ + embeddable, + } as ActionExecutionContext<{ embeddable: IEmbeddable }>); + + expect(isCompatible).toBeTruthy(); + }); + }); + describe('execution', () => { + it('opens flyout when executed', async () => { + const embeddable = { + type: DOC_TYPE, + isTextBasedLanguage: () => true, + openConfingPanel: jest.fn().mockResolvedValue(Lens Config Panel Component), + getRoot: () => { + return { + openOverlay: jest.fn(), + clearOverlays: jest.fn(), + }; + }, + } as unknown as IEmbeddable; + const configurablePanelAction = new ConfigureInLensPanelAction( + mockStartDependencies, + overlays, + theme + ); + const spy = jest.spyOn(overlays, 'openFlyout'); + + await configurablePanelAction.execute({ + embeddable, + } as ActionExecutionContext<{ embeddable: IEmbeddable }>); + + expect(spy).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/action.tsx b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/action.tsx new file mode 100644 index 0000000000000..376cae58d1339 --- /dev/null +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/action.tsx @@ -0,0 +1,59 @@ +/* + * 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 type { IEmbeddable } from '@kbn/embeddable-plugin/public'; +import { OverlayStart, ThemeServiceStart } from '@kbn/core/public'; +import { Action } from '@kbn/ui-actions-plugin/public'; +import type { LensPluginStartDependencies } from '../../plugin'; +import { isLensEmbeddable } from '../utils'; + +const ACTION_CONFIGURE_IN_LENS = 'ACTION_CONFIGURE_IN_LENS'; + +interface Context { + embeddable: IEmbeddable; +} + +export const getConfigureLensHelpersAsync = async () => await import('../../async_services'); + +export class ConfigureInLensPanelAction implements Action { + public type = ACTION_CONFIGURE_IN_LENS; + public id = ACTION_CONFIGURE_IN_LENS; + public order = 50; + + constructor( + protected readonly startDependencies: LensPluginStartDependencies, + protected readonly overlays: OverlayStart, + protected readonly theme: ThemeServiceStart + ) {} + + public getDisplayName({ embeddable }: Context): string { + const language = isLensEmbeddable(embeddable) ? embeddable.getTextBasedLanguage() : undefined; + return i18n.translate('xpack.lens.app.editVisualizationLabel', { + defaultMessage: 'Edit {lang} visualization', + values: { lang: language }, + }); + } + + public getIconType() { + return 'pencil'; + } + + public async isCompatible({ embeddable }: Context) { + const { isActionCompatible } = await getConfigureLensHelpersAsync(); + return isActionCompatible(embeddable); + } + + public async execute({ embeddable }: Context) { + const { executeAction } = await getConfigureLensHelpersAsync(); + return executeAction({ + embeddable, + startDependencies: this.startDependencies, + overlays: this.overlays, + theme: this.theme, + }); + } +} diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/helpers.ts b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/helpers.ts new file mode 100644 index 0000000000000..985424d18e1c2 --- /dev/null +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/helpers.ts @@ -0,0 +1,70 @@ +/* + * 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 React from 'react'; +import type { IEmbeddable } from '@kbn/embeddable-plugin/public'; +import type { OverlayRef, OverlayStart, ThemeServiceStart } from '@kbn/core/public'; +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import { isLensEmbeddable } from '../utils'; +import type { LensPluginStartDependencies } from '../../plugin'; + +interface Context { + embeddable: IEmbeddable; + startDependencies: LensPluginStartDependencies; + overlays: OverlayStart; + theme: ThemeServiceStart; +} + +interface TracksOverlays { + openOverlay: (ref: OverlayRef) => void; + clearOverlays: () => void; +} + +function tracksOverlays(root: unknown): root is TracksOverlays { + return Boolean((root as TracksOverlays).openOverlay && (root as TracksOverlays).clearOverlays); +} + +export async function isActionCompatible(embeddable: IEmbeddable) { + return Boolean(isLensEmbeddable(embeddable) && embeddable.isTextBasedLanguage()); +} + +export async function executeAction({ embeddable, startDependencies, overlays, theme }: Context) { + const isCompatibleAction = await isActionCompatible(embeddable); + if (!isCompatibleAction || !isLensEmbeddable(embeddable)) { + throw new IncompatibleActionError(); + } + const rootEmbeddable = embeddable.getRoot(); + const overlayTracker = tracksOverlays(rootEmbeddable) ? rootEmbeddable : undefined; + const ConfigPanel = await embeddable.openConfingPanel(startDependencies); + if (ConfigPanel) { + const handle = overlays.openFlyout( + toMountPoint( + React.cloneElement(ConfigPanel, { + closeFlyout: () => { + if (overlayTracker) overlayTracker.clearOverlays(); + handle.close(); + }, + }), + { + theme$: theme.theme$, + } + ), + { + size: 's', + 'data-test-subj': 'customizeLens', + type: 'push', + hideCloseButton: true, + onClose: (overlayRef) => { + if (overlayTracker) overlayTracker.clearOverlays(); + overlayRef.close(); + }, + outsideClickCloses: true, + } + ); + overlayTracker?.openOverlay(handle); + } +} diff --git a/x-pack/plugins/lens/public/trigger_actions/utils.ts b/x-pack/plugins/lens/public/trigger_actions/utils.ts new file mode 100644 index 0000000000000..527f1adcf7629 --- /dev/null +++ b/x-pack/plugins/lens/public/trigger_actions/utils.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { IEmbeddable } from '@kbn/embeddable-plugin/public'; +import type { Embeddable } from '../embeddable'; +import { DOC_TYPE } from '../../common/constants'; + +export function isLensEmbeddable(embeddable: IEmbeddable): embeddable is Embeddable { + return embeddable.type === DOC_TYPE; +} diff --git a/x-pack/plugins/lens/tsconfig.json b/x-pack/plugins/lens/tsconfig.json index 10517769418f7..9cb461d1f1491 100644 --- a/x-pack/plugins/lens/tsconfig.json +++ b/x-pack/plugins/lens/tsconfig.json @@ -78,7 +78,9 @@ "@kbn/core-notifications-browser-mocks", "@kbn/core-saved-objects-utils-server", "@kbn/core-lifecycle-browser-mocks", - "@kbn/unified-field-list" + "@kbn/unified-field-list", + "@kbn/core-overlays-browser-mocks", + "@kbn/core-theme-browser-mocks" ], "exclude": [ "target/**/*", From 94e850dcb52bfbcd8850a5aef6d96eef303a179f Mon Sep 17 00:00:00 2001 From: Jedrzej Blaszyk Date: Fri, 7 Jul 2023 10:51:11 +0200 Subject: [PATCH 3/3] [Enterprise Search] Add Google Drive connector tile to content UI (#161359) ## Summary This adds Google Drive connector tile to Enterprise Search. ### Preview Google drive connector documentation link: https://www.elastic.co/guide/en/enterprise-search/master/connectors-google-drive.html Tile view: Screenshot 2023-07-06 at 16 18 33 Google Drive configuration - we only require service account JSON: Screenshot 2023-07-06 at 16 20 03 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- packages/kbn-doc-links/src/get_doc_links.ts | 1 + packages/kbn-doc-links/src/types.ts | 1 + .../enterprise_search/common/connectors/connectors.ts | 10 ++++++++++ .../components/search_index/connector/constants.ts | 6 ++++++ .../public/applications/shared/doc_links/doc_links.ts | 3 +++ .../applications/shared/icons/connector_icons.ts | 2 ++ 6 files changed, 23 insertions(+) diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 3b28dafbcd761..def26211291d6 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -139,6 +139,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { connectorsDropbox: `${ENTERPRISE_SEARCH_DOCS}connectors-dropbox.html`, connectorsContentExtraction: `${ENTERPRISE_SEARCH_DOCS}connectors-content-extraction.html`, connectorsGoogleCloudStorage: `${ENTERPRISE_SEARCH_DOCS}connectors-google-cloud.html`, + connectorsGoogleDrive: `${ENTERPRISE_SEARCH_DOCS}connectors-google-drive.html`, connectorsJira: `${ENTERPRISE_SEARCH_DOCS}connectors-jira.html`, connectorsMicrosoftSQL: `${ENTERPRISE_SEARCH_DOCS}connectors-ms-sql.html`, connectorsMongoDB: `${ENTERPRISE_SEARCH_DOCS}connectors-mongodb.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index b8f15cbde68ca..c4fdc8657e2ac 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -123,6 +123,7 @@ export interface DocLinks { readonly connectorsContentExtraction: string; readonly connectorsDropbox: string; readonly connectorsGoogleCloudStorage: string; + readonly connectorsGoogleDrive: string; readonly connectorsJira: string; readonly connectorsMicrosoftSQL: string; readonly connectorsMongoDB: string; diff --git a/x-pack/plugins/enterprise_search/common/connectors/connectors.ts b/x-pack/plugins/enterprise_search/common/connectors/connectors.ts index 34562ed68f36a..30b8186c0053d 100644 --- a/x-pack/plugins/enterprise_search/common/connectors/connectors.ts +++ b/x-pack/plugins/enterprise_search/common/connectors/connectors.ts @@ -58,6 +58,16 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ }), serviceType: 'google_cloud_storage', }, + { + iconPath: 'google_drive.svg', + isBeta: true, + isNative: false, + keywords: ['google', 'drive', 'connector'], + name: i18n.translate('xpack.enterpriseSearch.content.nativeConnectors.googleDrive.name', { + defaultMessage: 'Google Drive', + }), + serviceType: 'google_drive', + }, { iconPath: 'mongodb.svg', isBeta: false, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts index 0f1603ea4fe0d..24b9d342f52d4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts @@ -43,6 +43,12 @@ export const CONNECTORS_DICT: Record = { externalDocsUrl: 'https://cloud.google.com/storage/docs', icon: CONNECTOR_ICONS.google_cloud_storage, }, + google_drive: { + docsUrl: docLinks.connectorsGoogleDrive, + externalAuthDocsUrl: 'https://cloud.google.com/iam/docs/service-account-overview', + externalDocsUrl: 'https://developers.google.com/drive', + icon: CONNECTOR_ICONS.google_drive, + }, jira: { docsUrl: docLinks.connectorsJira, externalAuthDocsUrl: '', diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts index 2861c8032e0a3..c61deecb811d0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts @@ -68,6 +68,7 @@ class DocLinks { public connectorsContentExtraction: string; public connectorsDropbox: string; public connectorsGoogleCloudStorage: string; + public connectorsGoogleDrive: string; public connectorsJira: string; public connectorsMicrosoftSQL: string; public connectorsMongoDB: string; @@ -223,6 +224,7 @@ class DocLinks { this.connectorsClients = ''; this.connectorsDropbox = ''; this.connectorsGoogleCloudStorage = ''; + this.connectorsGoogleDrive = ''; this.connectorsJira = ''; this.connectorsMicrosoftSQL = ''; this.connectorsMongoDB = ''; @@ -380,6 +382,7 @@ class DocLinks { this.connectorsDropbox = docLinks.links.enterpriseSearch.connectorsDropbox; this.connectorsGoogleCloudStorage = docLinks.links.enterpriseSearch.connectorsGoogleCloudStorage; + this.connectorsGoogleDrive = docLinks.links.enterpriseSearch.connectorsGoogleDrive; this.connectorsJira = docLinks.links.enterpriseSearch.connectorsJira; this.connectorsMicrosoftSQL = docLinks.links.enterpriseSearch.connectorsMicrosoftSQL; this.connectorsMongoDB = docLinks.links.enterpriseSearch.connectorsMongoDB; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/icons/connector_icons.ts b/x-pack/plugins/enterprise_search/public/applications/shared/icons/connector_icons.ts index cb7871bbe9f02..5533e6e5d3d3c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/icons/connector_icons.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/icons/connector_icons.ts @@ -11,6 +11,7 @@ import custom from '../../../assets/source_icons/custom.svg'; import dropbox from '../../../assets/source_icons/dropbox.svg'; import github from '../../../assets/source_icons/github.svg'; import google_cloud_storage from '../../../assets/source_icons/google_cloud_storage.svg'; +import google_drive from '../../../assets/source_icons/google_drive.svg'; import jira_cloud from '../../../assets/source_icons/jira_cloud.svg'; import mongodb from '../../../assets/source_icons/mongodb.svg'; import microsoft_sql from '../../../assets/source_icons/mssql.svg'; @@ -31,6 +32,7 @@ export const CONNECTOR_ICONS = { dropbox, github, google_cloud_storage, + google_drive, jira_cloud, microsoft_sql, mongodb,