refactor action filter creation utils (#62969)
This commit is contained in:
parent
2f794e6c42
commit
222fdc5029
|
@ -8,6 +8,7 @@
|
|||
|
||||
```typescript
|
||||
actions: {
|
||||
createFiltersFromEvent: typeof createFiltersFromEvent;
|
||||
createFiltersFromValueClickAction: typeof createFiltersFromValueClickAction;
|
||||
createFiltersFromRangeSelectAction: typeof createFiltersFromRangeSelectAction;
|
||||
};
|
||||
```
|
||||
|
|
|
@ -14,7 +14,7 @@ export interface DataPublicPluginStart
|
|||
|
||||
| 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> | |
|
||||
| [fieldFormats](./kibana-plugin-plugins-data-public.datapublicpluginstart.fieldformats.md) | <code>FieldFormatsStart</code> | |
|
||||
| [indexPatterns](./kibana-plugin-plugins-data-public.datapublicpluginstart.indexpatterns.md) | <code>IndexPatternsContract</code> | |
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
fieldFormats: {
|
||||
FieldFormat: typeof FieldFormat;
|
||||
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: {
|
||||
range: string;
|
||||
regex: string;
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
fieldFormats: {
|
||||
FieldFormatsRegistry: typeof FieldFormatsRegistry;
|
||||
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;
|
||||
BytesFormat: typeof BytesFormat;
|
||||
ColorFormat: typeof ColorFormat;
|
||||
|
|
|
@ -108,6 +108,6 @@ export class VisTypeVislibPlugin implements Plugin<void, void> {
|
|||
|
||||
public start(core: CoreStart, { data }: VisTypeVislibPluginStartDependencies) {
|
||||
setFormatService(data.fieldFormats);
|
||||
setDataActions({ createFiltersFromEvent: data.actions.createFiltersFromEvent });
|
||||
setDataActions(data.actions);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,9 @@ jest.mock('../../../legacy_imports', () => ({
|
|||
}));
|
||||
|
||||
jest.mock('../../../services', () => ({
|
||||
getDataActions: () => ({ createFiltersFromEvent: jest.fn().mockResolvedValue(['yes']) }),
|
||||
getDataActions: () => ({
|
||||
createFiltersFromValueClickAction: jest.fn().mockResolvedValue(['yes']),
|
||||
}),
|
||||
}));
|
||||
|
||||
const vis = {
|
||||
|
|
|
@ -101,7 +101,7 @@ export class VisLegend extends PureComponent<VisLegendProps, VisLegendState> {
|
|||
return false;
|
||||
}
|
||||
|
||||
const filters = await getDataActions().createFiltersFromEvent(item.values);
|
||||
const filters = await getDataActions().createFiltersFromValueClickAction({ data: item.values });
|
||||
return Boolean(filters.length);
|
||||
};
|
||||
|
||||
|
|
|
@ -83,10 +83,21 @@ export class Handler {
|
|||
|
||||
// memoize so that the same function is returned every time,
|
||||
// allowing us to remove/re-add the same function
|
||||
this.getProxyHandler = _.memoize(function(event) {
|
||||
this.getProxyHandler = _.memoize(function(eventType) {
|
||||
const self = this;
|
||||
return function(e) {
|
||||
self.vis.emit(event, e);
|
||||
return function(eventPayload) {
|
||||
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);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -377,7 +377,8 @@ export const npStart = {
|
|||
},
|
||||
data: {
|
||||
actions: {
|
||||
createFiltersFromEvent: Promise.resolve(['yes']),
|
||||
createFiltersFromValueClickAction: Promise.resolve(['yes']),
|
||||
createFiltersFromRangeSelectAction: sinon.fake(),
|
||||
},
|
||||
autocomplete: {
|
||||
getProvider: sinon.fake(),
|
||||
|
|
|
@ -19,30 +19,34 @@
|
|||
|
||||
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 { setIndexPatterns } from '../../../public/services';
|
||||
import { mockDataServices } from '../../../public/search/aggs/test_helpers';
|
||||
import { TriggerContextMapping } from '../../../../ui_actions/public';
|
||||
|
||||
describe('brushEvent', () => {
|
||||
const DAY_IN_MS = 24 * 60 * 60 * 1000;
|
||||
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 = [
|
||||
{
|
||||
params: {
|
||||
field: {},
|
||||
},
|
||||
getIndexPattern: () => ({
|
||||
timeFieldName: 'time',
|
||||
fields: {
|
||||
getByName: () => undefined,
|
||||
filter: () => [],
|
||||
},
|
||||
}),
|
||||
getIndexPattern: () => indexPattern,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -50,56 +54,37 @@ describe('brushEvent', () => {
|
|||
mockDataServices();
|
||||
setIndexPatterns(({
|
||||
...dataPluginMock.createStartContract().indexPatterns,
|
||||
get: async () => ({
|
||||
id: 'indexPatternId',
|
||||
timeFieldName: 'time',
|
||||
fields: {
|
||||
getByName: () => undefined,
|
||||
filter: () => [],
|
||||
},
|
||||
}),
|
||||
get: async () => indexPattern,
|
||||
} as unknown) as IndexPatternsContract);
|
||||
|
||||
baseEvent = {
|
||||
data: {
|
||||
ordered: {
|
||||
date: false,
|
||||
},
|
||||
series: [
|
||||
column: 0,
|
||||
table: {
|
||||
type: 'kibana_datatable',
|
||||
columns: [
|
||||
{
|
||||
values: [
|
||||
{
|
||||
xRaw: {
|
||||
column: 0,
|
||||
table: {
|
||||
columns: [
|
||||
{
|
||||
id: '1',
|
||||
meta: {
|
||||
type: 'histogram',
|
||||
indexPatternId: 'indexPatternId',
|
||||
aggConfigParams: aggConfigs[0].params,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
id: '1',
|
||||
name: '1',
|
||||
meta: {
|
||||
type: 'histogram',
|
||||
indexPatternId: 'indexPatternId',
|
||||
aggConfigParams: aggConfigs[0].params,
|
||||
},
|
||||
},
|
||||
],
|
||||
rows: [],
|
||||
},
|
||||
range: [],
|
||||
};
|
||||
});
|
||||
|
||||
test('should be a function', () => {
|
||||
expect(typeof onBrushEvent).toBe('function');
|
||||
expect(typeof createFiltersFromRangeSelectAction).toBe('function');
|
||||
});
|
||||
|
||||
test('ignores event when data.xAxisField not provided', async () => {
|
||||
const filter = await onBrushEvent(baseEvent);
|
||||
expect(filter).toBeUndefined();
|
||||
const filter = await createFiltersFromRangeSelectAction(baseEvent);
|
||||
expect(filter).toEqual([]);
|
||||
});
|
||||
|
||||
describe('handles an event when the x-axis field is a date field', () => {
|
||||
|
@ -109,29 +94,29 @@ describe('brushEvent', () => {
|
|||
name: 'time',
|
||||
type: 'date',
|
||||
};
|
||||
baseEvent.data.ordered = { date: true };
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
baseEvent.range = [];
|
||||
baseEvent.data.ordered = { date: false };
|
||||
aggConfigs[0].params.field = {};
|
||||
});
|
||||
|
||||
test('by ignoring the event when range spans zero time', async () => {
|
||||
baseEvent.range = [JAN_01_2014, JAN_01_2014];
|
||||
const filter = await onBrushEvent(baseEvent);
|
||||
expect(filter).toBeUndefined();
|
||||
const filter = await createFiltersFromRangeSelectAction(baseEvent);
|
||||
expect(filter).toEqual([]);
|
||||
});
|
||||
|
||||
test('by updating the timefilter', async () => {
|
||||
baseEvent.range = [JAN_01_2014, JAN_01_2014 + DAY_IN_MS];
|
||||
const filter = await onBrushEvent(baseEvent);
|
||||
const filter = await createFiltersFromRangeSelectAction(baseEvent);
|
||||
expect(filter).toBeDefined();
|
||||
|
||||
if (filter) {
|
||||
expect(filter.range.time.gte).toBe(new Date(JAN_01_2014).toISOString());
|
||||
if (filter.length) {
|
||||
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.
|
||||
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',
|
||||
type: 'date',
|
||||
};
|
||||
baseEvent.data.ordered = { date: true };
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
baseEvent.range = [];
|
||||
baseEvent.data.ordered = { date: false };
|
||||
aggConfigs[0].params.field = {};
|
||||
});
|
||||
|
||||
test('creates a new range filter', async () => {
|
||||
const rangeBegin = JAN_01_2014;
|
||||
const rangeEnd = rangeBegin + DAY_IN_MS;
|
||||
baseEvent.range = [rangeBegin, rangeEnd];
|
||||
const filter = await onBrushEvent(baseEvent);
|
||||
const filter = await createFiltersFromRangeSelectAction(baseEvent);
|
||||
|
||||
expect(filter).toBeDefined();
|
||||
|
||||
if (filter) {
|
||||
expect(filter.range.anotherTimeField.gte).toBe(moment(rangeBegin).toISOString());
|
||||
expect(filter.range.anotherTimeField.lt).toBe(moment(rangeEnd).toISOString());
|
||||
expect(filter.range.anotherTimeField).toHaveProperty(
|
||||
if (filter.length) {
|
||||
const rangeFilter = filter[0] as RangeFilter;
|
||||
expect(rangeFilter.range.anotherTimeField.gte).toBe(moment(rangeBegin).toISOString());
|
||||
expect(rangeFilter.range.anotherTimeField.lt).toBe(moment(rangeEnd).toISOString());
|
||||
expect(rangeFilter.range.anotherTimeField).toHaveProperty(
|
||||
'format',
|
||||
'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 () => {
|
||||
baseEvent.range = [1];
|
||||
const filter = await onBrushEvent(baseEvent);
|
||||
expect(filter).toBeUndefined();
|
||||
const filter = await createFiltersFromRangeSelectAction(baseEvent);
|
||||
expect(filter).toEqual([]);
|
||||
});
|
||||
|
||||
test('by creating a new filter', async () => {
|
||||
baseEvent.range = [1, 2, 3, 4];
|
||||
const filter = await onBrushEvent(baseEvent);
|
||||
const filter = await createFiltersFromRangeSelectAction(baseEvent);
|
||||
|
||||
expect(filter).toBeDefined();
|
||||
|
||||
if (filter) {
|
||||
expect(filter.range.numberField.gte).toBe(1);
|
||||
expect(filter.range.numberField.lt).toBe(4);
|
||||
expect(filter.range.numberField).not.toHaveProperty('format');
|
||||
if (filter.length) {
|
||||
const rangeFilter = filter[0] as RangeFilter;
|
||||
expect(rangeFilter.range.numberField.gte).toBe(1);
|
||||
expect(rangeFilter.range.numberField.lt).toBe(4);
|
||||
expect(rangeFilter.range.numberField).not.toHaveProperty('format');
|
||||
}
|
||||
});
|
||||
});
|
|
@ -17,34 +17,18 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { get, last } from 'lodash';
|
||||
import { last } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { esFilters, IFieldType, RangeFilterParams } from '../../../public';
|
||||
import { getIndexPatterns } from '../../../public/services';
|
||||
import { deserializeAggConfig } from '../../search/expressions/utils';
|
||||
import { RangeSelectTriggerContext } from '../../../../embeddable/public';
|
||||
|
||||
export interface BrushEvent {
|
||||
data: {
|
||||
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];
|
||||
export async function createFiltersFromRangeSelectAction(event: RangeSelectTriggerContext['data']) {
|
||||
const column: Record<string, any> = event.table.columns[event.column];
|
||||
|
||||
if (!column || !column.meta) {
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
|
||||
const indexPattern = await getIndexPatterns().get(column.meta.indexPatternId);
|
||||
|
@ -55,16 +39,18 @@ export async function onBrushEvent(event: BrushEvent) {
|
|||
const field: IFieldType = aggConfig.params.field;
|
||||
|
||||
if (!field || event.range.length <= 1) {
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
|
||||
const min = event.range[0];
|
||||
const max = last(event.range);
|
||||
|
||||
if (min === max) {
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
|
||||
const isDate = field.type === 'date';
|
||||
|
||||
const range: RangeFilterParams = {
|
||||
gte: isDate ? moment(min).toISOString() : min,
|
||||
lt: isDate ? moment(max).toISOString() : max,
|
||||
|
@ -74,5 +60,5 @@ export async function onBrushEvent(event: BrushEvent) {
|
|||
range.format = 'strict_date_optional_time';
|
||||
}
|
||||
|
||||
return esFilters.buildRangeFilter(field, range, indexPattern);
|
||||
return esFilters.mapAndFlattenFilters([esFilters.buildRangeFilter(field, range, indexPattern)]);
|
||||
}
|
|
@ -26,7 +26,8 @@ import {
|
|||
import { dataPluginMock } from '../../../public/mocks';
|
||||
import { setIndexPatterns } from '../../../public/services';
|
||||
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 = {
|
||||
name: 'bytes',
|
||||
|
@ -37,8 +38,8 @@ const mockField = {
|
|||
format: new fieldFormats.BytesFormat({}, (() => {}) as FieldFormatsGetConfigFn),
|
||||
};
|
||||
|
||||
describe('createFiltersFromEvent', () => {
|
||||
let dataPoints: EventData[];
|
||||
describe('createFiltersFromValueClick', () => {
|
||||
let dataPoints: ValueClickTriggerContext['data']['data'];
|
||||
|
||||
beforeEach(() => {
|
||||
dataPoints = [
|
||||
|
@ -86,7 +87,7 @@ describe('createFiltersFromEvent', () => {
|
|||
|
||||
test('ignores event when value for rows is not provided', async () => {
|
||||
dataPoints[0].table.rows[0]['1-1'] = null;
|
||||
const filters = await createFiltersFromEvent(dataPoints);
|
||||
const filters = await createFiltersFromValueClickAction({ data: dataPoints });
|
||||
|
||||
expect(filters.length).toEqual(0);
|
||||
});
|
||||
|
@ -95,14 +96,14 @@ describe('createFiltersFromEvent', () => {
|
|||
if (dataPoints[0].table.columns[0].meta) {
|
||||
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[0].query.match_phrase.bytes).toEqual('2048');
|
||||
});
|
||||
|
||||
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);
|
||||
|
|
@ -21,13 +21,7 @@ import { KibanaDatatable } from '../../../../../plugins/expressions/public';
|
|||
import { deserializeAggConfig } from '../../search/expressions';
|
||||
import { esFilters, Filter } from '../../../public';
|
||||
import { getIndexPatterns } from '../../../public/services';
|
||||
|
||||
export interface EventData {
|
||||
table: Pick<KibanaDatatable, 'rows' | 'columns'>;
|
||||
column: number;
|
||||
row: number;
|
||||
value: any;
|
||||
}
|
||||
import { ValueClickTriggerContext } from '../../../../embeddable/public';
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
const getOtherBucketFilterTerms = (
|
||||
table: EventData['table'],
|
||||
table: Pick<KibanaDatatable, 'rows' | 'columns'>,
|
||||
columnIndex: number,
|
||||
rowIndex: number
|
||||
) => {
|
||||
|
@ -76,7 +70,11 @@ const getOtherBucketFilterTerms = (
|
|||
* @param {string} cellValue - value of the current cell
|
||||
* @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]) {
|
||||
return;
|
||||
}
|
||||
|
@ -113,11 +111,14 @@ const createFilter = async (table: EventData['table'], columnIndex: number, rowI
|
|||
};
|
||||
|
||||
/** @public */
|
||||
export const createFiltersFromEvent = async (dataPoints: EventData[], negate?: boolean) => {
|
||||
export const createFiltersFromValueClickAction = async ({
|
||||
data,
|
||||
negate,
|
||||
}: ValueClickTriggerContext['data']) => {
|
||||
const filters: Filter[] = [];
|
||||
|
||||
await Promise.all(
|
||||
dataPoints
|
||||
data
|
||||
.filter(point => point)
|
||||
.map(async val => {
|
||||
const { table, column, row } = val;
|
||||
|
@ -133,5 +134,5 @@ export const createFiltersFromEvent = async (dataPoints: EventData[], negate?: b
|
|||
})
|
||||
);
|
||||
|
||||
return filters;
|
||||
return esFilters.mapAndFlattenFilters(filters);
|
||||
};
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
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 { valueClickAction } from './value_click_action';
|
||||
|
|
|
@ -23,19 +23,17 @@ import {
|
|||
IncompatibleActionError,
|
||||
ActionByType,
|
||||
} 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 '..';
|
||||
|
||||
export const ACTION_SELECT_RANGE = 'ACTION_SELECT_RANGE';
|
||||
|
||||
export interface SelectRangeActionContext {
|
||||
data: any;
|
||||
timeFieldName: string;
|
||||
}
|
||||
export type SelectRangeActionContext = RangeSelectTriggerContext;
|
||||
|
||||
async function isCompatible(context: SelectRangeActionContext) {
|
||||
try {
|
||||
return Boolean(await onBrushEvent(context.data));
|
||||
return Boolean(await createFiltersFromRangeSelectAction(context.data));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
@ -59,13 +57,7 @@ export function selectRangeAction(
|
|||
throw new IncompatibleActionError();
|
||||
}
|
||||
|
||||
const filter = await onBrushEvent(data);
|
||||
|
||||
if (!filter) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedFilters = esFilters.mapAndFlattenFilters([filter]);
|
||||
const selectedFilters = await createFiltersFromRangeSelectAction(data);
|
||||
|
||||
if (timeFieldName) {
|
||||
const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter(
|
||||
|
|
|
@ -26,21 +26,17 @@ import {
|
|||
} from '../../../../plugins/ui_actions/public';
|
||||
import { getOverlays, getIndexPatterns } from '../services';
|
||||
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 '..';
|
||||
|
||||
export const ACTION_VALUE_CLICK = 'ACTION_VALUE_CLICK';
|
||||
|
||||
export interface ValueClickActionContext {
|
||||
data: any;
|
||||
timeFieldName: string;
|
||||
}
|
||||
export type ValueClickActionContext = ValueClickTriggerContext;
|
||||
|
||||
async function isCompatible(context: ValueClickActionContext) {
|
||||
try {
|
||||
const filters: Filter[] =
|
||||
(await createFiltersFromEvent(context.data.data || [context.data], context.data.negate)) ||
|
||||
[];
|
||||
const filters: Filter[] = await createFiltersFromValueClickAction(context.data);
|
||||
return filters.length > 0;
|
||||
} catch {
|
||||
return false;
|
||||
|
@ -60,17 +56,16 @@ export function valueClickAction(
|
|||
});
|
||||
},
|
||||
isCompatible,
|
||||
execute: async ({ timeFieldName, data }: ValueClickActionContext) => {
|
||||
if (!(await isCompatible({ timeFieldName, data }))) {
|
||||
execute: async (context: ValueClickActionContext) => {
|
||||
if (!(await isCompatible(context))) {
|
||||
throw new IncompatibleActionError();
|
||||
}
|
||||
|
||||
const filters: Filter[] =
|
||||
(await createFiltersFromEvent(data.data || [data], data.negate)) || [];
|
||||
const filters: Filter[] = await createFiltersFromValueClickAction(context.data);
|
||||
|
||||
let selectedFilters: Filter[] = esFilters.mapAndFlattenFilters(filters);
|
||||
let selectedFilters = filters;
|
||||
|
||||
if (selectedFilters.length > 1) {
|
||||
if (filters.length > 1) {
|
||||
const indexPatterns = await Promise.all(
|
||||
filters.map(filter => {
|
||||
return getIndexPatterns().get(filter.meta.index!);
|
||||
|
@ -102,9 +97,9 @@ export function valueClickAction(
|
|||
selectedFilters = await filterSelectionPromise;
|
||||
}
|
||||
|
||||
if (timeFieldName) {
|
||||
if (context.timeFieldName) {
|
||||
const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter(
|
||||
timeFieldName,
|
||||
context.timeFieldName,
|
||||
selectedFilters
|
||||
);
|
||||
filterManager.addFilters(restOfFilters);
|
||||
|
|
|
@ -45,7 +45,8 @@ const createStartContract = (): Start => {
|
|||
const queryStartMock = queryServiceMock.createStartContract();
|
||||
return {
|
||||
actions: {
|
||||
createFiltersFromEvent: jest.fn().mockResolvedValue(['yes']),
|
||||
createFiltersFromValueClickAction: jest.fn().mockResolvedValue(['yes']),
|
||||
createFiltersFromRangeSelectAction: jest.fn(),
|
||||
},
|
||||
autocomplete: autocompleteMock,
|
||||
search: searchStartMock,
|
||||
|
|
|
@ -58,7 +58,12 @@ import {
|
|||
VALUE_CLICK_TRIGGER,
|
||||
APPLY_FILTER_TRIGGER,
|
||||
} 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 {
|
||||
selectRangeAction,
|
||||
|
@ -162,7 +167,8 @@ export class DataPublicPlugin implements Plugin<DataPublicPluginSetup, DataPubli
|
|||
|
||||
const dataServices = {
|
||||
actions: {
|
||||
createFiltersFromEvent,
|
||||
createFiltersFromValueClickAction,
|
||||
createFiltersFromRangeSelectAction,
|
||||
},
|
||||
autocomplete: this.autocomplete.start(),
|
||||
fieldFormats,
|
||||
|
|
|
@ -248,7 +248,8 @@ export interface DataPublicPluginSetup {
|
|||
export interface DataPublicPluginStart {
|
||||
// (undocumented)
|
||||
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
|
||||
//
|
||||
|
@ -484,7 +485,7 @@ export type FieldFormatId = FIELD_FORMAT_IDS | string;
|
|||
export const fieldFormats: {
|
||||
FieldFormat: typeof FieldFormat;
|
||||
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: {
|
||||
range: 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/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/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: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: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: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)
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ import { ExpressionsSetup } from 'src/plugins/expressions/public';
|
|||
import { UiActionsSetup, UiActionsStart } from 'src/plugins/ui_actions/public';
|
||||
import { AutocompleteSetup, AutocompleteStart } from './autocomplete';
|
||||
import { FieldFormatsSetup, FieldFormatsStart } from './field_formats';
|
||||
import { createFiltersFromEvent } from './actions';
|
||||
import { createFiltersFromRangeSelectAction, createFiltersFromValueClickAction } from './actions';
|
||||
import { ISearchSetup, ISearchStart } from './search';
|
||||
import { QuerySetup, QueryStart } from './query';
|
||||
import { IndexPatternSelectProps } from './ui/index_pattern_select';
|
||||
|
@ -49,7 +49,8 @@ export interface DataPublicPluginSetup {
|
|||
|
||||
export interface DataPublicPluginStart {
|
||||
actions: {
|
||||
createFiltersFromEvent: typeof createFiltersFromEvent;
|
||||
createFiltersFromValueClickAction: typeof createFiltersFromValueClickAction;
|
||||
createFiltersFromRangeSelectAction: typeof createFiltersFromRangeSelectAction;
|
||||
};
|
||||
autocomplete: AutocompleteStart;
|
||||
indexPatterns: IndexPatternsContract;
|
||||
|
|
|
@ -283,7 +283,7 @@ export interface FieldFormatConfig {
|
|||
export const fieldFormats: {
|
||||
FieldFormatsRegistry: typeof FieldFormatsRegistry;
|
||||
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;
|
||||
BytesFormat: typeof BytesFormat;
|
||||
ColorFormat: typeof ColorFormat;
|
||||
|
|
|
@ -47,7 +47,8 @@ export {
|
|||
EmbeddableOutput,
|
||||
EmbeddablePanel,
|
||||
EmbeddableRoot,
|
||||
EmbeddableVisTriggerContext,
|
||||
ValueClickTriggerContext,
|
||||
RangeSelectTriggerContext,
|
||||
ErrorEmbeddable,
|
||||
IContainer,
|
||||
IEmbeddable,
|
||||
|
|
|
@ -18,18 +18,34 @@
|
|||
*/
|
||||
|
||||
import { Trigger } from '../../../../ui_actions/public';
|
||||
import { KibanaDatatable } from '../../../../expressions';
|
||||
import { IEmbeddable } from '..';
|
||||
|
||||
export interface EmbeddableContext {
|
||||
embeddable: IEmbeddable;
|
||||
}
|
||||
|
||||
export interface EmbeddableVisTriggerContext {
|
||||
export interface ValueClickTriggerContext {
|
||||
embeddable?: IEmbeddable;
|
||||
timeFieldName?: string;
|
||||
data: {
|
||||
e?: MouseEvent;
|
||||
data: unknown;
|
||||
data: Array<{
|
||||
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[];
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -19,9 +19,10 @@
|
|||
|
||||
import { ActionByType } from './actions/action';
|
||||
import { TriggerInternal } from './triggers/trigger_internal';
|
||||
import { EmbeddableVisTriggerContext, IEmbeddable } from '../../embeddable/public';
|
||||
import { Filter } from '../../data/public';
|
||||
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 ActionRegistry = Map<string, ActionByType<any>>;
|
||||
|
@ -36,8 +37,8 @@ export type TriggerContext = BaseContext;
|
|||
|
||||
export interface TriggerContextMapping {
|
||||
[DEFAULT_TRIGGER]: TriggerContext;
|
||||
[SELECT_RANGE_TRIGGER]: EmbeddableVisTriggerContext;
|
||||
[VALUE_CLICK_TRIGGER]: EmbeddableVisTriggerContext;
|
||||
[SELECT_RANGE_TRIGGER]: RangeSelectTriggerContext;
|
||||
[VALUE_CLICK_TRIGGER]: ValueClickTriggerContext;
|
||||
[APPLY_FILTER_TRIGGER]: {
|
||||
embeddable: IEmbeddable;
|
||||
filters: Filter[];
|
||||
|
|
|
@ -33,7 +33,6 @@ import {
|
|||
EmbeddableInput,
|
||||
EmbeddableOutput,
|
||||
Embeddable,
|
||||
EmbeddableVisTriggerContext,
|
||||
IContainer,
|
||||
} from '../../../../plugins/embeddable/public';
|
||||
import { dispatchRenderComplete } from '../../../../plugins/kibana_utils/public';
|
||||
|
@ -261,7 +260,7 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
|
|||
if (!this.input.disableTriggers) {
|
||||
const triggerId =
|
||||
event.name === 'brush' ? VIS_EVENT_TO_TRIGGER.brush : VIS_EVENT_TO_TRIGGER.filter;
|
||||
const context: EmbeddableVisTriggerContext = {
|
||||
const context = {
|
||||
embeddable: this,
|
||||
timeFieldName: this.vis.data.indexPattern!.timeFieldName!,
|
||||
data: event.data,
|
||||
|
|
|
@ -69,7 +69,15 @@ export class ExprVis extends EventEmitter {
|
|||
events: {
|
||||
filter: (data: any) => {
|
||||
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) => {
|
||||
if (!this.eventsSubject) return;
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
import { EuiIcon, EuiText, IconType, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
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 { LensMultiTable, FormatFactory } from '../types';
|
||||
import { XYArgs, SeriesType, visualizationTypes } from './types';
|
||||
|
@ -277,7 +277,7 @@ export function XYChart({
|
|||
|
||||
const timeFieldName = xDomain && xAxisFieldName;
|
||||
|
||||
const context: EmbeddableVisTriggerContext = {
|
||||
const context: ValueClickTriggerContext = {
|
||||
data: {
|
||||
data: points.map(point => ({
|
||||
row: point.row,
|
||||
|
|
Loading…
Reference in a new issue