refactor action filter creation utils (#62969)

This commit is contained in:
Peter Pisljar 2020-04-22 10:41:58 +02:00 committed by GitHub
parent 2f794e6c42
commit 222fdc5029
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 184 additions and 172 deletions

View file

@ -8,6 +8,7 @@
```typescript ```typescript
actions: { actions: {
createFiltersFromEvent: typeof createFiltersFromEvent; createFiltersFromValueClickAction: typeof createFiltersFromValueClickAction;
createFiltersFromRangeSelectAction: typeof createFiltersFromRangeSelectAction;
}; };
``` ```

View file

@ -14,7 +14,7 @@ export interface DataPublicPluginStart
| Property | Type | Description | | Property | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| [actions](./kibana-plugin-plugins-data-public.datapublicpluginstart.actions.md) | <code>{</code><br/><code> createFiltersFromEvent: typeof createFiltersFromEvent;</code><br/><code> }</code> | | | [actions](./kibana-plugin-plugins-data-public.datapublicpluginstart.actions.md) | <code>{</code><br/><code> createFiltersFromValueClickAction: typeof createFiltersFromValueClickAction;</code><br/><code> createFiltersFromRangeSelectAction: typeof createFiltersFromRangeSelectAction;</code><br/><code> }</code> | |
| [autocomplete](./kibana-plugin-plugins-data-public.datapublicpluginstart.autocomplete.md) | <code>AutocompleteStart</code> | | | [autocomplete](./kibana-plugin-plugins-data-public.datapublicpluginstart.autocomplete.md) | <code>AutocompleteStart</code> | |
| [fieldFormats](./kibana-plugin-plugins-data-public.datapublicpluginstart.fieldformats.md) | <code>FieldFormatsStart</code> | | | [fieldFormats](./kibana-plugin-plugins-data-public.datapublicpluginstart.fieldformats.md) | <code>FieldFormatsStart</code> | |
| [indexPatterns](./kibana-plugin-plugins-data-public.datapublicpluginstart.indexpatterns.md) | <code>IndexPatternsContract</code> | | | [indexPatterns](./kibana-plugin-plugins-data-public.datapublicpluginstart.indexpatterns.md) | <code>IndexPatternsContract</code> | |

View file

@ -10,7 +10,7 @@
fieldFormats: { fieldFormats: {
FieldFormat: typeof FieldFormat; FieldFormat: typeof FieldFormat;
FieldFormatsRegistry: typeof FieldFormatsRegistry; FieldFormatsRegistry: typeof FieldFormatsRegistry;
serialize: (agg: import("./search").AggConfig) => import("../../expressions/common").SerializedFieldFormat<object>; serialize: (agg: import("./search").AggConfig) => import("../../expressions").SerializedFieldFormat<object>;
DEFAULT_CONVERTER_COLOR: { DEFAULT_CONVERTER_COLOR: {
range: string; range: string;
regex: string; regex: string;

View file

@ -10,7 +10,7 @@
fieldFormats: { fieldFormats: {
FieldFormatsRegistry: typeof FieldFormatsRegistry; FieldFormatsRegistry: typeof FieldFormatsRegistry;
FieldFormat: typeof FieldFormat; FieldFormat: typeof FieldFormat;
serializeFieldFormat: (agg: import("../public/search").AggConfig) => import("../../expressions/common").SerializedFieldFormat<object>; serializeFieldFormat: (agg: import("../public/search").AggConfig) => import("../../expressions").SerializedFieldFormat<object>;
BoolFormat: typeof BoolFormat; BoolFormat: typeof BoolFormat;
BytesFormat: typeof BytesFormat; BytesFormat: typeof BytesFormat;
ColorFormat: typeof ColorFormat; ColorFormat: typeof ColorFormat;

View file

@ -108,6 +108,6 @@ export class VisTypeVislibPlugin implements Plugin<void, void> {
public start(core: CoreStart, { data }: VisTypeVislibPluginStartDependencies) { public start(core: CoreStart, { data }: VisTypeVislibPluginStartDependencies) {
setFormatService(data.fieldFormats); setFormatService(data.fieldFormats);
setDataActions({ createFiltersFromEvent: data.actions.createFiltersFromEvent }); setDataActions(data.actions);
} }
} }

View file

@ -36,7 +36,9 @@ jest.mock('../../../legacy_imports', () => ({
})); }));
jest.mock('../../../services', () => ({ jest.mock('../../../services', () => ({
getDataActions: () => ({ createFiltersFromEvent: jest.fn().mockResolvedValue(['yes']) }), getDataActions: () => ({
createFiltersFromValueClickAction: jest.fn().mockResolvedValue(['yes']),
}),
})); }));
const vis = { const vis = {

View file

@ -101,7 +101,7 @@ export class VisLegend extends PureComponent<VisLegendProps, VisLegendState> {
return false; return false;
} }
const filters = await getDataActions().createFiltersFromEvent(item.values); const filters = await getDataActions().createFiltersFromValueClickAction({ data: item.values });
return Boolean(filters.length); return Boolean(filters.length);
}; };

View file

@ -83,10 +83,21 @@ export class Handler {
// memoize so that the same function is returned every time, // memoize so that the same function is returned every time,
// allowing us to remove/re-add the same function // allowing us to remove/re-add the same function
this.getProxyHandler = _.memoize(function(event) { this.getProxyHandler = _.memoize(function(eventType) {
const self = this; const self = this;
return function(e) { return function(eventPayload) {
self.vis.emit(event, e); switch (eventType) {
case 'brush':
const xRaw = _.get(eventPayload.data, 'series[0].values[0].xRaw');
if (!xRaw) return; // not sure if this is possible?
return self.vis.emit(eventType, {
table: xRaw.table,
range: eventPayload.range,
column: xRaw.column,
});
case 'click':
return self.vis.emit(eventType, eventPayload);
}
}; };
}); });

View file

@ -377,7 +377,8 @@ export const npStart = {
}, },
data: { data: {
actions: { actions: {
createFiltersFromEvent: Promise.resolve(['yes']), createFiltersFromValueClickAction: Promise.resolve(['yes']),
createFiltersFromRangeSelectAction: sinon.fake(),
}, },
autocomplete: { autocomplete: {
getProvider: sinon.fake(), getProvider: sinon.fake(),

View file

@ -19,30 +19,34 @@
import moment from 'moment'; import moment from 'moment';
import { onBrushEvent, BrushEvent } from './brush_event'; import { createFiltersFromRangeSelectAction } from './create_filters_from_range_select';
import { IndexPatternsContract } from '../../../public'; import { IndexPatternsContract, RangeFilter } from '../../../public';
import { dataPluginMock } from '../../../public/mocks'; import { dataPluginMock } from '../../../public/mocks';
import { setIndexPatterns } from '../../../public/services'; import { setIndexPatterns } from '../../../public/services';
import { mockDataServices } from '../../../public/search/aggs/test_helpers'; import { mockDataServices } from '../../../public/search/aggs/test_helpers';
import { TriggerContextMapping } from '../../../../ui_actions/public';
describe('brushEvent', () => { describe('brushEvent', () => {
const DAY_IN_MS = 24 * 60 * 60 * 1000; const DAY_IN_MS = 24 * 60 * 60 * 1000;
const JAN_01_2014 = 1388559600000; const JAN_01_2014 = 1388559600000;
let baseEvent: BrushEvent; let baseEvent: TriggerContextMapping['SELECT_RANGE_TRIGGER']['data'];
const indexPattern = {
id: 'indexPatternId',
timeFieldName: 'time',
fields: {
getByName: () => undefined,
filter: () => [],
},
};
const aggConfigs = [ const aggConfigs = [
{ {
params: { params: {
field: {}, field: {},
}, },
getIndexPattern: () => ({ getIndexPattern: () => indexPattern,
timeFieldName: 'time',
fields: {
getByName: () => undefined,
filter: () => [],
},
}),
}, },
]; ];
@ -50,56 +54,37 @@ describe('brushEvent', () => {
mockDataServices(); mockDataServices();
setIndexPatterns(({ setIndexPatterns(({
...dataPluginMock.createStartContract().indexPatterns, ...dataPluginMock.createStartContract().indexPatterns,
get: async () => ({ get: async () => indexPattern,
id: 'indexPatternId',
timeFieldName: 'time',
fields: {
getByName: () => undefined,
filter: () => [],
},
}),
} as unknown) as IndexPatternsContract); } as unknown) as IndexPatternsContract);
baseEvent = { baseEvent = {
data: { column: 0,
ordered: { table: {
date: false, type: 'kibana_datatable',
}, columns: [
series: [
{ {
values: [ id: '1',
{ name: '1',
xRaw: { meta: {
column: 0, type: 'histogram',
table: { indexPatternId: 'indexPatternId',
columns: [ aggConfigParams: aggConfigs[0].params,
{ },
id: '1',
meta: {
type: 'histogram',
indexPatternId: 'indexPatternId',
aggConfigParams: aggConfigs[0].params,
},
},
],
},
},
},
],
}, },
], ],
rows: [],
}, },
range: [], range: [],
}; };
}); });
test('should be a function', () => { test('should be a function', () => {
expect(typeof onBrushEvent).toBe('function'); expect(typeof createFiltersFromRangeSelectAction).toBe('function');
}); });
test('ignores event when data.xAxisField not provided', async () => { test('ignores event when data.xAxisField not provided', async () => {
const filter = await onBrushEvent(baseEvent); const filter = await createFiltersFromRangeSelectAction(baseEvent);
expect(filter).toBeUndefined(); expect(filter).toEqual([]);
}); });
describe('handles an event when the x-axis field is a date field', () => { describe('handles an event when the x-axis field is a date field', () => {
@ -109,29 +94,29 @@ describe('brushEvent', () => {
name: 'time', name: 'time',
type: 'date', type: 'date',
}; };
baseEvent.data.ordered = { date: true };
}); });
afterAll(() => { afterAll(() => {
baseEvent.range = []; baseEvent.range = [];
baseEvent.data.ordered = { date: false }; aggConfigs[0].params.field = {};
}); });
test('by ignoring the event when range spans zero time', async () => { test('by ignoring the event when range spans zero time', async () => {
baseEvent.range = [JAN_01_2014, JAN_01_2014]; baseEvent.range = [JAN_01_2014, JAN_01_2014];
const filter = await onBrushEvent(baseEvent); const filter = await createFiltersFromRangeSelectAction(baseEvent);
expect(filter).toBeUndefined(); expect(filter).toEqual([]);
}); });
test('by updating the timefilter', async () => { test('by updating the timefilter', async () => {
baseEvent.range = [JAN_01_2014, JAN_01_2014 + DAY_IN_MS]; baseEvent.range = [JAN_01_2014, JAN_01_2014 + DAY_IN_MS];
const filter = await onBrushEvent(baseEvent); const filter = await createFiltersFromRangeSelectAction(baseEvent);
expect(filter).toBeDefined(); expect(filter).toBeDefined();
if (filter) { if (filter.length) {
expect(filter.range.time.gte).toBe(new Date(JAN_01_2014).toISOString()); const rangeFilter = filter[0] as RangeFilter;
expect(rangeFilter.range.time.gte).toBe(new Date(JAN_01_2014).toISOString());
// Set to a baseline timezone for comparison. // Set to a baseline timezone for comparison.
expect(filter.range.time.lt).toBe(new Date(JAN_01_2014 + DAY_IN_MS).toISOString()); expect(rangeFilter.range.time.lt).toBe(new Date(JAN_01_2014 + DAY_IN_MS).toISOString());
} }
}); });
}); });
@ -142,26 +127,26 @@ describe('brushEvent', () => {
name: 'anotherTimeField', name: 'anotherTimeField',
type: 'date', type: 'date',
}; };
baseEvent.data.ordered = { date: true };
}); });
afterAll(() => { afterAll(() => {
baseEvent.range = []; baseEvent.range = [];
baseEvent.data.ordered = { date: false }; aggConfigs[0].params.field = {};
}); });
test('creates a new range filter', async () => { test('creates a new range filter', async () => {
const rangeBegin = JAN_01_2014; const rangeBegin = JAN_01_2014;
const rangeEnd = rangeBegin + DAY_IN_MS; const rangeEnd = rangeBegin + DAY_IN_MS;
baseEvent.range = [rangeBegin, rangeEnd]; baseEvent.range = [rangeBegin, rangeEnd];
const filter = await onBrushEvent(baseEvent); const filter = await createFiltersFromRangeSelectAction(baseEvent);
expect(filter).toBeDefined(); expect(filter).toBeDefined();
if (filter) { if (filter.length) {
expect(filter.range.anotherTimeField.gte).toBe(moment(rangeBegin).toISOString()); const rangeFilter = filter[0] as RangeFilter;
expect(filter.range.anotherTimeField.lt).toBe(moment(rangeEnd).toISOString()); expect(rangeFilter.range.anotherTimeField.gte).toBe(moment(rangeBegin).toISOString());
expect(filter.range.anotherTimeField).toHaveProperty( expect(rangeFilter.range.anotherTimeField.lt).toBe(moment(rangeEnd).toISOString());
expect(rangeFilter.range.anotherTimeField).toHaveProperty(
'format', 'format',
'strict_date_optional_time' 'strict_date_optional_time'
); );
@ -184,20 +169,21 @@ describe('brushEvent', () => {
test('by ignoring the event when range does not span at least 2 values', async () => { test('by ignoring the event when range does not span at least 2 values', async () => {
baseEvent.range = [1]; baseEvent.range = [1];
const filter = await onBrushEvent(baseEvent); const filter = await createFiltersFromRangeSelectAction(baseEvent);
expect(filter).toBeUndefined(); expect(filter).toEqual([]);
}); });
test('by creating a new filter', async () => { test('by creating a new filter', async () => {
baseEvent.range = [1, 2, 3, 4]; baseEvent.range = [1, 2, 3, 4];
const filter = await onBrushEvent(baseEvent); const filter = await createFiltersFromRangeSelectAction(baseEvent);
expect(filter).toBeDefined(); expect(filter).toBeDefined();
if (filter) { if (filter.length) {
expect(filter.range.numberField.gte).toBe(1); const rangeFilter = filter[0] as RangeFilter;
expect(filter.range.numberField.lt).toBe(4); expect(rangeFilter.range.numberField.gte).toBe(1);
expect(filter.range.numberField).not.toHaveProperty('format'); expect(rangeFilter.range.numberField.lt).toBe(4);
expect(rangeFilter.range.numberField).not.toHaveProperty('format');
} }
}); });
}); });

View file

@ -17,34 +17,18 @@
* under the License. * under the License.
*/ */
import { get, last } from 'lodash'; import { last } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import { esFilters, IFieldType, RangeFilterParams } from '../../../public'; import { esFilters, IFieldType, RangeFilterParams } from '../../../public';
import { getIndexPatterns } from '../../../public/services'; import { getIndexPatterns } from '../../../public/services';
import { deserializeAggConfig } from '../../search/expressions/utils'; import { deserializeAggConfig } from '../../search/expressions/utils';
import { RangeSelectTriggerContext } from '../../../../embeddable/public';
export interface BrushEvent { export async function createFiltersFromRangeSelectAction(event: RangeSelectTriggerContext['data']) {
data: { const column: Record<string, any> = event.table.columns[event.column];
ordered: {
date: boolean;
};
series: Array<Record<string, any>>;
};
range: number[];
}
export async function onBrushEvent(event: BrushEvent) {
const isDate = get(event.data, 'ordered.date');
const xRaw: Record<string, any> = get(event.data, 'series[0].values[0].xRaw');
if (!xRaw) {
return;
}
const column: Record<string, any> = xRaw.table.columns[xRaw.column];
if (!column || !column.meta) { if (!column || !column.meta) {
return; return [];
} }
const indexPattern = await getIndexPatterns().get(column.meta.indexPatternId); const indexPattern = await getIndexPatterns().get(column.meta.indexPatternId);
@ -55,16 +39,18 @@ export async function onBrushEvent(event: BrushEvent) {
const field: IFieldType = aggConfig.params.field; const field: IFieldType = aggConfig.params.field;
if (!field || event.range.length <= 1) { if (!field || event.range.length <= 1) {
return; return [];
} }
const min = event.range[0]; const min = event.range[0];
const max = last(event.range); const max = last(event.range);
if (min === max) { if (min === max) {
return; return [];
} }
const isDate = field.type === 'date';
const range: RangeFilterParams = { const range: RangeFilterParams = {
gte: isDate ? moment(min).toISOString() : min, gte: isDate ? moment(min).toISOString() : min,
lt: isDate ? moment(max).toISOString() : max, lt: isDate ? moment(max).toISOString() : max,
@ -74,5 +60,5 @@ export async function onBrushEvent(event: BrushEvent) {
range.format = 'strict_date_optional_time'; range.format = 'strict_date_optional_time';
} }
return esFilters.buildRangeFilter(field, range, indexPattern); return esFilters.mapAndFlattenFilters([esFilters.buildRangeFilter(field, range, indexPattern)]);
} }

View file

@ -26,7 +26,8 @@ import {
import { dataPluginMock } from '../../../public/mocks'; import { dataPluginMock } from '../../../public/mocks';
import { setIndexPatterns } from '../../../public/services'; import { setIndexPatterns } from '../../../public/services';
import { mockDataServices } from '../../../public/search/aggs/test_helpers'; import { mockDataServices } from '../../../public/search/aggs/test_helpers';
import { createFiltersFromEvent, EventData } from './create_filters_from_event'; import { createFiltersFromValueClickAction } from './create_filters_from_value_click';
import { ValueClickTriggerContext } from '../../../../embeddable/public';
const mockField = { const mockField = {
name: 'bytes', name: 'bytes',
@ -37,8 +38,8 @@ const mockField = {
format: new fieldFormats.BytesFormat({}, (() => {}) as FieldFormatsGetConfigFn), format: new fieldFormats.BytesFormat({}, (() => {}) as FieldFormatsGetConfigFn),
}; };
describe('createFiltersFromEvent', () => { describe('createFiltersFromValueClick', () => {
let dataPoints: EventData[]; let dataPoints: ValueClickTriggerContext['data']['data'];
beforeEach(() => { beforeEach(() => {
dataPoints = [ dataPoints = [
@ -86,7 +87,7 @@ describe('createFiltersFromEvent', () => {
test('ignores event when value for rows is not provided', async () => { test('ignores event when value for rows is not provided', async () => {
dataPoints[0].table.rows[0]['1-1'] = null; dataPoints[0].table.rows[0]['1-1'] = null;
const filters = await createFiltersFromEvent(dataPoints); const filters = await createFiltersFromValueClickAction({ data: dataPoints });
expect(filters.length).toEqual(0); expect(filters.length).toEqual(0);
}); });
@ -95,14 +96,14 @@ describe('createFiltersFromEvent', () => {
if (dataPoints[0].table.columns[0].meta) { if (dataPoints[0].table.columns[0].meta) {
dataPoints[0].table.columns[0].meta.type = 'terms'; dataPoints[0].table.columns[0].meta.type = 'terms';
} }
const filters = await createFiltersFromEvent(dataPoints); const filters = await createFiltersFromValueClickAction({ data: dataPoints });
expect(filters.length).toEqual(1); expect(filters.length).toEqual(1);
expect(filters[0].query.match_phrase.bytes).toEqual('2048'); expect(filters[0].query.match_phrase.bytes).toEqual('2048');
}); });
test('handles an event when aggregations type is not terms', async () => { test('handles an event when aggregations type is not terms', async () => {
const filters = await createFiltersFromEvent(dataPoints); const filters = await createFiltersFromValueClickAction({ data: dataPoints });
expect(filters.length).toEqual(1); expect(filters.length).toEqual(1);

View file

@ -21,13 +21,7 @@ import { KibanaDatatable } from '../../../../../plugins/expressions/public';
import { deserializeAggConfig } from '../../search/expressions'; import { deserializeAggConfig } from '../../search/expressions';
import { esFilters, Filter } from '../../../public'; import { esFilters, Filter } from '../../../public';
import { getIndexPatterns } from '../../../public/services'; import { getIndexPatterns } from '../../../public/services';
import { ValueClickTriggerContext } from '../../../../embeddable/public';
export interface EventData {
table: Pick<KibanaDatatable, 'rows' | 'columns'>;
column: number;
row: number;
value: any;
}
/** /**
* For terms aggregations on `__other__` buckets, this assembles a list of applicable filter * For terms aggregations on `__other__` buckets, this assembles a list of applicable filter
@ -39,7 +33,7 @@ export interface EventData {
* @return {array} - array of terms to filter against * @return {array} - array of terms to filter against
*/ */
const getOtherBucketFilterTerms = ( const getOtherBucketFilterTerms = (
table: EventData['table'], table: Pick<KibanaDatatable, 'rows' | 'columns'>,
columnIndex: number, columnIndex: number,
rowIndex: number rowIndex: number
) => { ) => {
@ -76,7 +70,11 @@ const getOtherBucketFilterTerms = (
* @param {string} cellValue - value of the current cell * @param {string} cellValue - value of the current cell
* @return {Filter[]|undefined} - list of filters to provide to queryFilter.addFilters() * @return {Filter[]|undefined} - list of filters to provide to queryFilter.addFilters()
*/ */
const createFilter = async (table: EventData['table'], columnIndex: number, rowIndex: number) => { const createFilter = async (
table: Pick<KibanaDatatable, 'rows' | 'columns'>,
columnIndex: number,
rowIndex: number
) => {
if (!table || !table.columns || !table.columns[columnIndex]) { if (!table || !table.columns || !table.columns[columnIndex]) {
return; return;
} }
@ -113,11 +111,14 @@ const createFilter = async (table: EventData['table'], columnIndex: number, rowI
}; };
/** @public */ /** @public */
export const createFiltersFromEvent = async (dataPoints: EventData[], negate?: boolean) => { export const createFiltersFromValueClickAction = async ({
data,
negate,
}: ValueClickTriggerContext['data']) => {
const filters: Filter[] = []; const filters: Filter[] = [];
await Promise.all( await Promise.all(
dataPoints data
.filter(point => point) .filter(point => point)
.map(async val => { .map(async val => {
const { table, column, row } = val; const { table, column, row } = val;
@ -133,5 +134,5 @@ export const createFiltersFromEvent = async (dataPoints: EventData[], negate?: b
}) })
); );
return filters; return esFilters.mapAndFlattenFilters(filters);
}; };

View file

@ -18,6 +18,7 @@
*/ */
export { ACTION_GLOBAL_APPLY_FILTER, createFilterAction } from './apply_filter_action'; export { ACTION_GLOBAL_APPLY_FILTER, createFilterAction } from './apply_filter_action';
export { createFiltersFromEvent } from './filters/create_filters_from_event'; export { createFiltersFromValueClickAction } from './filters/create_filters_from_value_click';
export { createFiltersFromRangeSelectAction } from './filters/create_filters_from_range_select';
export { selectRangeAction } from './select_range_action'; export { selectRangeAction } from './select_range_action';
export { valueClickAction } from './value_click_action'; export { valueClickAction } from './value_click_action';

View file

@ -23,19 +23,17 @@ import {
IncompatibleActionError, IncompatibleActionError,
ActionByType, ActionByType,
} from '../../../../plugins/ui_actions/public'; } from '../../../../plugins/ui_actions/public';
import { onBrushEvent } from './filters/brush_event'; import { createFiltersFromRangeSelectAction } from './filters/create_filters_from_range_select';
import { RangeSelectTriggerContext } from '../../../embeddable/public';
import { FilterManager, TimefilterContract, esFilters } from '..'; import { FilterManager, TimefilterContract, esFilters } from '..';
export const ACTION_SELECT_RANGE = 'ACTION_SELECT_RANGE'; export const ACTION_SELECT_RANGE = 'ACTION_SELECT_RANGE';
export interface SelectRangeActionContext { export type SelectRangeActionContext = RangeSelectTriggerContext;
data: any;
timeFieldName: string;
}
async function isCompatible(context: SelectRangeActionContext) { async function isCompatible(context: SelectRangeActionContext) {
try { try {
return Boolean(await onBrushEvent(context.data)); return Boolean(await createFiltersFromRangeSelectAction(context.data));
} catch { } catch {
return false; return false;
} }
@ -59,13 +57,7 @@ export function selectRangeAction(
throw new IncompatibleActionError(); throw new IncompatibleActionError();
} }
const filter = await onBrushEvent(data); const selectedFilters = await createFiltersFromRangeSelectAction(data);
if (!filter) {
return;
}
const selectedFilters = esFilters.mapAndFlattenFilters([filter]);
if (timeFieldName) { if (timeFieldName) {
const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter( const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter(

View file

@ -26,21 +26,17 @@ import {
} from '../../../../plugins/ui_actions/public'; } from '../../../../plugins/ui_actions/public';
import { getOverlays, getIndexPatterns } from '../services'; import { getOverlays, getIndexPatterns } from '../services';
import { applyFiltersPopover } from '../ui/apply_filters'; import { applyFiltersPopover } from '../ui/apply_filters';
import { createFiltersFromEvent } from './filters/create_filters_from_event'; import { createFiltersFromValueClickAction } from './filters/create_filters_from_value_click';
import { ValueClickTriggerContext } from '../../../embeddable/public';
import { Filter, FilterManager, TimefilterContract, esFilters } from '..'; import { Filter, FilterManager, TimefilterContract, esFilters } from '..';
export const ACTION_VALUE_CLICK = 'ACTION_VALUE_CLICK'; export const ACTION_VALUE_CLICK = 'ACTION_VALUE_CLICK';
export interface ValueClickActionContext { export type ValueClickActionContext = ValueClickTriggerContext;
data: any;
timeFieldName: string;
}
async function isCompatible(context: ValueClickActionContext) { async function isCompatible(context: ValueClickActionContext) {
try { try {
const filters: Filter[] = const filters: Filter[] = await createFiltersFromValueClickAction(context.data);
(await createFiltersFromEvent(context.data.data || [context.data], context.data.negate)) ||
[];
return filters.length > 0; return filters.length > 0;
} catch { } catch {
return false; return false;
@ -60,17 +56,16 @@ export function valueClickAction(
}); });
}, },
isCompatible, isCompatible,
execute: async ({ timeFieldName, data }: ValueClickActionContext) => { execute: async (context: ValueClickActionContext) => {
if (!(await isCompatible({ timeFieldName, data }))) { if (!(await isCompatible(context))) {
throw new IncompatibleActionError(); throw new IncompatibleActionError();
} }
const filters: Filter[] = const filters: Filter[] = await createFiltersFromValueClickAction(context.data);
(await createFiltersFromEvent(data.data || [data], data.negate)) || [];
let selectedFilters: Filter[] = esFilters.mapAndFlattenFilters(filters); let selectedFilters = filters;
if (selectedFilters.length > 1) { if (filters.length > 1) {
const indexPatterns = await Promise.all( const indexPatterns = await Promise.all(
filters.map(filter => { filters.map(filter => {
return getIndexPatterns().get(filter.meta.index!); return getIndexPatterns().get(filter.meta.index!);
@ -102,9 +97,9 @@ export function valueClickAction(
selectedFilters = await filterSelectionPromise; selectedFilters = await filterSelectionPromise;
} }
if (timeFieldName) { if (context.timeFieldName) {
const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter( const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter(
timeFieldName, context.timeFieldName,
selectedFilters selectedFilters
); );
filterManager.addFilters(restOfFilters); filterManager.addFilters(restOfFilters);

View file

@ -45,7 +45,8 @@ const createStartContract = (): Start => {
const queryStartMock = queryServiceMock.createStartContract(); const queryStartMock = queryServiceMock.createStartContract();
return { return {
actions: { actions: {
createFiltersFromEvent: jest.fn().mockResolvedValue(['yes']), createFiltersFromValueClickAction: jest.fn().mockResolvedValue(['yes']),
createFiltersFromRangeSelectAction: jest.fn(),
}, },
autocomplete: autocompleteMock, autocomplete: autocompleteMock,
search: searchStartMock, search: searchStartMock,

View file

@ -58,7 +58,12 @@ import {
VALUE_CLICK_TRIGGER, VALUE_CLICK_TRIGGER,
APPLY_FILTER_TRIGGER, APPLY_FILTER_TRIGGER,
} from '../../ui_actions/public'; } from '../../ui_actions/public';
import { ACTION_GLOBAL_APPLY_FILTER, createFilterAction, createFiltersFromEvent } from './actions'; import {
ACTION_GLOBAL_APPLY_FILTER,
createFilterAction,
createFiltersFromValueClickAction,
createFiltersFromRangeSelectAction,
} from './actions';
import { ApplyGlobalFilterActionContext } from './actions/apply_filter_action'; import { ApplyGlobalFilterActionContext } from './actions/apply_filter_action';
import { import {
selectRangeAction, selectRangeAction,
@ -162,7 +167,8 @@ export class DataPublicPlugin implements Plugin<DataPublicPluginSetup, DataPubli
const dataServices = { const dataServices = {
actions: { actions: {
createFiltersFromEvent, createFiltersFromValueClickAction,
createFiltersFromRangeSelectAction,
}, },
autocomplete: this.autocomplete.start(), autocomplete: this.autocomplete.start(),
fieldFormats, fieldFormats,

View file

@ -248,7 +248,8 @@ export interface DataPublicPluginSetup {
export interface DataPublicPluginStart { export interface DataPublicPluginStart {
// (undocumented) // (undocumented)
actions: { actions: {
createFiltersFromEvent: typeof createFiltersFromEvent; createFiltersFromValueClickAction: typeof createFiltersFromValueClickAction;
createFiltersFromRangeSelectAction: typeof createFiltersFromRangeSelectAction;
}; };
// Warning: (ae-forgotten-export) The symbol "AutocompleteStart" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "AutocompleteStart" needs to be exported by the entry point index.d.ts
// //
@ -484,7 +485,7 @@ export type FieldFormatId = FIELD_FORMAT_IDS | string;
export const fieldFormats: { export const fieldFormats: {
FieldFormat: typeof FieldFormat; FieldFormat: typeof FieldFormat;
FieldFormatsRegistry: typeof FieldFormatsRegistry; FieldFormatsRegistry: typeof FieldFormatsRegistry;
serialize: (agg: import("./search").AggConfig) => import("../../expressions/common").SerializedFieldFormat<object>; serialize: (agg: import("./search").AggConfig) => import("../../expressions").SerializedFieldFormat<object>;
DEFAULT_CONVERTER_COLOR: { DEFAULT_CONVERTER_COLOR: {
range: string; range: string;
regex: string; regex: string;
@ -1892,8 +1893,9 @@ export type TSearchStrategyProvider<T extends TStrategyTypes> = (context: ISearc
// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:33:33 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:33:33 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:37:1 - (ae-forgotten-export) The symbol "QueryStateChange" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:37:1 - (ae-forgotten-export) The symbol "QueryStateChange" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/types.ts:52:5 - (ae-forgotten-export) The symbol "createFiltersFromEvent" needs to be exported by the entry point index.d.ts // src/plugins/data/public/types.ts:52:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/types.ts:60:5 - (ae-forgotten-export) The symbol "IndexPatternSelectProps" needs to be exported by the entry point index.d.ts // src/plugins/data/public/types.ts:53:5 - (ae-forgotten-export) The symbol "createFiltersFromRangeSelectAction" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/types.ts:61:5 - (ae-forgotten-export) The symbol "IndexPatternSelectProps" needs to be exported by the entry point index.d.ts
// (No @packageDocumentation comment for this package) // (No @packageDocumentation comment for this package)

View file

@ -24,7 +24,7 @@ import { ExpressionsSetup } from 'src/plugins/expressions/public';
import { UiActionsSetup, UiActionsStart } from 'src/plugins/ui_actions/public'; import { UiActionsSetup, UiActionsStart } from 'src/plugins/ui_actions/public';
import { AutocompleteSetup, AutocompleteStart } from './autocomplete'; import { AutocompleteSetup, AutocompleteStart } from './autocomplete';
import { FieldFormatsSetup, FieldFormatsStart } from './field_formats'; import { FieldFormatsSetup, FieldFormatsStart } from './field_formats';
import { createFiltersFromEvent } from './actions'; import { createFiltersFromRangeSelectAction, createFiltersFromValueClickAction } from './actions';
import { ISearchSetup, ISearchStart } from './search'; import { ISearchSetup, ISearchStart } from './search';
import { QuerySetup, QueryStart } from './query'; import { QuerySetup, QueryStart } from './query';
import { IndexPatternSelectProps } from './ui/index_pattern_select'; import { IndexPatternSelectProps } from './ui/index_pattern_select';
@ -49,7 +49,8 @@ export interface DataPublicPluginSetup {
export interface DataPublicPluginStart { export interface DataPublicPluginStart {
actions: { actions: {
createFiltersFromEvent: typeof createFiltersFromEvent; createFiltersFromValueClickAction: typeof createFiltersFromValueClickAction;
createFiltersFromRangeSelectAction: typeof createFiltersFromRangeSelectAction;
}; };
autocomplete: AutocompleteStart; autocomplete: AutocompleteStart;
indexPatterns: IndexPatternsContract; indexPatterns: IndexPatternsContract;

View file

@ -283,7 +283,7 @@ export interface FieldFormatConfig {
export const fieldFormats: { export const fieldFormats: {
FieldFormatsRegistry: typeof FieldFormatsRegistry; FieldFormatsRegistry: typeof FieldFormatsRegistry;
FieldFormat: typeof FieldFormat; FieldFormat: typeof FieldFormat;
serializeFieldFormat: (agg: import("../public/search").AggConfig) => import("../../expressions/common").SerializedFieldFormat<object>; serializeFieldFormat: (agg: import("../public/search").AggConfig) => import("../../expressions").SerializedFieldFormat<object>;
BoolFormat: typeof BoolFormat; BoolFormat: typeof BoolFormat;
BytesFormat: typeof BytesFormat; BytesFormat: typeof BytesFormat;
ColorFormat: typeof ColorFormat; ColorFormat: typeof ColorFormat;

View file

@ -47,7 +47,8 @@ export {
EmbeddableOutput, EmbeddableOutput,
EmbeddablePanel, EmbeddablePanel,
EmbeddableRoot, EmbeddableRoot,
EmbeddableVisTriggerContext, ValueClickTriggerContext,
RangeSelectTriggerContext,
ErrorEmbeddable, ErrorEmbeddable,
IContainer, IContainer,
IEmbeddable, IEmbeddable,

View file

@ -18,18 +18,34 @@
*/ */
import { Trigger } from '../../../../ui_actions/public'; import { Trigger } from '../../../../ui_actions/public';
import { KibanaDatatable } from '../../../../expressions';
import { IEmbeddable } from '..'; import { IEmbeddable } from '..';
export interface EmbeddableContext { export interface EmbeddableContext {
embeddable: IEmbeddable; embeddable: IEmbeddable;
} }
export interface EmbeddableVisTriggerContext { export interface ValueClickTriggerContext {
embeddable?: IEmbeddable; embeddable?: IEmbeddable;
timeFieldName?: string; timeFieldName?: string;
data: { data: {
e?: MouseEvent; data: Array<{
data: unknown; table: Pick<KibanaDatatable, 'rows' | 'columns'>;
column: number;
row: number;
value: any;
}>;
negate?: boolean;
};
}
export interface RangeSelectTriggerContext {
embeddable?: IEmbeddable;
timeFieldName?: string;
data: {
table: KibanaDatatable;
column: number;
range: number[];
}; };
} }

View file

@ -19,9 +19,10 @@
import { ActionByType } from './actions/action'; import { ActionByType } from './actions/action';
import { TriggerInternal } from './triggers/trigger_internal'; import { TriggerInternal } from './triggers/trigger_internal';
import { EmbeddableVisTriggerContext, IEmbeddable } from '../../embeddable/public';
import { Filter } from '../../data/public'; import { Filter } from '../../data/public';
import { SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER, APPLY_FILTER_TRIGGER } from './triggers'; import { SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER, APPLY_FILTER_TRIGGER } from './triggers';
import { IEmbeddable } from '../../embeddable/public';
import { RangeSelectTriggerContext, ValueClickTriggerContext } from '../../embeddable/public';
export type TriggerRegistry = Map<TriggerId, TriggerInternal<any>>; export type TriggerRegistry = Map<TriggerId, TriggerInternal<any>>;
export type ActionRegistry = Map<string, ActionByType<any>>; export type ActionRegistry = Map<string, ActionByType<any>>;
@ -36,8 +37,8 @@ export type TriggerContext = BaseContext;
export interface TriggerContextMapping { export interface TriggerContextMapping {
[DEFAULT_TRIGGER]: TriggerContext; [DEFAULT_TRIGGER]: TriggerContext;
[SELECT_RANGE_TRIGGER]: EmbeddableVisTriggerContext; [SELECT_RANGE_TRIGGER]: RangeSelectTriggerContext;
[VALUE_CLICK_TRIGGER]: EmbeddableVisTriggerContext; [VALUE_CLICK_TRIGGER]: ValueClickTriggerContext;
[APPLY_FILTER_TRIGGER]: { [APPLY_FILTER_TRIGGER]: {
embeddable: IEmbeddable; embeddable: IEmbeddable;
filters: Filter[]; filters: Filter[];

View file

@ -33,7 +33,6 @@ import {
EmbeddableInput, EmbeddableInput,
EmbeddableOutput, EmbeddableOutput,
Embeddable, Embeddable,
EmbeddableVisTriggerContext,
IContainer, IContainer,
} from '../../../../plugins/embeddable/public'; } from '../../../../plugins/embeddable/public';
import { dispatchRenderComplete } from '../../../../plugins/kibana_utils/public'; import { dispatchRenderComplete } from '../../../../plugins/kibana_utils/public';
@ -261,7 +260,7 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
if (!this.input.disableTriggers) { if (!this.input.disableTriggers) {
const triggerId = const triggerId =
event.name === 'brush' ? VIS_EVENT_TO_TRIGGER.brush : VIS_EVENT_TO_TRIGGER.filter; event.name === 'brush' ? VIS_EVENT_TO_TRIGGER.brush : VIS_EVENT_TO_TRIGGER.filter;
const context: EmbeddableVisTriggerContext = { const context = {
embeddable: this, embeddable: this,
timeFieldName: this.vis.data.indexPattern!.timeFieldName!, timeFieldName: this.vis.data.indexPattern!.timeFieldName!,
data: event.data, data: event.data,

View file

@ -69,7 +69,15 @@ export class ExprVis extends EventEmitter {
events: { events: {
filter: (data: any) => { filter: (data: any) => {
if (!this.eventsSubject) return; if (!this.eventsSubject) return;
this.eventsSubject.next({ name: 'filterBucket', data }); this.eventsSubject.next({
name: 'filterBucket',
data: data.data
? {
data: data.data,
negate: data.negate,
}
: { data: [data] },
});
}, },
brush: (data: any) => { brush: (data: any) => {
if (!this.eventsSubject) return; if (!this.eventsSubject) return;

View file

@ -28,7 +28,7 @@ import {
import { EuiIcon, EuiText, IconType, EuiSpacer } from '@elastic/eui'; import { EuiIcon, EuiText, IconType, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { EmbeddableVisTriggerContext } from '../../../../../src/plugins/embeddable/public'; import { ValueClickTriggerContext } from '../../../../../src/plugins/embeddable/public';
import { VIS_EVENT_TO_TRIGGER } from '../../../../../src/plugins/visualizations/public'; import { VIS_EVENT_TO_TRIGGER } from '../../../../../src/plugins/visualizations/public';
import { LensMultiTable, FormatFactory } from '../types'; import { LensMultiTable, FormatFactory } from '../types';
import { XYArgs, SeriesType, visualizationTypes } from './types'; import { XYArgs, SeriesType, visualizationTypes } from './types';
@ -277,7 +277,7 @@ export function XYChart({
const timeFieldName = xDomain && xAxisFieldName; const timeFieldName = xDomain && xAxisFieldName;
const context: EmbeddableVisTriggerContext = { const context: ValueClickTriggerContext = {
data: { data: {
data: points.map(point => ({ data: points.map(point => ({
row: point.row, row: point.row,