mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Elasticsearch: Support chained pipeline aggregations (#27945)
* Elasticsearch: Support multiple pipeline aggregations for a query * Ensuring descendents are deleted with their parents. * Update public/app/plugins/datasource/elasticsearch/metric_agg.ts Co-authored-by: Giordano Ricci <grdnricci@gmail.com> * Update public/app/plugins/datasource/elasticsearch/query_def.ts Co-authored-by: Giordano Ricci <grdnricci@gmail.com> * Update public/app/plugins/datasource/elasticsearch/specs/query_def.test.ts Co-authored-by: Giordano Ricci <grdnricci@gmail.com> * Fixing typo Co-authored-by: Giordano Ricci <grdnricci@gmail.com>
This commit is contained in:
parent
7d63b2c473
commit
3af193358c
@ -5,6 +5,10 @@ import { ElasticsearchAggregation } from './types';
|
|||||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||||
import { CoreEvents } from 'app/types';
|
import { CoreEvents } from 'app/types';
|
||||||
|
|
||||||
|
function createDefaultMetric(id = 0): ElasticsearchAggregation {
|
||||||
|
return { type: 'count', field: 'select field', id: (id + 1).toString() };
|
||||||
|
}
|
||||||
|
|
||||||
export class ElasticMetricAggCtrl {
|
export class ElasticMetricAggCtrl {
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope: any, uiSegmentSrv: any, $rootScope: GrafanaRootScope) {
|
constructor($scope: any, uiSegmentSrv: any, $rootScope: GrafanaRootScope) {
|
||||||
@ -21,7 +25,7 @@ export class ElasticMetricAggCtrl {
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.updatePipelineAggOptions = () => {
|
$scope.updatePipelineAggOptions = () => {
|
||||||
$scope.pipelineAggOptions = queryDef.getPipelineAggOptions($scope.target);
|
$scope.pipelineAggOptions = queryDef.getPipelineAggOptions($scope.target, $scope.agg);
|
||||||
};
|
};
|
||||||
|
|
||||||
$rootScope.onAppEvent(
|
$rootScope.onAppEvent(
|
||||||
@ -195,12 +199,19 @@ export class ElasticMetricAggCtrl {
|
|||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
|
||||||
metricAggs.splice(addIndex, 0, { type: 'count', field: 'select field', id: (id + 1).toString() });
|
metricAggs.splice(addIndex, 0, createDefaultMetric(id));
|
||||||
$scope.onChange();
|
$scope.onChange();
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.removeMetricAgg = () => {
|
$scope.removeMetricAgg = () => {
|
||||||
metricAggs.splice($scope.index, 1);
|
const metricBeingRemoved = metricAggs[$scope.index];
|
||||||
|
const metricsToRemove = queryDef.getAncestors($scope.target, metricBeingRemoved);
|
||||||
|
const newMetricAggs = metricAggs.filter(m => !metricsToRemove.includes(m.id));
|
||||||
|
if (newMetricAggs.length > 0) {
|
||||||
|
metricAggs.splice(0, metricAggs.length, ...newMetricAggs);
|
||||||
|
} else {
|
||||||
|
metricAggs.splice(0, metricAggs.length, createDefaultMetric());
|
||||||
|
}
|
||||||
$scope.onChange();
|
$scope.onChange();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { ElasticsearchAggregation, ElasticsearchQuery } from './types';
|
||||||
|
|
||||||
export const metricAggTypes = [
|
export const metricAggTypes = [
|
||||||
{ text: 'Count', value: 'count', requiresField: false },
|
{ text: 'Count', value: 'count', requiresField: false },
|
||||||
@ -207,15 +208,26 @@ export function isPipelineAggWithMultipleBucketPaths(metricType: any) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPipelineAggOptions(targets: any) {
|
export function getAncestors(target: ElasticsearchQuery, metric?: ElasticsearchAggregation) {
|
||||||
const result: any[] = [];
|
const { metrics } = target;
|
||||||
_.each(targets.metrics, metric => {
|
if (!metrics) {
|
||||||
if (!isPipelineAgg(metric.type)) {
|
return (metric && [metric.id]) || [];
|
||||||
result.push({ text: describeMetric(metric), value: metric.id });
|
}
|
||||||
}
|
const initialAncestors = metric != null ? [metric.id] : ([] as string[]);
|
||||||
});
|
return metrics.reduce((acc: string[], metric: ElasticsearchAggregation) => {
|
||||||
|
const includedInField = (metric.field && acc.includes(metric.field)) || false;
|
||||||
|
const includedInVariables = metric.pipelineVariables?.some(pv => acc.includes(pv?.pipelineAgg ?? ''));
|
||||||
|
return includedInField || includedInVariables ? [...acc, metric.id] : acc;
|
||||||
|
}, initialAncestors);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
export function getPipelineAggOptions(target: ElasticsearchQuery, metric?: ElasticsearchAggregation) {
|
||||||
|
const { metrics } = target;
|
||||||
|
if (!metrics) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const ancestors = getAncestors(target, metric);
|
||||||
|
return metrics.filter(m => !ancestors.includes(m.id)).map(m => ({ text: describeMetric(m), value: m.id }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMovingAvgSettings(model: any, filtered: boolean) {
|
export function getMovingAvgSettings(model: any, filtered: boolean) {
|
||||||
@ -247,7 +259,7 @@ export function describeOrder(order: string) {
|
|||||||
return def.text;
|
return def.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function describeMetric(metric: { type: string; field: string }) {
|
export function describeMetric(metric: ElasticsearchAggregation) {
|
||||||
const def: any = _.find(metricAggTypes, { value: metric.type });
|
const def: any = _.find(metricAggTypes, { value: metric.type });
|
||||||
if (!def.requiresField && !isPipelineAgg(metric.type)) {
|
if (!def.requiresField && !isPipelineAgg(metric.type)) {
|
||||||
return def.text;
|
return def.text;
|
||||||
|
@ -1,51 +1,104 @@
|
|||||||
import * as queryDef from '../query_def';
|
import * as queryDef from '../query_def';
|
||||||
|
|
||||||
describe('ElasticQueryDef', () => {
|
describe('ElasticQueryDef', () => {
|
||||||
|
describe('getAncestors', () => {
|
||||||
|
describe('with multiple pipeline aggs', () => {
|
||||||
|
const maxMetric = { id: '1', type: 'max', field: '@value' };
|
||||||
|
const derivativeMetric = { id: '2', type: 'derivative', field: '1' };
|
||||||
|
const bucketScriptMetric = {
|
||||||
|
id: '3',
|
||||||
|
type: 'bucket_script',
|
||||||
|
field: '2',
|
||||||
|
pipelineVariables: [{ name: 'var1', pipelineAgg: '2' }],
|
||||||
|
};
|
||||||
|
const target = {
|
||||||
|
refId: '1',
|
||||||
|
isLogsQuery: false,
|
||||||
|
metrics: [maxMetric, derivativeMetric, bucketScriptMetric],
|
||||||
|
};
|
||||||
|
test('should return id of derivative and bucket_script', () => {
|
||||||
|
const response = queryDef.getAncestors(target, derivativeMetric);
|
||||||
|
expect(response).toEqual(['2', '3']);
|
||||||
|
});
|
||||||
|
test('should return id of the bucket_script', () => {
|
||||||
|
const response = queryDef.getAncestors(target, bucketScriptMetric);
|
||||||
|
expect(response).toEqual(['3']);
|
||||||
|
});
|
||||||
|
test('should return id of all the metrics', () => {
|
||||||
|
const response = queryDef.getAncestors(target, maxMetric);
|
||||||
|
expect(response).toEqual(['1', '2', '3']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('getPipelineAggOptions', () => {
|
describe('getPipelineAggOptions', () => {
|
||||||
describe('with zero targets', () => {
|
describe('with zero metrics', () => {
|
||||||
const response = queryDef.getPipelineAggOptions([]);
|
const target = {
|
||||||
|
refId: '1',
|
||||||
|
isLogsQuery: false,
|
||||||
|
metrics: [],
|
||||||
|
};
|
||||||
|
const response = queryDef.getPipelineAggOptions(target);
|
||||||
|
|
||||||
test('should return zero', () => {
|
test('should return zero', () => {
|
||||||
expect(response.length).toBe(0);
|
expect(response.length).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with count and sum targets', () => {
|
describe('with count and sum metrics', () => {
|
||||||
const targets = {
|
const currentAgg = { type: 'moving_avg', field: '@value', id: '3' };
|
||||||
metrics: [
|
const target = {
|
||||||
{ type: 'count', field: '@value' },
|
refId: '1',
|
||||||
{ type: 'sum', field: '@value' },
|
isLogsQuery: false,
|
||||||
],
|
metrics: [{ type: 'count', field: '@value', id: '1' }, { type: 'sum', field: '@value', id: '2' }, currentAgg],
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = queryDef.getPipelineAggOptions(targets);
|
const response = queryDef.getPipelineAggOptions(target, currentAgg);
|
||||||
|
|
||||||
test('should return zero', () => {
|
test('should return zero', () => {
|
||||||
expect(response.length).toBe(2);
|
expect(response.length).toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with count and moving average targets', () => {
|
describe('with count and moving average metrics', () => {
|
||||||
const targets = {
|
const currentAgg = { type: 'moving_avg', field: '@value', id: '2' };
|
||||||
metrics: [
|
const target = {
|
||||||
{ type: 'count', field: '@value' },
|
refId: '1',
|
||||||
{ type: 'moving_avg', field: '@value' },
|
isLogsQuery: false,
|
||||||
],
|
metrics: [{ type: 'count', field: '@value', id: '1' }, currentAgg],
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = queryDef.getPipelineAggOptions(targets);
|
const response = queryDef.getPipelineAggOptions(target, currentAgg);
|
||||||
|
|
||||||
test('should return one', () => {
|
test('should return one', () => {
|
||||||
expect(response.length).toBe(1);
|
expect(response.length).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with derivatives targets', () => {
|
describe('with multiple chained pipeline aggs', () => {
|
||||||
const targets = {
|
const currentAgg = { type: 'moving_avg', field: '2', id: '3' };
|
||||||
metrics: [{ type: 'derivative', field: '@value' }],
|
const target = {
|
||||||
|
refId: '1',
|
||||||
|
isLogsQuery: false,
|
||||||
|
metrics: [{ type: 'count', field: '@value', id: '1' }, { type: 'moving_avg', field: '1', id: '2' }, currentAgg],
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = queryDef.getPipelineAggOptions(targets);
|
const response = queryDef.getPipelineAggOptions(target, currentAgg);
|
||||||
|
|
||||||
|
test('should return two', () => {
|
||||||
|
expect(response.length).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with derivatives metrics', () => {
|
||||||
|
const currentAgg = { type: 'derivative', field: '@value', id: '1' };
|
||||||
|
const target = {
|
||||||
|
refId: '1',
|
||||||
|
isLogsQuery: false,
|
||||||
|
metrics: [currentAgg],
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = queryDef.getPipelineAggOptions(target, currentAgg);
|
||||||
|
|
||||||
test('should return zero', () => {
|
test('should return zero', () => {
|
||||||
expect(response.length).toBe(0);
|
expect(response.length).toBe(0);
|
||||||
|
@ -16,6 +16,7 @@ export interface ElasticsearchAggregation {
|
|||||||
type: string;
|
type: string;
|
||||||
settings?: any;
|
settings?: any;
|
||||||
field?: string;
|
field?: string;
|
||||||
|
pipelineVariables?: Array<{ name?: string; pipelineAgg?: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ElasticsearchQuery extends DataQuery {
|
export interface ElasticsearchQuery extends DataQuery {
|
||||||
|
Loading…
Reference in New Issue
Block a user