Skip to content

Commit

Permalink
[TSVB] Filter by clicking on the timeseries chart (#97426)
Browse files Browse the repository at this point in the history
* WIP TSVB filter by click

* Disable filter click when showBar is set to false

* Exclude metric columns as they are not filtered

* Allow filters group by filter click event

* Fix CI and unit tests

* Add some comments

* Move to separate function for easier testing

* Add more unit tests

* Add a functional test

* Improve types

* Fix bug with group by filters and user applies custom labels

* fix time filter bug

* Address PR comments

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
stratoula and kibanamachine authored May 12, 2021
1 parent 4064b02 commit a900110
Show file tree
Hide file tree
Showing 18 changed files with 492 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export enum METRIC_TYPES {
// We should probably use BUCKET_TYPES from data plugin in future.
export enum BUCKET_TYPES {
TERMS = 'terms',
FILTERS = 'filters',
}

export const EXTENDED_STATS_TYPES = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ jest.mock('../../../services', () => {
getDataStart: jest.fn(() => {
return {
indexPatterns: jest.fn(),
query: {
timefilter: {
timefilter: {
getTime: jest.fn(() => {
return {
from: '2021-04-30T16:42:24.502Z',
to: '2021-05-05T14:42:24.502Z',
};
}),
},
},
},
};
}),
};
Expand All @@ -25,9 +37,12 @@ describe('convert series to datatables', () => {

beforeEach(() => {
const fieldMap: Record<string, IndexPatternField> = {
test1: { name: 'test1', spec: { type: 'date' } } as IndexPatternField,
test2: { name: 'test2' } as IndexPatternField,
test3: { name: 'test3', spec: { type: 'boolean' } } as IndexPatternField,
test1: { name: 'test1', spec: { type: 'date', name: 'test1' } } as IndexPatternField,
test2: {
name: 'test2',
spec: { type: 'number', name: 'Average of test2' },
} as IndexPatternField,
test3: { name: 'test3', spec: { type: 'boolean', name: 'test3' } } as IndexPatternField,
};

const getFieldByName = (name: string): IndexPatternField | undefined => fieldMap[name];
Expand All @@ -41,8 +56,8 @@ describe('convert series to datatables', () => {

describe('addMetaColumns()', () => {
test('adds the correct meta to a date column', () => {
const columns = [{ id: 0, name: 'test1', isSplit: false }];
const columnsWithMeta = addMetaToColumns(columns, indexPattern, 'count');
const columns = [{ id: 0, name: 'test1', isMetric: true, type: 'date_histogram' }];
const columnsWithMeta = addMetaToColumns(columns, indexPattern);
expect(columnsWithMeta).toEqual([
{
id: '0',
Expand All @@ -54,6 +69,13 @@ describe('convert series to datatables', () => {
enabled: true,
indexPatternId: 'index1',
type: 'date_histogram',
schema: 'metric',
params: {
timeRange: {
from: '2021-04-30T16:42:24.502Z',
to: '2021-05-05T14:42:24.502Z',
},
},
},
type: 'date',
},
Expand All @@ -63,8 +85,8 @@ describe('convert series to datatables', () => {
});

test('adds the correct meta to a non date column', () => {
const columns = [{ id: 1, name: 'Average of test2', isSplit: false }];
const columnsWithMeta = addMetaToColumns(columns, indexPattern, 'avg');
const columns = [{ id: 1, name: 'test2', isMetric: true, type: 'avg' }];
const columnsWithMeta = addMetaToColumns(columns, indexPattern);
expect(columnsWithMeta).toEqual([
{
id: '1',
Expand All @@ -76,17 +98,21 @@ describe('convert series to datatables', () => {
enabled: true,
indexPatternId: 'index1',
type: 'avg',
schema: 'metric',
params: {
field: 'Average of test2',
},
},
type: 'number',
},
name: 'Average of test2',
name: 'test2',
},
]);
});

test('adds the correct meta for a split column', () => {
const columns = [{ id: 2, name: 'test3', isSplit: true }];
const columnsWithMeta = addMetaToColumns(columns, indexPattern, 'avg');
const columns = [{ id: 2, name: 'test3', isMetric: false, type: 'terms' }];
const columnsWithMeta = addMetaToColumns(columns, indexPattern);
expect(columnsWithMeta).toEqual([
{
id: '2',
Expand All @@ -98,6 +124,10 @@ describe('convert series to datatables', () => {
enabled: true,
indexPatternId: 'index1',
type: 'terms',
schema: 'group',
params: {
field: 'test3',
},
},
type: 'boolean',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,51 +6,74 @@
* Side Public License, v 1.
*/
import { IndexPattern } from 'src/plugins/data/public';
import {
Datatable,
DatatableRow,
DatatableColumn,
DatatableColumnType,
} from 'src/plugins/expressions/public';
import { DatatableRow, DatatableColumn, DatatableColumnType } from 'src/plugins/expressions/public';
import { Query } from 'src/plugins/data/common';
import { TimeseriesVisParams } from '../../../types';
import type { PanelData } from '../../../../common/types';
import { BUCKET_TYPES } from '../../../../common/enums';
import { fetchIndexPattern } from '../../../../common/index_patterns_utils';
import { getDataStart } from '../../../services';
import { X_ACCESSOR_INDEX } from '../../visualizations/constants';
import type { TSVBTables } from './types';

interface TSVBTables {
[key: string]: Datatable;
interface FilterParams {
filter?: Query;
label?: string;
field?: string;
}

interface TSVBColumns {
id: number;
name: string;
isSplit: boolean;
isMetric: boolean;
type: string;
params?: FilterParams[];
}

export const addMetaToColumns = (
columns: TSVBColumns[],
indexPattern: IndexPattern,
metricsType: string
indexPattern: IndexPattern
): DatatableColumn[] => {
return columns.map((column) => {
const field = indexPattern.getFieldByName(column.name);
const type = (field?.spec.type as DatatableColumnType) || 'number';

let params: unknown = {
field: field?.spec.name,
};
if (column.type === BUCKET_TYPES.FILTERS && column.params) {
const filters = column.params.map((col) => ({
input: col.filter,
label: col.label,
}));
params = {
filters,
};
} else if (column.type === 'date_histogram') {
const { query } = getDataStart();
const timeRange = query.timefilter.timefilter.getTime();
params = {
timeRange,
};
}

const cleanedColumn = {
id: column.id.toString(),
name: column.name,
meta: {
type,
field: column.name,
field: field?.spec.name,
index: indexPattern.title,
source: 'esaggs',
sourceParams: {
enabled: true,
indexPatternId: indexPattern?.id,
type: type === 'date' ? 'date_histogram' : column.isSplit ? 'terms' : metricsType,
type: column.type,
schema: column.isMetric ? 'metric' : 'group',
params,
},
},
};
} as DatatableColumn;
return cleanedColumn;
});
};
Expand All @@ -73,30 +96,58 @@ export const convertSeriesToDataTable = async (
usedIndexPattern = indexPattern;
}
}
const isGroupedByTerms = layer.split_mode === 'terms';
const isGroupedByTerms = layer.split_mode === BUCKET_TYPES.TERMS;
const isGroupedByFilters = layer.split_mode === BUCKET_TYPES.FILTERS;
const seriesPerLayer = series.filter((s) => s.seriesId === layer.id);
let id = X_ACCESSOR_INDEX;

const columns: TSVBColumns[] = [
{ id, name: usedIndexPattern.timeFieldName || '', isSplit: false },
{
id,
name: usedIndexPattern.timeFieldName || '',
isMetric: false,
type: 'date_histogram',
},
];

if (seriesPerLayer.length) {
id++;
columns.push({ id, name: seriesPerLayer[0].splitByLabel, isSplit: false });
// Adds an extra column, if the layer is split by terms aggregation
const metrics = layer.metrics;
columns.push({
id,
name: metrics[metrics.length - 1].field || seriesPerLayer[0].splitByLabel,
isMetric: true,
type: metrics[metrics.length - 1].type,
});

// Adds an extra column, if the layer is split by terms or filters aggregation
if (isGroupedByTerms) {
id++;
columns.push({ id, name: layer.terms_field || '', isSplit: true });
columns.push({
id,
name: layer.terms_field || '',
isMetric: false,
type: BUCKET_TYPES.TERMS,
});
} else if (isGroupedByFilters) {
id++;
columns.push({
id,
name: BUCKET_TYPES.FILTERS,
isMetric: false,
params: layer?.split_filters as FilterParams[],
type: BUCKET_TYPES.FILTERS,
});
}
}
const columnsWithMeta = addMetaToColumns(columns, usedIndexPattern, layer.metrics[0].type);

const columnsWithMeta = addMetaToColumns(columns, usedIndexPattern);
const filtersColumn = columns.find((col) => col.type === BUCKET_TYPES.FILTERS);
let rows: DatatableRow[] = [];
for (let j = 0; j < seriesPerLayer.length; j++) {
const data = seriesPerLayer[j].data.map((rowData) => {
const row: DatatableRow = [rowData[0], rowData[1]];
// If the layer is split by terms aggregation, the data array should also contain the split value.
if (isGroupedByTerms) {
if (isGroupedByTerms || filtersColumn) {
row.push(seriesPerLayer[j].label);
}
return row;
Expand Down
Loading

0 comments on commit a900110

Please sign in to comment.