Skip to content

Commit

Permalink
[Lens] Create a filter with field:value when last value metric is use…
Browse files Browse the repository at this point in the history
…d on a datatable (#160509)

Fixes #152883

The task says this is the behavior that we want for datatable, but with
the way data plugin interacts with Lens, it's actually impossible to
modify only for datatable. In my opinion that's an improvement though,
we probably want the same behavior for all supported visualization (so
also metric).


### Scenarios to test:
<img width="316" alt="Screenshot 2023-06-27 at 09 10 35"
src="https:/elastic/kibana/assets/4283304/55b42b45-2154-4a32-b9e4-01f6fbdc4493">

1,2,3 returns the `price:${value}` filter
4. returns `price:exists` filter
5. returns `category.keyword:${value}` coming from filter by
6. `OR` filter for array values
<img width="419" alt="Screenshot 2023-06-28 at 16 31 43"
src="https:/elastic/kibana/assets/4283304/d7842617-7be3-4b8a-a39f-206a145f9b81">
  • Loading branch information
mbondyra authored Jun 29, 2023
1 parent 4e3f699 commit d9b3a53
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 20 deletions.
84 changes: 72 additions & 12 deletions src/plugins/data/common/search/aggs/metrics/filtered_metric.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,36 @@
*/

import { AggConfigs, IAggConfigs } from '../agg_configs';
import { mockAggTypesRegistry } from '../test_helpers';
import { AggTypesDependencies } from '../agg_types';
import { mockAggTypesDependencies, mockAggTypesRegistry } from '../test_helpers';
import { getFilteredMetricAgg } from './filtered_metric';
import { IMetricAggConfig } from './metric_agg_type';
import { METRIC_TYPES } from './metric_agg_types';

describe('filtered metric agg type', () => {
let aggConfigs: IAggConfigs;
let aggTypesDependencies: AggTypesDependencies;
const typesRegistry = mockAggTypesRegistry();
const field = {
name: 'bytes',
filterable: true,
};
const indexPattern = {
id: '1234',
title: 'logstash-*',
fields: {
getByName: () => field,
filter: () => [field],
find: () => field,
},
} as any;

beforeEach(() => {
const typesRegistry = mockAggTypesRegistry();
const field = {
name: 'bytes',
jest.resetAllMocks();
aggTypesDependencies = {
...mockAggTypesDependencies,
getConfig: jest.fn(),
};
const indexPattern = {
id: '1234',
title: 'logstash-*',
fields: {
getByName: () => field,
filter: () => [field],
},
} as any;

aggConfigs = new AggConfigs(
indexPattern,
Expand Down Expand Up @@ -76,4 +87,53 @@ describe('filtered metric agg type', () => {

expect(agg.getResponseId()).toEqual('filtered_metric-bucket');
});
it.each`
aggType
${'top_metrics'}
${'top_hits'}
`('returns phrase filter for filtered metric for $aggType', ({ aggType }) => {
const topMetricsAggConfigs = new AggConfigs(
indexPattern,
[
{
id: METRIC_TYPES.FILTERED_METRIC,
type: METRIC_TYPES.FILTERED_METRIC,
schema: 'metric',
params: {
customBucket: {
type: 'filter',
params: {
filter: { language: 'kuery', query: 'a: b' },
},
},
customMetric: {
type: aggType,
params: {
field: 'bytes',
},
},
},
},
],
{
typesRegistry,
},
jest.fn()
);

expect(
getFilteredMetricAgg(aggTypesDependencies).createFilter!(
topMetricsAggConfigs.aggs[0] as IMetricAggConfig,
10
).query.match_phrase
).toEqual({ bytes: 10 });
});
it('returns filter from the custom bucket filter parameter for metric', () => {
expect(
getFilteredMetricAgg(aggTypesDependencies).createFilter!(
aggConfigs.aggs[0] as IMetricAggConfig,
'10'
).query.bool.filter[0].bool.should[0].match
).toEqual({ bytes: 'b' });
});
});
16 changes: 9 additions & 7 deletions src/plugins/data/common/search/aggs/metrics/filtered_metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,18 @@ export const getFilteredMetricAgg = ({ getConfig }: FiltersMetricAggDependencies
hasNoDslParams: true,
getSerializedFormat,
createFilter: (agg, inputState) => {
const indexPattern = agg.getIndexPattern();
if (
agg.params.customMetric.type.name === 'top_hits' ||
agg.params.customMetric.type.name === 'top_metrics'
) {
return agg.params.customMetric.createFilter(inputState);
}
if (!agg.params.customBucket.params.filter) return;
const esQueryConfigs = getEsQueryConfig({ get: getConfig });
return buildQueryFilter(
buildEsQuery(
agg.getIndexPattern(),
[agg.params.customBucket.params.filter],
[],
esQueryConfigs
),
agg.getIndexPattern().id!,
buildEsQuery(indexPattern, [agg.params.customBucket.params.filter], [], esQueryConfigs),
indexPattern.id!,
agg.params.customBucket.params.filter.query
);
},
Expand Down
25 changes: 24 additions & 1 deletion src/plugins/data/common/search/aggs/metrics/lib/create_filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
* Side Public License, v 1.
*/

import { buildExistsFilter } from '@kbn/es-query';
import {
BooleanRelation,
buildCombinedFilter,
buildExistsFilter,
buildPhraseFilter,
} from '@kbn/es-query';
import { AggConfig } from '../../agg_config';
import { IMetricAggConfig } from '../metric_agg_type';

Expand All @@ -19,3 +24,21 @@ export const createMetricFilter = <TMetricAggConfig extends AggConfig = IMetricA
return buildExistsFilter(aggConfig.getField(), indexPattern);
}
};

export const createTopHitFilter = <TMetricAggConfig extends AggConfig = IMetricAggConfig>(
aggConfig: TMetricAggConfig,
key: string
) => {
const indexPattern = aggConfig.getIndexPattern();
const field = aggConfig.getField();
if (!field) {
return;
}
return Array.isArray(key)
? buildCombinedFilter(
BooleanRelation.OR,
key.map((k) => buildPhraseFilter(field, k, indexPattern)),
indexPattern
)
: buildPhraseFilter(field, key, indexPattern);
};
5 changes: 5 additions & 0 deletions src/plugins/data/common/search/aggs/metrics/top_hit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,5 +402,10 @@ describe('Top hit metric', () => {
});
});
});
it('returns phrase filter', () => {
expect(getTopHitMetricAgg().createFilter!(aggConfig, '10').query.match_phrase).toEqual({
bytes: 10,
});
});
});
});
2 changes: 2 additions & 0 deletions src/plugins/data/common/search/aggs/metrics/top_hit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { IMetricAggConfig, MetricAggType } from './metric_agg_type';
import { METRIC_TYPES } from './metric_agg_types';
import { flattenHit, KBN_FIELD_TYPES } from '../../..';
import { BaseAggParams } from '../types';
import { createTopHitFilter } from './lib/create_filter';

