Skip to content

Commit

Permalink
[TSVB] Top_hit supports runtime fields (elastic#103401)
Browse files Browse the repository at this point in the history
* [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>
  • Loading branch information
DianaDerevyankina and kibanamachine committed Jul 12, 2021
1 parent 20ce332 commit 86d37cd
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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', {
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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}
Expand Down Expand Up @@ -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"
Expand All @@ -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>
Expand All @@ -231,6 +245,7 @@ const TopHitAggUi = (props) => {
onChange={handleSelectChange('order_by')}
indexPattern={indexPattern}
fields={fields}
data-test-subj="topHitOrderByFieldSelect"
/>
</EuiFlexItem>
<EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export const bucketTransform = {
docs: {
top_hits: {
size: bucket.size,
_source: { includes: [bucket.field] },
fields: [bucket.field],
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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] } }],
},
},
},
Expand Down
103 changes: 88 additions & 15 deletions test/functional/apps/visualize/_tsvb_chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
'timePicker',
'visChart',
'common',
'settings',
]);

describe('visual builder', function describeIndexTests() {
Expand All @@ -44,43 +45,115 @@ 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 () => {
await inspector.expectIsNotEnabled();
});

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', () => {
Expand Down
Loading

0 comments on commit 86d37cd

Please sign in to comment.