From 452d8f980dea557cdd880314b957eeb46b269e46 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Fri, 29 Apr 2022 15:19:37 +0200 Subject: [PATCH 01/11] make open in discover drilldown work --- x-pack/plugins/lens/kibana.json | 1 + x-pack/plugins/lens/public/plugin.ts | 21 ++- .../open_in_discover_action.ts | 20 ++- .../open_in_discover_drilldown.tsx | 127 ++++++++++++++++++ x-pack/plugins/lens/tsconfig.json | 1 + 5 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json index 8ed33fb304525..63ce9497fc664 100644 --- a/x-pack/plugins/lens/kibana.json +++ b/x-pack/plugins/lens/kibana.json @@ -16,6 +16,7 @@ "visualizations", "dashboard", "uiActions", + "uiActionsEnhanced", "embeddable", "share", "presentationUtil", diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index b39c14cd82454..70f69fd8924a7 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -43,6 +43,7 @@ import { VISUALIZE_EDITOR_TRIGGER } from '@kbn/visualizations-plugin/public'; import { createStartServicesGetter } from '@kbn/kibana-utils-plugin/public'; import type { DiscoverSetup, DiscoverStart } from '@kbn/discover-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import { AdvancedUiActionsSetup } from '@kbn/ui-actions-enhanced-plugin/public'; import type { EditorFrameService as EditorFrameServiceType } from './editor_frame_service'; import type { IndexPatternDatasource as IndexPatternDatasourceType, @@ -92,6 +93,7 @@ import type { SaveModalContainerProps } from './app_plugin/save_modal_container' import { setupExpressions } from './expressions'; import { getSearchProvider } from './search_provider'; +import { OpenInDiscoverDrilldown } from './trigger_actions/open_in_discover_drilldown'; export interface LensPluginSetupDependencies { urlForwarding: UrlForwardingSetup; @@ -105,6 +107,7 @@ export interface LensPluginSetupDependencies { globalSearch?: GlobalSearchPluginSetup; usageCollection?: UsageCollectionSetup; discover?: DiscoverSetup; + uiActionsEnhanced: AdvancedUiActionsSetup; } export interface LensPluginStartDependencies { @@ -222,6 +225,7 @@ export class LensPlugin { private heatmapVisualization: HeatmapVisualizationType | undefined; private gaugeVisualization: GaugeVisualizationType | undefined; private topNavMenuEntries: LensTopNavMenuEntryGenerator[] = []; + private hasDiscoverAccess: boolean = false; private stopReportManager?: () => void; @@ -238,6 +242,8 @@ export class LensPlugin { eventAnnotation, globalSearch, usageCollection, + uiActionsEnhanced, + discover, }: LensPluginSetupDependencies ) { const startServices = createStartServicesGetter(core.getStartServices); @@ -283,6 +289,15 @@ export class LensPlugin { visualizations.registerAlias(getLensAliasConfig()); + if (discover) { + uiActionsEnhanced.registerDrilldown( + new OpenInDiscoverDrilldown({ + discover, + hasDiscoverAccess: () => this.hasDiscoverAccess, + }) + ); + } + setupExpressions( expressions, () => startServices().plugins.fieldFormats.deserialize, @@ -425,6 +440,7 @@ export class LensPlugin { } start(core: CoreStart, startDependencies: LensPluginStartDependencies): LensPublicStart { + this.hasDiscoverAccess = core.application.capabilities.discover.show as boolean; // unregisters the Visualize action and registers the lens one if (startDependencies.uiActions.hasAction(ACTION_VISUALIZE_FIELD)) { startDependencies.uiActions.unregisterAction(ACTION_VISUALIZE_FIELD); @@ -441,10 +457,7 @@ export class LensPlugin { startDependencies.uiActions.addTriggerAction( CONTEXT_MENU_TRIGGER, - createOpenInDiscoverAction( - startDependencies.discover!, - core.application.capabilities.discover.show as boolean - ) + createOpenInDiscoverAction(startDependencies.discover!, this.hasDiscoverAccess) ); return { diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts index bd666f52bf0bc..bbf660a00d5c5 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts @@ -5,17 +5,25 @@ * 2.0. */ -import type { IEmbeddable } from '@kbn/embeddable-plugin/public'; import { i18n } from '@kbn/i18n'; import { createAction } from '@kbn/ui-actions-plugin/public'; import type { DiscoverStart } from '@kbn/discover-plugin/public'; +import { Filter } from '@kbn/es-query'; +import { TimeRange } from '@kbn/data-plugin/public'; import type { Embeddable } from '../embeddable'; import { DOC_TYPE } from '../../common'; const ACTION_OPEN_IN_DISCOVER = 'ACTION_OPEN_IN_DISCOVER'; +interface Context { + embeddable: Embeddable; + filters?: Filter[]; + timeRange?: TimeRange; + openInSameTab?: boolean; +} + export const createOpenInDiscoverAction = (discover: DiscoverStart, hasDiscoverAccess: boolean) => - createAction<{ embeddable: IEmbeddable }>({ + createAction({ type: ACTION_OPEN_IN_DISCOVER, id: ACTION_OPEN_IN_DISCOVER, order: 19, // right after Inspect which is 20 @@ -24,18 +32,20 @@ export const createOpenInDiscoverAction = (discover: DiscoverStart, hasDiscoverA i18n.translate('xpack.lens.app.exploreDataInDiscover', { defaultMessage: 'Explore data in Discover', }), - isCompatible: async (context: { embeddable: IEmbeddable }) => { + isCompatible: async (context: Context) => { if (!hasDiscoverAccess) return false; return ( context.embeddable.type === DOC_TYPE && (await (context.embeddable as Embeddable).canViewUnderlyingData()) ); }, - execute: async (context: { embeddable: Embeddable }) => { + execute: async (context: Context) => { const args = context.embeddable.getViewUnderlyingDataArgs()!; const discoverUrl = discover.locator?.getRedirectUrl({ ...args, + timeRange: context.timeRange || args.timeRange, + filters: [...(context.filters || []), ...args.filters], }); - window.open(discoverUrl, '_blank'); + window.open(discoverUrl, !context.openInSameTab ? '_blank' : '_self'); }, }); diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx new file mode 100644 index 0000000000000..82baca5ac4e7f --- /dev/null +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx @@ -0,0 +1,127 @@ +/* + * 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 { IEmbeddable, EmbeddableInput, Embeddable } from '@kbn/embeddable-plugin/public'; +import { + Query, + Filter, + TimeRange, + extractTimeRange, + APPLY_FILTER_TRIGGER, +} from '@kbn/data-plugin/public'; +import { CollectConfigProps as CollectConfigPropsBase } from '@kbn/kibana-utils-plugin/public'; +import { reactToUiComponent } from '@kbn/kibana-react-plugin/public'; +import { + UiActionsEnhancedDrilldownDefinition as Drilldown, + UiActionsEnhancedBaseActionFactoryContext as BaseActionFactoryContext, +} from '@kbn/ui-actions-enhanced-plugin/public'; +import { EuiFormRow, EuiSwitch } from '@elastic/eui'; +import { DiscoverSetup, DiscoverStart } from '@kbn/discover-plugin/public'; +import { ApplyGlobalFilterActionContext } from '@kbn/unified-search-plugin/public'; +import { createOpenInDiscoverAction } from './open_in_discover_action'; + +interface EmbeddableQueryInput extends EmbeddableInput { + query?: Query; + filters?: Filter[]; + timeRange?: TimeRange; +} + +/** @internal */ +export type EmbeddableWithQueryInput = IEmbeddable; + +interface UrlDrilldownDeps { + discover: DiscoverSetup; + hasDiscoverAccess: () => boolean; +} + +export type ActionContext = ApplyGlobalFilterActionContext; + +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +export type Config = { + openInNewTab: boolean; +}; + +export type OpenInDiscoverTrigger = typeof APPLY_FILTER_TRIGGER; + +export interface ActionFactoryContext extends BaseActionFactoryContext { + embeddable?: EmbeddableWithQueryInput; +} +export type CollectConfigProps = CollectConfigPropsBase; + +const URL_DRILLDOWN = 'OPEN_IN_DISCOVER_DRILLDOWN'; + +export class OpenInDiscoverDrilldown + implements Drilldown +{ + public readonly id = URL_DRILLDOWN; + + constructor(private readonly deps: UrlDrilldownDeps) {} + + public readonly order = 8; + + public readonly getDisplayName = () => 'Open in Discover'; + + public readonly euiIcon = 'link'; + + supportedTriggers(): OpenInDiscoverTrigger[] { + return [APPLY_FILTER_TRIGGER]; + } + + private readonly ReactCollectConfig: React.FC = ({ + config, + onConfig, + context, + }) => { + return ( + + onConfig({ ...config, openInNewTab: !config.openInNewTab })} + data-test-subj="openInDiscoverDrilldownOpenInNewTab" + /> + + ); + }; + + public readonly CollectConfig = reactToUiComponent(this.ReactCollectConfig); + + public readonly createConfig = () => ({ + openInNewTab: true, + }); + + public readonly isConfigValid = (config: Config): config is Config => { + return true; + }; + + public readonly isCompatible = async (config: Config, context: ActionContext) => { + return Boolean( + context.embeddable && + (await createOpenInDiscoverAction( + this.deps.discover, + this.deps.hasDiscoverAccess() + ).isCompatible({ embeddable: context.embeddable as Embeddable, trigger: { id: '' } })) + ); + }; + + public readonly execute = async (config: Config, context: ActionContext) => { + const { restOfFilters: filtersFromEvent, timeRange: timeRangeFromEvent } = extractTimeRange( + context.filters, + context.timeFieldName + ); + await createOpenInDiscoverAction(this.deps.discover, this.deps.hasDiscoverAccess()).execute({ + embeddable: context.embeddable as Embeddable, + filters: filtersFromEvent, + timeRange: timeRangeFromEvent, + openInSameTab: !config.openInNewTab, + trigger: { id: '' }, + }); + }; +} diff --git a/x-pack/plugins/lens/tsconfig.json b/x-pack/plugins/lens/tsconfig.json index 380d387249e17..254e9d2f44592 100644 --- a/x-pack/plugins/lens/tsconfig.json +++ b/x-pack/plugins/lens/tsconfig.json @@ -12,6 +12,7 @@ { "path": "../../../src/core/tsconfig.json" }, { "path": "../task_manager/tsconfig.json" }, { "path": "../global_search/tsconfig.json" }, + { "path": "../ui_actions_enhanced/tsconfig.json" }, { "path": "../saved_objects_tagging/tsconfig.json" }, { "path": "../../../src/plugins/data/tsconfig.json" }, { "path": "../../../src/plugins/data_views/tsconfig.json" }, From 317445417b57cd3e00679f0ac1694a84ff1addcd Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 2 May 2022 12:19:49 +0200 Subject: [PATCH 02/11] cleanup and tests --- .../open_in_discover_action.test.ts | 14 ++--- .../open_in_discover_action.ts | 29 +++------ .../open_in_discover_drilldown.test.tsx | 63 +++++++++++++++++++ .../open_in_discover_drilldown.tsx | 46 ++++++++------ .../open_in_discover_helpers.ts | 45 +++++++++++++ 5 files changed, 149 insertions(+), 48 deletions(-) create mode 100644 x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx create mode 100644 x-pack/plugins/lens/public/trigger_actions/open_in_discover_helpers.ts diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.test.ts b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.test.ts index 084bd65b70d31..7647fba3d47bf 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.test.ts +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DiscoverStart } from '@kbn/discover-plugin/public'; +import { DiscoverSetup } from '@kbn/discover-plugin/public'; import type { IEmbeddable } from '@kbn/embeddable-plugin/public'; import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; import { DOC_TYPE } from '../../common'; @@ -17,7 +17,7 @@ describe('open in discover action', () => { it('is incompatible with non-lens embeddables', async () => { const embeddable = { type: 'NOT_LENS' } as IEmbeddable; - const isCompatible = await createOpenInDiscoverAction({} as DiscoverStart, true).isCompatible( + const isCompatible = await createOpenInDiscoverAction({} as DiscoverSetup, true).isCompatible( { embeddable, } as ActionExecutionContext<{ embeddable: IEmbeddable }> @@ -33,7 +33,7 @@ describe('open in discover action', () => { let hasDiscoverAccess = true; // make sure it would work if we had access to Discover expect( - await createOpenInDiscoverAction({} as DiscoverStart, hasDiscoverAccess).isCompatible({ + await createOpenInDiscoverAction({} as DiscoverSetup, hasDiscoverAccess).isCompatible({ embeddable, } as unknown as ActionExecutionContext<{ embeddable: IEmbeddable }>) ).toBeTruthy(); @@ -41,7 +41,7 @@ describe('open in discover action', () => { // make sure no Discover access makes the action incompatible hasDiscoverAccess = false; expect( - await createOpenInDiscoverAction({} as DiscoverStart, hasDiscoverAccess).isCompatible({ + await createOpenInDiscoverAction({} as DiscoverSetup, hasDiscoverAccess).isCompatible({ embeddable, } as unknown as ActionExecutionContext<{ embeddable: IEmbeddable }>) ).toBeFalsy(); @@ -53,7 +53,7 @@ describe('open in discover action', () => { // test false embeddable.canViewUnderlyingData = jest.fn(() => Promise.resolve(false)); expect( - await createOpenInDiscoverAction({} as DiscoverStart, true).isCompatible({ + await createOpenInDiscoverAction({} as DiscoverSetup, true).isCompatible({ embeddable, } as unknown as ActionExecutionContext<{ embeddable: IEmbeddable }>) ).toBeFalsy(); @@ -63,7 +63,7 @@ describe('open in discover action', () => { // test true embeddable.canViewUnderlyingData = jest.fn(() => Promise.resolve(true)); expect( - await createOpenInDiscoverAction({} as DiscoverStart, true).isCompatible({ + await createOpenInDiscoverAction({} as DiscoverSetup, true).isCompatible({ embeddable, } as unknown as ActionExecutionContext<{ embeddable: IEmbeddable }>) ).toBeTruthy(); @@ -90,7 +90,7 @@ describe('open in discover action', () => { locator: { getRedirectUrl: jest.fn(() => discoverUrl), }, - } as unknown as DiscoverStart; + } as unknown as DiscoverSetup; globalThis.open = jest.fn(); diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts index bbf660a00d5c5..3002cf7d59298 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts @@ -7,22 +7,17 @@ import { i18n } from '@kbn/i18n'; import { createAction } from '@kbn/ui-actions-plugin/public'; -import type { DiscoverStart } from '@kbn/discover-plugin/public'; -import { Filter } from '@kbn/es-query'; -import { TimeRange } from '@kbn/data-plugin/public'; -import type { Embeddable } from '../embeddable'; -import { DOC_TYPE } from '../../common'; +import type { DiscoverSetup } from '@kbn/discover-plugin/public'; +import { IEmbeddable } from '@kbn/embeddable-plugin/public'; +import { execute, isCompatible } from './open_in_discover_helpers'; const ACTION_OPEN_IN_DISCOVER = 'ACTION_OPEN_IN_DISCOVER'; interface Context { - embeddable: Embeddable; - filters?: Filter[]; - timeRange?: TimeRange; - openInSameTab?: boolean; + embeddable: IEmbeddable; } -export const createOpenInDiscoverAction = (discover: DiscoverStart, hasDiscoverAccess: boolean) => +export const createOpenInDiscoverAction = (discover: DiscoverSetup, hasDiscoverAccess: boolean) => createAction({ type: ACTION_OPEN_IN_DISCOVER, id: ACTION_OPEN_IN_DISCOVER, @@ -33,19 +28,9 @@ export const createOpenInDiscoverAction = (discover: DiscoverStart, hasDiscoverA defaultMessage: 'Explore data in Discover', }), isCompatible: async (context: Context) => { - if (!hasDiscoverAccess) return false; - return ( - context.embeddable.type === DOC_TYPE && - (await (context.embeddable as Embeddable).canViewUnderlyingData()) - ); + return isCompatible({ hasDiscoverAccess, discover, embeddable: context.embeddable }); }, execute: async (context: Context) => { - const args = context.embeddable.getViewUnderlyingDataArgs()!; - const discoverUrl = discover.locator?.getRedirectUrl({ - ...args, - timeRange: context.timeRange || args.timeRange, - filters: [...(context.filters || []), ...args.filters], - }); - window.open(discoverUrl, !context.openInSameTab ? '_blank' : '_self'); + return execute({ ...context, discover, hasDiscoverAccess }); }, }); diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx new file mode 100644 index 0000000000000..d2bb6e1836500 --- /dev/null +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx @@ -0,0 +1,63 @@ +/* + * 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, { FormEvent } from 'react'; +import { IEmbeddable, EmbeddableInput } from '@kbn/embeddable-plugin/public'; +import { DiscoverSetup } from '@kbn/discover-plugin/public'; +import { execute, isCompatible } from './open_in_discover_helpers'; +import { mount } from 'enzyme'; +import { Filter } from '@kbn/es-query'; +import { + ActionFactoryContext, + CollectConfigProps, + OpenInDiscoverDrilldown, +} from './open_in_discover_drilldown'; + +jest.mock('./open_in_discover_helpers', () => ({ + isCompatible: jest.fn(() => true), + execute: jest.fn(), +})); + +describe('open in discover drilldown', () => { + let drilldown: OpenInDiscoverDrilldown; + beforeEach(() => { + drilldown = new OpenInDiscoverDrilldown({ + discover: {} as DiscoverSetup, + hasDiscoverAccess: () => true, + }); + }); + it('provides UI to edit config', () => { + const Component = (drilldown as unknown as { ReactCollectConfig: React.FC }) + .ReactCollectConfig; + const setConfig = jest.fn(); + const instance = mount( + + ); + instance.find('EuiSwitch').prop('onChange')!({} as unknown as FormEvent<{}>); + expect(setConfig).toHaveBeenCalledWith({ openInNewTab: true }); + }); + it('calls through to isCompatible helper', () => { + const filters: Filter[] = [{ meta: { disabled: false } }]; + drilldown.isCompatible( + { openInNewTab: true }, + { embeddable: { type: 'lens' } as IEmbeddable, filters } + ); + expect(isCompatible).toHaveBeenCalledWith(expect.objectContaining({ filters })); + }); + it('calls through to execute helper', () => { + const filters: Filter[] = [{ meta: { disabled: false } }]; + drilldown.execute( + { openInNewTab: true }, + { embeddable: { type: 'lens' } as IEmbeddable, filters } + ); + expect(execute).toHaveBeenCalledWith(expect.objectContaining({ filters, openInNewTab: true })); + }); +}); diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx index 82baca5ac4e7f..c83c9351cd653 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { IEmbeddable, EmbeddableInput, Embeddable } from '@kbn/embeddable-plugin/public'; +import { IEmbeddable, EmbeddableInput } from '@kbn/embeddable-plugin/public'; import { Query, Filter, @@ -21,9 +21,10 @@ import { UiActionsEnhancedBaseActionFactoryContext as BaseActionFactoryContext, } from '@kbn/ui-actions-enhanced-plugin/public'; import { EuiFormRow, EuiSwitch } from '@elastic/eui'; -import { DiscoverSetup, DiscoverStart } from '@kbn/discover-plugin/public'; +import { DiscoverSetup } from '@kbn/discover-plugin/public'; import { ApplyGlobalFilterActionContext } from '@kbn/unified-search-plugin/public'; -import { createOpenInDiscoverAction } from './open_in_discover_action'; +import { i18n } from '@kbn/i18n'; +import { execute, isCompatible } from './open_in_discover_helpers'; interface EmbeddableQueryInput extends EmbeddableInput { query?: Query; @@ -64,7 +65,10 @@ export class OpenInDiscoverDrilldown public readonly order = 8; - public readonly getDisplayName = () => 'Open in Discover'; + public readonly getDisplayName = () => + i18n.translate('xpack.lens.app.exploreDataInDiscoverDrilldown', { + defaultMessage: 'Open in Discover', + }); public readonly euiIcon = 'link'; @@ -82,7 +86,9 @@ export class OpenInDiscoverDrilldown onConfig({ ...config, openInNewTab: !config.openInNewTab })} data-test-subj="openInDiscoverDrilldownOpenInNewTab" @@ -102,26 +108,28 @@ export class OpenInDiscoverDrilldown }; public readonly isCompatible = async (config: Config, context: ActionContext) => { - return Boolean( - context.embeddable && - (await createOpenInDiscoverAction( - this.deps.discover, - this.deps.hasDiscoverAccess() - ).isCompatible({ embeddable: context.embeddable as Embeddable, trigger: { id: '' } })) - ); + return isCompatible({ + discover: this.deps.discover, + hasDiscoverAccess: this.deps.hasDiscoverAccess(), + ...context, + embeddable: context.embeddable as IEmbeddable, + ...config, + }); }; public readonly execute = async (config: Config, context: ActionContext) => { - const { restOfFilters: filtersFromEvent, timeRange: timeRangeFromEvent } = extractTimeRange( + const { restOfFilters: filters, timeRange: timeRange } = extractTimeRange( context.filters, context.timeFieldName ); - await createOpenInDiscoverAction(this.deps.discover, this.deps.hasDiscoverAccess()).execute({ - embeddable: context.embeddable as Embeddable, - filters: filtersFromEvent, - timeRange: timeRangeFromEvent, - openInSameTab: !config.openInNewTab, - trigger: { id: '' }, + await execute({ + discover: this.deps.discover, + hasDiscoverAccess: this.deps.hasDiscoverAccess(), + ...context, + embeddable: context.embeddable as IEmbeddable, + ...config, + filters, + timeRange, }); }; } 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 new file mode 100644 index 0000000000000..2e17d0f1601fd --- /dev/null +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_helpers.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { DiscoverSetup } from '@kbn/discover-plugin/public'; +import { Filter } from '@kbn/es-query'; +import { TimeRange } from '@kbn/data-plugin/public'; +import { IEmbeddable } from '@kbn/embeddable-plugin/public'; +import type { Embeddable } from '../embeddable'; +import { DOC_TYPE } from '../../common'; + +interface Context { + embeddable: IEmbeddable; + filters?: Filter[]; + timeRange?: TimeRange; + openInSameTab?: boolean; + hasDiscoverAccess: boolean; + discover: DiscoverSetup; +} + +function isLensEmbeddable(embeddable: IEmbeddable): embeddable is Embeddable { + return embeddable.type === DOC_TYPE; +} + +export async function isCompatible({ hasDiscoverAccess, embeddable }: Context) { + if (!hasDiscoverAccess) return false; + return isLensEmbeddable(embeddable) && (await embeddable.canViewUnderlyingData()); +} + +export function execute({ embeddable, discover, timeRange, filters, openInSameTab }: Context) { + if (!isLensEmbeddable(embeddable)) { + // shouldn't be executed because of the isCompatible check + throw new Error('Can only be executed in the context of Lens visualization'); + } + const args = embeddable.getViewUnderlyingDataArgs()!; + const discoverUrl = discover.locator?.getRedirectUrl({ + ...args, + timeRange: timeRange || args.timeRange, + filters: [...(filters || []), ...args.filters], + }); + window.open(discoverUrl, !openInSameTab ? '_blank' : '_self'); +} From c426c54c7c12e042f9585e4edd0f2ee8c5e116dc Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 2 May 2022 12:21:42 +0200 Subject: [PATCH 03/11] fix test --- .../lens/public/trigger_actions/open_in_discover_action.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.test.ts b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.test.ts index 7647fba3d47bf..3633bb87f6e1c 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.test.ts +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.test.ts @@ -83,6 +83,7 @@ describe('open in discover action', () => { const embeddable = { getViewUnderlyingDataArgs: jest.fn(() => viewUnderlyingDataArgs), + type: 'lens', }; const discoverUrl = 'https://discover-redirect-url'; From f6dd31d267ce34f70bb1edc3f703f5b86210b4af Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 2 May 2022 12:28:59 +0200 Subject: [PATCH 04/11] fix icon --- .../lens/public/trigger_actions/open_in_discover_drilldown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx index c83c9351cd653..93f7f1bdd24cf 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx @@ -70,7 +70,7 @@ export class OpenInDiscoverDrilldown defaultMessage: 'Open in Discover', }); - public readonly euiIcon = 'link'; + public readonly euiIcon = 'discoverApp'; supportedTriggers(): OpenInDiscoverTrigger[] { return [APPLY_FILTER_TRIGGER]; From c5707eec05f2e448d7fb285bfecb0f4f3515ddf1 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 2 May 2022 14:34:03 +0200 Subject: [PATCH 05/11] fix type --- .../lens/public/trigger_actions/open_in_discover_action.ts | 5 ++++- .../public/trigger_actions/open_in_discover_drilldown.tsx | 2 +- .../lens/public/trigger_actions/open_in_discover_helpers.ts | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts index 3002cf7d59298..2158fe2878e12 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts @@ -17,7 +17,10 @@ interface Context { embeddable: IEmbeddable; } -export const createOpenInDiscoverAction = (discover: DiscoverSetup, hasDiscoverAccess: boolean) => +export const createOpenInDiscoverAction = ( + discover: Pick, + hasDiscoverAccess: boolean +) => createAction({ type: ACTION_OPEN_IN_DISCOVER, id: ACTION_OPEN_IN_DISCOVER, diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx index 93f7f1bdd24cf..bd1588d7eff40 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx @@ -36,7 +36,7 @@ interface EmbeddableQueryInput extends EmbeddableInput { export type EmbeddableWithQueryInput = IEmbeddable; interface UrlDrilldownDeps { - discover: DiscoverSetup; + discover: Pick; hasDiscoverAccess: () => boolean; } 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 2e17d0f1601fd..726930068b392 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 @@ -18,7 +18,7 @@ interface Context { timeRange?: TimeRange; openInSameTab?: boolean; hasDiscoverAccess: boolean; - discover: DiscoverSetup; + discover: Pick; } function isLensEmbeddable(embeddable: IEmbeddable): embeddable is Embeddable { From d101a0d90c5cfc9c0c71aac92f0dc00bd5858c87 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 3 May 2022 11:28:18 +0200 Subject: [PATCH 06/11] fix open in new tab --- .../public/trigger_actions/open_in_discover_drilldown.test.tsx | 2 +- .../lens/public/trigger_actions/open_in_discover_drilldown.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx index d2bb6e1836500..887b334265f13 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx @@ -58,6 +58,6 @@ describe('open in discover drilldown', () => { { openInNewTab: true }, { embeddable: { type: 'lens' } as IEmbeddable, filters } ); - expect(execute).toHaveBeenCalledWith(expect.objectContaining({ filters, openInNewTab: true })); + expect(execute).toHaveBeenCalledWith(expect.objectContaining({ filters, openInSameTab: true })); }); }); diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx index bd1588d7eff40..68f41bb0c8a9e 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx @@ -127,7 +127,7 @@ export class OpenInDiscoverDrilldown hasDiscoverAccess: this.deps.hasDiscoverAccess(), ...context, embeddable: context.embeddable as IEmbeddable, - ...config, + openInSameTab: !config.openInNewTab, filters, timeRange, }); From 9c483643adca4b5adbcc7e1b9270211ce0832e28 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 3 May 2022 11:28:54 +0200 Subject: [PATCH 07/11] fix open in new tab --- .../lens/public/trigger_actions/open_in_discover_drilldown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx index 68f41bb0c8a9e..5bd3a2b25ac41 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx @@ -122,7 +122,7 @@ export class OpenInDiscoverDrilldown context.filters, context.timeFieldName ); - await execute({ + execute({ discover: this.deps.discover, hasDiscoverAccess: this.deps.hasDiscoverAccess(), ...context, From 7225c505a5fe70a98971a947db9427f18b0930df Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 3 May 2022 11:31:06 +0200 Subject: [PATCH 08/11] fix test --- .../trigger_actions/open_in_discover_drilldown.test.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx index 887b334265f13..bd1fc948eb937 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx @@ -58,6 +58,8 @@ describe('open in discover drilldown', () => { { openInNewTab: true }, { embeddable: { type: 'lens' } as IEmbeddable, filters } ); - expect(execute).toHaveBeenCalledWith(expect.objectContaining({ filters, openInSameTab: true })); + expect(execute).toHaveBeenCalledWith( + expect.objectContaining({ filters, openInSameTab: false }) + ); }); }); From 1854a21d9a1d5fd214c796954d938fa04681cb62 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 3 May 2022 12:37:06 +0200 Subject: [PATCH 09/11] make it possible to filter out drilldowns from list based on context --- .../trigger_actions/open_in_discover_drilldown.tsx | 10 +++++++--- .../public/trigger_actions/open_in_discover_helpers.ts | 2 +- .../public/drilldowns/drilldown_definition.ts | 6 ++++++ .../action_factory_picker/action_factory_picker.tsx | 5 ++++- .../public/dynamic_actions/action_factory.ts | 2 ++ .../dynamic_actions/action_factory_definition.ts | 6 ++++++ .../public/services/ui_actions_service_enhancements.ts | 2 ++ 7 files changed, 28 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx index 5bd3a2b25ac41..d957b9cafd4be 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx @@ -24,7 +24,7 @@ import { EuiFormRow, EuiSwitch } from '@elastic/eui'; import { DiscoverSetup } from '@kbn/discover-plugin/public'; import { ApplyGlobalFilterActionContext } from '@kbn/unified-search-plugin/public'; import { i18n } from '@kbn/i18n'; -import { execute, isCompatible } from './open_in_discover_helpers'; +import { execute, isCompatible, isLensEmbeddable } from './open_in_discover_helpers'; interface EmbeddableQueryInput extends EmbeddableInput { query?: Query; @@ -54,12 +54,12 @@ export interface ActionFactoryContext extends BaseActionFactoryContext { } export type CollectConfigProps = CollectConfigPropsBase; -const URL_DRILLDOWN = 'OPEN_IN_DISCOVER_DRILLDOWN'; +const OPEN_IN_DISCOVER_DRILLDOWN = 'OPEN_IN_DISCOVER_DRILLDOWN'; export class OpenInDiscoverDrilldown implements Drilldown { - public readonly id = URL_DRILLDOWN; + public readonly id = OPEN_IN_DISCOVER_DRILLDOWN; constructor(private readonly deps: UrlDrilldownDeps) {} @@ -117,6 +117,10 @@ export class OpenInDiscoverDrilldown }); }; + public readonly isConfigurable = (context: ActionFactoryContext) => { + return this.deps.hasDiscoverAccess() && isLensEmbeddable(context.embeddable as IEmbeddable); + }; + public readonly execute = async (config: Config, context: ActionContext) => { const { restOfFilters: filters, timeRange: timeRange } = extractTimeRange( context.filters, 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 726930068b392..d922d47083b6b 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 @@ -21,7 +21,7 @@ interface Context { discover: Pick; } -function isLensEmbeddable(embeddable: IEmbeddable): embeddable is Embeddable { +export function isLensEmbeddable(embeddable: IEmbeddable): embeddable is Embeddable { return embeddable.type === DOC_TYPE; } diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_definition.ts b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_definition.ts index 4d64f02d2c14b..4928b368a96b4 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_definition.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_definition.ts @@ -95,6 +95,12 @@ export interface DrilldownDefinition< */ isConfigValid: ActionFactoryDefinition['isConfigValid']; + /** + * Compatibility check during drilldown creation. + * Could be used to filter out a drilldown if it's not compatible with the current context. + */ + isConfigurable?(context: FactoryContext): boolean; + /** * Name of EUI icon to display when showing this drilldown to user. */ diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/containers/action_factory_picker/action_factory_picker.tsx b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/containers/action_factory_picker/action_factory_picker.tsx index db9951f235dfc..6600197f08046 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/containers/action_factory_picker/action_factory_picker.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/containers/action_factory_picker/action_factory_picker.tsx @@ -14,6 +14,9 @@ export const ActionFactoryPicker: React.FC = ({}) => { const drilldowns = useDrilldownManager(); const factory = drilldowns.useActionFactory(); const context = React.useMemo(() => drilldowns.getActionFactoryContext(), [drilldowns]); + const applicableFactories = drilldowns.deps.actionFactories.filter((actionFactory) => + actionFactory.isConfigurable(context) + ); if (!!factory) { return ; @@ -21,7 +24,7 @@ export const ActionFactoryPicker: React.FC = ({}) => { return ( { drilldowns.setActionFactory(actionFactory); diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.ts index 436c2e7bf8e8a..80d1e2e1c2df6 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.ts @@ -55,6 +55,7 @@ export class ActionFactory< public readonly createConfig: (context: FactoryContext) => Config; public readonly isConfigValid: (config: Config, context: FactoryContext) => boolean; public readonly migrations: MigrateFunctionsObject | GetMigrationFunctionObjectFn; + public readonly isConfigurable: (context: FactoryContext) => boolean; constructor( protected readonly def: ActionFactoryDefinition, @@ -77,6 +78,7 @@ export class ActionFactory< this.ReactCollectConfig = uiToReactComponent(this.CollectConfig); this.createConfig = this.def.createConfig; this.isConfigValid = this.def.isConfigValid; + this.isConfigurable = this.def.isConfigurable || (() => true); this.migrations = this.def.migrations || {}; } diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory_definition.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory_definition.ts index 029933e3b28d3..c8a0eb0c12b51 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory_definition.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory_definition.ts @@ -63,5 +63,11 @@ export interface ActionFactoryDefinition< serializedAction: Omit, 'factoryId'> ): ActionDefinition; + /** + * Compatibility check during drilldown creation. + * Could be used to filter out a drilldown if it's not compatible with the current context. + */ + isConfigurable?(context: FactoryContext): boolean; + supportedTriggers(): string[]; } diff --git a/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts b/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts index 63f90d5a55a1f..6721d86a94345 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts @@ -116,6 +116,7 @@ export class UiActionsServiceEnhancements licenseFeatureName, supportedTriggers, isCompatible, + isConfigurable, telemetry, extract, inject, @@ -136,6 +137,7 @@ export class UiActionsServiceEnhancements inject, getIconType: () => euiIcon, isCompatible: async () => true, + isConfigurable: (context) => !isConfigurable || isConfigurable(context), create: (serializedAction) => ({ id: '', type: factoryId, From 718d779237c807c42927ebcde0f57bf1090b2e5e Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 3 May 2022 17:52:25 +0200 Subject: [PATCH 10/11] review comments --- .../open_in_discover_action.test.ts | 14 +++++++------- .../trigger_actions/open_in_discover_action.ts | 4 ++-- .../trigger_actions/open_in_discover_helpers.ts | 6 +++++- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.test.ts b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.test.ts index 3633bb87f6e1c..eebdf04337f69 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.test.ts +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DiscoverSetup } from '@kbn/discover-plugin/public'; +import { DiscoverStart } from '@kbn/discover-plugin/public'; import type { IEmbeddable } from '@kbn/embeddable-plugin/public'; import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; import { DOC_TYPE } from '../../common'; @@ -17,7 +17,7 @@ describe('open in discover action', () => { it('is incompatible with non-lens embeddables', async () => { const embeddable = { type: 'NOT_LENS' } as IEmbeddable; - const isCompatible = await createOpenInDiscoverAction({} as DiscoverSetup, true).isCompatible( + const isCompatible = await createOpenInDiscoverAction({} as DiscoverStart, true).isCompatible( { embeddable, } as ActionExecutionContext<{ embeddable: IEmbeddable }> @@ -33,7 +33,7 @@ describe('open in discover action', () => { let hasDiscoverAccess = true; // make sure it would work if we had access to Discover expect( - await createOpenInDiscoverAction({} as DiscoverSetup, hasDiscoverAccess).isCompatible({ + await createOpenInDiscoverAction({} as DiscoverStart, hasDiscoverAccess).isCompatible({ embeddable, } as unknown as ActionExecutionContext<{ embeddable: IEmbeddable }>) ).toBeTruthy(); @@ -41,7 +41,7 @@ describe('open in discover action', () => { // make sure no Discover access makes the action incompatible hasDiscoverAccess = false; expect( - await createOpenInDiscoverAction({} as DiscoverSetup, hasDiscoverAccess).isCompatible({ + await createOpenInDiscoverAction({} as DiscoverStart, hasDiscoverAccess).isCompatible({ embeddable, } as unknown as ActionExecutionContext<{ embeddable: IEmbeddable }>) ).toBeFalsy(); @@ -53,7 +53,7 @@ describe('open in discover action', () => { // test false embeddable.canViewUnderlyingData = jest.fn(() => Promise.resolve(false)); expect( - await createOpenInDiscoverAction({} as DiscoverSetup, true).isCompatible({ + await createOpenInDiscoverAction({} as DiscoverStart, true).isCompatible({ embeddable, } as unknown as ActionExecutionContext<{ embeddable: IEmbeddable }>) ).toBeFalsy(); @@ -63,7 +63,7 @@ describe('open in discover action', () => { // test true embeddable.canViewUnderlyingData = jest.fn(() => Promise.resolve(true)); expect( - await createOpenInDiscoverAction({} as DiscoverSetup, true).isCompatible({ + await createOpenInDiscoverAction({} as DiscoverStart, true).isCompatible({ embeddable, } as unknown as ActionExecutionContext<{ embeddable: IEmbeddable }>) ).toBeTruthy(); @@ -91,7 +91,7 @@ describe('open in discover action', () => { locator: { getRedirectUrl: jest.fn(() => discoverUrl), }, - } as unknown as DiscoverSetup; + } as unknown as DiscoverStart; globalThis.open = jest.fn(); diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts index 2158fe2878e12..54a24aac269b5 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { createAction } from '@kbn/ui-actions-plugin/public'; -import type { DiscoverSetup } from '@kbn/discover-plugin/public'; +import type { DiscoverStart } from '@kbn/discover-plugin/public'; import { IEmbeddable } from '@kbn/embeddable-plugin/public'; import { execute, isCompatible } from './open_in_discover_helpers'; @@ -18,7 +18,7 @@ interface Context { } export const createOpenInDiscoverAction = ( - discover: Pick, + discover: Pick, hasDiscoverAccess: boolean ) => createAction({ 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 d922d47083b6b..87f0931f1a3db 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 @@ -35,7 +35,11 @@ export function execute({ embeddable, discover, timeRange, filters, openInSameTa // shouldn't be executed because of the isCompatible check throw new Error('Can only be executed in the context of Lens visualization'); } - const args = embeddable.getViewUnderlyingDataArgs()!; + const args = embeddable.getViewUnderlyingDataArgs(); + if (!args) { + // shouldn't be executed because of the isCompatible check + throw new Error('Underlying data is not ready'); + } const discoverUrl = discover.locator?.getRedirectUrl({ ...args, timeRange: timeRange || args.timeRange, From be542fdddf2e198fba2dc68d72621505c887a4dc Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 5 May 2022 18:01:23 +0200 Subject: [PATCH 11/11] remove isConfigurable from the actionfactory --- .../action_factory_picker.tsx | 11 +++++--- .../state/drilldown_manager_state.ts | 26 ++++++++++++++++++- .../public/dynamic_actions/action_factory.ts | 2 -- .../action_factory_definition.ts | 6 ----- .../ui_actions_service_enhancements.ts | 3 +-- 5 files changed, 33 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/containers/action_factory_picker/action_factory_picker.tsx b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/containers/action_factory_picker/action_factory_picker.tsx index 6600197f08046..f52ac6e161577 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/containers/action_factory_picker/action_factory_picker.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/containers/action_factory_picker/action_factory_picker.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { EuiLoadingSpinner } from '@elastic/eui'; import { ActionFactoryPicker as ActionFactoryPickerUi } from '../../../../components/action_factory_picker'; import { useDrilldownManager } from '../context'; import { ActionFactoryView } from '../action_factory_view'; @@ -14,17 +15,19 @@ export const ActionFactoryPicker: React.FC = ({}) => { const drilldowns = useDrilldownManager(); const factory = drilldowns.useActionFactory(); const context = React.useMemo(() => drilldowns.getActionFactoryContext(), [drilldowns]); - const applicableFactories = drilldowns.deps.actionFactories.filter((actionFactory) => - actionFactory.isConfigurable(context) - ); + const compatibleFactories = drilldowns.useCompatibleActionFactories(context); if (!!factory) { return ; } + if (!compatibleFactories) { + return ; + } + return ( { drilldowns.setActionFactory(actionFactory); diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/state/drilldown_manager_state.ts b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/state/drilldown_manager_state.ts index 15997355a2ae2..231057a50ee1f 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/state/drilldown_manager_state.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/state/drilldown_manager_state.ts @@ -6,9 +6,10 @@ */ import useObservable from 'react-use/lib/useObservable'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import type { SerializableRecord } from '@kbn/utility-types'; +import { useMemo } from 'react'; import { PublicDrilldownManagerProps, DrilldownManagerDependencies, @@ -255,6 +256,24 @@ export class DrilldownManagerState { return context; } + public getCompatibleActionFactories( + context: BaseActionFactoryContext + ): Observable { + const compatibleActionFactories$ = new BehaviorSubject(undefined); + Promise.allSettled( + this.deps.actionFactories.map((factory) => factory.isCompatible(context)) + ).then((factoryCompatibility) => { + compatibleActionFactories$.next( + this.deps.actionFactories.filter((_factory, i) => { + const result = factoryCompatibility[i]; + // treat failed isCompatible checks as non-compatible + return result.status === 'fulfilled' && result.value; + }) + ); + }); + return compatibleActionFactories$.asObservable(); + } + /** * Get state object of the drilldown which is currently being created. */ @@ -478,4 +497,9 @@ export class DrilldownManagerState { public readonly useActionFactory = () => useObservable(this.actionFactory$, this.actionFactory$.getValue()); public readonly useEvents = () => useObservable(this.events$, this.events$.getValue()); + public readonly useCompatibleActionFactories = (context: BaseActionFactoryContext) => + useObservable( + useMemo(() => this.getCompatibleActionFactories(context), [context]), + undefined + ); } diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.ts index 80d1e2e1c2df6..436c2e7bf8e8a 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.ts @@ -55,7 +55,6 @@ export class ActionFactory< public readonly createConfig: (context: FactoryContext) => Config; public readonly isConfigValid: (config: Config, context: FactoryContext) => boolean; public readonly migrations: MigrateFunctionsObject | GetMigrationFunctionObjectFn; - public readonly isConfigurable: (context: FactoryContext) => boolean; constructor( protected readonly def: ActionFactoryDefinition, @@ -78,7 +77,6 @@ export class ActionFactory< this.ReactCollectConfig = uiToReactComponent(this.CollectConfig); this.createConfig = this.def.createConfig; this.isConfigValid = this.def.isConfigValid; - this.isConfigurable = this.def.isConfigurable || (() => true); this.migrations = this.def.migrations || {}; } diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory_definition.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory_definition.ts index c8a0eb0c12b51..029933e3b28d3 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory_definition.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory_definition.ts @@ -63,11 +63,5 @@ export interface ActionFactoryDefinition< serializedAction: Omit, 'factoryId'> ): ActionDefinition; - /** - * Compatibility check during drilldown creation. - * Could be used to filter out a drilldown if it's not compatible with the current context. - */ - isConfigurable?(context: FactoryContext): boolean; - supportedTriggers(): string[]; } diff --git a/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts b/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts index 6721d86a94345..fb2dc3ea5bd03 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts @@ -136,8 +136,7 @@ export class UiActionsServiceEnhancements extract, inject, getIconType: () => euiIcon, - isCompatible: async () => true, - isConfigurable: (context) => !isConfigurable || isConfigurable(context), + isCompatible: async (context) => !isConfigurable || isConfigurable(context), create: (serializedAction) => ({ id: '', type: factoryId,