forked from elastic/kibana
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[TSVB] Enables url drilldowns for range selection (elastic#95296)
* Temp research code * Make it work * Cleanup * Convert series to datatable * Remove unecessary log * Minor * Fix types problem * Add unit tests * Take under consideration the override index pattern setting * Implement brush event for dual mode * Move indexpatterns fetch outside the loop Co-authored-by: Kibana Machine <[email protected]>
- Loading branch information
1 parent
ebdcd92
commit be25e9c
Showing
12 changed files
with
335 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
185 changes: 185 additions & 0 deletions
185
...vis_type_timeseries/public/application/components/lib/convert_series_to_datatable.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
import { IndexPattern, IndexPatternField } from 'src/plugins/data/public'; | ||
import { PanelData } from '../../../../common/types'; | ||
import { TimeseriesVisParams } from '../../../types'; | ||
import { convertSeriesToDataTable, addMetaToColumns } from './convert_series_to_datatable'; | ||
|
||
jest.mock('../../../services', () => { | ||
return { | ||
getDataStart: jest.fn(() => { | ||
return { | ||
indexPatterns: jest.fn(), | ||
}; | ||
}), | ||
}; | ||
}); | ||
|
||
describe('convert series to datatables', () => { | ||
let indexPattern: IndexPattern; | ||
|
||
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, | ||
}; | ||
|
||
const getFieldByName = (name: string): IndexPatternField | undefined => fieldMap[name]; | ||
indexPattern = { | ||
id: 'index1', | ||
title: 'index1', | ||
timeFieldName: 'timestamp', | ||
getFieldByName, | ||
} as IndexPattern; | ||
}); | ||
|
||
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'); | ||
expect(columnsWithMeta).toEqual([ | ||
{ | ||
id: '0', | ||
meta: { | ||
field: 'test1', | ||
index: 'index1', | ||
source: 'esaggs', | ||
sourceParams: { | ||
enabled: true, | ||
indexPatternId: 'index1', | ||
type: 'date_histogram', | ||
}, | ||
type: 'date', | ||
}, | ||
name: 'test1', | ||
}, | ||
]); | ||
}); | ||
|
||
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'); | ||
expect(columnsWithMeta).toEqual([ | ||
{ | ||
id: '1', | ||
meta: { | ||
field: 'Average of test2', | ||
index: 'index1', | ||
source: 'esaggs', | ||
sourceParams: { | ||
enabled: true, | ||
indexPatternId: 'index1', | ||
type: 'avg', | ||
}, | ||
type: 'number', | ||
}, | ||
name: 'Average of test2', | ||
}, | ||
]); | ||
}); | ||
|
||
test('adds the correct meta for a split column', () => { | ||
const columns = [{ id: 2, name: 'test3', isSplit: true }]; | ||
const columnsWithMeta = addMetaToColumns(columns, indexPattern, 'avg'); | ||
expect(columnsWithMeta).toEqual([ | ||
{ | ||
id: '2', | ||
meta: { | ||
field: 'test3', | ||
index: 'index1', | ||
source: 'esaggs', | ||
sourceParams: { | ||
enabled: true, | ||
indexPatternId: 'index1', | ||
type: 'terms', | ||
}, | ||
type: 'boolean', | ||
}, | ||
name: 'test3', | ||
}, | ||
]); | ||
}); | ||
}); | ||
|
||
describe('convertSeriesToDataTable()', () => { | ||
const model = { | ||
series: [ | ||
{ | ||
formatter: 'number', | ||
id: 'series1', | ||
label: '', | ||
line_width: 1, | ||
metrics: [ | ||
{ | ||
field: 'test2', | ||
id: 'series1', | ||
type: 'avg', | ||
}, | ||
], | ||
split_mode: 'terms', | ||
terms_field: 'Cancelled', | ||
type: 'timeseries', | ||
}, | ||
], | ||
} as TimeseriesVisParams; | ||
const series = ([ | ||
{ | ||
id: 'series1:0', | ||
label: 0, | ||
splitByLabel: 'Average of test2', | ||
labelFormatted: 'false', | ||
data: [ | ||
[1616454000000, 0], | ||
[1616457600000, 5], | ||
[1616461200000, 7], | ||
[1616464800000, 8], | ||
], | ||
seriesId: 'series1', | ||
isSplitByTerms: true, | ||
}, | ||
{ | ||
id: 'series1:1', | ||
label: 1, | ||
splitByLabel: 'Average of test2', | ||
labelFormatted: 'true', | ||
data: [ | ||
[1616454000000, 10], | ||
[1616457600000, 12], | ||
[1616461200000, 1], | ||
[1616464800000, 14], | ||
], | ||
seriesId: 'series1', | ||
isSplitByTerms: true, | ||
}, | ||
] as unknown) as PanelData[]; | ||
test('creates one table for one layer series with the correct columns', async () => { | ||
const tables = await convertSeriesToDataTable(model, series, indexPattern); | ||
expect(Object.keys(tables).sort()).toEqual([model.series[0].id].sort()); | ||
|
||
expect(tables.series1.columns.length).toEqual(3); | ||
expect(tables.series1.rows.length).toEqual(8); | ||
}); | ||
|
||
test('the table rows for a series with term aggregation should be a combination of the different terms', async () => { | ||
const tables = await convertSeriesToDataTable(model, series, indexPattern); | ||
expect(Object.keys(tables).sort()).toEqual([model.series[0].id].sort()); | ||
|
||
expect(tables.series1.rows.length).toEqual(8); | ||
const expected1 = series[0].data.map((d) => { | ||
d.push(parseInt(series[0].label, 10)); | ||
return d; | ||
}); | ||
const expected2 = series[1].data.map((d) => { | ||
d.push(parseInt(series[1].label, 10)); | ||
return d; | ||
}); | ||
expect(tables.series1.rows).toEqual([...expected1, ...expected2]); | ||
}); | ||
}); | ||
}); |
113 changes: 113 additions & 0 deletions
113
...gins/vis_type_timeseries/public/application/components/lib/convert_series_to_datatable.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
import { IndexPattern } from 'src/plugins/data/public'; | ||
import { | ||
Datatable, | ||
DatatableRow, | ||
DatatableColumn, | ||
DatatableColumnType, | ||
} from 'src/plugins/expressions/public'; | ||
import { TimeseriesVisParams } from '../../../types'; | ||
import { PanelData } from '../../../../common/types'; | ||
import { fetchIndexPattern } from '../../../../common/index_patterns_utils'; | ||
import { getDataStart } from '../../../services'; | ||
import { X_ACCESSOR_INDEX } from '../../visualizations/constants'; | ||
|
||
interface TSVBTables { | ||
[key: string]: Datatable; | ||
} | ||
|
||
interface TSVBColumns { | ||
id: number; | ||
name: string; | ||
isSplit: boolean; | ||
} | ||
|
||
export const addMetaToColumns = ( | ||
columns: TSVBColumns[], | ||
indexPattern: IndexPattern, | ||
metricsType: string | ||
): DatatableColumn[] => { | ||
return columns.map((column) => { | ||
const field = indexPattern.getFieldByName(column.name); | ||
const type = (field?.spec.type as DatatableColumnType) || 'number'; | ||
const cleanedColumn = { | ||
id: column.id.toString(), | ||
name: column.name, | ||
meta: { | ||
type, | ||
field: column.name, | ||
index: indexPattern.title, | ||
source: 'esaggs', | ||
sourceParams: { | ||
enabled: true, | ||
indexPatternId: indexPattern?.id, | ||
type: type === 'date' ? 'date_histogram' : column.isSplit ? 'terms' : metricsType, | ||
}, | ||
}, | ||
}; | ||
return cleanedColumn; | ||
}); | ||
}; | ||
|
||
export const convertSeriesToDataTable = async ( | ||
model: TimeseriesVisParams, | ||
series: PanelData[], | ||
initialIndexPattern: IndexPattern | ||
) => { | ||
const tables: TSVBTables = {}; | ||
const { indexPatterns } = getDataStart(); | ||
for (let layerIdx = 0; layerIdx < model.series.length; layerIdx++) { | ||
const layer = model.series[layerIdx]; | ||
let usedIndexPattern = initialIndexPattern; | ||
// The user can overwrite the index pattern of a layer. | ||
// In that case, the index pattern should be fetched again. | ||
if (layer.override_index_pattern) { | ||
const { indexPattern } = await fetchIndexPattern(layer.series_index_pattern, indexPatterns); | ||
if (indexPattern) { | ||
usedIndexPattern = indexPattern; | ||
} | ||
} | ||
const isGroupedByTerms = layer.split_mode === 'terms'; | ||
const seriesPerLayer = series.filter((s) => s.seriesId === layer.id); | ||
let id = X_ACCESSOR_INDEX; | ||
|
||
const columns: TSVBColumns[] = [ | ||
{ id, name: usedIndexPattern.timeFieldName || '', isSplit: false }, | ||
]; | ||
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 | ||
if (isGroupedByTerms) { | ||
id++; | ||
columns.push({ id, name: layer.terms_field || '', isSplit: true }); | ||
} | ||
} | ||
const columnsWithMeta = addMetaToColumns(columns, usedIndexPattern, layer.metrics[0].type); | ||
|
||
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) { | ||
row.push(seriesPerLayer[j].label); | ||
} | ||
return row; | ||
}); | ||
rows = [...rows, ...data]; | ||
} | ||
tables[layer.id] = { | ||
type: 'datatable', | ||
rows, | ||
columns: columnsWithMeta, | ||
}; | ||
} | ||
return tables; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
Oops, something went wrong.