Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Lens] Allow user to drag and select a subset of the timeline in the chart (aka brush interaction) #62636

Merged
merged 25 commits into from
Apr 30, 2020
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c213932
feat: brushing basic example for time histogram
mbondyra Apr 6, 2020
8c99c2d
test: added
mbondyra Apr 7, 2020
0e83e13
refactor: simplify the structure
mbondyra Apr 8, 2020
e868030
Merge branch 'master' into brushing-on-lens
elasticmachine Apr 8, 2020
4ff7e63
refactor: move to inline function
mbondyra Apr 8, 2020
e984281
refactor
mbondyra Apr 9, 2020
5806bd6
Merge branch 'master' into brushing-on-lens
elasticmachine Apr 13, 2020
8e23141
refactor
mbondyra Apr 14, 2020
5a6bfe9
Merge branch 'master' into brushing-on-lens
elasticmachine Apr 14, 2020
609b8c0
Merge remote-tracking branch 'origin/master' into HEAD
wylieconlon Apr 14, 2020
436cc5a
Always use time field from index pattern
wylieconlon Apr 14, 2020
b162d60
types
mbondyra Apr 15, 2020
f6228cb
use the meta.aggConfigParams for timefieldName
mbondyra Apr 16, 2020
1fb9a9d
Merge commit 'fdc962f348ed54c63e49c8f745753caadf394ad6' into brushing…
mbondyra Apr 16, 2020
3b59c4b
fix: test snapshot update
mbondyra Apr 17, 2020
61695ad
Update embeddable.tsx
mbondyra Apr 20, 2020
789baf1
Merge branch 'master' into brushing-on-lens
elasticmachine Apr 20, 2020
006c9cf
Merge commit 'ffc8741da2ffcf4d31ae3ccc143fd483a61cbb1b' into brushing…
mbondyra Apr 22, 2020
7ce6088
fix: moment remov
mbondyra Apr 22, 2020
0919adc
Merge branch 'master' into brushing-on-lens
elasticmachine Apr 24, 2020
793aaa1
Merge branch 'master' into brushing-on-lens
elasticmachine Apr 27, 2020
19ae66f
Merge commit '6e2691358fdb59af717a9aaa2b32887b9cdf2d2a' into brushing…
mbondyra Apr 30, 2020
a3491ee
fix: corrections for adapting to timepicker on every timefield
mbondyra Apr 30, 2020
6c94c86
fix: fix single bar condition
mbondyra Apr 30, 2020
674564a
types
mbondyra Apr 30, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/plugins/embeddable/public/lib/triggers/triggers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ export interface EmbeddableVisTriggerContext {
embeddable?: IEmbeddable;
timeFieldName?: string;
data: {
e?: MouseEvent;
data: unknown;
e?: MouseEvent;
range?: string[];
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export function createMockDatasource(id: string): DatasourceMock {
datasourceId: id,
getTableSpec: jest.fn(() => []),
getOperationForColumnId: jest.fn(),
primaryTimeFieldName: 'timefield',
};

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export interface DraggedField {
}

export function columnToOperation(column: IndexPatternColumn, uniqueLabel?: string): Operation {
const { dataType, label, isBucketed, scale } = column;
const { dataType, label, isBucketed, scale, sourceField } = column;
return {
dataType: normalizeOperationDataType(dataType),
isBucketed,
Expand Down Expand Up @@ -318,6 +318,8 @@ export function getIndexPatternDatasource({
}
return null;
},
primaryTimeFieldName:
state.indexPatterns[state.layers[layerId].indexPatternId]?.timeFieldName,
};
},
getDatasourceSuggestionsForField(state, draggedField) {
Expand Down
1 change: 1 addition & 0 deletions x-pack/legacy/plugins/lens/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ export interface DatasourcePublicAPI {
datasourceId: string;
getTableSpec: () => Array<{ columnId: string }>;
getOperationForColumnId: (columnId: string) => Operation | null;
primaryTimeFieldName?: string | null;
}

export interface DatasourceDataPanelProps<T = unknown> {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ export const buildExpression = (
return null;
}

const timeFieldName = frame?.datasourceLayers[validLayers[0].layerId].primaryTimeFieldName;

return {
type: 'expression',
chain: [
Expand All @@ -118,6 +120,7 @@ export const buildExpression = (
arguments: {
xTitle: [xTitle],
yTitle: [yTitle],
primaryTimeFieldName: timeFieldName ? [timeFieldName] : [],
legend: [
{
type: 'expression',
Expand All @@ -142,7 +145,7 @@ export const buildExpression = (
.concat(layer.splitAccessor ? [layer.splitAccessor] : [])
.forEach(accessor => {
const operation = datasource.getOperationForColumnId(accessor);
if (operation && operation.label) {
if (operation?.label) {
columnToLabel[accessor] = operation.label;
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ export type LayerArgs = LayerConfig & {
export interface XYArgs {
xTitle: string;
yTitle: string;
primaryTimeFieldName: string | null;
legend: LegendConfig & { type: 'lens_xy_legendConfig' };
layers: LayerArgs[];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,145 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers';

const executeTriggerActions = jest.fn();

const dateHistogramData: LensMultiTable = {
type: 'lens_multitable',
tables: {
timeLayer: {
type: 'kibana_datatable',
rows: [
{
xAccessorId: 1585758120000,
splitAccessorId: "Men's Clothing",
yAccessorId: 1,
},
{
xAccessorId: 1585758360000,
splitAccessorId: "Women's Accessories",
yAccessorId: 1,
},
{
xAccessorId: 1585758360000,
splitAccessorId: "Women's Clothing",
yAccessorId: 1,
},
{
xAccessorId: 1585759380000,
splitAccessorId: "Men's Clothing",
yAccessorId: 1,
},
{
xAccessorId: 1585759380000,
splitAccessorId: "Men's Shoes",
yAccessorId: 1,
},
{
xAccessorId: 1585759380000,
splitAccessorId: "Women's Clothing",
yAccessorId: 1,
},
{
xAccessorId: 1585760700000,
splitAccessorId: "Men's Clothing",
yAccessorId: 1,
},
{
xAccessorId: 1585760760000,
splitAccessorId: "Men's Clothing",
yAccessorId: 1,
},
{
xAccessorId: 1585760760000,
splitAccessorId: "Men's Shoes",
yAccessorId: 1,
},
{
xAccessorId: 1585761120000,
splitAccessorId: "Men's Shoes",
yAccessorId: 1,
},
],
columns: [
{
id: 'xAccessorId',
name: 'order_date per minute',
meta: {
type: 'date_histogram',
indexPatternId: 'indexPatternId',
aggConfigParams: {
field: 'order_date',
timeRange: { from: '2020-04-01T16:14:16.246Z', to: '2020-04-01T17:15:41.263Z' },
useNormalizedEsInterval: true,
scaleMetricValues: false,
interval: '1m',
drop_partials: false,
min_doc_count: 0,
extended_bounds: {},
},
},
formatHint: { id: 'date', params: { pattern: 'HH:mm' } },
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left this comment in a few places, but it will hopefully simplify this test too: we should not use the table metadata, because it's specific to only one data source.

{
id: 'splitAccessorId',
name: 'Top values of category.keyword',
meta: {
type: 'terms',
indexPatternId: 'indexPatternId',
aggConfigParams: {
field: 'category.keyword',
orderBy: 'yAccessorId',
order: 'desc',
size: 3,
otherBucket: false,
otherBucketLabel: 'Other',
missingBucket: false,
missingBucketLabel: 'Missing',
},
},
formatHint: {
id: 'terms',
params: {
id: 'string',
otherBucketLabel: 'Other',
missingBucketLabel: 'Missing',
parsedUrl: {
origin: 'http://localhost:5601',
pathname: '/jiy/app/kibana',
basePath: '/jiy',
},
},
},
},
{
id: 'yAccessorId',
name: 'Count of records',
meta: {
type: 'count',
indexPatternId: 'indexPatternId',
aggConfigParams: {},
},
formatHint: { id: 'number' },
},
],
},
},
dateRange: {
fromDate: new Date('2020-04-01T16:14:16.246Z'),
toDate: new Date('2020-04-01T17:15:41.263Z'),
},
};

const dateHistogramLayer: LayerArgs = {
layerId: 'timeLayer',
hide: false,
xAccessor: 'xAccessorId',
yScaleType: 'linear',
xScaleType: 'time',
isHistogram: true,
splitAccessor: 'splitAccessorId',
seriesType: 'bar_stacked',
accessors: ['yAccessorId'],
};

const createSampleDatatableWithRows = (rows: KibanaDatatableRow[]): KibanaDatatable => ({
type: 'kibana_datatable',
columns: [
Expand Down Expand Up @@ -65,6 +204,7 @@ const sampleLayer: LayerArgs = {
const createArgsWithLayers = (layers: LayerArgs[] = [sampleLayer]): XYArgs => ({
xTitle: '',
yTitle: '',
primaryTimeFieldName: null,
legend: {
type: 'lens_xy_legendConfig',
isVisible: false,
Expand Down Expand Up @@ -441,6 +581,55 @@ describe('xy_expression', () => {
expect(component.find(Settings).prop('rotation')).toEqual(90);
});

test('onBrushEnd returns correct context data', () => {
const { args } = sampleArgs();

const wrapper = mountWithIntl(
<XYChart
data={dateHistogramData}
args={{
...args,
primaryTimeFieldName: 'order_date',
layers: [dateHistogramLayer],
}}
formatFactory={getFormatSpy}
timeZone="UTC"
chartTheme={{}}
executeTriggerActions={executeTriggerActions}
/>
);

wrapper
.find(Settings)
.first()
.prop('onBrushEnd')!(1585757732783, 1585758880838);

expect(executeTriggerActions).toHaveBeenCalledWith('SELECT_RANGE_TRIGGER', {
data: {
data: {
ordered: {
date: true,
},
series: [
{
values: expect.arrayContaining([
{
xRaw: {
column: 0,
row: 2,
table: dateHistogramData.tables.timeLayer,
},
},
]),
},
],
},
range: ['2020-04-01T16:15:32.783Z', '2020-04-01T16:34:40.838Z'],
},
timeFieldName: 'order_date',
});
});

test('onElementClick returns correct context data', () => {
const geometry: GeometryValue = { x: 5, y: 1, accessor: 'y1' };
const series = {
Expand All @@ -458,6 +647,7 @@ describe('xy_expression', () => {
data={data}
args={{
...args,
primaryTimeFieldName: 'my_time',
layers: [
{
layerId: 'first',
Expand Down Expand Up @@ -501,6 +691,7 @@ describe('xy_expression', () => {
},
],
},
timeFieldName: 'my_time',
});
});

Expand Down
Loading