From 4e180aa19301de9e4d13e3f2ad905c3c5b9568a4 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 18 Jun 2020 11:11:45 +0200 Subject: [PATCH 01/21] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20rename=20folde?= =?UTF-8?q?r=20to=20"explore=5Fdata"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../explore_data_context_menu_action.test.ts | 0 .../explore_data_context_menu_action.ts | 0 .../public/actions/{view_in_discover => explore_data}/index.ts | 0 x-pack/plugins/discover_enhanced/public/actions/index.ts | 2 +- 4 files changed, 1 insertion(+), 1 deletion(-) rename x-pack/plugins/discover_enhanced/public/actions/{view_in_discover => explore_data}/explore_data_context_menu_action.test.ts (100%) rename x-pack/plugins/discover_enhanced/public/actions/{view_in_discover => explore_data}/explore_data_context_menu_action.ts (100%) rename x-pack/plugins/discover_enhanced/public/actions/{view_in_discover => explore_data}/index.ts (100%) diff --git a/x-pack/plugins/discover_enhanced/public/actions/view_in_discover/explore_data_context_menu_action.test.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts similarity index 100% rename from x-pack/plugins/discover_enhanced/public/actions/view_in_discover/explore_data_context_menu_action.test.ts rename to x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts diff --git a/x-pack/plugins/discover_enhanced/public/actions/view_in_discover/explore_data_context_menu_action.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts similarity index 100% rename from x-pack/plugins/discover_enhanced/public/actions/view_in_discover/explore_data_context_menu_action.ts rename to x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts diff --git a/x-pack/plugins/discover_enhanced/public/actions/view_in_discover/index.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/index.ts similarity index 100% rename from x-pack/plugins/discover_enhanced/public/actions/view_in_discover/index.ts rename to x-pack/plugins/discover_enhanced/public/actions/explore_data/index.ts diff --git a/x-pack/plugins/discover_enhanced/public/actions/index.ts b/x-pack/plugins/discover_enhanced/public/actions/index.ts index cbb955fa46340..209ae6bee09b5 100644 --- a/x-pack/plugins/discover_enhanced/public/actions/index.ts +++ b/x-pack/plugins/discover_enhanced/public/actions/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './view_in_discover'; +export * from './explore_data'; From 428bbab202974e8d6557aa9aa7d2a3abcebcd04f Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 18 Jun 2020 11:12:58 +0200 Subject: [PATCH 02/21] =?UTF-8?q?style:=20=F0=9F=92=84=20check=20for=20"sh?= =?UTF-8?q?are"=20plugin=20in=20more=20semantic=20way?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "explore data" actions use Discover URL generator, which is registered in "share" plugin, which is optional plugin, so we check for its existance, because otherwise URL generator is not available. --- x-pack/plugins/discover_enhanced/public/plugin.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/discover_enhanced/public/plugin.ts b/x-pack/plugins/discover_enhanced/public/plugin.ts index f55c5dab3449b..e68c16042cf32 100644 --- a/x-pack/plugins/discover_enhanced/public/plugin.ts +++ b/x-pack/plugins/discover_enhanced/public/plugin.ts @@ -48,8 +48,9 @@ export class DiscoverEnhancedPlugin { uiActions, share }: DiscoverEnhancedSetupDependencies ) { const start = createStartServicesGetter(core.getStartServices); + const isSharePluginInstalled = !!share; - if (!!share) { + if (isSharePluginInstalled) { const exploreDataAction = new ExploreDataContextMenuAction({ start }); uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, exploreDataAction); } From 11c6b644267e848e03c0f795c137cbb15c706737 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 18 Jun 2020 11:17:03 +0200 Subject: [PATCH 03/21] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20move=20KibanaU?= =?UTF-8?q?RL=20to=20a=20separate=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../explore_data_context_menu_action.ts | 29 +---------------- .../public/actions/explore_data/kibana_url.ts | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 28 deletions(-) create mode 100644 x-pack/plugins/discover_enhanced/public/actions/explore_data/kibana_url.ts diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts index d66ca129934a8..7e0576e4fca90 100644 --- a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -/* eslint-disable max-classes-per-file */ - import { i18n } from '@kbn/i18n'; import { Action } from '../../../../../../src/plugins/ui_actions/public'; import { DiscoverStart } from '../../../../../../src/plugins/discover/public'; @@ -20,32 +18,7 @@ import { VisualizeEmbeddableContract, VISUALIZE_EMBEDDABLE_TYPE, } from '../../../../../../src/plugins/visualizations/public'; - -// TODO: Replace this logic with KibanaURL once it is available. -// https://github.com/elastic/kibana/issues/64497 -class KibanaURL { - public readonly path: string; - public readonly appName: string; - public readonly appPath: string; - - constructor(path: string) { - const match = path.match(/^.*\/app\/([^\/#]+)(.+)$/); - - if (!match) { - throw new Error('Unexpected Discover URL path.'); - } - - const [, appName, appPath] = match; - - if (!appName || !appPath) { - throw new Error('Could not parse Discover URL path.'); - } - - this.path = path; - this.appName = appName; - this.appPath = appPath; - } -} +import { KibanaURL } from './kibana_url'; export const ACTION_EXPLORE_DATA = 'ACTION_EXPLORE_DATA'; diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/kibana_url.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/kibana_url.ts new file mode 100644 index 0000000000000..3c25fc2b3c3d1 --- /dev/null +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/kibana_url.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// TODO: Replace this logic with KibanaURL once it is available. +// https://github.com/elastic/kibana/issues/64497 +export class KibanaURL { + public readonly path: string; + public readonly appName: string; + public readonly appPath: string; + + constructor(path: string) { + const match = path.match(/^.*\/app\/([^\/#]+)(.+)$/); + + if (!match) { + throw new Error('Unexpected Discover URL path.'); + } + + const [, appName, appPath] = match; + + if (!appName || !appPath) { + throw new Error('Could not parse Discover URL path.'); + } + + this.path = path; + this.appName = appName; + this.appPath = appPath; + } +} From 795bfbb54c59c9b5c1d9125e8745fd5a10eb1bfc Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 18 Jun 2020 12:30:52 +0200 Subject: [PATCH 04/21] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20"Explore=20u?= =?UTF-8?q?nderlying=20data"=20in-chart=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../abstract_explore_data_action.ts | 73 ++++++++++++ .../explore_data/explore_data_chart_action.ts | 55 +++++++++ .../explore_data_context_menu_action.test.ts | 11 +- .../explore_data_context_menu_action.ts | 105 ++---------------- .../public/actions/explore_data/index.ts | 1 + .../public/actions/explore_data/shared.ts | 36 ++++++ .../discover_enhanced/public/plugin.ts | 24 +++- 7 files changed, 204 insertions(+), 101 deletions(-) create mode 100644 x-pack/plugins/discover_enhanced/public/actions/explore_data/abstract_explore_data_action.ts create mode 100644 x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.ts create mode 100644 x-pack/plugins/discover_enhanced/public/actions/explore_data/shared.ts diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/abstract_explore_data_action.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/abstract_explore_data_action.ts new file mode 100644 index 0000000000000..3c722d214b4ac --- /dev/null +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/abstract_explore_data_action.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { DiscoverStart } from '../../../../../../src/plugins/discover/public'; +import { ViewMode, IEmbeddable } from '../../../../../../src/plugins/embeddable/public'; +import { StartServicesGetter } from '../../../../../../src/plugins/kibana_utils/public'; +import { CoreStart } from '../../../../../../src/core/public'; +import { VisualizeEmbeddableContract } from '../../../../../../src/plugins/visualizations/public'; +import { KibanaURL } from './kibana_url'; +import * as shared from './shared'; + +export const ACTION_EXPLORE_DATA = 'ACTION_EXPLORE_DATA'; + +interface PluginDeps { + discover: Pick; +} + +interface CoreDeps { + application: Pick; +} + +interface Params { + start: StartServicesGetter; +} + +export abstract class AbstractExploreDataAction { + public readonly getIconType = (context: Context): string => 'discoverApp'; + + public readonly getDisplayName = (context: Context): string => + i18n.translate('xpack.discover.FlyoutCreateDrilldownAction.displayName', { + defaultMessage: 'Explore underlying data', + }); + + constructor(protected readonly params: Params) {} + + protected abstract async getUrl(embeddable: VisualizeEmbeddableContract): Promise; + + public async isCompatible({ embeddable }: Context): Promise { + if (!embeddable) return false; + if (!this.params.start().plugins.discover.urlGenerator) return false; + if (!shared.isVisualizeEmbeddable(embeddable)) return false; + if (!shared.getIndexPattern(embeddable)) return false; + if (embeddable.getInput().viewMode !== ViewMode.VIEW) return false; + return true; + } + + public async execute({ embeddable }: Context): Promise { + if (!shared.isVisualizeEmbeddable(embeddable)) return; + + const { core } = this.params.start(); + const { appName, appPath } = await this.getUrl(embeddable); + + await core.application.navigateToApp(appName, { + path: appPath, + }); + } + + public async getHref(context: Context): Promise { + const { embeddable } = context; + + if (!shared.isVisualizeEmbeddable(embeddable)) { + throw new Error(`Embeddable not supported for "${this.getDisplayName(context)}" action.`); + } + + const { path } = await this.getUrl(embeddable); + + return path; + } +} diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.ts new file mode 100644 index 0000000000000..37c2dfadd078f --- /dev/null +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Action } from '../../../../../../src/plugins/ui_actions/public'; +import { + ValueClickTriggerContext, + RangeSelectTriggerContext, +} from '../../../../../../src/plugins/embeddable/public'; +import { VisualizeEmbeddableContract } from '../../../../../../src/plugins/visualizations/public'; +import { KibanaURL } from './kibana_url'; +import * as shared from './shared'; +import { AbstractExploreDataAction } from './abstract_explore_data_action'; + +export type ExploreDataChartActionContext = ValueClickTriggerContext | RangeSelectTriggerContext; + +export const ACTION_EXPLORE_DATA_CHART = 'ACTION_EXPLORE_DATA_CHART'; + +/** + * This is "Explore underlying data" action which appears in popup context + * menu when user clicks a value in visualization or brushes a time range. + */ +export class ExploreDataChartAction extends AbstractExploreDataAction + implements Action { + public readonly id = ACTION_EXPLORE_DATA_CHART; + + public readonly type = ACTION_EXPLORE_DATA_CHART; + + public readonly order = 200; + + protected readonly getUrl = async ( + embeddable: VisualizeEmbeddableContract + ): Promise => { + const { plugins } = this.params.start(); + const { urlGenerator } = plugins.discover; + + if (!urlGenerator) { + throw new Error('Discover URL generator not available.'); + } + + const { timeRange, query, filters } = embeddable.getInput(); + const indexPatternId = shared.getIndexPattern(embeddable); + + const path = await urlGenerator.createUrl({ + indexPatternId, + filters, + query, + timeRange, + }); + + return new KibanaURL(path); + }; +} diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts index a7167d2e2e691..8f84ca1133eaa 100644 --- a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts @@ -83,19 +83,20 @@ const setup = () => { describe('"Explore underlying data" panel action', () => { test('action has Discover icon', () => { - const { action } = setup(); - expect(action.getIconType()).toBe('discoverApp'); + const { action, context } = setup(); + expect(action.getIconType(context)).toBe('discoverApp'); }); test('title is "Explore underlying data"', () => { - const { action } = setup(); - expect(action.getDisplayName()).toBe('Explore underlying data'); + const { action, context } = setup(); + expect(action.getDisplayName(context)).toBe('Explore underlying data'); }); test('translates title', () => { expect(i18nTranslateSpy).toHaveBeenCalledTimes(0); - setup().action.getDisplayName(); + const { action, context } = setup(); + action.getDisplayName(context); expect(i18nTranslateSpy).toHaveBeenCalledTimes(1); expect(i18nTranslateSpy.mock.calls[0][0]).toBe( diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts index 7e0576e4fca90..61555c149127b 100644 --- a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts @@ -4,96 +4,28 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; import { Action } from '../../../../../../src/plugins/ui_actions/public'; -import { DiscoverStart } from '../../../../../../src/plugins/discover/public'; -import { - EmbeddableContext, - IEmbeddable, - ViewMode, -} from '../../../../../../src/plugins/embeddable/public'; -import { StartServicesGetter } from '../../../../../../src/plugins/kibana_utils/public'; -import { CoreStart } from '../../../../../../src/core/public'; -import { - VisualizeEmbeddableContract, - VISUALIZE_EMBEDDABLE_TYPE, -} from '../../../../../../src/plugins/visualizations/public'; +import { EmbeddableContext } from '../../../../../../src/plugins/embeddable/public'; +import { VisualizeEmbeddableContract } from '../../../../../../src/plugins/visualizations/public'; import { KibanaURL } from './kibana_url'; +import * as shared from './shared'; +import { AbstractExploreDataAction } from './abstract_explore_data_action'; export const ACTION_EXPLORE_DATA = 'ACTION_EXPLORE_DATA'; -const isOutputWithIndexPatterns = ( - output: unknown -): output is { indexPatterns: Array<{ id: string }> } => { - if (!output || typeof output !== 'object') return false; - return Array.isArray((output as any).indexPatterns); -}; - -const isVisualizeEmbeddable = ( - embeddable: IEmbeddable -): embeddable is VisualizeEmbeddableContract => embeddable?.type === VISUALIZE_EMBEDDABLE_TYPE; - -export interface PluginDeps { - discover: Pick; -} - -export interface CoreDeps { - application: Pick; -} - -export interface Params { - start: StartServicesGetter; -} - -export class ExploreDataContextMenuAction implements Action { +/** + * This is "Explore underlying data" action which appears in the context + * menu of a dashboard panel. + */ +export class ExploreDataContextMenuAction extends AbstractExploreDataAction + implements Action { public readonly id = ACTION_EXPLORE_DATA; public readonly type = ACTION_EXPLORE_DATA; public readonly order = 200; - constructor(private readonly params: Params) {} - - public getDisplayName() { - return i18n.translate('xpack.discover.FlyoutCreateDrilldownAction.displayName', { - defaultMessage: 'Explore underlying data', - }); - } - - public getIconType() { - return 'discoverApp'; - } - - public async isCompatible({ embeddable }: EmbeddableContext) { - if (!this.params.start().plugins.discover.urlGenerator) return false; - if (!isVisualizeEmbeddable(embeddable)) return false; - if (!this.getIndexPattern(embeddable)) return false; - if (embeddable.getInput().viewMode !== ViewMode.VIEW) return false; - return true; - } - - public async execute({ embeddable }: EmbeddableContext) { - if (!isVisualizeEmbeddable(embeddable)) return; - - const { core } = this.params.start(); - const { appName, appPath } = await this.getUrl(embeddable); - - await core.application.navigateToApp(appName, { - path: appPath, - }); - } - - public async getHref({ embeddable }: EmbeddableContext): Promise { - if (!isVisualizeEmbeddable(embeddable)) { - throw new Error(`Embeddable not supported for "${this.getDisplayName()}" action.`); - } - - const { path } = await this.getUrl(embeddable); - - return path; - } - - private async getUrl(embeddable: VisualizeEmbeddableContract): Promise { + protected async getUrl(embeddable: VisualizeEmbeddableContract): Promise { const { plugins } = this.params.start(); const { urlGenerator } = plugins.discover; @@ -102,7 +34,7 @@ export class ExploreDataContextMenuAction implements Action { } const { timeRange, query, filters } = embeddable.getInput(); - const indexPatternId = this.getIndexPattern(embeddable); + const indexPatternId = shared.getIndexPattern(embeddable); const path = await urlGenerator.createUrl({ indexPatternId, @@ -113,17 +45,4 @@ export class ExploreDataContextMenuAction implements Action { return new KibanaURL(path); } - - /** - * @returns Returns empty string if no index pattern ID found. - */ - private getIndexPattern(embeddable: VisualizeEmbeddableContract): string { - const output = embeddable!.getOutput(); - - if (isOutputWithIndexPatterns(output) && output.indexPatterns.length > 0) { - return output.indexPatterns[0].id; - } - - return ''; - } } diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/index.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/index.ts index 8788621365385..e6d7d4b59149e 100644 --- a/x-pack/plugins/discover_enhanced/public/actions/explore_data/index.ts +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/index.ts @@ -5,3 +5,4 @@ */ export * from './explore_data_context_menu_action'; +export * from './explore_data_chart_action'; diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/shared.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/shared.ts new file mode 100644 index 0000000000000..80ec2b1175786 --- /dev/null +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/shared.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public'; +import { + VISUALIZE_EMBEDDABLE_TYPE, + VisualizeEmbeddableContract, +} from '../../../../../../src/plugins/visualizations/public'; + +export const isOutputWithIndexPatterns = ( + output: unknown +): output is { indexPatterns: Array<{ id: string }> } => { + if (!output || typeof output !== 'object') return false; + return Array.isArray((output as any).indexPatterns); +}; + +export const isVisualizeEmbeddable = ( + embeddable?: IEmbeddable +): embeddable is VisualizeEmbeddableContract => + embeddable && embeddable?.type === VISUALIZE_EMBEDDABLE_TYPE ? true : false; + +/** + * @returns Returns empty string if no index pattern ID found. + */ +export const getIndexPattern = (embeddable: VisualizeEmbeddableContract): string => { + const output = embeddable!.getOutput(); + + if (isOutputWithIndexPatterns(output) && output.indexPatterns.length > 0) { + return output.indexPatterns[0].id; + } + + return ''; +}; diff --git a/x-pack/plugins/discover_enhanced/public/plugin.ts b/x-pack/plugins/discover_enhanced/public/plugin.ts index e68c16042cf32..ea3c1222eb369 100644 --- a/x-pack/plugins/discover_enhanced/public/plugin.ts +++ b/x-pack/plugins/discover_enhanced/public/plugin.ts @@ -6,7 +6,12 @@ import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; import { PluginInitializerContext } from 'kibana/public'; -import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; +import { + UiActionsSetup, + UiActionsStart, + SELECT_RANGE_TRIGGER, + VALUE_CLICK_TRIGGER, +} from '../../../../src/plugins/ui_actions/public'; import { createStartServicesGetter } from '../../../../src/plugins/kibana_utils/public'; import { DiscoverSetup, DiscoverStart } from '../../../../src/plugins/discover/public'; import { SharePluginSetup, SharePluginStart } from '../../../../src/plugins/share/public'; @@ -16,11 +21,18 @@ import { EmbeddableContext, CONTEXT_MENU_TRIGGER, } from '../../../../src/plugins/embeddable/public'; -import { ExploreDataContextMenuAction, ACTION_EXPLORE_DATA } from './actions'; +import { + ExploreDataContextMenuAction, + ExploreDataChartAction, + ACTION_EXPLORE_DATA, + ACTION_EXPLORE_DATA_CHART, + ExploreDataChartActionContext, +} from './actions'; declare module '../../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { [ACTION_EXPLORE_DATA]: EmbeddableContext; + [ACTION_EXPLORE_DATA_CHART]: ExploreDataChartActionContext; } } @@ -51,8 +63,14 @@ export class DiscoverEnhancedPlugin const isSharePluginInstalled = !!share; if (isSharePluginInstalled) { - const exploreDataAction = new ExploreDataContextMenuAction({ start }); + const params = { start }; + + const exploreDataAction = new ExploreDataContextMenuAction(params); uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, exploreDataAction); + + const exploreDataChartAction = new ExploreDataChartAction(params); + uiActions.addTriggerAction(SELECT_RANGE_TRIGGER, exploreDataChartAction); + uiActions.addTriggerAction(VALUE_CLICK_TRIGGER, exploreDataChartAction); } } From 64ce4c4564d2cea36a44237f1aba97285c15876c Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 18 Jun 2020 12:33:55 +0200 Subject: [PATCH 05/21] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20imports=20aft?= =?UTF-8?q?er=20refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../actions/explore_data/abstract_explore_data_action.ts | 6 +++--- .../explore_data/explore_data_context_menu_action.test.ts | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/abstract_explore_data_action.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/abstract_explore_data_action.ts index 3c722d214b4ac..60f20cf9ce916 100644 --- a/x-pack/plugins/discover_enhanced/public/actions/explore_data/abstract_explore_data_action.ts +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/abstract_explore_data_action.ts @@ -15,15 +15,15 @@ import * as shared from './shared'; export const ACTION_EXPLORE_DATA = 'ACTION_EXPLORE_DATA'; -interface PluginDeps { +export interface PluginDeps { discover: Pick; } -interface CoreDeps { +export interface CoreDeps { application: Pick; } -interface Params { +export interface Params { start: StartServicesGetter; } diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts index 8f84ca1133eaa..1d606fce64746 100644 --- a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts @@ -7,9 +7,8 @@ import { ExploreDataContextMenuAction, ACTION_EXPLORE_DATA, - Params, - PluginDeps, } from './explore_data_context_menu_action'; +import { Params, PluginDeps } from './abstract_explore_data_action'; import { coreMock } from '../../../../../../src/core/public/mocks'; import { UrlGeneratorContract } from '../../../../../../src/plugins/share/public'; import { i18n } from '@kbn/i18n'; From 55992da7b97b02d875cc72287211ea4a9dc8b75d Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 18 Jun 2020 13:47:56 +0200 Subject: [PATCH 06/21] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20start.filter?= =?UTF-8?q?sFromContext=20to=20embeddable=20plugin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/embeddable/kibana.json | 1 + src/plugins/embeddable/public/index.ts | 1 + .../public/lib/triggers/triggers.ts | 14 +++++++---- src/plugins/embeddable/public/plugin.tsx | 24 ++++++++++++++++++- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/plugins/embeddable/kibana.json b/src/plugins/embeddable/kibana.json index 06b0e88da334f..332237d19e218 100644 --- a/src/plugins/embeddable/kibana.json +++ b/src/plugins/embeddable/kibana.json @@ -4,6 +4,7 @@ "server": false, "ui": true, "requiredPlugins": [ + "data", "inspector", "uiActions" ], diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index d595197373a21..24e75f2614100 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -29,6 +29,7 @@ export { ACTION_EDIT_PANEL, Adapters, AddPanelAction, + ChartActionContext, Container, ContainerInput, ContainerOutput, diff --git a/src/plugins/embeddable/public/lib/triggers/triggers.ts b/src/plugins/embeddable/public/lib/triggers/triggers.ts index 2b447c89e2850..5bb96a708b7ac 100644 --- a/src/plugins/embeddable/public/lib/triggers/triggers.ts +++ b/src/plugins/embeddable/public/lib/triggers/triggers.ts @@ -39,10 +39,6 @@ export interface ValueClickTriggerContext { }; } -export const isValueClickTriggerContext = ( - context: ValueClickTriggerContext | RangeSelectTriggerContext -): context is ValueClickTriggerContext => context.data && 'data' in context.data; - export interface RangeSelectTriggerContext { embeddable?: T; data: { @@ -53,8 +49,16 @@ export interface RangeSelectTriggerContext }; } +export type ChartActionContext = + | ValueClickTriggerContext + | RangeSelectTriggerContext; + +export const isValueClickTriggerContext = ( + context: ChartActionContext +): context is ValueClickTriggerContext => context.data && 'data' in context.data; + export const isRangeSelectTriggerContext = ( - context: ValueClickTriggerContext | RangeSelectTriggerContext + context: ChartActionContext ): context is RangeSelectTriggerContext => context.data && 'range' in context.data; export const CONTEXT_MENU_TRIGGER = 'CONTEXT_MENU_TRIGGER'; diff --git a/src/plugins/embeddable/public/plugin.tsx b/src/plugins/embeddable/public/plugin.tsx index 269b1ffed829b..958eacf3c844e 100644 --- a/src/plugins/embeddable/public/plugin.tsx +++ b/src/plugins/embeddable/public/plugin.tsx @@ -17,6 +17,7 @@ * under the License. */ import React from 'react'; +import { DataPublicPluginSetup, DataPublicPluginStart, Filter } from '../../data/public'; import { getSavedObjectFinder } from '../../saved_objects/public'; import { UiActionsSetup, UiActionsStart } from '../../ui_actions/public'; import { Start as InspectorStart } from '../../inspector/public'; @@ -30,14 +31,19 @@ import { defaultEmbeddableFactoryProvider, IEmbeddable, EmbeddablePanel, + ChartActionContext, + isRangeSelectTriggerContext, + isValueClickTriggerContext, } from './lib'; import { EmbeddableFactoryDefinition } from './lib/embeddables/embeddable_factory_definition'; export interface EmbeddableSetupDependencies { + data: DataPublicPluginSetup; uiActions: UiActionsSetup; } export interface EmbeddableStartDependencies { + data: DataPublicPluginStart; uiActions: UiActionsStart; inspector: InspectorStart; } @@ -64,6 +70,7 @@ export interface EmbeddableStart { ) => EmbeddableFactory | undefined; getEmbeddableFactories: () => IterableIterator; EmbeddablePanel: React.FC<{ embeddable: IEmbeddable; hideHeader?: boolean }>; + filtersFromContext: (context: ChartActionContext) => Promise; } export class EmbeddablePublicPlugin implements Plugin { @@ -95,7 +102,7 @@ export class EmbeddablePublicPlugin implements Plugin { this.embeddableFactories.set( @@ -130,6 +137,21 @@ export class EmbeddablePublicPlugin implements Plugin ), + + filtersFromContext: async (context) => { + try { + if (isRangeSelectTriggerContext(context)) + return await data.actions.createFiltersFromRangeSelectAction(context.data); + if (isValueClickTriggerContext(context)) + return await data.actions.createFiltersFromValueClickAction(context.data); + // eslint-disable-next-line no-console + console.warn("Can't extract filters from action.", context); + } catch (error) { + // eslint-disable-next-line no-console + console.warn('Error extracting filters from action. Returning empty filter list.', error); + } + return []; + }, }; } From a730c55fdaff9e13529949171686d76525252f81 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 18 Jun 2020 15:09:19 +0200 Subject: [PATCH 07/21] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20type=20check?= =?UTF-8?q?ers=20to=20data=20plugin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/es_query/filters/meta_filter.ts | 10 +++++++ src/plugins/data/common/index.ts | 3 ++- src/plugins/data/common/query/index.ts | 1 + src/plugins/data/common/query/is_query.ts | 27 +++++++++++++++++++ src/plugins/data/common/timefilter/index.ts | 20 ++++++++++++++ .../data/common/timefilter/is_time_range.ts | 26 ++++++++++++++++++ src/plugins/data/public/index.ts | 2 ++ 7 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/plugins/data/common/query/is_query.ts create mode 100644 src/plugins/data/common/timefilter/index.ts create mode 100644 src/plugins/data/common/timefilter/is_time_range.ts diff --git a/src/plugins/data/common/es_query/filters/meta_filter.ts b/src/plugins/data/common/es_query/filters/meta_filter.ts index ff6dff9d8b749..e3099ae6a4026 100644 --- a/src/plugins/data/common/es_query/filters/meta_filter.ts +++ b/src/plugins/data/common/es_query/filters/meta_filter.ts @@ -107,3 +107,13 @@ export const pinFilter = (filter: Filter) => export const unpinFilter = (filter: Filter) => !isFilterPinned(filter) ? filter : toggleFilterPinned(filter); + +export const isFilter = (x: unknown): x is Filter => + !!x && + typeof x === 'object' && + !!(x as Filter).meta && + typeof (x as Filter).meta === 'object' && + typeof (x as Filter).meta.disabled === 'boolean'; + +export const isFilters = (x: unknown): x is Filter[] => + Array.isArray(x) && !x.find((y) => !isFilter(y)); diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts index adbd93d518fc7..b40e02b709d30 100644 --- a/src/plugins/data/common/index.ts +++ b/src/plugins/data/common/index.ts @@ -20,11 +20,12 @@ export * from './constants'; export * from './es_query'; export * from './field_formats'; +export * from './field_mapping'; export * from './index_patterns'; export * from './kbn_field_types'; export * from './query'; export * from './search'; export * from './search/aggs'; +export * from './timefilter'; export * from './types'; export * from './utils'; -export * from './field_mapping'; diff --git a/src/plugins/data/common/query/index.ts b/src/plugins/data/common/query/index.ts index 421cc4f63e4ef..4e90f6f8bb83e 100644 --- a/src/plugins/data/common/query/index.ts +++ b/src/plugins/data/common/query/index.ts @@ -19,3 +19,4 @@ export * from './filter_manager'; export * from './types'; +export * from './is_query'; diff --git a/src/plugins/data/common/query/is_query.ts b/src/plugins/data/common/query/is_query.ts new file mode 100644 index 0000000000000..08a99a39b1ac1 --- /dev/null +++ b/src/plugins/data/common/query/is_query.ts @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Query } from './types'; + +export const isQuery = (x: unknown): x is Query => + !!x && + typeof x === 'object' && + typeof (x as Query).language === 'string' && + (typeof (x as Query).query === 'string' || + (typeof (x as Query).query === 'object' && !!(x as Query).query)); diff --git a/src/plugins/data/common/timefilter/index.ts b/src/plugins/data/common/timefilter/index.ts new file mode 100644 index 0000000000000..e0c509e119fda --- /dev/null +++ b/src/plugins/data/common/timefilter/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { isTimeRange } from './is_time_range'; diff --git a/src/plugins/data/common/timefilter/is_time_range.ts b/src/plugins/data/common/timefilter/is_time_range.ts new file mode 100644 index 0000000000000..f206cd04dde31 --- /dev/null +++ b/src/plugins/data/common/timefilter/is_time_range.ts @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { TimeRange } from './types'; + +export const isTimeRange = (x: unknown): x is TimeRange => + !!x && + typeof x === 'object' && + typeof (x as TimeRange).from === 'string' && + typeof (x as TimeRange).to === 'string'; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 1554ac71f8c55..0762fea784737 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -443,6 +443,8 @@ export { getKbnTypeNames, } from '../common'; +export { isTimeRange, isQuery, isFilter, isFilters } from '../common'; + export * from '../common/field_mapping'; /* From 00a7aa3c2f414e8b169690695f0a887d257bd0ba Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 18 Jun 2020 15:11:10 +0200 Subject: [PATCH 08/21] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20better=20handle=20?= =?UTF-8?q?empty=20filters=20in=20Discover=20URL=20generator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/discover/public/index.ts | 2 +- src/plugins/discover/public/url_generator.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/plugins/discover/public/index.ts b/src/plugins/discover/public/index.ts index 4154fdfeb3ff4..6ac8f674b6153 100644 --- a/src/plugins/discover/public/index.ts +++ b/src/plugins/discover/public/index.ts @@ -27,4 +27,4 @@ export function plugin(initializerContext: PluginInitializerContext) { export { SavedSearch, SavedSearchLoader, createSavedSearchesLoader } from './saved_searches'; export { ISearchEmbeddable, SEARCH_EMBEDDABLE_TYPE, SearchInput } from './application/embeddable'; -export { DISCOVER_APP_URL_GENERATOR } from './url_generator'; +export { DISCOVER_APP_URL_GENERATOR, DiscoverUrlGeneratorState } from './url_generator'; diff --git a/src/plugins/discover/public/url_generator.ts b/src/plugins/discover/public/url_generator.ts index 42d689050d5ad..c7f2e2147e819 100644 --- a/src/plugins/discover/public/url_generator.ts +++ b/src/plugins/discover/public/url_generator.ts @@ -98,11 +98,13 @@ export class DiscoverUrlGenerator const queryState: QueryState = {}; if (query) appState.query = query; - if (filters) appState.filters = filters?.filter((f) => !esFilters.isFilterPinned(f)); + if (filters && filters.length) + appState.filters = filters?.filter((f) => !esFilters.isFilterPinned(f)); if (indexPatternId) appState.index = indexPatternId; if (timeRange) queryState.time = timeRange; - if (filters) queryState.filters = filters?.filter((f) => esFilters.isFilterPinned(f)); + if (filters && filters.length) + queryState.filters = filters?.filter((f) => esFilters.isFilterPinned(f)); if (refreshInterval) queryState.refreshInterval = refreshInterval; let url = `${this.params.appBasePath}#/${savedSearchPath}`; From 088feb14ab1a8cacaaa6badff0607601d6c757c8 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 18 Jun 2020 15:13:01 +0200 Subject: [PATCH 09/21] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20implement=20.getUr?= =?UTF-8?q?l()=20method=20of=20explore=20data=20in-chart=20act?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../abstract_explore_data_action.ts | 13 +++++---- .../explore_data/explore_data_chart_action.ts | 28 +++++++++++++------ .../public/actions/explore_data/shared.ts | 5 ++-- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/abstract_explore_data_action.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/abstract_explore_data_action.ts index 60f20cf9ce916..620cabe652778 100644 --- a/x-pack/plugins/discover_enhanced/public/actions/explore_data/abstract_explore_data_action.ts +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/abstract_explore_data_action.ts @@ -6,10 +6,10 @@ import { i18n } from '@kbn/i18n'; import { DiscoverStart } from '../../../../../../src/plugins/discover/public'; +import { EmbeddableStart } from '../../../../../../src/plugins/embeddable/public'; import { ViewMode, IEmbeddable } from '../../../../../../src/plugins/embeddable/public'; import { StartServicesGetter } from '../../../../../../src/plugins/kibana_utils/public'; import { CoreStart } from '../../../../../../src/core/public'; -import { VisualizeEmbeddableContract } from '../../../../../../src/plugins/visualizations/public'; import { KibanaURL } from './kibana_url'; import * as shared from './shared'; @@ -17,6 +17,7 @@ export const ACTION_EXPLORE_DATA = 'ACTION_EXPLORE_DATA'; export interface PluginDeps { discover: Pick; + embeddable: Pick; } export interface CoreDeps { @@ -37,7 +38,7 @@ export abstract class AbstractExploreDataAction; + protected abstract async getUrl(context: Context): Promise; public async isCompatible({ embeddable }: Context): Promise { if (!embeddable) return false; @@ -48,11 +49,11 @@ export abstract class AbstractExploreDataAction { - if (!shared.isVisualizeEmbeddable(embeddable)) return; + public async execute(context: Context): Promise { + if (!shared.isVisualizeEmbeddable(context.embeddable)) return; const { core } = this.params.start(); - const { appName, appPath } = await this.getUrl(embeddable); + const { appName, appPath } = await this.getUrl(context); await core.application.navigateToApp(appName, { path: appPath, @@ -66,7 +67,7 @@ export abstract class AbstractExploreDataAction => { const { plugins } = this.params.start(); const { urlGenerator } = plugins.discover; @@ -40,15 +41,24 @@ export class ExploreDataChartAction extends AbstractExploreDataAction { - const output = embeddable!.getOutput(); +export const getIndexPattern = (embeddable?: IEmbeddable): string => { + if (!embeddable) return ''; + const output = embeddable.getOutput(); if (isOutputWithIndexPatterns(output) && output.indexPatterns.length > 0) { return output.indexPatterns[0].id; From 302702f6a745698267adfa636dc72fe11fa0e14e Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 18 Jun 2020 15:15:28 +0200 Subject: [PATCH 10/21] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20embeddable.f?= =?UTF-8?q?iltersAndTimeRangeFromContext()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/embeddable/public/plugin.tsx | 71 ++++++++++++++++++------ 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/src/plugins/embeddable/public/plugin.tsx b/src/plugins/embeddable/public/plugin.tsx index 958eacf3c844e..fcf52c0f56460 100644 --- a/src/plugins/embeddable/public/plugin.tsx +++ b/src/plugins/embeddable/public/plugin.tsx @@ -17,7 +17,13 @@ * under the License. */ import React from 'react'; -import { DataPublicPluginSetup, DataPublicPluginStart, Filter } from '../../data/public'; +import { + DataPublicPluginSetup, + DataPublicPluginStart, + Filter, + TimeRange, + esFilters, +} from '../../data/public'; import { getSavedObjectFinder } from '../../saved_objects/public'; import { UiActionsSetup, UiActionsStart } from '../../ui_actions/public'; import { Start as InspectorStart } from '../../inspector/public'; @@ -70,7 +76,18 @@ export interface EmbeddableStart { ) => EmbeddableFactory | undefined; getEmbeddableFactories: () => IterableIterator; EmbeddablePanel: React.FC<{ embeddable: IEmbeddable; hideHeader?: boolean }>; + + /** + * Given {@link ChartActionContext} returns a list of `data` plugin {@link Filter} entries. + */ filtersFromContext: (context: ChartActionContext) => Promise; + + /** + * Returns possible time range and filters that can be constructed from {@link ChartActionContext} object. + */ + filtersAndTimeRangeFromContext: ( + context: ChartActionContext + ) => Promise<{ filters: Filter[]; timeRange?: TimeRange }>; } export class EmbeddablePublicPlugin implements Plugin { @@ -114,6 +131,41 @@ export class EmbeddablePublicPlugin implements Plugin { + try { + if (isRangeSelectTriggerContext(context)) + return await data.actions.createFiltersFromRangeSelectAction(context.data); + if (isValueClickTriggerContext(context)) + return await data.actions.createFiltersFromValueClickAction(context.data); + // eslint-disable-next-line no-console + console.warn("Can't extract filters from action.", context); + } catch (error) { + // eslint-disable-next-line no-console + console.warn('Error extracting filters from action. Returning empty filter list.', error); + } + return []; + }; + + const filtersAndTimeRangeFromContext: EmbeddableStart['filtersAndTimeRangeFromContext'] = async ( + context + ) => { + const filters = await filtersFromContext(context); + + if (!context.data.timeFieldName) return { filters }; + + const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter( + context.data.timeFieldName, + filters + ); + + return { + filters: restOfFilters, + timeRange: timeRangeFilter + ? esFilters.convertRangeFilterToTimeRangeString(timeRangeFilter) + : undefined, + }; + }; + return { getEmbeddableFactory: this.getEmbeddableFactory, getEmbeddableFactories: this.getEmbeddableFactories, @@ -137,21 +189,8 @@ export class EmbeddablePublicPlugin implements Plugin ), - - filtersFromContext: async (context) => { - try { - if (isRangeSelectTriggerContext(context)) - return await data.actions.createFiltersFromRangeSelectAction(context.data); - if (isValueClickTriggerContext(context)) - return await data.actions.createFiltersFromValueClickAction(context.data); - // eslint-disable-next-line no-console - console.warn("Can't extract filters from action.", context); - } catch (error) { - // eslint-disable-next-line no-console - console.warn('Error extracting filters from action. Returning empty filter list.', error); - } - return []; - }, + filtersFromContext, + filtersAndTimeRangeFromContext, }; } From 5217996f6cc6be2623f676925700e443c219145f Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 18 Jun 2020 15:17:10 +0200 Subject: [PATCH 11/21] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20improve=20getUrl()?= =?UTF-8?q?=20method=20of=20explore=20data=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../explore_data_context_menu_action.ts | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts index 61555c149127b..6691089f875d8 100644 --- a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts @@ -6,7 +6,8 @@ import { Action } from '../../../../../../src/plugins/ui_actions/public'; import { EmbeddableContext } from '../../../../../../src/plugins/embeddable/public'; -import { VisualizeEmbeddableContract } from '../../../../../../src/plugins/visualizations/public'; +import { DiscoverUrlGeneratorState } from '../../../../../../src/plugins/discover/public'; +import { isTimeRange, isQuery, isFilters } from '../../../../../../src/plugins/data/public'; import { KibanaURL } from './kibana_url'; import * as shared from './shared'; import { AbstractExploreDataAction } from './abstract_explore_data_action'; @@ -25,7 +26,7 @@ export class ExploreDataContextMenuAction extends AbstractExploreDataAction { + protected readonly getUrl = async (context: EmbeddableContext): Promise => { const { plugins } = this.params.start(); const { urlGenerator } = plugins.discover; @@ -33,16 +34,21 @@ export class ExploreDataContextMenuAction extends AbstractExploreDataAction Date: Thu, 18 Jun 2020 15:20:26 +0200 Subject: [PATCH 12/21] =?UTF-8?q?test:=20=F0=9F=92=8D=20update=20test=20mo?= =?UTF-8?q?ck?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../explore_data/explore_data_context_menu_action.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts index 1d606fce64746..e945802a6c95e 100644 --- a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts @@ -11,6 +11,7 @@ import { import { Params, PluginDeps } from './abstract_explore_data_action'; import { coreMock } from '../../../../../../src/core/public/mocks'; import { UrlGeneratorContract } from '../../../../../../src/plugins/share/public'; +import { EmbeddableStart } from '../../../../../../src/plugins/embeddable/public'; import { i18n } from '@kbn/i18n'; import { VisualizeEmbeddableContract, @@ -40,10 +41,17 @@ const setup = () => { createUrl: jest.fn(() => Promise.resolve('/xyz/app/discover/foo#bar')), } as unknown) as UrlGenerator; + const filtersAndTimeRangeFromContext = jest.fn((async () => ({ + filters: [], + })) as EmbeddableStart['filtersAndTimeRangeFromContext']); + const plugins: PluginDeps = { discover: { urlGenerator, }, + embeddable: { + filtersAndTimeRangeFromContext, + }, }; const params: Params = { From 9b5739a886488f3c07c8bafb3456d3d250315250 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Wed, 24 Jun 2020 17:53:02 +0200 Subject: [PATCH 13/21] fix possible stale hashHistory.location in discover --- src/plugins/discover/public/kibana_services.ts | 16 +++++++++++++++- src/plugins/discover/public/plugin.ts | 2 ++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/plugins/discover/public/kibana_services.ts b/src/plugins/discover/public/kibana_services.ts index bbd0357f41ed4..b8d3129878970 100644 --- a/src/plugins/discover/public/kibana_services.ts +++ b/src/plugins/discover/public/kibana_services.ts @@ -59,10 +59,24 @@ export const [getDocViewsRegistry, setDocViewsRegistry] = createGetterSetter createHashHistory()); +/** + * Discover & context are using same hash history instance, + * When getting back to discover internal history.location could get out of sync with window.location + * This helper function syncs window.location state to internal hash history + * + * Using this on discover mount also workarounds possible issues with async hash event: https://github.com/elastic/kibana/pull/65163 + * This helper is temp until: https://github.com/elastic/kibana/issues/65161 resolved + */ +export const ensureHashHistoryLocation = () => { + const h = getHistory(); + Object.assign(h.location, createHashHistory().location); + return h; +}; + export const [getScopedHistory, setScopedHistory] = createGetterSetter( 'scopedHistory' ); diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index 091288e3e65aa..5416462147b31 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -55,6 +55,7 @@ import { setServices, setScopedHistory, getScopedHistory, + ensureHashHistoryLocation, } from './kibana_services'; import { createSavedSearchesLoader } from './saved_searches'; import { registerFeature } from './register_feature'; @@ -242,6 +243,7 @@ export class DiscoverPlugin throw Error('Discover plugin method initializeInnerAngular is undefined'); } setScopedHistory(params.history); + ensureHashHistoryLocation(); appMounted(); const { plugins: { data: dataStart }, From eb6145227c9a1ca75275e9174bcd6dc294def3e4 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 25 Jun 2020 11:33:30 +0200 Subject: [PATCH 14/21] =?UTF-8?q?style:=20=F0=9F=92=84=20ensureHashHistory?= =?UTF-8?q?Location=20->=20syncHistoryLocations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/discover/public/kibana_services.ts | 11 +++++------ src/plugins/discover/public/plugin.ts | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/plugins/discover/public/kibana_services.ts b/src/plugins/discover/public/kibana_services.ts index 993e9b81a97f6..2c6bbcc3ecce1 100644 --- a/src/plugins/discover/public/kibana_services.ts +++ b/src/plugins/discover/public/kibana_services.ts @@ -65,14 +65,13 @@ export const [getDocViewsRegistry, setDocViewsRegistry] = createGetterSetter createHashHistory()); /** - * Discover & context are using same hash history instance, - * When getting back to discover internal history.location could get out of sync with window.location - * This helper function syncs window.location state to internal hash history + * Discover currently uses two `history` instances: one from Kibana Platform and + * another from `history` package. Below function is used every time Discover + * app is loaded to synchronize both instances. * - * Using this on discover mount also workarounds possible issues with async hash event: https://github.com/elastic/kibana/pull/65163 - * This helper is temp until: https://github.com/elastic/kibana/issues/65161 resolved + * This helper is temporary until https://github.com/elastic/kibana/issues/65161 is resolved. */ -export const ensureHashHistoryLocation = () => { +export const syncHistoryLocations = () => { const h = getHistory(); Object.assign(h.location, createHashHistory().location); return h; diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index 16694dd030d8d..e97ac783c616f 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -55,7 +55,7 @@ import { setServices, setScopedHistory, getScopedHistory, - ensureHashHistoryLocation, + syncHistoryLocations, getServices, } from './kibana_services'; import { createSavedSearchesLoader } from './saved_searches'; @@ -246,7 +246,7 @@ export class DiscoverPlugin throw Error('Discover plugin method initializeInnerAngular is undefined'); } setScopedHistory(params.history); - ensureHashHistoryLocation(); + syncHistoryLocations(); appMounted(); const { plugins: { data: dataStart }, From 406e02d3628c734e409a1c6e99008d806a0abb91 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 25 Jun 2020 12:14:43 +0200 Subject: [PATCH 15/21] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20update=20aut?= =?UTF-8?q?ogenerated=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ana-plugin-plugins-data-public.isfilter.md | 11 ++++++++++ ...na-plugin-plugins-data-public.isfilters.md | 11 ++++++++++ ...bana-plugin-plugins-data-public.isquery.md | 11 ++++++++++ ...-plugin-plugins-data-public.istimerange.md | 11 ++++++++++ .../kibana-plugin-plugins-data-public.md | 4 ++++ src/plugins/data/public/public.api.md | 20 +++++++++++++++++++ 6 files changed, 68 insertions(+) create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isfilter.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isfilters.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isquery.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.istimerange.md diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isfilter.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isfilter.md new file mode 100644 index 0000000000000..f1916e89c2c98 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isfilter.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [isFilter](./kibana-plugin-plugins-data-public.isfilter.md) + +## isFilter variable + +Signature: + +```typescript +isFilter: (x: unknown) => x is Filter +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isfilters.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isfilters.md new file mode 100644 index 0000000000000..558da72cc26bb --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isfilters.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [isFilters](./kibana-plugin-plugins-data-public.isfilters.md) + +## isFilters variable + +Signature: + +```typescript +isFilters: (x: unknown) => x is Filter[] +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isquery.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isquery.md new file mode 100644 index 0000000000000..0884566333aa8 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isquery.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [isQuery](./kibana-plugin-plugins-data-public.isquery.md) + +## isQuery variable + +Signature: + +```typescript +isQuery: (x: unknown) => x is Query +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.istimerange.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.istimerange.md new file mode 100644 index 0000000000000..e9420493c82fb --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.istimerange.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [isTimeRange](./kibana-plugin-plugins-data-public.istimerange.md) + +## isTimeRange variable + +Signature: + +```typescript +isTimeRange: (x: unknown) => x is TimeRange +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index f62479f02926e..feeb686a1f5ed 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -110,6 +110,10 @@ | [getKbnTypeNames](./kibana-plugin-plugins-data-public.getkbntypenames.md) | Get the esTypes known by all kbnFieldTypes {Array} | | [indexPatterns](./kibana-plugin-plugins-data-public.indexpatterns.md) | | | [injectSearchSourceReferences](./kibana-plugin-plugins-data-public.injectsearchsourcereferences.md) | | +| [isFilter](./kibana-plugin-plugins-data-public.isfilter.md) | | +| [isFilters](./kibana-plugin-plugins-data-public.isfilters.md) | | +| [isQuery](./kibana-plugin-plugins-data-public.isquery.md) | | +| [isTimeRange](./kibana-plugin-plugins-data-public.istimerange.md) | | | [parseSearchSourceJSON](./kibana-plugin-plugins-data-public.parsesearchsourcejson.md) | | | [QueryStringInput](./kibana-plugin-plugins-data-public.querystringinput.md) | | | [search](./kibana-plugin-plugins-data-public.search.md) | | diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 31dc5b51a06f5..45781035dad6d 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1278,6 +1278,26 @@ export interface ISearchStrategy { search: ISearch; } +// Warning: (ae-missing-release-tag) "isFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const isFilter: (x: unknown) => x is Filter; + +// Warning: (ae-missing-release-tag) "isFilters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const isFilters: (x: unknown) => x is Filter[]; + +// Warning: (ae-missing-release-tag) "isQuery" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const isQuery: (x: unknown) => x is Query; + +// Warning: (ae-missing-release-tag) "isTimeRange" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const isTimeRange: (x: unknown) => x is TimeRange; + // Warning: (ae-missing-release-tag) "ISyncSearchRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) From 599382ba329580ab62385bdb94e8e03f07ec15f6 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 25 Jun 2020 12:15:38 +0200 Subject: [PATCH 16/21] =?UTF-8?q?test:=20=F0=9F=92=8D=20add=20in-chart=20"?= =?UTF-8?q?Explore=20underlying=20data"=20unit=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../explore_data_chart_action.test.ts | 274 ++++++++++++++++++ .../explore_data_context_menu_action.test.ts | 6 +- 2 files changed, 275 insertions(+), 5 deletions(-) create mode 100644 x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.test.ts diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.test.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.test.ts new file mode 100644 index 0000000000000..a273f0d50e45e --- /dev/null +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.test.ts @@ -0,0 +1,274 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ExploreDataChartAction } from './explore_data_chart_action'; +import { Params, PluginDeps } from './abstract_explore_data_action'; +import { coreMock } from '../../../../../../src/core/public/mocks'; +import { UrlGeneratorContract } from '../../../../../../src/plugins/share/public'; +import { + EmbeddableStart, + RangeSelectTriggerContext, + ValueClickTriggerContext, + ChartActionContext, +} from '../../../../../../src/plugins/embeddable/public'; +import { i18n } from '@kbn/i18n'; +import { + VisualizeEmbeddableContract, + VISUALIZE_EMBEDDABLE_TYPE, +} from '../../../../../../src/plugins/visualizations/public'; +import { ViewMode } from '../../../../../../src/plugins/embeddable/public'; +import { Filter, TimeRange } from '../../../../../../src/plugins/data/public'; + +const i18nTranslateSpy = (i18n.translate as unknown) as jest.SpyInstance; + +jest.mock('@kbn/i18n', () => ({ + i18n: { + translate: jest.fn((key, options) => options.defaultMessage), + }, +})); + +afterEach(() => { + i18nTranslateSpy.mockClear(); +}); + +const setup = ({ useRangeEvent = false }: { useRangeEvent?: boolean } = {}) => { + type UrlGenerator = UrlGeneratorContract<'DISCOVER_APP_URL_GENERATOR'>; + + const core = coreMock.createStart(); + + const urlGenerator: UrlGenerator = ({ + createUrl: jest.fn(() => Promise.resolve('/xyz/app/discover/foo#bar')), + } as unknown) as UrlGenerator; + + const filtersAndTimeRangeFromContext = jest.fn((async () => ({ + filters: [], + })) as EmbeddableStart['filtersAndTimeRangeFromContext']); + + const plugins: PluginDeps = { + discover: { + urlGenerator, + }, + embeddable: { + filtersAndTimeRangeFromContext, + }, + }; + + const params: Params = { + start: () => ({ + plugins, + self: {}, + core, + }), + }; + const action = new ExploreDataChartAction(params); + + const input = { + viewMode: ViewMode.VIEW, + }; + + const output = { + indexPatterns: [ + { + id: 'index-ptr-foo', + }, + ], + }; + + const embeddable: VisualizeEmbeddableContract = ({ + type: VISUALIZE_EMBEDDABLE_TYPE, + getInput: () => input, + getOutput: () => output, + } as unknown) as VisualizeEmbeddableContract; + + const data: ChartActionContext['data'] = { + ...(useRangeEvent + ? ({ range: {} } as RangeSelectTriggerContext['data']) + : ({ data: [] } as ValueClickTriggerContext['data'])), + timeFieldName: 'order_date', + }; + + const context = { + embeddable, + data, + } as ChartActionContext; + + return { core, plugins, urlGenerator, params, action, input, output, embeddable, data, context }; +}; + +describe('"Explore underlying data" panel action', () => { + test('action has Discover icon', () => { + const { action, context } = setup(); + expect(action.getIconType(context)).toBe('discoverApp'); + }); + + test('title is "Explore underlying data"', () => { + const { action, context } = setup(); + expect(action.getDisplayName(context)).toBe('Explore underlying data'); + }); + + test('translates title', () => { + expect(i18nTranslateSpy).toHaveBeenCalledTimes(0); + + const { action, context } = setup(); + action.getDisplayName(context); + + expect(i18nTranslateSpy).toHaveBeenCalledTimes(1); + expect(i18nTranslateSpy.mock.calls[0][0]).toBe( + 'xpack.discover.FlyoutCreateDrilldownAction.displayName' + ); + }); + + describe('isCompatible()', () => { + test('returns true when all conditions are met', async () => { + const { action, context } = setup(); + + const isCompatible = await action.isCompatible(context); + + expect(isCompatible).toBe(true); + }); + + test('returns false when URL generator is not present', async () => { + const { action, plugins, context } = setup(); + (plugins.discover as any).urlGenerator = undefined; + + const isCompatible = await action.isCompatible(context); + + expect(isCompatible).toBe(false); + }); + + test('returns false if embeddable is not Visualize embeddable', async () => { + const { action, embeddable, context } = setup(); + (embeddable as any).type = 'NOT_VISUALIZE_EMBEDDABLE'; + + const isCompatible = await action.isCompatible(context); + + expect(isCompatible).toBe(false); + }); + + test('returns false if embeddable does not have index patterns', async () => { + const { action, output, context } = setup(); + delete output.indexPatterns; + + const isCompatible = await action.isCompatible(context); + + expect(isCompatible).toBe(false); + }); + + test('returns false if embeddable index patterns are empty', async () => { + const { action, output, context } = setup(); + output.indexPatterns = []; + + const isCompatible = await action.isCompatible(context); + + expect(isCompatible).toBe(false); + }); + + test('returns false if dashboard is in edit mode', async () => { + const { action, input, context } = setup(); + input.viewMode = ViewMode.EDIT; + + const isCompatible = await action.isCompatible(context); + + expect(isCompatible).toBe(false); + }); + }); + + describe('getHref()', () => { + test('returns URL path generated by URL generator', async () => { + const { action, context } = setup(); + + const href = await action.getHref(context); + + expect(href).toBe('/xyz/app/discover/foo#bar'); + }); + + test('calls URL generator with right arguments', async () => { + const { action, urlGenerator, context } = setup(); + + expect(urlGenerator.createUrl).toHaveBeenCalledTimes(0); + + await action.getHref(context); + + expect(urlGenerator.createUrl).toHaveBeenCalledTimes(1); + expect(urlGenerator.createUrl).toHaveBeenCalledWith({ + filters: [], + indexPatternId: 'index-ptr-foo', + timeRange: undefined, + }); + }); + + test('applies chart event filters', async () => { + const { action, context, urlGenerator, plugins } = setup(); + + ((plugins.embeddable + .filtersAndTimeRangeFromContext as unknown) as jest.SpyInstance).mockImplementation(() => { + const filters: Filter[] = [ + { + meta: { + alias: 'alias', + disabled: false, + negate: false, + }, + }, + ]; + const timeRange: TimeRange = { + from: 'from', + to: 'to', + }; + return { filters, timeRange }; + }); + + expect(plugins.embeddable.filtersAndTimeRangeFromContext).toHaveBeenCalledTimes(0); + + await action.getHref(context); + + expect(plugins.embeddable.filtersAndTimeRangeFromContext).toHaveBeenCalledTimes(1); + expect(plugins.embeddable.filtersAndTimeRangeFromContext).toHaveBeenCalledWith(context); + expect(urlGenerator.createUrl).toHaveBeenCalledWith({ + filters: [ + { + meta: { + alias: 'alias', + disabled: false, + negate: false, + }, + }, + ], + indexPatternId: 'index-ptr-foo', + timeRange: { + from: 'from', + to: 'to', + }, + }); + }); + }); + + describe('execute()', () => { + test('calls platform SPA navigation method', async () => { + const { action, context, core } = setup(); + + expect(core.application.navigateToApp).toHaveBeenCalledTimes(0); + + await action.execute(context); + + expect(core.application.navigateToApp).toHaveBeenCalledTimes(1); + }); + + test('calls platform SPA navigation method with right arguments', async () => { + const { action, context, core } = setup(); + + await action.execute(context); + + expect(core.application.navigateToApp).toHaveBeenCalledTimes(1); + expect(core.application.navigateToApp.mock.calls[0]).toEqual([ + 'discover', + { + path: '/foo#bar', + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts index e945802a6c95e..e742b69380973 100644 --- a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts @@ -4,10 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - ExploreDataContextMenuAction, - ACTION_EXPLORE_DATA, -} from './explore_data_context_menu_action'; +import { ExploreDataContextMenuAction } from './explore_data_context_menu_action'; import { Params, PluginDeps } from './abstract_explore_data_action'; import { coreMock } from '../../../../../../src/core/public/mocks'; import { UrlGeneratorContract } from '../../../../../../src/plugins/share/public'; @@ -37,7 +34,6 @@ const setup = () => { const core = coreMock.createStart(); const urlGenerator: UrlGenerator = ({ - id: ACTION_EXPLORE_DATA, createUrl: jest.fn(() => Promise.resolve('/xyz/app/discover/foo#bar')), } as unknown) as UrlGenerator; From 4ceefdd9343b363d9bd31ec0691fe3fe68bafea4 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 25 Jun 2020 13:17:54 +0200 Subject: [PATCH 17/21] =?UTF-8?q?test:=20=F0=9F=92=8D=20add=20in-chart=20"?= =?UTF-8?q?Explore=20underlying=20data"=20functional=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../drilldowns/explore_data_chart_action.ts | 98 +++++++++++++++++++ .../drilldowns/explore_data_panel_action.ts | 10 +- .../apps/dashboard/drilldowns/index.ts | 1 + 3 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 x-pack/test/functional/apps/dashboard/drilldowns/explore_data_chart_action.ts diff --git a/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_chart_action.ts b/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_chart_action.ts new file mode 100644 index 0000000000000..d9103f08ce36c --- /dev/null +++ b/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_chart_action.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +const ACTION_ID = 'ACTION_EXPLORE_DATA_CHART'; +const ACTION_TEST_SUBJ = `embeddablePanelAction-${ACTION_ID}`; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const drilldowns = getService('dashboardDrilldownsManage'); + const { dashboard, discover, common, timePicker } = getPageObjects([ + 'dashboard', + 'discover', + 'common', + 'timePicker', + ]); + const testSubjects = getService('testSubjects'); + const pieChart = getService('pieChart'); + const dashboardDrilldownPanelActions = getService('dashboardDrilldownPanelActions'); + const filterBar = getService('filterBar'); + const browser = getService('browser'); + + describe('Explore underlying data - chart action', () => { + describe('value click action', () => { + it('action exists in chart click popup menu', async () => { + await common.navigateToApp('dashboard'); + await dashboard.preserveCrossAppState(); + await dashboard.loadSavedDashboard(drilldowns.DASHBOARD_WITH_PIE_CHART_NAME); + await pieChart.filterOnPieSlice('160,000'); + await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened(); + await testSubjects.existOrFail(ACTION_TEST_SUBJ); + }); + + it('action is a link element', async () => { + const actionElement = await testSubjects.find(ACTION_TEST_SUBJ); + const tag = await actionElement.getTagName(); + const href = await actionElement.getAttribute('href'); + + expect(tag.toLowerCase()).to.be('a'); + expect(typeof href).to.be('string'); + expect(href.length > 5).to.be(true); + }); + + it('navigates to Discover app on action click carrying over pie slice filter', async () => { + await testSubjects.clickWhenNotDisabled(ACTION_TEST_SUBJ); + await discover.waitForDiscoverAppOnScreen(); + await filterBar.hasFilter('memory', '160,000 to 200,000'); + const filterCount = await filterBar.getFilterCount(); + + expect(filterCount).to.be(1); + }); + }); + + describe('brush action', () => { + let originalTimeRangeDurationHours: number | undefined; + + it('action exists in chart brush popup menu', async () => { + await common.navigateToApp('dashboard'); + await dashboard.preserveCrossAppState(); + await dashboard.loadSavedDashboard(drilldowns.DASHBOARD_WITH_AREA_CHART_NAME); + + originalTimeRangeDurationHours = await timePicker.getTimeDurationInHours(); + const areaChart = await testSubjects.find('visualizationLoader'); + await browser.dragAndDrop( + { + location: areaChart, + offset: { + x: -100, + y: 0, + }, + }, + { + location: areaChart, + offset: { + x: 100, + y: 0, + }, + } + ); + + await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened(); + await testSubjects.existOrFail(ACTION_TEST_SUBJ); + }); + + it('navigates to Discover on click carrying over brushed time range', async () => { + await testSubjects.clickWhenNotDisabled(ACTION_TEST_SUBJ); + await discover.waitForDiscoverAppOnScreen(); + const newTimeRangeDurationHours = await timePicker.getTimeDurationInHours(); + + expect(newTimeRangeDurationHours).to.be.lessThan(originalTimeRangeDurationHours as number); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_panel_action.ts b/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_panel_action.ts index 24d6e820ac0eb..d165a41ecbdf4 100644 --- a/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_panel_action.ts +++ b/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_panel_action.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; const ACTION_ID = 'ACTION_EXPLORE_DATA'; -const EXPLORE_RAW_DATA_ACTION_TEST_SUBJ = `embeddablePanelAction-${ACTION_ID}`; +const ACTION_TEST_SUBJ = `embeddablePanelAction-${ACTION_ID}`; export default function ({ getService, getPageObjects }: FtrProviderContext) { const drilldowns = getService('dashboardDrilldownsManage'); @@ -37,18 +37,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('action exists in panel context menu', async () => { await dashboard.loadSavedDashboard(drilldowns.DASHBOARD_WITH_PIE_CHART_NAME); await panelActions.openContextMenu(); - await testSubjects.existOrFail(EXPLORE_RAW_DATA_ACTION_TEST_SUBJ); + await testSubjects.existOrFail(ACTION_TEST_SUBJ); }); it('is a link element', async () => { - const actionElement = await testSubjects.find(EXPLORE_RAW_DATA_ACTION_TEST_SUBJ); + const actionElement = await testSubjects.find(ACTION_TEST_SUBJ); const tag = await actionElement.getTagName(); expect(tag.toLowerCase()).to.be('a'); }); it('navigates to Discover app to index pattern of the panel on action click', async () => { - await testSubjects.clickWhenNotDisabled(EXPLORE_RAW_DATA_ACTION_TEST_SUBJ); + await testSubjects.clickWhenNotDisabled(ACTION_TEST_SUBJ); await discover.waitForDiscoverAppOnScreen(); const el = await testSubjects.find('indexPattern-switch-link'); @@ -71,7 +71,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboard.saveDashboard('Dashboard with Pie Chart'); await panelActions.openContextMenu(); - await testSubjects.clickWhenNotDisabled(EXPLORE_RAW_DATA_ACTION_TEST_SUBJ); + await testSubjects.clickWhenNotDisabled(ACTION_TEST_SUBJ); await discover.waitForDiscoverAppOnScreen(); const text = await timePicker.getShowDatesButtonText(); diff --git a/x-pack/test/functional/apps/dashboard/drilldowns/index.ts b/x-pack/test/functional/apps/dashboard/drilldowns/index.ts index 19d85ad0e448f..4cdb33c06947f 100644 --- a/x-pack/test/functional/apps/dashboard/drilldowns/index.ts +++ b/x-pack/test/functional/apps/dashboard/drilldowns/index.ts @@ -24,5 +24,6 @@ export default function ({ loadTestFile, getService }: FtrProviderContext) { loadTestFile(require.resolve('./dashboard_drilldowns')); loadTestFile(require.resolve('./explore_data_panel_action')); + loadTestFile(require.resolve('./explore_data_chart_action')); }); } From 0e29de3eef4bf65f23f04f3d6be23ee5073e3c0a Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 25 Jun 2020 13:27:11 +0200 Subject: [PATCH 18/21] =?UTF-8?q?test:=20=F0=9F=92=8D=20clean-up=20custom?= =?UTF-8?q?=20time=20range=20after=20panel=20action=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/drilldowns/explore_data_panel_action.ts | 8 ++++++++ .../functional/services/dashboard/panel_time_range.ts | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_panel_action.ts b/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_panel_action.ts index d165a41ecbdf4..244cc831e06e1 100644 --- a/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_panel_action.ts +++ b/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_panel_action.ts @@ -32,6 +32,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { after(async () => { await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' }); + + // Clean-up custom time range on panel + await common.navigateToApp('dashboard'); + await dashboard.gotoDashboardEditMode(drilldowns.DASHBOARD_WITH_PIE_CHART_NAME); + await panelActions.openContextMenu(); + await panelActionsTimeRange.clickTimeRangeActionInContextMenu(); + await panelActionsTimeRange.clickRemovePerPanelTimeRangeButton(); + await dashboard.saveDashboard('Dashboard with Pie Chart'); }); it('action exists in panel context menu', async () => { diff --git a/x-pack/test/functional/services/dashboard/panel_time_range.ts b/x-pack/test/functional/services/dashboard/panel_time_range.ts index 6a91a6ff0584b..f71e8284c30d9 100644 --- a/x-pack/test/functional/services/dashboard/panel_time_range.ts +++ b/x-pack/test/functional/services/dashboard/panel_time_range.ts @@ -52,5 +52,11 @@ export function DashboardPanelTimeRangeProvider({ getService }: FtrProviderConte const button = await this.findModalTestSubject('addPerPanelTimeRangeButton'); await button.click(); } + + public async clickRemovePerPanelTimeRangeButton() { + log.debug('clickRemovePerPanelTimeRangeButton'); + const button = await this.findModalTestSubject('removePerPanelTimeRangeButton'); + await button.click(); + } })(); } From 424414f3f45c309eec70cd421a80c0c1ba8fc924 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 25 Jun 2020 13:34:19 +0200 Subject: [PATCH 19/21] =?UTF-8?q?chore:=20=F0=9F=A4=96=20fix=20embeddable?= =?UTF-8?q?=20plugin=20mocks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/embeddable/public/mocks.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/plugins/embeddable/public/mocks.tsx b/src/plugins/embeddable/public/mocks.tsx index 49910525c7ab1..c98416cb3e8c7 100644 --- a/src/plugins/embeddable/public/mocks.tsx +++ b/src/plugins/embeddable/public/mocks.tsx @@ -31,6 +31,7 @@ import { coreMock } from '../../../core/public/mocks'; import { UiActionsService } from './lib/ui_actions'; import { CoreStart } from '../../../core/public'; import { Start as InspectorStart } from '../../inspector/public'; +import { dataPluginMock } from '../../data/public/mocks'; // eslint-disable-next-line import { inspectorPluginMock } from '../../inspector/public/mocks'; @@ -100,6 +101,8 @@ const createStartContract = (): Start => { EmbeddablePanel: jest.fn(), getEmbeddablePanel: jest.fn(), getStateTransfer: jest.fn(() => createEmbeddableStateTransferMock() as EmbeddableStateTransfer), + filtersAndTimeRangeFromContext: jest.fn(), + filtersFromContext: jest.fn(), }; return startContract; }; @@ -108,11 +111,13 @@ const createInstance = (setupPlugins: Partial = {}) const plugin = new EmbeddablePublicPlugin({} as any); const setup = plugin.setup(coreMock.createSetup(), { uiActions: setupPlugins.uiActions || uiActionsPluginMock.createSetupContract(), + data: dataPluginMock.createSetupContract(), }); const doStart = (startPlugins: Partial = {}) => plugin.start(coreMock.createStart(), { uiActions: startPlugins.uiActions || uiActionsPluginMock.createStartContract(), inspector: inspectorPluginMock.createStartContract(), + data: dataPluginMock.createStartContract(), }); return { plugin, From dcef452f59ed317c3aaaa358fd2394f9ffed5dfe Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 25 Jun 2020 13:40:43 +0200 Subject: [PATCH 20/21] =?UTF-8?q?chore:=20=F0=9F=A4=96=20fix=20another=20m?= =?UTF-8?q?ock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/embeddable/public/tests/test_plugin.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/plugins/embeddable/public/tests/test_plugin.ts b/src/plugins/embeddable/public/tests/test_plugin.ts index e13a906e30338..bb12e3d7b9011 100644 --- a/src/plugins/embeddable/public/tests/test_plugin.ts +++ b/src/plugins/embeddable/public/tests/test_plugin.ts @@ -23,6 +23,7 @@ import { UiActionsStart } from '../../../ui_actions/public'; import { uiActionsPluginMock } from '../../../ui_actions/public/mocks'; // eslint-disable-next-line import { inspectorPluginMock } from '../../../inspector/public/mocks'; +import { dataPluginMock } from '../../../data/public/mocks'; import { coreMock } from '../../../../core/public/mocks'; import { EmbeddablePublicPlugin, EmbeddableSetup, EmbeddableStart } from '../plugin'; @@ -42,7 +43,10 @@ export const testPlugin = ( const uiActions = uiActionsPluginMock.createPlugin(coreSetup, coreStart); const initializerContext = {} as any; const plugin = new EmbeddablePublicPlugin(initializerContext); - const setup = plugin.setup(coreSetup, { uiActions: uiActions.setup }); + const setup = plugin.setup(coreSetup, { + data: dataPluginMock.createSetupContract(), + uiActions: uiActions.setup, + }); return { plugin, @@ -51,8 +55,9 @@ export const testPlugin = ( setup, doStart: (anotherCoreStart: CoreStart = coreStart) => { const start = plugin.start(anotherCoreStart, { - uiActions: uiActionsPluginMock.createStartContract(), + data: dataPluginMock.createStartContract(), inspector: inspectorPluginMock.createStartContract(), + uiActions: uiActionsPluginMock.createStartContract(), }); return start; }, From 905f0bc5b9861205673da5cbfb340160bcd940ff Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 25 Jun 2020 14:23:28 +0200 Subject: [PATCH 21/21] =?UTF-8?q?test:=20=F0=9F=92=8D=20add=20support=20fo?= =?UTF-8?q?r=20new=20action=20to=20pie=20chart=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../services/dashboard/panel_actions.ts | 14 ++++++++++++++ .../services/visualizations/pie_chart.ts | 17 +++++++++++++++-- .../drilldowns/dashboard_drilldowns.ts | 2 +- .../drilldowns/explore_data_chart_action.ts | 2 +- .../drilldowns/explore_data_panel_action.ts | 15 +++++++++++---- 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/test/functional/services/dashboard/panel_actions.ts b/test/functional/services/dashboard/panel_actions.ts index c9a5dcfba32b1..0f5d6ea74a6b6 100644 --- a/test/functional/services/dashboard/panel_actions.ts +++ b/test/functional/services/dashboard/panel_actions.ts @@ -213,5 +213,19 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }: Ft await testSubjects.click('saveNewTitleButton'); await this.toggleContextMenu(panel); } + + async getActionWebElementByText(text: string): Promise { + log.debug(`getActionWebElement: "${text}"`); + const menu = await testSubjects.find('multipleActionsContextMenu'); + const items = await menu.findAllByCssSelector('[data-test-subj*="embeddablePanelAction-"]'); + for (const item of items) { + const currentText = await item.getVisibleText(); + if (currentText === text) { + return item; + } + } + + throw new Error(`No action matching text "${text}"`); + } })(); } diff --git a/test/functional/services/visualizations/pie_chart.ts b/test/functional/services/visualizations/pie_chart.ts index 66f32d246b31f..a25695a5bfcb7 100644 --- a/test/functional/services/visualizations/pie_chart.ts +++ b/test/functional/services/visualizations/pie_chart.ts @@ -28,10 +28,13 @@ export function PieChartProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const find = getService('find'); const defaultFindTimeout = config.get('timeouts.find'); + const panelActions = getService('dashboardPanelActions'); return new (class PieChart { - async filterOnPieSlice(name?: string) { - log.debug(`PieChart.filterOnPieSlice(${name})`); + private readonly filterActionText = 'Apply filter to current view'; + + async clickOnPieSlice(name?: string) { + log.debug(`PieChart.clickOnPieSlice(${name})`); if (name) { await testSubjects.click(`pieSlice-${name.split(' ').join('-')}`); } else { @@ -44,6 +47,16 @@ export function PieChartProvider({ getService }: FtrProviderContext) { } } + async filterOnPieSlice(name?: string) { + log.debug(`PieChart.filterOnPieSlice(${name})`); + await this.clickOnPieSlice(name); + const hasUiActionsPopup = await testSubjects.exists('multipleActionsContextMenu'); + if (hasUiActionsPopup) { + const actionElement = await panelActions.getActionWebElementByText(this.filterActionText); + await actionElement.click(); + } + } + async filterByLegendItem(label: string) { log.debug(`PieChart.filterByLegendItem(${label})`); await testSubjects.click(`legend-${label}`); diff --git a/x-pack/test/functional/apps/dashboard/drilldowns/dashboard_drilldowns.ts b/x-pack/test/functional/apps/dashboard/drilldowns/dashboard_drilldowns.ts index bcdd3d1f82e7d..29ead0db1c634 100644 --- a/x-pack/test/functional/apps/dashboard/drilldowns/dashboard_drilldowns.ts +++ b/x-pack/test/functional/apps/dashboard/drilldowns/dashboard_drilldowns.ts @@ -59,7 +59,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); // trigger drilldown action by clicking on a pie and picking drilldown action by it's name - await pieChart.filterOnPieSlice('40,000'); + await pieChart.clickOnPieSlice('40,000'); await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened(); const href = await dashboardDrilldownPanelActions.getActionHrefByText( diff --git a/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_chart_action.ts b/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_chart_action.ts index d9103f08ce36c..12363f8800c28 100644 --- a/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_chart_action.ts +++ b/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_chart_action.ts @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await common.navigateToApp('dashboard'); await dashboard.preserveCrossAppState(); await dashboard.loadSavedDashboard(drilldowns.DASHBOARD_WITH_PIE_CHART_NAME); - await pieChart.filterOnPieSlice('160,000'); + await pieChart.clickOnPieSlice('160,000'); await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened(); await testSubjects.existOrFail(ACTION_TEST_SUBJ); }); diff --git a/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_panel_action.ts b/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_panel_action.ts index 244cc831e06e1..fedc83a2f81c7 100644 --- a/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_panel_action.ts +++ b/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_panel_action.ts @@ -24,16 +24,23 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); describe('Explore underlying data - panel action', function () { - before(async () => { - await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash*' }); + before( + 'change default index pattern to verify action navigates to correct index pattern', + async () => { + await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash*' }); + } + ); + + before('start on Dashboard landing page', async () => { await common.navigateToApp('dashboard'); await dashboard.preserveCrossAppState(); }); - after(async () => { + after('set back default index pattern', async () => { await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' }); + }); - // Clean-up custom time range on panel + after('clean-up custom time range on panel', async () => { await common.navigateToApp('dashboard'); await dashboard.gotoDashboardEditMode(drilldowns.DASHBOARD_WITH_PIE_CHART_NAME); await panelActions.openContextMenu();