D7net
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
usr
/
share
/
grafana
/
public
/
app
/
features
/
alerting
/
unified
/
components
/
rule-editor
/
Filename :
util.test.ts
back
Copy
import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; import { ClassicCondition, ExpressionQuery } from 'app/features/expressions/types'; import { AlertQuery } from 'app/types/unified-alerting-dto'; import { checkForPathSeparator, findRenamedDataQueryReferences, getThresholdsForQueries, 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: '__expr__', queryType: '', model: { refId: 'B', type: 'classic_conditions', datasource: ExpressionDatasourceRef, conditions: [ { type: 'query', evaluator: { params: [3], type: 'gt', }, operator: { type: 'and', }, query: { params: ['A'], }, reducer: { params: [], type: 'last', }, }, ], }, }; const mathExpression = { refId: 'B', datasourceUid: '__expr__', queryType: '', model: { refId: 'B', type: 'math', datasource: ExpressionDatasourceRef, conditions: [], expression: 'abs($A) + $A', }, }; const reduceExpression = { refId: 'B', datasourceUid: '__expr__', queryType: '', model: { refId: 'B', type: 'reduce', datasource: ExpressionDatasourceRef, conditions: [], reducer: 'mean', expression: 'A', }, }; const resampleExpression = { refId: 'A', datasourceUid: '__expr__', model: { refId: 'A', type: 'resample', datasource: { type: '__expr__', uid: '__expr__', }, conditions: [], downsampler: 'mean', upsampler: 'fillna', expression: 'A', window: '30m', }, queryType: '', }; const thresholdExpression = { refId: 'C', datasourceUid: '__expr__', model: { refId: 'C', type: 'threshold', expression: 'B', datasource: { type: '__expr__', uid: '__expr__', }, conditions: [ { evaluator: { params: [0, 'gt'], }, }, ], }, 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 threshold expressions', () => { const queries: AlertQuery[] = [dataSource, reduceExpression, thresholdExpression]; const rewiredQueries = queriesWithUpdatedReferences(queries, 'B', 'REDUCER'); const queryModel = rewiredQueries[2].model as ExpressionQuery; expect(queryModel.expression).toBe('REDUCER'); }); 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]); }); it('should not rewire non-referencing expressions', () => { const dataSource1 = { ...dataSource, refId: 'Q1' }; const dataSource2 = { ...dataSource, refId: 'Q2' }; const condition1 = { ...classicCondition, refId: 'A', model: { ...classicCondition.model, conditions: [ { ...classicCondition.model.conditions[0], query: { params: ['Q1'] }, }, ], }, }; const condition2 = { ...reduceExpression, refId: 'B', model: { ...reduceExpression.model, expression: 'Q1' } }; const condition3 = { ...mathExpression, refId: 'C', model: { ...mathExpression.model, expression: '${Q1}' } }; const queries: AlertQuery[] = [dataSource1, dataSource2, condition1, condition2, condition3]; const rewiredQueries = queriesWithUpdatedReferences(queries, 'Q2', 'Q3'); expect(rewiredQueries[0]).toEqual(queries[0]); expect(rewiredQueries[1]).toEqual(queries[1]); expect(rewiredQueries[2]).toEqual(queries[2]); expect(rewiredQueries[3]).toEqual(queries[3]); expect(rewiredQueries[4]).toEqual(queries[4]); }); }); 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}'); }); it('should not rewire refs with partial variable match', () => { expect(updateMathExpressionRefs('$A3 + $B', 'A', 'C')).toBe('$A3 + $B'); }); }); }); describe('checkForPathSeparator', () => { it('should not allow strings with /', () => { expect(checkForPathSeparator('foo / bar')).not.toBe(true); expect(typeof checkForPathSeparator('foo / bar')).toBe('string'); }); it('should not allow strings with \\', () => { expect(checkForPathSeparator('foo \\ bar')).not.toBe(true); expect(typeof checkForPathSeparator('foo \\ bar')).toBe('string'); }); it('should allow anything without / or \\', () => { expect(checkForPathSeparator('foo bar')).toBe(true); }); }); describe('getThresholdsForQueries', () => { it('should work for threshold condition', () => { const queries = createThresholdExample('gt'); expect(getThresholdsForQueries(queries)).toMatchSnapshot(); }); it('should work for classic_condition', () => { const [dataQuery] = createThresholdExample('gt'); const classicCondition = { refId: 'B', datasourceUid: '__expr__', queryType: '', model: { refId: 'B', type: 'classic_conditions', datasource: ExpressionDatasourceRef, conditions: [ { type: 'query', evaluator: { params: [0], type: 'gt', }, operator: { type: 'and', }, query: { params: ['A'], }, reducer: { params: [], type: 'last', }, }, ], }, }; const thresholdsClassic = getThresholdsForQueries([dataQuery, classicCondition]); expect(thresholdsClassic).toMatchSnapshot(); }); it('should not throw if no refId exists', () => { const dataQuery: AlertQuery = { refId: 'A', datasourceUid: 'abc123', queryType: '', relativeTimeRange: { from: 600, to: 0, }, model: { refId: 'A', }, }; const classicCondition = { refId: 'B', datasourceUid: '__expr__', queryType: '', model: { refId: 'B', type: 'classic_conditions', datasource: ExpressionDatasourceRef, conditions: [ { type: 'query', evaluator: { params: [0], type: 'gt', }, operator: { type: 'and', }, query: { params: [''], }, reducer: { params: [], type: 'last', }, }, ], }, }; expect(() => { const thresholds = getThresholdsForQueries([dataQuery, classicCondition]); expect(thresholds).toStrictEqual({}); }).not.toThrowError(); }); it('should work for within_range', () => { const queries = createThresholdExample('within_range'); const thresholds = getThresholdsForQueries(queries); expect(thresholds).toMatchSnapshot(); }); it('should work for lt and gt', () => { expect(getThresholdsForQueries(createThresholdExample('gt'))).toMatchSnapshot(); expect(getThresholdsForQueries(createThresholdExample('lt'))).toMatchSnapshot(); }); it('should work for outside_range', () => { const queries = createThresholdExample('outside_range'); const thresholds = getThresholdsForQueries(queries); expect(thresholds).toMatchSnapshot(); }); }); function createThresholdExample(thresholdType: string): AlertQuery[] { const dataQuery: AlertQuery = { refId: 'A', datasourceUid: 'abc123', queryType: '', relativeTimeRange: { from: 600, to: 0, }, model: { refId: 'A', }, }; const reduceExpression = { refId: 'B', datasourceUid: '__expr__', queryType: '', model: { refId: 'B', type: 'reduce', datasource: ExpressionDatasourceRef, conditions: [], reducer: 'mean', expression: 'A', }, }; const thresholdExpression = { refId: 'C', datasourceUid: '__expr__', queryType: '', model: { refId: 'C', type: 'threshold', datasource: ExpressionDatasourceRef, conditions: [ { type: 'query', evaluator: { params: [0, 10], type: thresholdType ?? 'gt', }, }, ], expression: 'B', }, }; return [dataQuery, reduceExpression, thresholdExpression]; } describe('findRenamedReferences', () => { it('should find the renamed ids', () => { const previous = [{ refId: 'A' }, { refId: 'B' }, { refId: 'C' }] as AlertQuery[]; const updated = [{ refId: 'FOO' }, { refId: 'B' }, { refId: 'C' }] as AlertQuery[]; expect(findRenamedDataQueryReferences(previous, updated)).toEqual(['A', 'FOO']); }); it('should ignore expression queries', () => { // @ts-expect-error const previous = [ { refId: 'A' }, { refId: 'REDUCE', model: { datasource: '-100' } }, { refId: 'MATH', model: { datasource: '-100' } }, { refId: 'B' }, { refId: 'C' }, ] as AlertQuery[]; // @ts-expect-error const updated = [ { refId: 'FOO' }, { refId: 'REDUCE', model: { datasource: '-100' } }, { refId: 'B' }, { refId: 'C' }, ] as AlertQuery[]; expect(findRenamedDataQueryReferences(previous, updated)).toEqual(['A', 'FOO']); }); });