export interface BaseAggParamsTopHit extends BaseAggParams {
field: string;
Expand Down Expand Up @@ -256,5 +257,6 @@ export const getTopHitMetricAgg = () => {
}
return values;
},
createFilter: createTopHitFilter,
});
};
11 changes: 11 additions & 0 deletions src/plugins/data/common/search/aggs/metrics/top_metrics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { AggConfigs } from '../agg_configs';
import { mockAggTypesRegistry } from '../test_helpers';
import { IMetricAggConfig } from './metric_agg_type';
import { KBN_FIELD_TYPES } from '../../..';
import { CombinedFilter } from '@kbn/es-query';

describe('Top metrics metric', () => {
let aggConfig: IMetricAggConfig;
Expand Down Expand Up @@ -191,5 +192,15 @@ describe('Top metrics metric', () => {
init({ fieldName: 'bytes' });
expect(getTopMetricsMetricAgg().getValue(aggConfig, bucket)).toEqual([1024, 512, 256]);
});
it('returns phrase filter', () => {
expect(getTopMetricsMetricAgg().createFilter!(aggConfig, '10').query.match_phrase).toEqual({
bytes: 10,
});
});
it('returns combined OR filter for array values', () => {
const params = getTopMetricsMetricAgg().createFilter!(aggConfig, ['10', '20']).meta
.params as CombinedFilter['meta']['params'];
expect(params.map((p) => p.query!.match_phrase)).toEqual([{ bytes: 10 }, { bytes: 20 }]);
});
});
});
2 changes: 2 additions & 0 deletions src/plugins/data/common/search/aggs/metrics/top_metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { IMetricAggConfig, MetricAggType } from './metric_agg_type';
import { METRIC_TYPES } from './metric_agg_types';
import { DataViewField, KBN_FIELD_TYPES } from '../../..';
import { BaseAggParams } from '../types';
import { createTopHitFilter } from './lib/create_filter';

export interface BaseAggParamsTopMetrics extends BaseAggParams {
field: string;
Expand Down Expand Up @@ -162,5 +163,6 @@ export const getTopMetricsMetricAgg = () => {
if (results.length === 1) return results[0];
return results;
},
createFilter: createTopHitFilter,
});
};

0 comments on commit d9b3a53

Please sign in to comment.