Skip to content

Commit

Permalink
[Lens] Drop partial buckets option (#127153)
Browse files Browse the repository at this point in the history
* ✨ Add drop partial buckets support in date histogram

* ✅ Add tests

* ✅ Fix borken unit tests

* 💄 Changed switch order

* 👌 Disable drop feature for unbound date field

* ✨ Extend feature to the tsvb2lens transition code

* ✅ Fix tests

* 🐛 Support layer override in tsvb

* Update x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
dej611 and kibanamachine authored Mar 10, 2022
1 parent c261114 commit d0b3c65
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ describe('triggerTSVBtoLensConfiguration', () => {
splitWithDateHistogram: false,
timeFieldName: 'timeField2',
timeInterval: 'auto',
dropPartialBuckets: false,
},
},
});
Expand Down Expand Up @@ -254,6 +255,15 @@ describe('triggerTSVBtoLensConfiguration', () => {
expect(triggerOptions?.layers[0]?.timeInterval).toBe('1h');
});

test('should return dropPartialbuckets if enabled', async () => {
const modelWithDropBuckets = {
...model,
drop_last_bucket: 1,
};
const triggerOptions = await triggerTSVBtoLensConfiguration(modelWithDropBuckets);
expect(triggerOptions?.layers[0]?.dropPartialBuckets).toBe(true);
});

test('should return the correct chart configuration', async () => {
const modelWithConfig = {
...model,
Expand Down Expand Up @@ -299,6 +309,7 @@ describe('triggerTSVBtoLensConfiguration', () => {
splitWithDateHistogram: false,
timeFieldName: 'timeField2',
timeInterval: 'auto',
dropPartialBuckets: false,
},
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ export const triggerTSVBtoLensConfiguration = async (
timeInterval: model.interval && !model.interval?.includes('=') ? model.interval : 'auto',
...(SUPPORTED_FORMATTERS.includes(layer.formatter) && { format: layer.formatter }),
...(layer.label && { label: layer.label }),
dropPartialBuckets: layer.override_index_pattern
? layer.series_drop_last_bucket > 0
: model.drop_last_bucket > 0,
};
layersConfiguration[layerIdx] = layerConfiguration;
}
Expand Down
1 change: 1 addition & 0 deletions src/plugins/visualizations/public/vis_types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export interface VisualizeEditorLayersContext {
format?: string;
label?: string;
layerId?: string;
dropPartialBuckets?: boolean;
}

interface AxisExtents {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,12 @@ describe('IndexPatternDimensionEditorPanel', () => {
deserialize: jest.fn().mockReturnValue({
convert: () => 'formatted',
}),
} as unknown as DataPublicPluginStart['fieldFormats'],
},
search: {
aggs: {
calculateAutoTimeExpression: jest.fn(),
},
},
} as unknown as DataPublicPluginStart,
core: {} as CoreSetup,
dimensionGroups: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ function createNewTimeseriesLayerWithMetricAggregationFromVizEditor(
indexPattern: IndexPattern,
layer: VisualizeEditorLayersContext
): IndexPatternLayer | undefined {
const { timeFieldName, splitMode, splitFilters, metrics, timeInterval } = layer;
const { timeFieldName, splitMode, splitFilters, metrics, timeInterval, dropPartialBuckets } =
layer;
const dateField = indexPattern.getFieldByName(timeFieldName!);

const splitFields = layer.splitFields
Expand Down Expand Up @@ -217,6 +218,7 @@ function createNewTimeseriesLayerWithMetricAggregationFromVizEditor(
visualizationGroups: [],
columnParams: {
interval: timeInterval,
dropPartials: dropPartialBuckets,
},
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ describe('date_histogram', () => {
interval: ['42w'],
field: ['timestamp'],
useNormalizedEsInterval: [true],
drop_partials: [false],
}),
})
);
Expand Down Expand Up @@ -249,6 +250,7 @@ describe('date_histogram', () => {
field: ['timestamp'],
time_zone: ['UTC'],
useNormalizedEsInterval: [false],
drop_partials: [false],
}),
})
);
Expand Down Expand Up @@ -382,7 +384,7 @@ describe('date_histogram', () => {
);
expect(instance.find('[data-test-subj="lensDateHistogramValue"]').exists()).toBeFalsy();
expect(instance.find('[data-test-subj="lensDateHistogramUnit"]').exists()).toBeFalsy();
expect(instance.find(EuiSwitch).prop('checked')).toBe(false);
expect(instance.find(EuiSwitch).at(1).prop('checked')).toBe(false);
});

