mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[TSVB] Top_hit supports runtime fields (#103401)
* [TSVB] Refactor top-hit aggregation to work with fields instead of _source * Allow select date strings for top_hit aggregation in table, metric, and markdown * Fix agg_with handling for top_hit and add some tests * Refactor get_agg_value and fix type check for _tsvb_chart * Refactor top_hit.js Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
832d349930
commit
87066e06b3
8 changed files with 168 additions and 36 deletions
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo, useEffect } from 'react';
|
||||
import { AggRow } from './agg_row';
|
||||
import { AggSelect } from './agg_select';
|
||||
import { FieldSelect } from './field_select';
|
||||
|
@ -62,6 +62,7 @@ const getAggWithOptions = (field = {}, fieldTypesRestriction) => {
|
|||
},
|
||||
];
|
||||
case KBN_FIELD_TYPES.STRING:
|
||||
case KBN_FIELD_TYPES.DATE:
|
||||
return [
|
||||
{
|
||||
label: i18n.translate('visTypeTimeseries.topHit.aggWithOptions.concatenate', {
|
||||
|
@ -91,16 +92,18 @@ const getOrderOptions = () => [
|
|||
},
|
||||
];
|
||||
|
||||
const AGG_WITH_KEY = 'agg_with';
|
||||
const ORDER_DATE_RESTRICT_FIELDS = [KBN_FIELD_TYPES.DATE];
|
||||
|
||||
const getModelDefaults = () => ({
|
||||
size: 1,
|
||||
order: 'desc',
|
||||
[AGG_WITH_KEY]: 'noop',
|
||||
});
|
||||
|
||||
const TopHitAggUi = (props) => {
|
||||
const { fields, series, panel } = props;
|
||||
const defaults = {
|
||||
size: 1,
|
||||
agg_with: 'noop',
|
||||
order: 'desc',
|
||||
};
|
||||
const model = { ...defaults, ...props.model };
|
||||
const model = useMemo(() => ({ ...getModelDefaults(), ...props.model }), [props.model]);
|
||||
const indexPattern = series.override_index_pattern
|
||||
? series.series_index_pattern
|
||||
: panel.index_pattern;
|
||||
|
@ -110,7 +113,7 @@ const TopHitAggUi = (props) => {
|
|||
PANEL_TYPES.METRIC,
|
||||
PANEL_TYPES.MARKDOWN,
|
||||
].includes(panel.type)
|
||||
? [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.STRING]
|
||||
? [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.STRING, KBN_FIELD_TYPES.DATE]
|
||||
: [KBN_FIELD_TYPES.NUMBER];
|
||||
|
||||
const handleChange = createChangeHandler(props.onChange, model);
|
||||
|
@ -124,13 +127,23 @@ const TopHitAggUi = (props) => {
|
|||
const htmlId = htmlIdGenerator();
|
||||
|
||||
const selectedAggWithOption = aggWithOptions.find((option) => {
|
||||
return model.agg_with === option.value;
|
||||
return model[AGG_WITH_KEY] === option.value;
|
||||
});
|
||||
|
||||
const selectedOrderOption = orderOptions.find((option) => {
|
||||
return model.order === option.value;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const defaultFn = aggWithOptions?.[0]?.value;
|
||||
const aggWith = model[AGG_WITH_KEY];
|
||||
if (aggWith && defaultFn && aggWith !== defaultFn && !selectedAggWithOption) {
|
||||
handleChange({
|
||||
[AGG_WITH_KEY]: defaultFn,
|
||||
});
|
||||
}
|
||||
}, [model, selectedAggWithOption, aggWithOptions, handleChange]);
|
||||
|
||||
return (
|
||||
<AggRow
|
||||
disableDelete={props.disableDelete}
|
||||
|
@ -195,7 +208,7 @@ const TopHitAggUi = (props) => {
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
id={htmlId('agg_with')}
|
||||
id={htmlId(AGG_WITH_KEY)}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.topHit.aggregateWithLabel"
|
||||
|
@ -213,8 +226,9 @@ const TopHitAggUi = (props) => {
|
|||
)}
|
||||
options={aggWithOptions}
|
||||
selectedOptions={selectedAggWithOption ? [selectedAggWithOption] : []}
|
||||
onChange={handleSelectChange('agg_with')}
|
||||
onChange={handleSelectChange(AGG_WITH_KEY)}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
data-test-subj="topHitAggregateWithComboBox"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
|
@ -231,6 +245,7 @@ const TopHitAggUi = (props) => {
|
|||
onChange={handleSelectChange('order_by')}
|
||||
indexPattern={indexPattern}
|
||||
fields={fields}
|
||||
data-test-subj="topHitOrderByFieldSelect"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
|
|
|
@ -16,7 +16,7 @@ export const createTickFormatter = (format = '0,0.[00]', template, getConfig = n
|
|||
const fieldFormats = getFieldFormats();
|
||||
|
||||
if (!template) template = '{{value}}';
|
||||
const render = handlebars.compile(template, { knownHelpersOnly: true });
|
||||
const render = handlebars.compile(template, { noEscape: true, knownHelpersOnly: true });
|
||||
let formatter;
|
||||
|
||||
if (isDuration(format)) {
|
||||
|
|
|
@ -111,7 +111,7 @@ export const bucketTransform = {
|
|||
docs: {
|
||||
top_hits: {
|
||||
size: bucket.size,
|
||||
_source: { includes: [bucket.field] },
|
||||
fields: [bucket.field],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -45,10 +45,10 @@ export const getAggValue = (row, metric) => {
|
|||
}
|
||||
|
||||
const hits = get(row, [metric.id, 'docs', 'hits', 'hits'], []);
|
||||
const values = hits.map((doc) => get(doc, `_source.${metric.field}`));
|
||||
const values = hits.map((doc) => doc.fields[metric.field]);
|
||||
const aggWith = (metric.agg_with && aggFns[metric.agg_with]) || aggFns.noop;
|
||||
|
||||
return aggWith(values);
|
||||
return aggWith(values.flat());
|
||||
case METRIC_TYPES.COUNT:
|
||||
return get(row, 'doc_count', null);
|
||||
default:
|
||||
|
|
|
@ -67,11 +67,7 @@ describe('getAggValue', () => {
|
|||
doc_count: 1,
|
||||
docs: {
|
||||
hits: {
|
||||
hits: [
|
||||
{ _source: { example: { value: 25 } } },
|
||||
{ _source: { example: { value: 25 } } },
|
||||
{ _source: { example: { value: 25 } } },
|
||||
],
|
||||
hits: [{ fields: { 'example.value': [25, 25, 25] } }],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -24,6 +24,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
'timePicker',
|
||||
'visChart',
|
||||
'common',
|
||||
'settings',
|
||||
]);
|
||||
|
||||
describe('visual builder', function describeIndexTests() {
|
||||
|
@ -44,14 +45,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
describe('metric', () => {
|
||||
const { visualBuilder } = PageObjects;
|
||||
|
||||
beforeEach(async () => {
|
||||
await PageObjects.visualBuilder.resetPage();
|
||||
await PageObjects.visualBuilder.clickMetric();
|
||||
await PageObjects.visualBuilder.checkMetricTabIsPresent();
|
||||
await PageObjects.visualBuilder.clickPanelOptions('metric');
|
||||
await PageObjects.visualBuilder.setMetricsDataTimerangeMode('Last value');
|
||||
await PageObjects.visualBuilder.setDropLastBucket(true);
|
||||
await PageObjects.visualBuilder.clickDataTab('metric');
|
||||
await visualBuilder.resetPage();
|
||||
await visualBuilder.clickMetric();
|
||||
await visualBuilder.checkMetricTabIsPresent();
|
||||
await visualBuilder.clickPanelOptions('metric');
|
||||
await visualBuilder.setMetricsDataTimerangeMode('Last value');
|
||||
await visualBuilder.setDropLastBucket(true);
|
||||
await visualBuilder.clickDataTab('metric');
|
||||
});
|
||||
|
||||
it('should not have inspector enabled', async () => {
|
||||
|
@ -59,28 +62,98 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('should show correct data', async () => {
|
||||
const value = await PageObjects.visualBuilder.getMetricValue();
|
||||
const value = await visualBuilder.getMetricValue();
|
||||
expect(value).to.eql('156');
|
||||
});
|
||||
|
||||
it('should show correct data with Math Aggregation', async () => {
|
||||
await PageObjects.visualBuilder.createNewAgg();
|
||||
await PageObjects.visualBuilder.selectAggType('math', 1);
|
||||
await PageObjects.visualBuilder.fillInVariable();
|
||||
await PageObjects.visualBuilder.fillInExpression('params.test + 1');
|
||||
const value = await PageObjects.visualBuilder.getMetricValue();
|
||||
await visualBuilder.createNewAgg();
|
||||
await visualBuilder.selectAggType('math', 1);
|
||||
await visualBuilder.fillInVariable();
|
||||
await visualBuilder.fillInExpression('params.test + 1');
|
||||
const value = await visualBuilder.getMetricValue();
|
||||
expect(value).to.eql('157');
|
||||
});
|
||||
|
||||
it('should populate fields for basic functions', async () => {
|
||||
const { visualBuilder } = PageObjects;
|
||||
|
||||
await visualBuilder.selectAggType('Average');
|
||||
await visualBuilder.setFieldForAggregation('machine.ram');
|
||||
const isFieldForAggregationValid = await visualBuilder.checkFieldForAggregationValidity();
|
||||
|
||||
expect(isFieldForAggregationValid).to.be(true);
|
||||
});
|
||||
|
||||
it('should show correct data for Value Count with Entire time range mode', async () => {
|
||||
await visualBuilder.selectAggType('Value Count');
|
||||
await visualBuilder.setFieldForAggregation('machine.ram');
|
||||
|
||||
await visualBuilder.clickPanelOptions('metric');
|
||||
await visualBuilder.setMetricsDataTimerangeMode('Entire time range');
|
||||
|
||||
const value = await visualBuilder.getMetricValue();
|
||||
expect(value).to.eql('13,492');
|
||||
});
|
||||
|
||||
it('should show same data for kibana and string index pattern modes', async () => {
|
||||
await visualBuilder.selectAggType('Max');
|
||||
await visualBuilder.setFieldForAggregation('machine.ram');
|
||||
const kibanaIndexPatternModeValue = await visualBuilder.getMetricValue();
|
||||
|
||||
await visualBuilder.clickPanelOptions('metric');
|
||||
await visualBuilder.switchIndexPatternSelectionMode(false);
|
||||
const stringIndexPatternModeValue = await visualBuilder.getMetricValue();
|
||||
|
||||
expect(kibanaIndexPatternModeValue).to.eql(stringIndexPatternModeValue);
|
||||
expect(kibanaIndexPatternModeValue).to.eql('32,212,254,720');
|
||||
});
|
||||
|
||||
describe('Color rules', () => {
|
||||
beforeEach(async () => {
|
||||
await visualBuilder.selectAggType('Min');
|
||||
await visualBuilder.setFieldForAggregation('machine.ram');
|
||||
|
||||
await visualBuilder.clickPanelOptions('metric');
|
||||
await visualBuilder.setColorRuleOperator('>= greater than or equal');
|
||||
await visualBuilder.setColorRuleValue(0);
|
||||
});
|
||||
|
||||
it('should apply color rules to visualization background', async () => {
|
||||
await visualBuilder.setColorPickerValue('#FFCFDF');
|
||||
|
||||
const backGroundStyle = await visualBuilder.getBackgroundStyle();
|
||||
expect(backGroundStyle).to.eql('background-color: rgb(255, 207, 223);');
|
||||
});
|
||||
|
||||
it('should apply color rules to metric value', async () => {
|
||||
await visualBuilder.setColorPickerValue('#AD7DE6', 1);
|
||||
|
||||
const backGroundStyle = await visualBuilder.getMetricValueStyle();
|
||||
expect(backGroundStyle).to.eql('color: rgb(173, 125, 230);');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Top Hit aggregation', () => {
|
||||
beforeEach(async () => {
|
||||
await visualBuilder.selectAggType('Top Hit');
|
||||
await visualBuilder.setTopHitOrderByField('@timestamp');
|
||||
});
|
||||
|
||||
it('should show correct data for string type field', async () => {
|
||||
await visualBuilder.setFieldForAggregation('machine.os.raw');
|
||||
await visualBuilder.setTopHitAggregateWithOption('Concatenate');
|
||||
|
||||
const value = await visualBuilder.getMetricValue();
|
||||
expect(value).to.eql('win 7');
|
||||
});
|
||||
|
||||
it('should show correct data for runtime field', async () => {
|
||||
await visualBuilder.setFieldForAggregation('hello_world_runtime_field');
|
||||
await visualBuilder.setTopHitAggregateWithOption('Concatenate');
|
||||
|
||||
const value = await visualBuilder.getMetricValue();
|
||||
expect(value).to.eql('hello world');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('gauge', () => {
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -575,6 +575,42 @@ export class VisualBuilderPageObject extends FtrService {
|
|||
await this.testSubjects.existOrFail('euiColorPickerPopover', { timeout: 5000 });
|
||||
}
|
||||
|
||||
public async setColorPickerValue(colorHex: string, nth: number = 0): Promise<void> {
|
||||
const picker = await this.find.allByCssSelector('.tvbColorPicker button');
|
||||
await picker[nth].clickMouseButton();
|
||||
await this.checkColorPickerPopUpIsPresent();
|
||||
await this.find.setValue('.euiColorPicker input', colorHex);
|
||||
await this.visChart.waitForVisualizationRenderingStabilized();
|
||||
}
|
||||
|
||||
public async setColorRuleOperator(condition: string): Promise<void> {
|
||||
await this.retry.try(async () => {
|
||||
await this.comboBox.clearInputField('colorRuleOperator');
|
||||
await this.comboBox.set('colorRuleOperator', condition);
|
||||
});
|
||||
}
|
||||
|
||||
public async setColorRuleValue(value: number): Promise<void> {
|
||||
await this.retry.try(async () => {
|
||||
const colorRuleValueInput = await this.find.byCssSelector(
|
||||
'[data-test-subj="colorRuleValue"]'
|
||||
);
|
||||
await colorRuleValueInput.type(value.toString());
|
||||
});
|
||||
}
|
||||
|
||||
public async getBackgroundStyle(): Promise<string> {
|
||||
await this.visChart.waitForVisualizationRenderingStabilized();
|
||||
const visualization = await this.find.byClassName('tvbVis');
|
||||
return await visualization.getAttribute('style');
|
||||
}
|
||||
|
||||
public async getMetricValueStyle(): Promise<string> {
|
||||
await this.visChart.waitForVisualizationRenderingStabilized();
|
||||
const metricValue = await this.find.byCssSelector('[data-test-subj="tsvbMetricValue"]');
|
||||
return await metricValue.getAttribute('style');
|
||||
}
|
||||
|
||||
public async changePanelPreview(nth: number = 0): Promise<void> {
|
||||
const prevRenderingCount = await this.visChart.getVisualizationRenderingCount();
|
||||
const changePreviewBtnArray = await this.testSubjects.findAll('AddActivatePanelBtn');
|
||||
|
@ -680,4 +716,15 @@ export class VisualBuilderPageObject extends FtrService {
|
|||
const dataTimeRangeMode = await this.testSubjects.find('dataTimeRangeMode');
|
||||
return await this.comboBox.isOptionSelected(dataTimeRangeMode, value);
|
||||
}
|
||||
|
||||
public async setTopHitAggregateWithOption(option: string): Promise<void> {
|
||||
await this.comboBox.set('topHitAggregateWithComboBox', option);
|
||||
}
|
||||
|
||||
public async setTopHitOrderByField(timeField: string) {
|
||||
await this.retry.try(async () => {
|
||||
await this.comboBox.clearInputField('topHitOrderByFieldSelect');
|
||||
await this.comboBox.set('topHitOrderByFieldSelect', timeField);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue