Alerting: rewire expression references when queries are updated (#41478)

This commit is contained in:
Gilles De Mey 2021-11-17 12:56:37 +01:00 committed by GitHub
parent fb2436af18
commit 9199a8b800
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 240 additions and 1 deletions

View File

@ -13,6 +13,7 @@ import { getDataSourceSrv } from '@grafana/runtime';
import { QueryWrapper } from './QueryWrapper';
import { AlertQuery } from 'app/types/unified-alerting-dto';
import { isExpressionQuery } from 'app/features/expressions/guards';
import { queriesWithUpdatedReferences } from './util';
interface Props {
// The query configuration
@ -128,11 +129,16 @@ export class QueryRows extends PureComponent<Props, State> {
onChangeQuery = (query: DataQuery, index: number) => {
const { queries, onQueriesChange } = this.props;
// find what queries still have a reference to the old name
const previousRefId = queries[index].refId;
const newRefId = query.refId;
onQueriesChange(
queries.map((item, itemIndex) => {
queriesWithUpdatedReferences(queries, previousRefId, newRefId).map((item, itemIndex) => {
if (itemIndex !== index) {
return item;
}
return {
...item,
refId: query.refId,

View File

@ -0,0 +1,169 @@
import { ClassicCondition, ExpressionQuery } from 'app/features/expressions/types';
import { AlertQuery } from 'app/types/unified-alerting-dto';
import { queriesWithUpdatedReferences, updateMathExpressionRefs } from './util';
describe('rule-editor', () => {
const dataSource: AlertQuery = {
refId: 'A',
datasourceUid: 'abc123',
queryType: '',
relativeTimeRange: {
from: 600,
to: 0,
},
model: {
refId: 'A',
},
};
const classicCondition = {
refId: 'B',
datasourceUid: '-100',
queryType: '',
model: {
refId: 'B',
type: 'classic_conditions',
datasource: {
uid: '-100',
type: 'grafana-expression',
},
conditions: [
{
type: 'query',
evaluator: {
params: [3],
type: 'gt',
},
operator: {
type: 'and',
},
query: {
params: ['A'],
},
reducer: {
params: [],
type: 'last',
},
},
],
},
};
const mathExpression = {
refId: 'B',
datasourceUid: '-100',
queryType: '',
model: {
refId: 'B',
type: 'math',
datasource: {
uid: '-100',
type: 'grafana-expression',
},
conditions: [],
expression: 'abs($A) + $A',
},
};
const reduceExpression = {
refId: 'B',
datasourceUid: '-100',
queryType: '',
model: {
refId: 'B',
type: 'reduce',
datasource: {
uid: '-100',
type: 'grafana-expression',
},
conditions: [],
reducer: 'mean',
expression: 'A',
},
};
const resampleExpression = {
refId: 'A',
datasourceUid: '-100',
model: {
refId: 'A',
type: 'resample',
datasource: {
type: '__expr__',
uid: '__expr__',
},
conditions: [],
downsampler: 'mean',
upsampler: 'fillna',
expression: 'A',
window: '30m',
},
queryType: '',
};
describe('rewires query names', () => {
it('should rewire classic expressions', () => {
const queries: AlertQuery[] = [dataSource, classicCondition];
const rewiredQueries = queriesWithUpdatedReferences(queries, 'A', 'C');
const queryModel = rewiredQueries[1].model as ExpressionQuery;
const checkConditionParams = (condition: ClassicCondition) => {
return expect(condition.query.params).toEqual(['C']);
};
expect(queryModel.conditions?.every(checkConditionParams));
});
it('should rewire math expressions', () => {
const queries: AlertQuery[] = [dataSource, mathExpression];
const rewiredQueries = queriesWithUpdatedReferences(queries, 'A', 'Query A');
const queryModel = rewiredQueries[1].model as ExpressionQuery;
expect(queryModel.expression).toBe('abs(${Query A}) + ${Query A}');
});
it('should rewire reduce expressions', () => {
const queries: AlertQuery[] = [dataSource, reduceExpression];
const rewiredQueries = queriesWithUpdatedReferences(queries, 'A', 'C');
const queryModel = rewiredQueries[1].model as ExpressionQuery;
expect(queryModel.expression).toBe('C');
});
it('should rewire resample expressions', () => {
const queries: AlertQuery[] = [dataSource, resampleExpression];
const rewiredQueries = queriesWithUpdatedReferences(queries, 'A', 'C');
const queryModel = rewiredQueries[1].model as ExpressionQuery;
expect(queryModel.expression).toBe('C');
});
it('should rewire multiple expressions', () => {
const queries: AlertQuery[] = [dataSource, mathExpression, resampleExpression];
const rewiredQueries = queriesWithUpdatedReferences(queries, 'A', 'C');
expect(rewiredQueries[1].model as ExpressionQuery).toHaveProperty('expression', 'abs(${C}) + ${C}');
expect(rewiredQueries[2].model as ExpressionQuery).toHaveProperty('expression', 'C');
});
it('should skip if refs are identical', () => {
const queries: AlertQuery[] = [dataSource, reduceExpression, mathExpression];
const rewiredQueries = queriesWithUpdatedReferences(queries, 'A', 'A');
expect(rewiredQueries[0]).toEqual(queries[0]);
expect(rewiredQueries[1]).toEqual(queries[1]);
expect(rewiredQueries[2]).toEqual(queries[2]);
});
});
describe('updateMathExpressionRefs', () => {
it('should rewire refs without brackets', () => {
expect(updateMathExpressionRefs('abs($Foo) + $Foo', 'Foo', 'Bar')).toBe('abs(${Bar}) + ${Bar}');
});
it('should rewire refs with brackets', () => {
expect(updateMathExpressionRefs('abs(${Foo}) + $Foo', 'Foo', 'Bar')).toBe('abs(${Bar}) + ${Bar}');
});
});
});

View File

@ -0,0 +1,64 @@
import { isExpressionQuery } from 'app/features/expressions/guards';
import { AlertQuery } from 'app/types/unified-alerting-dto';
export function queriesWithUpdatedReferences(
queries: AlertQuery[],
previousRefId: string,
newRefId: string
): AlertQuery[] {
return queries.map((query) => {
if (previousRefId === newRefId) {
return query;
}
if (!isExpressionQuery(query.model)) {
return query;
}
const isMathExpression = query.model.type === 'math';
const isReduceExpression = query.model.type === 'reduce';
const isResampleExpression = query.model.type === 'resample';
const isClassicExpression = query.model.type === 'classic_conditions';
if (isMathExpression) {
return {
...query,
model: {
...query.model,
expression: updateMathExpressionRefs(query.model.expression ?? '', previousRefId, newRefId),
},
};
}
if (isResampleExpression || isReduceExpression) {
return {
...query,
model: {
...query.model,
expression: newRefId,
},
};
}
if (isClassicExpression) {
const conditions = query.model.conditions?.map((condition) => ({
...condition,
query: {
...condition.query,
params: condition.query.params.map((param: string) => (param === previousRefId ? newRefId : param)),
},
}));
return { ...query, model: { ...query.model, conditions } };
}
return query;
});
}
export function updateMathExpressionRefs(expression: string, previousRefId: string, newRefId: string): string {
const oldExpression = new RegExp('\\${?' + previousRefId + '}?', 'gm');
const newExpression = '${' + newRefId + '}';
return expression.replace(oldExpression, newExpression);
}