it('should allow switching to manual interval', () => {
Expand Down Expand Up @@ -415,9 +417,12 @@ describe('date_histogram', () => {
currentColumn={thirdLayer.columns.col1 as DateHistogramIndexPatternColumn}
/>
);
instance.find(EuiSwitch).simulate('change', {
target: { checked: true },
});
instance
.find(EuiSwitch)
.at(1)
.simulate('change', {
target: { checked: true },
});
expect(updateLayerSpy).toHaveBeenCalled();
const newLayer = updateLayerSpy.mock.calls[0][0];
expect(newLayer).toHaveProperty('columns.col1.params.interval', '30d');
Expand Down Expand Up @@ -456,7 +461,7 @@ describe('date_histogram', () => {
);
instance
.find(EuiSwitch)
.at(1)
.last()
.simulate('change', {
target: { checked: false },
});
Expand Down Expand Up @@ -499,7 +504,7 @@ describe('date_histogram', () => {
);
instance
.find(EuiSwitch)
.at(0)
.at(1)
.simulate('change', {
target: { checked: false },
});
Expand All @@ -509,6 +514,41 @@ describe('date_histogram', () => {
expect(newLayer).toHaveProperty('columns.col1.params.interval', 'auto');
});

it('turns off drop partial bucket on tuning off time range ignore', () => {
const thirdLayer: IndexPatternLayer = {
indexPatternId: '1',
columnOrder: ['col1'],
columns: {
col1: {
label: 'Value of timestamp',
dataType: 'date',
isBucketed: true,

// Private
operationType: 'date_histogram',
params: {
interval: '1h',
ignoreTimeRange: true,
},
sourceField: 'timestamp',
} as DateHistogramIndexPatternColumn,
},
};

const updateLayerSpy = jest.fn();
const instance = shallow(
<InlineOptions
{...defaultOptions}
layer={thirdLayer}
updateLayer={updateLayerSpy}
columnId="col1"
currentColumn={thirdLayer.columns.col1 as DateHistogramIndexPatternColumn}
indexPattern={{ ...indexPattern1, timeFieldName: 'other_timestamp' }}
/>
);
expect(instance.find(EuiSwitch).first().prop('disabled')).toBeTruthy();
});

it('should force calendar values to 1', () => {
const updateLayerSpy = jest.fn();
const instance = shallow(
Expand Down Expand Up @@ -657,6 +697,47 @@ describe('date_histogram', () => {

expect(instance.find('[data-test-subj="lensDateHistogramValue"]').exists()).toBeFalsy();
});

it('should allow the drop of partial buckets', () => {
const thirdLayer: IndexPatternLayer = {
indexPatternId: '1',
columnOrder: ['col1'],
columns: {
col1: {
label: 'Value of timestamp',
dataType: 'date',
isBucketed: true,

// Private
operationType: 'date_histogram',
params: {
interval: 'auto',
},
sourceField: 'timestamp',
} as DateHistogramIndexPatternColumn,
},
};

const updateLayerSpy = jest.fn();
const instance = shallow(
<InlineOptions
{...defaultOptions}
layer={thirdLayer}
updateLayer={updateLayerSpy}
columnId="col1"
currentColumn={thirdLayer.columns.col1 as DateHistogramIndexPatternColumn}
/>
);
instance
.find(EuiSwitch)
.first()
.simulate('change', {
target: { checked: true },
});
expect(updateLayerSpy).toHaveBeenCalled();
const newLayer = updateLayerSpy.mock.calls[0][0];
expect(newLayer).toHaveProperty('columns.col1.params.dropPartials', true);
});
});

describe('getDefaultLabel', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React, { useState } from 'react';
import React, { useCallback, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';

Expand Down Expand Up @@ -38,6 +38,7 @@ import { buildExpressionFunction } from '../../../../../../../src/plugins/expres
import { getInvalidFieldMessage, getSafeName } from './helpers';
import { HelpPopover, HelpPopoverButton } from '../../help_popover';
import { IndexPatternLayer } from '../../types';
import { TooltipWrapper } from '../../../shared_components';

const { isValidInterval } = search.aggs;
const autoInterval = 'auto';
Expand All @@ -48,6 +49,7 @@ export interface DateHistogramIndexPatternColumn extends FieldBasedIndexPatternC
params: {
interval: string;
ignoreTimeRange?: boolean;
dropPartials?: boolean;
};
}

Expand Down Expand Up @@ -76,7 +78,7 @@ function getMultipleDateHistogramsErrorMessage(layer: IndexPatternLayer, columnI
export const dateHistogramOperation: OperationDefinition<
DateHistogramIndexPatternColumn,
'field',
{ interval: string }
{ interval: string; dropPartials?: boolean }
> = {
type: 'date_histogram',
displayName: i18n.translate('xpack.lens.indexPattern.dateHistogram', {
Expand Down Expand Up @@ -118,6 +120,7 @@ export const dateHistogramOperation: OperationDefinition<
scale: 'interval',
params: {
interval: columnParams?.interval ?? autoInterval,
dropPartials: Boolean(columnParams?.dropPartials),
},
};
},
Expand All @@ -142,6 +145,11 @@ export const dateHistogramOperation: OperationDefinition<
const usedField = indexPattern.getFieldByName(column.sourceField);
let timeZone: string | undefined;
let interval = column.params?.interval ?? autoInterval;
const dropPartials = Boolean(
column.params?.dropPartials &&
// set to false when detached from time picker
(indexPattern.timeFieldName === usedField?.name || !column.params?.ignoreTimeRange)
);
if (
usedField &&
usedField.aggregationRestrictions &&
Expand All @@ -158,7 +166,7 @@ export const dateHistogramOperation: OperationDefinition<
time_zone: timeZone,
useNormalizedEsInterval: !usedField?.aggregationRestrictions?.date_histogram,
interval,
drop_partials: false,
drop_partials: dropPartials,
min_doc_count: 0,
extended_bounds: extendedBoundsToAst({}),
}).toAst();
Expand Down Expand Up @@ -186,20 +194,37 @@ export const dateHistogramOperation: OperationDefinition<
restrictedInterval(field!.aggregationRestrictions)
);

function onChangeAutoInterval(ev: EuiSwitchEvent) {
const { fromDate, toDate } = dateRange;
const value = ev.target.checked
? data.search.aggs.calculateAutoTimeExpression({ from: fromDate, to: toDate }) || '1h'
: autoInterval;
updateLayer(
updateColumnParam({
layer: updateColumnParam({ layer, columnId, paramName: 'interval', value }),
columnId,
paramName: 'ignoreTimeRange',
value: false,
})
);
}
const onChangeAutoInterval = useCallback(
(ev: EuiSwitchEvent) => {
const { fromDate, toDate } = dateRange;
const value = ev.target.checked
? data.search.aggs.calculateAutoTimeExpression({ from: fromDate, to: toDate }) || '1h'
: autoInterval;
updateLayer(
updateColumnParam({
layer: updateColumnParam({ layer, columnId, paramName: 'interval', value }),
columnId,
paramName: 'ignoreTimeRange',
value: false,
})
);
},
[dateRange, data.search.aggs, updateLayer, layer, columnId]
);

const onChangeDropPartialBuckets = useCallback(
(ev: EuiSwitchEvent) => {
updateLayer(
updateColumnParam({
layer,
columnId,
paramName: 'dropPartials',
value: ev.target.checked,
})
);
},
[columnId, layer, updateLayer]
);

const setInterval = (newInterval: typeof interval) => {
const isCalendarInterval = calendarOnlyIntervals.has(newInterval.unit);
Expand All @@ -208,8 +233,33 @@ export const dateHistogramOperation: OperationDefinition<
updateLayer(updateColumnParam({ layer, columnId, paramName: 'interval', value }));
};

const bindToGlobalTimePickerValue =
indexPattern.timeFieldName === field?.name || !currentColumn.params.ignoreTimeRange;

return (
<>
<EuiFormRow display="rowCompressed" hasChildLabel={false}>
<TooltipWrapper
tooltipContent={i18n.translate(
'xpack.lens.indexPattern.dateHistogram.dropPartialBucketsHelp',
{
defaultMessage:
'Drop partial buckets is disabled as these can be computed only for a time field bound to global time picker in the top right.',
}
)}
condition={!bindToGlobalTimePickerValue}
>
<EuiSwitch
label={i18n.translate('xpack.lens.indexPattern.dateHistogram.dropPartialBuckets', {
defaultMessage: 'Drop partial buckets',
})}
checked={Boolean(currentColumn.params.dropPartials)}
onChange={onChangeDropPartialBuckets}
compressed
disabled={!bindToGlobalTimePickerValue}
/>
</TooltipWrapper>
</EuiFormRow>
{!intervalIsRestricted && (
<EuiFormRow display="rowCompressed" hasChildLabel={false}>
<EuiSwitch
Expand Down Expand Up @@ -375,10 +425,7 @@ export const dateHistogramOperation: OperationDefinition<
</>
}
disabled={indexPattern.timeFieldName === field?.name}
checked={
indexPattern.timeFieldName === field?.name ||
!currentColumn.params.ignoreTimeRange
}
checked={bindToGlobalTimePickerValue}
onChange={() => {
updateLayer(
updateColumnParam({
Expand Down
Loading

0 comments on commit d0b3c65

Please sign in to comment.