diff --git a/src/plugins/data/public/actions/select_range_action.ts b/src/plugins/data/public/actions/select_range_action.ts index 4882e8eafc0d3..18853f7e292f6 100644 --- a/src/plugins/data/public/actions/select_range_action.ts +++ b/src/plugins/data/public/actions/select_range_action.ts @@ -53,16 +53,16 @@ export function selectRangeAction( }); }, isCompatible, - execute: async ({ timeFieldName, data }: SelectRangeActionContext) => { - if (!(await isCompatible({ timeFieldName, data }))) { + execute: async ({ data }: SelectRangeActionContext) => { + if (!(await isCompatible({ data }))) { throw new IncompatibleActionError(); } const selectedFilters = await createFiltersFromRangeSelectAction(data); - if (timeFieldName) { + if (data.timeFieldName) { const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter( - timeFieldName, + data.timeFieldName, selectedFilters ); filterManager.addFilters(restOfFilters); diff --git a/src/plugins/data/public/actions/value_click_action.ts b/src/plugins/data/public/actions/value_click_action.ts index 210a58b3f75aa..17c1b1b1e1769 100644 --- a/src/plugins/data/public/actions/value_click_action.ts +++ b/src/plugins/data/public/actions/value_click_action.ts @@ -57,12 +57,12 @@ export function valueClickAction( }); }, isCompatible, - execute: async (context: ValueClickActionContext) => { - if (!(await isCompatible(context))) { + execute: async ({ data }: ValueClickActionContext) => { + if (!(await isCompatible({ data }))) { throw new IncompatibleActionError(); } - const filters: Filter[] = await createFiltersFromValueClickAction(context.data); + const filters: Filter[] = await createFiltersFromValueClickAction(data); let selectedFilters = filters; @@ -98,9 +98,9 @@ export function valueClickAction( selectedFilters = await filterSelectionPromise; } - if (context.timeFieldName) { + if (data.timeFieldName) { const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter( - context.timeFieldName, + data.timeFieldName, selectedFilters ); filterManager.addFilters(restOfFilters); diff --git a/src/plugins/embeddable/public/lib/triggers/triggers.ts b/src/plugins/embeddable/public/lib/triggers/triggers.ts index c097e3e8c13be..2b447c89e2850 100644 --- a/src/plugins/embeddable/public/lib/triggers/triggers.ts +++ b/src/plugins/embeddable/public/lib/triggers/triggers.ts @@ -27,7 +27,6 @@ export interface EmbeddableContext { export interface ValueClickTriggerContext { embeddable?: T; - timeFieldName?: string; data: { data: Array<{ table: Pick; @@ -35,6 +34,7 @@ export interface ValueClickTriggerContext { row: number; value: any; }>; + timeFieldName?: string; negate?: boolean; }; } @@ -45,11 +45,11 @@ export const isValueClickTriggerContext = ( export interface RangeSelectTriggerContext { embeddable?: T; - timeFieldName?: string; data: { table: KibanaDatatable; column: number; range: number[]; + timeFieldName?: string; }; } diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts index 9f0cfd7bf4d58..0306b943cbf2b 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -264,8 +264,7 @@ export class VisualizeEmbeddable extends Embeddable | undefined; -const emptyAction = { - id: 'empty-action', - type: '', - getDisplayName: () => 'empty action', - getIconType: () => undefined, - isCompatible: async () => true, - execute: async () => undefined, -} as ActionByType; - export const renderApp = ( coreStart: CoreStart, plugins: CanvasStartDeps, @@ -134,17 +121,6 @@ export const initializeCanvas = async ( }, }); - // TODO: We need this to disable the filtering modal from popping up in lens embeds until - // they honor the disableTriggers parameter - const action = startPlugins.uiActions.getAction(ACTION_VALUE_CLICK); - - if (action) { - restoreAction = action; - - startPlugins.uiActions.detachAction(VALUE_CLICK_TRIGGER, action.id); - startPlugins.uiActions.addTriggerAction(VALUE_CLICK_TRIGGER, emptyAction); - } - if (setupPlugins.usageCollection) { initStatsReporter(setupPlugins.usageCollection.reportUiStats); } @@ -158,12 +134,6 @@ export const teardownCanvas = (coreStart: CoreStart, startPlugins: CanvasStartDe resetInterpreter(); destroyStore(); - startPlugins.uiActions.detachAction(VALUE_CLICK_TRIGGER, emptyAction.id); - if (restoreAction) { - startPlugins.uiActions.addTriggerAction(VALUE_CLICK_TRIGGER, restoreAction); - restoreAction = undefined; - } - coreStart.chrome.setBadge(undefined); coreStart.chrome.setHelpExtension(undefined); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx index 381cad443e3ad..326cd551c7f84 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx @@ -42,14 +42,6 @@ export class FlyoutCreateDrilldownAction implements ActionByType -1; } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx index 1ead7a38d4c9b..c94d19d28e6da 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx @@ -134,10 +134,12 @@ describe('.execute() & getHref', () => { }; const context = ({ - data: useRangeEvent - ? ({ range: {} } as RangeSelectTriggerContext['data']) - : ({ data: [] } as ValueClickTriggerContext['data']), - timeFieldName: 'order_date', + data: { + ...(useRangeEvent + ? ({ range: {} } as RangeSelectTriggerContext['data']) + : ({ data: [] } as ValueClickTriggerContext['data'])), + timeFieldName: 'order_date', + }, embeddable: { getInput: () => ({ filters: [], diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx index d33dd1ef64e0d..21afa6e822dc5 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx @@ -127,9 +127,9 @@ export class DashboardToDashboardDrilldown } })(); - if (context.timeFieldName) { + if (context.data.timeFieldName) { const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter( - context.timeFieldName, + context.data.timeFieldName, filtersFromEvent ); filtersFromEvent = restOfFilters; diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json index d86a3180f64d9..a8b22b3e22750 100644 --- a/x-pack/plugins/lens/kibana.json +++ b/x-pack/plugins/lens/kibana.json @@ -9,10 +9,9 @@ "expressions", "navigation", "kibanaLegacy", - "uiActions", "visualizations", "dashboard" ], - "optionalPlugins": ["embeddable", "usageCollection", "taskManager"], + "optionalPlugins": ["embeddable", "usageCollection", "taskManager", "uiActions"], "configPath": ["xpack", "lens"] } diff --git a/x-pack/plugins/lens/public/datatable_visualization/expression.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/expression.test.tsx index 6d5b1153ad1bc..5407389c7fc4c 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/expression.test.tsx @@ -13,7 +13,7 @@ import { DatatableProps } from './expression'; import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks'; import { IFieldFormat } from '../../../../../src/plugins/data/public'; import { IAggType } from 'src/plugins/data/public'; -const executeTriggerActions = jest.fn(); +const onClickValue = jest.fn(); function sampleArgs() { const data: LensMultiTable = { @@ -66,7 +66,7 @@ describe('datatable_expression', () => { data={data} args={args} formatFactory={x => x as IFieldFormat} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} getType={jest.fn()} /> ) @@ -87,7 +87,7 @@ describe('datatable_expression', () => { }} args={args} formatFactory={x => x as IFieldFormat} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} getType={jest.fn(() => ({ type: 'buckets' } as IAggType))} /> ); @@ -97,18 +97,16 @@ describe('datatable_expression', () => { .first() .simulate('click'); - expect(executeTriggerActions).toHaveBeenCalledWith('VALUE_CLICK_TRIGGER', { - data: { - data: [ - { - column: 0, - row: 0, - table: data.tables.l1, - value: 10110, - }, - ], - negate: true, - }, + expect(onClickValue).toHaveBeenCalledWith({ + data: [ + { + column: 0, + row: 0, + table: data.tables.l1, + value: 10110, + }, + ], + negate: true, timeFieldName: undefined, }); }); @@ -127,7 +125,7 @@ describe('datatable_expression', () => { }} args={args} formatFactory={x => x as IFieldFormat} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} getType={jest.fn(() => ({ type: 'buckets' } as IAggType))} /> ); @@ -137,18 +135,16 @@ describe('datatable_expression', () => { .at(3) .simulate('click'); - expect(executeTriggerActions).toHaveBeenCalledWith('VALUE_CLICK_TRIGGER', { - data: { - data: [ - { - column: 1, - row: 0, - table: data.tables.l1, - value: 1588024800000, - }, - ], - negate: false, - }, + expect(onClickValue).toHaveBeenCalledWith({ + data: [ + { + column: 1, + row: 0, + table: data.tables.l1, + value: 1588024800000, + }, + ], + negate: false, timeFieldName: 'b', }); }); diff --git a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx index 71d29be1744bb..3be5c72d2af37 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx @@ -10,17 +10,17 @@ import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n/react'; import { EuiBasicTable, EuiFlexGroup, EuiButtonIcon, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { IAggType } from 'src/plugins/data/public'; -import { FormatFactory, LensMultiTable } from '../types'; +import { + FormatFactory, + ILensInterpreterRenderHandlers, + LensFilterEvent, + LensMultiTable, +} from '../types'; import { ExpressionFunctionDefinition, ExpressionRenderDefinition, - IInterpreterRenderHandlers, } from '../../../../../src/plugins/expressions/public'; import { VisualizationContainer } from '../visualization_container'; -import { ValueClickTriggerContext } from '../../../../../src/plugins/embeddable/public'; -import { VIS_EVENT_TO_TRIGGER } from '../../../../../src/plugins/visualizations/public'; -import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; -import { getExecuteTriggerActions } from '../services'; export interface DatatableColumns { columnIds: string[]; } @@ -37,7 +37,7 @@ export interface DatatableProps { type DatatableRenderProps = DatatableProps & { formatFactory: FormatFactory; - executeTriggerActions: UiActionsStart['executeTriggerActions']; + onClickValue: (data: LensFilterEvent['data']) => void; getType: (name: string) => IAggType; }; @@ -125,17 +125,19 @@ export const getDatatableRenderer = (dependencies: { render: async ( domNode: Element, config: DatatableProps, - handlers: IInterpreterRenderHandlers + handlers: ILensInterpreterRenderHandlers ) => { const resolvedFormatFactory = await dependencies.formatFactory; - const executeTriggerActions = getExecuteTriggerActions(); const resolvedGetType = await dependencies.getType; + const onClickValue = (data: LensFilterEvent['data']) => { + handlers.event({ name: 'filter', data }); + }; ReactDOM.render( , @@ -162,21 +164,19 @@ export function DatatableComponent(props: DatatableRenderProps) { const timeFieldName = negate && isDateHistogram ? undefined : col?.meta?.aggConfigParams?.field; const rowIndex = firstTable.rows.findIndex(row => row[field] === value); - const context: ValueClickTriggerContext = { - data: { - negate, - data: [ - { - row: rowIndex, - column: colIndex, - value, - table: firstTable, - }, - ], - }, + const data: LensFilterEvent['data'] = { + negate, + data: [ + { + row: rowIndex, + column: colIndex, + value, + table: firstTable, + }, + ], timeFieldName, }; - props.executeTriggerActions(VIS_EVENT_TO_TRIGGER.filter, context); + props.onClickValue(data); }; return ( diff --git a/x-pack/plugins/lens/public/datatable_visualization/index.ts b/x-pack/plugins/lens/public/datatable_visualization/index.ts index 44894d31da51d..5cc3c40591c3f 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/index.ts +++ b/x-pack/plugins/lens/public/datatable_visualization/index.ts @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup, CoreStart } from 'kibana/public'; +import { CoreSetup } from 'kibana/public'; import { datatableVisualization } from './visualization'; import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public'; import { datatable, datatableColumns, getDatatableRenderer } from './expression'; import { EditorFrameSetup, FormatFactory } from '../types'; -import { setExecuteTriggerActions } from '../services'; import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; @@ -42,7 +41,4 @@ export class DatatableVisualization { ); editorFrame.registerVisualization(datatableVisualization); } - start(core: CoreStart, { uiActions }: DatatableVisualizationPluginStartPlugins) { - setExecuteTriggerActions(uiActions.executeTriggerActions); - } } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx index 78b038cf702f8..f9c5668ca1e06 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx @@ -21,6 +21,9 @@ import { import { ReactExpressionRendererType } from 'src/plugins/expressions/public'; import { DragDrop } from '../../drag_drop'; import { FrameLayout } from './frame_layout'; +import { uiActionsPluginMock } from '../../../../../../src/plugins/ui_actions/public/mocks'; +import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; +import { expressionsPluginMock } from '../../../../../../src/plugins/expressions/public/mocks'; function generateSuggestion(state = {}): DatasourceSuggestion { return { @@ -48,6 +51,11 @@ function getDefaultProps() { query: { query: '', language: 'lucene' }, filters: [], core: coreMock.createSetup(), + plugins: { + uiActions: uiActionsPluginMock.createStartContract(), + data: dataPluginMock.createStartContract(), + expressions: expressionsPluginMock.createStartContract(), + }, }; } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index 6da9a94711081..06d417ad18d54 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -26,6 +26,7 @@ import { getSavedObjectFormat } from './save'; import { WorkspacePanelWrapper } from './workspace_panel_wrapper'; import { generateId } from '../../id_generator'; import { Filter, Query, SavedQuery } from '../../../../../../src/plugins/data/public'; +import { EditorFrameStartPlugins } from '../service'; export interface EditorFrameProps { doc?: Document; @@ -36,6 +37,7 @@ export interface EditorFrameProps { ExpressionRenderer: ReactExpressionRendererType; onError: (e: { message: string }) => void; core: CoreSetup | CoreStart; + plugins: EditorFrameStartPlugins; dateRange: { fromDate: string; toDate: string; @@ -285,6 +287,7 @@ export function EditorFrame(props: EditorFrameProps) { dispatch={dispatch} ExpressionRenderer={props.ExpressionRenderer} core={props.core} + plugins={props.plugins} /> ) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts index 1f62929783b63..71aabaae3c65c 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts @@ -9,6 +9,9 @@ import { EditorFrameProps } from './index'; import { Datasource, Visualization } from '../../types'; import { createExpressionRendererMock } from '../mocks'; import { coreMock } from 'src/core/public/mocks'; +import { uiActionsPluginMock } from '../../../../../../src/plugins/ui_actions/public/mocks'; +import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; +import { expressionsPluginMock } from '../../../../../../src/plugins/expressions/public/mocks'; describe('editor_frame state management', () => { describe('initialization', () => { @@ -24,6 +27,11 @@ describe('editor_frame state management', () => { ExpressionRenderer: createExpressionRendererMock(), onChange: jest.fn(), core: coreMock.createSetup(), + plugins: { + uiActions: uiActionsPluginMock.createStartContract(), + data: dataPluginMock.createStartContract(), + expressions: expressionsPluginMock.createStartContract(), + }, dateRange: { fromDate: 'now-7d', toDate: 'now' }, query: { query: '', language: 'lucene' }, filters: [], diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx index 33ecee53fa3bc..a20626ebaaad7 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx @@ -22,6 +22,10 @@ import { DragDrop, ChildDragDropProvider } from '../../drag_drop'; import { Ast } from '@kbn/interpreter/common'; import { coreMock } from 'src/core/public/mocks'; import { esFilters, IFieldType, IIndexPattern } from '../../../../../../src/plugins/data/public'; +import { TriggerId, UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; +import { uiActionsPluginMock } from '../../../../../../src/plugins/ui_actions/public/mocks'; +import { TriggerContract } from '../../../../../../src/plugins/ui_actions/public/triggers'; +import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public/embeddable'; describe('workspace_panel', () => { let mockVisualization: jest.Mocked; @@ -29,10 +33,15 @@ describe('workspace_panel', () => { let mockDatasource: DatasourceMock; let expressionRendererMock: jest.Mock; + let uiActionsMock: jest.Mocked; + let trigger: jest.Mocked>; let instance: ReactWrapper; beforeEach(() => { + trigger = ({ exec: jest.fn() } as unknown) as jest.Mocked>; + uiActionsMock = uiActionsPluginMock.createStartContract(); + uiActionsMock.getTrigger.mockReturnValue(trigger); mockVisualization = createMockVisualization(); mockVisualization2 = createMockVisualization(); @@ -60,6 +69,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} + plugins={{ uiActions: uiActionsMock }} /> ); @@ -82,6 +92,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} + plugins={{ uiActions: uiActionsMock }} /> ); @@ -104,6 +115,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} + plugins={{ uiActions: uiActionsMock }} /> ); @@ -140,6 +152,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} + plugins={{ uiActions: uiActionsMock }} /> ); @@ -198,6 +211,48 @@ describe('workspace_panel', () => { `); }); + it('should execute a trigger on expression event', () => { + const framePublicAPI = createMockFramePublicAPI(); + framePublicAPI.datasourceLayers = { + first: mockDatasource.publicAPIMock, + }; + mockDatasource.toExpression.mockReturnValue('datasource'); + mockDatasource.getLayers.mockReturnValue(['first']); + + instance = mount( + 'vis' }, + }} + visualizationState={{}} + dispatch={() => {}} + ExpressionRenderer={expressionRendererMock} + core={coreMock.createSetup()} + plugins={{ uiActions: uiActionsMock }} + /> + ); + + const onEvent = expressionRendererMock.mock.calls[0][0].onEvent!; + + const eventData = {}; + onEvent({ name: 'brush', data: eventData }); + + expect(uiActionsMock.getTrigger).toHaveBeenCalledWith(VIS_EVENT_TO_TRIGGER.brush); + expect(trigger.exec).toHaveBeenCalledWith({ data: eventData }); + }); + it('should include data fetching for each layer in the expression', () => { const mockDatasource2 = createMockDatasource('a'); const framePublicAPI = createMockFramePublicAPI(); @@ -237,6 +292,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} + plugins={{ uiActions: uiActionsMock }} /> ); @@ -316,6 +372,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} + plugins={{ uiActions: uiActionsMock }} /> ); }); @@ -370,6 +427,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} + plugins={{ uiActions: uiActionsMock }} /> ); }); @@ -424,6 +482,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} + plugins={{ uiActions: uiActionsMock }} /> ); @@ -461,6 +520,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} + plugins={{ uiActions: uiActionsMock }} /> ); }); @@ -504,6 +564,7 @@ describe('workspace_panel', () => { dispatch={() => {}} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} + plugins={{ uiActions: uiActionsMock }} /> ); }); @@ -559,6 +620,7 @@ describe('workspace_panel', () => { dispatch={mockDispatch} ExpressionRenderer={expressionRendererMock} core={coreMock.createSetup()} + plugins={{ uiActions: uiActionsMock }} /> ); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx index e246d8e27a708..b000fc7fa0176 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx @@ -17,14 +17,25 @@ import { EuiButtonEmpty, } from '@elastic/eui'; import { CoreStart, CoreSetup } from 'kibana/public'; -import { ReactExpressionRendererType } from '../../../../../../src/plugins/expressions/public'; +import { + ExpressionRendererEvent, + ReactExpressionRendererType, +} from '../../../../../../src/plugins/expressions/public'; import { Action } from './state_management'; -import { Datasource, Visualization, FramePublicAPI } from '../../types'; +import { + Datasource, + Visualization, + FramePublicAPI, + isLensBrushEvent, + isLensFilterEvent, +} from '../../types'; import { DragDrop, DragContext } from '../../drag_drop'; import { getSuggestions, switchToSuggestion } from './suggestion_helpers'; import { buildExpression } from './expression_helpers'; import { debouncedComponent } from '../../debounced_component'; import { trackUiEvent } from '../../lens_ui_telemetry'; +import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; +import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public'; export interface WorkspacePanelProps { activeVisualizationId: string | null; @@ -43,6 +54,7 @@ export interface WorkspacePanelProps { dispatch: (action: Action) => void; ExpressionRenderer: ReactExpressionRendererType; core: CoreStart | CoreSetup; + plugins: { uiActions?: UiActionsStart }; } export const WorkspacePanel = debouncedComponent(InnerWorkspacePanel); @@ -58,6 +70,7 @@ export function InnerWorkspacePanel({ framePublicAPI, dispatch, core, + plugins, ExpressionRenderer: ExpressionRendererComponent, }: WorkspacePanelProps) { const IS_DARK_THEME = core.uiSettings.get('theme:darkMode'); @@ -211,6 +224,22 @@ export function InnerWorkspacePanel({ className="lnsExpressionRenderer__component" padding="m" expression={expression!} + onEvent={(event: ExpressionRendererEvent) => { + if (!plugins.uiActions) { + // ui actions not available, not handling event... + return; + } + if (isLensBrushEvent(event)) { + plugins.uiActions.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ + data: event.data, + }); + } + if (isLensFilterEvent(event)) { + plugins.uiActions.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ + data: event.data, + }); + } + }} renderError={(errorMessage?: string | null) => { return ( diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx index 8d95540b3e8b5..4e5b32ad7f7a3 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx @@ -10,6 +10,7 @@ import { ReactExpressionRendererProps } from 'src/plugins/expressions/public'; import { Query, TimeRange, Filter, TimefilterContract } from 'src/plugins/data/public'; import { Document } from '../../persistence'; import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; +import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public/embeddable'; jest.mock('../../../../../../src/plugins/inspector/public/', () => ({ isAvailable: false, @@ -34,10 +35,14 @@ const savedVis: Document = { describe('embeddable', () => { let mountpoint: HTMLDivElement; let expressionRenderer: jest.Mock; + let getTrigger: jest.Mock; + let trigger: { exec: jest.Mock }; beforeEach(() => { mountpoint = document.createElement('div'); expressionRenderer = jest.fn(_props => null); + trigger = { exec: jest.fn() }; + getTrigger = jest.fn(() => trigger); }); afterEach(() => { @@ -48,6 +53,7 @@ describe('embeddable', () => { const embeddable = new Embeddable( dataPluginMock.createSetupContract().query.timefilter.timefilter, expressionRenderer, + getTrigger, { editPath: '', editUrl: '', @@ -70,6 +76,7 @@ describe('embeddable', () => { const embeddable = new Embeddable( dataPluginMock.createSetupContract().query.timefilter.timefilter, expressionRenderer, + getTrigger, { editPath: '', editUrl: '', @@ -97,6 +104,7 @@ describe('embeddable', () => { const embeddable = new Embeddable( dataPluginMock.createSetupContract().query.timefilter.timefilter, expressionRenderer, + getTrigger, { editPath: '', editUrl: '', @@ -114,6 +122,32 @@ describe('embeddable', () => { }); }); + it('should execute trigger on event from expression renderer', () => { + const embeddable = new Embeddable( + dataPluginMock.createSetupContract().query.timefilter.timefilter, + expressionRenderer, + getTrigger, + { + editPath: '', + editUrl: '', + editable: true, + savedVis, + }, + { id: '123' } + ); + embeddable.render(mountpoint); + + const onEvent = expressionRenderer.mock.calls[0][0].onEvent!; + + const eventData = {}; + onEvent({ name: 'brush', data: eventData }); + + expect(getTrigger).toHaveBeenCalledWith(VIS_EVENT_TO_TRIGGER.brush); + expect(trigger.exec).toHaveBeenCalledWith( + expect.objectContaining({ data: eventData, embeddable: expect.anything() }) + ); + }); + it('should not re-render if only change is in disabled filter', () => { const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; const query: Query = { language: 'kquery', query: '' }; @@ -122,6 +156,7 @@ describe('embeddable', () => { const embeddable = new Embeddable( dataPluginMock.createSetupContract().query.timefilter.timefilter, expressionRenderer, + getTrigger, { editPath: '', editUrl: '', @@ -154,6 +189,7 @@ describe('embeddable', () => { const embeddable = new Embeddable( timefilter, expressionRenderer, + getTrigger, { editPath: '', editUrl: '', diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx index 559854cbab39a..796cf5b32e3ba 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx @@ -8,25 +8,30 @@ import _ from 'lodash'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { - Query, - TimeRange, Filter, IIndexPattern, + Query, TimefilterContract, + TimeRange, } from 'src/plugins/data/public'; import { Subscription } from 'rxjs'; -import { ReactExpressionRendererType } from '../../../../../../src/plugins/expressions/public'; +import { + ExpressionRendererEvent, + ReactExpressionRendererType, +} from '../../../../../../src/plugins/expressions/public'; import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public'; import { Embeddable as AbstractEmbeddable, + EmbeddableInput, EmbeddableOutput, IContainer, - EmbeddableInput, } from '../../../../../../src/plugins/embeddable/public'; -import { Document, DOC_TYPE } from '../../persistence'; +import { DOC_TYPE, Document } from '../../persistence'; import { ExpressionWrapper } from './expression_wrapper'; +import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; +import { isLensBrushEvent, isLensFilterEvent } from '../../types'; export interface LensEmbeddableConfiguration { savedVis: Document; @@ -50,6 +55,7 @@ export class Embeddable extends AbstractEmbeddable this.onContainerStateChanged(input)); @@ -100,6 +108,9 @@ export class Embeddable extends AbstractEmbeddable, domNode ); } + handleEvent = (event: ExpressionRendererEvent) => { + if (!this.getTrigger || this.input.disableTriggers) { + return; + } + if (isLensBrushEvent(event)) { + this.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ + data: event.data, + embeddable: this, + }); + } + if (isLensFilterEvent(event)) { + this.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ + data: event.data, + embeddable: this, + }); + } + }; + destroy() { super.destroy(); if (this.domNode) { diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts index 852d377915856..c23d44aa8e4b6 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts @@ -26,6 +26,7 @@ import { import { Embeddable } from './embeddable'; import { SavedObjectIndexStore, DOC_TYPE } from '../../persistence'; import { getEditPath } from '../../../common'; +import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; interface StartServices { timefilter: TimefilterContract; @@ -34,6 +35,7 @@ interface StartServices { savedObjectsClient: SavedObjectsClientContract; expressionRenderer: ReactExpressionRendererType; indexPatternService: IndexPatternsContract; + uiActions?: UiActionsStart; } export class EmbeddableFactory implements EmbeddableFactoryDefinition { @@ -74,6 +76,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition { indexPatternService, timefilter, expressionRenderer, + uiActions, } = await this.getStartServices(); const store = new SavedObjectIndexStore(savedObjectsClient); const savedVis = await store.load(savedObjectId); @@ -99,6 +102,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition { return new Embeddable( timefilter, expressionRenderer, + uiActions?.getTrigger, { savedVis, editPath: getEditPath(savedObjectId), diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx index 49c91affe3dc4..41706121830cb 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx @@ -9,7 +9,10 @@ import { I18nProvider } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiText, EuiIcon } from '@elastic/eui'; import { TimeRange, Filter, Query } from 'src/plugins/data/public'; -import { ReactExpressionRendererType } from 'src/plugins/expressions/public'; +import { + ExpressionRendererEvent, + ReactExpressionRendererType, +} from 'src/plugins/expressions/public'; export interface ExpressionWrapperProps { ExpressionRenderer: ReactExpressionRendererType; @@ -20,12 +23,14 @@ export interface ExpressionWrapperProps { filters?: Filter[]; lastReloadRequestTime?: number; }; + handleEvent: (event: ExpressionRendererEvent) => void; } export function ExpressionWrapper({ ExpressionRenderer: ExpressionRendererComponent, expression, context, + handleEvent, }: ExpressionWrapperProps) { return ( @@ -51,6 +56,7 @@ export function ExpressionWrapper({ expression={expression} searchContext={{ ...context }} renderError={error =>
{error}
} + onEvent={handleEvent} /> )} diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.tsx index 15fe449d6563b..a815e70c58629 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.tsx @@ -26,6 +26,7 @@ import { mergeTables } from './merge_tables'; import { formatColumn } from './format_column'; import { EmbeddableFactory } from './embeddable/embeddable_factory'; import { getActiveDatasourceIdFromDoc } from './editor_frame/state_management'; +import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; export interface EditorFrameSetupPlugins { data: DataPublicPluginSetup; @@ -37,6 +38,7 @@ export interface EditorFrameStartPlugins { data: DataPublicPluginStart; embeddable?: EmbeddableStart; expressions: ExpressionsStart; + uiActions?: UiActionsStart; } async function collectAsyncDefinitions( @@ -73,6 +75,7 @@ export class EditorFrameService { timefilter: deps.data.query.timefilter.timefilter, expressionRenderer: deps.expressions.ReactExpressionRenderer, indexPatternService: deps.data.indexPatterns, + uiActions: deps.uiActions, }; }; @@ -116,6 +119,7 @@ export class EditorFrameService { (doc && doc.visualizationType) || firstVisualizationId || null } core={core} + plugins={plugins} ExpressionRenderer={plugins.expressions.ReactExpressionRenderer} doc={doc} dateRange={dateRange} diff --git a/x-pack/plugins/lens/public/pie_visualization/index.ts b/x-pack/plugins/lens/public/pie_visualization/index.ts index b2aae2e8529a5..dd828c6c35300 100644 --- a/x-pack/plugins/lens/public/pie_visualization/index.ts +++ b/x-pack/plugins/lens/public/pie_visualization/index.ts @@ -5,13 +5,12 @@ */ import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; -import { CoreSetup, CoreStart } from 'src/core/public'; +import { CoreSetup } from 'src/core/public'; import { ExpressionsSetup } from 'src/plugins/expressions/public'; import { pieVisualization } from './pie_visualization'; import { pie, getPieRenderer } from './register_expression'; import { EditorFrameSetup, FormatFactory } from '../types'; import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; -import { setExecuteTriggerActions } from '../services'; export interface PieVisualizationPluginSetupPlugins { editorFrame: EditorFrameSetup; @@ -44,10 +43,4 @@ export class PieVisualization { editorFrame.registerVisualization(pieVisualization); } - - start(core: CoreStart, { uiActions }: PieVisualizationPluginStartPlugins) { - setExecuteTriggerActions(uiActions.executeTriggerActions); - } - - stop() {} } diff --git a/x-pack/plugins/lens/public/pie_visualization/register_expression.tsx b/x-pack/plugins/lens/public/pie_visualization/register_expression.tsx index 7babf7ed7ff46..bbc6a1dc75c3a 100644 --- a/x-pack/plugins/lens/public/pie_visualization/register_expression.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/register_expression.tsx @@ -14,9 +14,8 @@ import { ExpressionRenderDefinition, ExpressionFunctionDefinition, } from 'src/plugins/expressions/public'; -import { LensMultiTable, FormatFactory } from '../types'; +import { LensMultiTable, FormatFactory, LensFilterEvent } from '../types'; import { PieExpressionProps, PieExpressionArgs } from './types'; -import { getExecuteTriggerActions } from '../services'; import { PieComponent } from './render_function'; export interface PieRender { @@ -109,7 +108,9 @@ export const getPieRenderer = (dependencies: { config: PieExpressionProps, handlers: IInterpreterRenderHandlers ) => { - const executeTriggerActions = getExecuteTriggerActions(); + const onClickValue = (data: LensFilterEvent['data']) => { + handlers.event({ name: 'filter', data }); + }; const formatFactory = await dependencies.formatFactory; ReactDOM.render( @@ -117,7 +118,7 @@ export const getPieRenderer = (dependencies: { {...config} {...dependencies} formatFactory={formatFactory} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} isDarkMode={dependencies.isDarkMode} /> , diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx index b0d4e0d2cc52b..a914efcead005 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { Settings } from '@elastic/charts'; +import { SeriesIdentifier, Settings } from '@elastic/charts'; import { shallow } from 'enzyme'; import { LensMultiTable } from '../types'; import { PieComponent } from './render_function'; @@ -59,7 +59,7 @@ describe('PieVisualization component', () => { formatFactory: getFormatSpy, isDarkMode: false, chartTheme: {}, - executeTriggerActions: jest.fn(), + onClickValue: jest.fn(), }; } @@ -111,6 +111,58 @@ describe('PieVisualization component', () => { expect(component.find(Settings).prop('legendMaxDepth')).toBeUndefined(); }); + test('it calls filter callback with the given context', () => { + const defaultArgs = getDefaultArgs(); + const component = shallow(); + component + .find(Settings) + .first() + .prop('onElementClick')!([[[{ groupByRollup: 6, value: 6 }], {} as SeriesIdentifier]]); + + expect(defaultArgs.onClickValue.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "data": Array [ + Object { + "column": 0, + "row": 0, + "table": Object { + "columns": Array [ + Object { + "id": "a", + "name": "a", + }, + Object { + "id": "b", + "name": "b", + }, + Object { + "id": "c", + "name": "c", + }, + ], + "rows": Array [ + Object { + "a": 6, + "b": 2, + "c": "I", + "d": "Row 1", + }, + Object { + "a": 1, + "b": 5, + "c": "J", + "d": "Row 2", + }, + ], + "type": "kibana_datatable", + }, + "value": 6, + }, + ], + } + `); + }); + test('it shows emptyPlaceholder for undefined grouped data', () => { const defaultData = getDefaultArgs().data; const emptyData: LensMultiTable = { diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx index 56019b3e6c891..d812803272f3e 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx @@ -24,12 +24,10 @@ import { RecursivePartial, LayerValue, } from '@elastic/charts'; -import { VIS_EVENT_TO_TRIGGER } from '../../../../../src/plugins/visualizations/public'; -import { FormatFactory } from '../types'; +import { FormatFactory, LensFilterEvent } from '../types'; import { VisualizationContainer } from '../visualization_container'; import { CHART_NAMES, DEFAULT_PERCENT_DECIMALS } from './constants'; import { ColumnGroups, PieExpressionProps } from './types'; -import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; import { getSliceValueWithFallback, getFilterContext } from './render_helpers'; import { EmptyPlaceholder } from '../shared_components'; import './visualization.scss'; @@ -43,13 +41,13 @@ export function PieComponent( formatFactory: FormatFactory; chartTheme: Exclude; isDarkMode: boolean; - executeTriggerActions: UiActionsStart['executeTriggerActions']; + onClickValue: (data: LensFilterEvent['data']) => void; } ) { const [firstTable] = Object.values(props.data.tables); const formatters: Record> = {}; - const { chartTheme, isDarkMode, executeTriggerActions } = props; + const { chartTheme, isDarkMode, onClickValue } = props; const { shape, groups, @@ -246,7 +244,7 @@ export function PieComponent( firstTable ); - executeTriggerActions(VIS_EVENT_TO_TRIGGER.filter, context); + onClickValue(context); }} /> { ], }; expect(getFilterContext([{ groupByRollup: 'Test', value: 100 }], ['a'], table)).toEqual({ - data: { - data: [ - { - row: 1, - column: 0, - value: 'Test', - table, - }, - ], - }, + data: [ + { + row: 1, + column: 0, + value: 'Test', + table, + }, + ], }); }); @@ -124,16 +122,14 @@ describe('render helpers', () => { ], }; expect(getFilterContext([{ groupByRollup: 'Test', value: 100 }], ['a', 'b'], table)).toEqual({ - data: { - data: [ - { - row: 1, - column: 0, - value: 'Test', - table, - }, - ], - }, + data: [ + { + row: 1, + column: 0, + value: 'Test', + table, + }, + ], }); }); @@ -161,22 +157,20 @@ describe('render helpers', () => { table ) ).toEqual({ - data: { - data: [ - { - row: 1, - column: 0, - value: 'Test', - table, - }, - { - row: 1, - column: 1, - value: 'Two', - table, - }, - ], - }, + data: [ + { + row: 1, + column: 0, + value: 'Test', + table, + }, + { + row: 1, + column: 1, + value: 'Two', + table, + }, + ], }); }); }); diff --git a/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts b/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts index bc3c29ba0fff1..3f7494661c049 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts +++ b/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts @@ -6,8 +6,8 @@ import { Datum, LayerValue } from '@elastic/charts'; import { KibanaDatatable, KibanaDatatableColumn } from 'src/plugins/expressions/public'; -import { ValueClickTriggerContext } from '../../../../../src/plugins/embeddable/public'; import { ColumnGroups } from './types'; +import { LensFilterEvent } from '../types'; export function getSliceValueWithFallback( d: Datum, @@ -28,7 +28,7 @@ export function getFilterContext( clickedLayers: LayerValue[], layerColumnIds: string[], table: KibanaDatatable -): ValueClickTriggerContext { +): LensFilterEvent['data'] { const matchingIndex = table.rows.findIndex(row => clickedLayers.every((layer, index) => { const columnId = layerColumnIds[index]; @@ -37,13 +37,11 @@ export function getFilterContext( ); return { - data: { - data: clickedLayers.map((clickedLayer, index) => ({ - column: table.columns.findIndex(col => col.id === layerColumnIds[index]), - row: matchingIndex, - value: clickedLayer.groupByRollup, - table, - })), - }, + data: clickedLayers.map((clickedLayer, index) => ({ + column: table.columns.findIndex(col => col.id === layerColumnIds[index]), + row: matchingIndex, + value: clickedLayer.groupByRollup, + table, + })), }; } diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index b2309657967f1..f9a577e001c64 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -103,9 +103,6 @@ export class LensPlugin { start(core: CoreStart, startDependencies: LensPluginStartDependencies) { this.createEditorFrame = this.editorFrameService.start(core, startDependencies).createInstance; - this.xyVisualization.start(core, startDependencies); - this.datatableVisualization.start(core, startDependencies); - this.pieVisualization.start(core, startDependencies); } stop() { diff --git a/x-pack/plugins/lens/public/services.ts b/x-pack/plugins/lens/public/services.ts deleted file mode 100644 index a66743dde2661..0000000000000 --- a/x-pack/plugins/lens/public/services.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { createGetterSetter } from '../../../../src/plugins/kibana_utils/public'; -import { UiActionsStart } from '../../../../src/plugins/ui_actions/public'; - -export const [getExecuteTriggerActions, setExecuteTriggerActions] = createGetterSetter< - UiActionsStart['executeTriggerActions'] ->('executeTriggerActions'); diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 04efc642793b0..42dcce0e438d7 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -7,11 +7,21 @@ import { Ast } from '@kbn/interpreter/common'; import { IconType } from '@elastic/eui/src/components/icon/icon'; import { CoreSetup } from 'kibana/public'; -import { KibanaDatatable, SerializedFieldFormat } from '../../../../src/plugins/expressions/public'; +import { + ExpressionRendererEvent, + IInterpreterRenderHandlers, + KibanaDatatable, + SerializedFieldFormat, +} from '../../../../src/plugins/expressions/public'; import { DragContextState } from './drag_drop'; import { Document } from './persistence'; import { DateRange } from '../common'; import { Query, Filter, SavedQuery, IFieldFormat } from '../../../../src/plugins/data/public'; +import { + SELECT_RANGE_TRIGGER, + TriggerContext, + VALUE_CLICK_TRIGGER, +} from '../../../../src/plugins/ui_actions/public'; export type ErrorCallback = (e: { message: string }) => void; @@ -467,3 +477,29 @@ export interface Visualization { */ toPreviewExpression?: (state: T, frame: FramePublicAPI) => Ast | string | null; } + +export interface LensFilterEvent { + name: 'filter'; + data: TriggerContext['data']; +} +export interface LensBrushEvent { + name: 'brush'; + data: TriggerContext['data']; +} + +export function isLensFilterEvent(event: ExpressionRendererEvent): event is LensFilterEvent { + return event.name === 'filter'; +} + +export function isLensBrushEvent(event: ExpressionRendererEvent): event is LensBrushEvent { + return event.name === 'brush'; +} + +/** + * Expression renderer handlers specifically for lens renderers. This is a narrowed down + * version of the general render handlers, specifying supported event types. If this type is + * used, dispatched events will be handled correctly. + */ +export interface ILensInterpreterRenderHandlers extends IInterpreterRenderHandlers { + event: (event: LensFilterEvent | LensBrushEvent) => void; +} diff --git a/x-pack/plugins/lens/public/xy_visualization/index.ts b/x-pack/plugins/lens/public/xy_visualization/index.ts index 9a0819d4f01c4..23cf9e7ff818f 100644 --- a/x-pack/plugins/lens/public/xy_visualization/index.ts +++ b/x-pack/plugins/lens/public/xy_visualization/index.ts @@ -5,15 +5,13 @@ */ import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; -import { CoreSetup, IUiSettingsClient, CoreStart } from 'kibana/public'; +import { CoreSetup, IUiSettingsClient } from 'kibana/public'; import moment from 'moment-timezone'; import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public'; import { xyVisualization } from './xy_visualization'; import { xyChart, getXyChartRenderer } from './xy_expression'; import { legendConfig, xConfig, layerConfig } from './types'; import { EditorFrameSetup, FormatFactory } from '../types'; -import { setExecuteTriggerActions } from '../services'; -import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; export interface XyVisualizationPluginSetupPlugins { expressions: ExpressionsSetup; @@ -21,10 +19,6 @@ export interface XyVisualizationPluginSetupPlugins { editorFrame: EditorFrameSetup; } -interface XyVisualizationPluginStartPlugins { - uiActions: UiActionsStart; -} - function getTimeZone(uiSettings: IUiSettingsClient) { const configuredTimeZone = uiSettings.get('dateFormat:tz'); if (configuredTimeZone === 'Browser') { @@ -59,7 +53,4 @@ export class XyVisualization { editorFrame.registerVisualization(xyVisualization); } - start(core: CoreStart, { uiActions }: XyVisualizationPluginStartPlugins) { - setExecuteTriggerActions(uiActions.executeTriggerActions); - } } diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx index 0f9aa1c10e127..72e51b175543c 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx @@ -25,7 +25,8 @@ import { XYArgs, LegendConfig, legendConfig, layerConfig, LayerArgs } from './ty import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -const executeTriggerActions = jest.fn(); +const onClickValue = jest.fn(); +const onSelectRange = jest.fn(); const dateHistogramData: LensMultiTable = { type: 'lens_multitable', @@ -296,7 +297,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); expect(component).toMatchSnapshot(); @@ -344,7 +346,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); expect(component.find(Settings).prop('xDomain')).toMatchInlineSnapshot(` @@ -379,7 +382,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); @@ -415,7 +419,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); @@ -452,7 +457,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); @@ -496,7 +502,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); @@ -530,7 +537,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); expect(component.find(Settings).prop('xDomain')).toBeUndefined(); @@ -546,7 +554,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); expect(component).toMatchSnapshot(); @@ -563,7 +572,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); expect(component).toMatchSnapshot(); @@ -580,7 +590,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); expect(component).toMatchSnapshot(); @@ -602,7 +613,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); @@ -611,12 +623,10 @@ describe('xy_expression', () => { .first() .prop('onBrushEnd')!({ x: [1585757732783, 1585758880838] }); - expect(executeTriggerActions).toHaveBeenCalledWith('SELECT_RANGE_TRIGGER', { - data: { - column: 0, - table: dateHistogramData.tables.timeLayer, - range: [1585757732783, 1585758880838], - }, + expect(onSelectRange).toHaveBeenCalledWith({ + column: 0, + table: dateHistogramData.tables.timeLayer, + range: [1585757732783, 1585758880838], timeFieldName: 'order_date', }); }); @@ -656,7 +666,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); @@ -665,23 +676,21 @@ describe('xy_expression', () => { .first() .prop('onElementClick')!([[geometry, series as XYChartSeriesIdentifier]]); - expect(executeTriggerActions).toHaveBeenCalledWith('VALUE_CLICK_TRIGGER', { - data: { - data: [ - { - column: 1, - row: 1, - table: data.tables.first, - value: 5, - }, - { - column: 1, - row: 0, - table: data.tables.first, - value: 2, - }, - ], - }, + expect(onClickValue).toHaveBeenCalledWith({ + data: [ + { + column: 1, + row: 1, + table: data.tables.first, + value: 5, + }, + { + column: 1, + row: 0, + table: data.tables.first, + value: 2, + }, + ], }); }); @@ -695,7 +704,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); expect(component).toMatchSnapshot(); @@ -713,7 +723,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); expect(component).toMatchSnapshot(); @@ -734,7 +745,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); expect(component).toMatchSnapshot(); @@ -753,7 +765,8 @@ describe('xy_expression', () => { timeZone="CEST" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); expect(component.find(LineSeries).prop('timeZone')).toEqual('CEST'); @@ -771,7 +784,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); expect(component.find(BarSeries).prop('enableHistogramMode')).toEqual(true); @@ -796,7 +810,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); expect(component.find(BarSeries).prop('enableHistogramMode')).toEqual(true); @@ -815,7 +830,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); expect(component.find(BarSeries).prop('enableHistogramMode')).toEqual(false); @@ -876,7 +892,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); }; @@ -1071,7 +1088,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); expect(component.find(LineSeries).prop('xScaleType')).toEqual(ScaleType.Ordinal); @@ -1088,7 +1106,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); expect(component.find(LineSeries).prop('yScaleType')).toEqual(ScaleType.Sqrt); @@ -1105,7 +1124,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); @@ -1123,7 +1143,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); @@ -1141,7 +1162,8 @@ describe('xy_expression', () => { chartTheme={{}} histogramBarTarget={50} timeZone="UTC" - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); expect(getFormatSpy).toHaveBeenCalledWith({ @@ -1161,7 +1183,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); @@ -1248,7 +1271,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); @@ -1302,7 +1326,8 @@ describe('xy_expression', () => { timeZone="UTC" chartTheme={{}} histogramBarTarget={50} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> ); diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx index d598b9c740655..cb2defbc54f49 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx @@ -21,24 +21,22 @@ import { } from '@elastic/charts'; import { I18nProvider } from '@kbn/i18n/react'; import { - IInterpreterRenderHandlers, - ExpressionRenderDefinition, ExpressionFunctionDefinition, + ExpressionRenderDefinition, ExpressionValueSearchContext, } from 'src/plugins/expressions/public'; import { IconType } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { - ValueClickTriggerContext, - RangeSelectTriggerContext, -} from '../../../../../src/plugins/embeddable/public'; -import { VIS_EVENT_TO_TRIGGER } from '../../../../../src/plugins/visualizations/public'; -import { LensMultiTable, FormatFactory } from '../types'; + LensMultiTable, + FormatFactory, + ILensInterpreterRenderHandlers, + LensFilterEvent, + LensBrushEvent, +} from '../types'; import { XYArgs, SeriesType, visualizationTypes } from './types'; import { VisualizationContainer } from '../visualization_container'; import { isHorizontalChart } from './state_helpers'; -import { getExecuteTriggerActions } from '../services'; -import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; import { parseInterval } from '../../../../../src/plugins/data/common'; import { EmptyPlaceholder } from '../shared_components'; @@ -63,7 +61,8 @@ type XYChartRenderProps = XYChartProps & { formatFactory: FormatFactory; timeZone: string; histogramBarTarget: number; - executeTriggerActions: UiActionsStart['executeTriggerActions']; + onClickValue: (data: LensFilterEvent['data']) => void; + onSelectRange: (data: LensBrushEvent['data']) => void; }; export const xyChart: ExpressionFunctionDefinition< @@ -125,9 +124,18 @@ export const getXyChartRenderer = (dependencies: { }), validate: () => undefined, reuseDomNode: true, - render: async (domNode: Element, config: XYChartProps, handlers: IInterpreterRenderHandlers) => { - const executeTriggerActions = getExecuteTriggerActions(); + render: async ( + domNode: Element, + config: XYChartProps, + handlers: ILensInterpreterRenderHandlers + ) => { handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode)); + const onClickValue = (data: LensFilterEvent['data']) => { + handlers.event({ name: 'filter', data }); + }; + const onSelectRange = (data: LensBrushEvent['data']) => { + handlers.event({ name: 'brush', data }); + }; const formatFactory = await dependencies.formatFactory; ReactDOM.render( @@ -137,7 +145,8 @@ export const getXyChartRenderer = (dependencies: { chartTheme={dependencies.chartTheme} timeZone={dependencies.timeZone} histogramBarTarget={dependencies.histogramBarTarget} - executeTriggerActions={executeTriggerActions} + onClickValue={onClickValue} + onSelectRange={onSelectRange} /> , domNode, @@ -177,7 +186,8 @@ export function XYChart({ timeZone, chartTheme, histogramBarTarget, - executeTriggerActions, + onClickValue, + onSelectRange, }: XYChartRenderProps) { const { legend, layers } = args; @@ -287,15 +297,13 @@ export function XYChart({ ); const timeFieldName = table.columns[xAxisColumnIndex]?.meta?.aggConfigParams?.field; - const context: RangeSelectTriggerContext = { - data: { - range: [min, max], - table, - column: xAxisColumnIndex, - }, + const context: LensBrushEvent['data'] = { + range: [min, max], + table, + column: xAxisColumnIndex, timeFieldName, }; - executeTriggerActions(VIS_EVENT_TO_TRIGGER.brush, context); + onSelectRange(context); }} onElementClick={([[geometry, series]]) => { // for xyChart series is always XYChartSeriesIdentifier and geometry is always type of GeometryValue @@ -337,18 +345,16 @@ export function XYChart({ ?.aggConfigParams?.field; const timeFieldName = xDomain && xAxisFieldName; - const context: ValueClickTriggerContext = { - data: { - data: points.map(point => ({ - row: point.row, - column: point.column, - value: point.value, - table, - })), - }, + const context: LensFilterEvent['data'] = { + data: points.map(point => ({ + row: point.row, + column: point.column, + value: point.value, + table, + })), timeFieldName, }; - executeTriggerActions(VIS_EVENT_TO_TRIGGER.filter, context); + onClickValue(context); }} />