1import { ClassicCondition, ExpressionQuery } from 'app/features/expressions/types'; 2import { AlertQuery } from 'app/types/unified-alerting-dto'; 3import { checkForPathSeparator, queriesWithUpdatedReferences, updateMathExpressionRefs } from './util'; 4import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; 5 6describe('rule-editor', () => { 7 const dataSource: AlertQuery = { 8 refId: 'A', 9 datasourceUid: 'abc123', 10 queryType: '', 11 relativeTimeRange: { 12 from: 600, 13 to: 0, 14 }, 15 model: { 16 refId: 'A', 17 }, 18 }; 19 20 const classicCondition = { 21 refId: 'B', 22 datasourceUid: '-100', 23 queryType: '', 24 model: { 25 refId: 'B', 26 type: 'classic_conditions', 27 datasource: ExpressionDatasourceRef, 28 conditions: [ 29 { 30 type: 'query', 31 evaluator: { 32 params: [3], 33 type: 'gt', 34 }, 35 operator: { 36 type: 'and', 37 }, 38 query: { 39 params: ['A'], 40 }, 41 reducer: { 42 params: [], 43 type: 'last', 44 }, 45 }, 46 ], 47 }, 48 }; 49 50 const mathExpression = { 51 refId: 'B', 52 datasourceUid: '-100', 53 queryType: '', 54 model: { 55 refId: 'B', 56 type: 'math', 57 datasource: ExpressionDatasourceRef, 58 conditions: [], 59 expression: 'abs($A) + $A', 60 }, 61 }; 62 63 const reduceExpression = { 64 refId: 'B', 65 datasourceUid: '-100', 66 queryType: '', 67 model: { 68 refId: 'B', 69 type: 'reduce', 70 datasource: ExpressionDatasourceRef, 71 conditions: [], 72 reducer: 'mean', 73 expression: 'A', 74 }, 75 }; 76 77 const resampleExpression = { 78 refId: 'A', 79 datasourceUid: '-100', 80 model: { 81 refId: 'A', 82 type: 'resample', 83 datasource: { 84 type: '__expr__', 85 uid: '__expr__', 86 }, 87 conditions: [], 88 downsampler: 'mean', 89 upsampler: 'fillna', 90 expression: 'A', 91 window: '30m', 92 }, 93 queryType: '', 94 }; 95 96 describe('rewires query names', () => { 97 it('should rewire classic expressions', () => { 98 const queries: AlertQuery[] = [dataSource, classicCondition]; 99 const rewiredQueries = queriesWithUpdatedReferences(queries, 'A', 'C'); 100 101 const queryModel = rewiredQueries[1].model as ExpressionQuery; 102 103 const checkConditionParams = (condition: ClassicCondition) => { 104 return expect(condition.query.params).toEqual(['C']); 105 }; 106 107 expect(queryModel.conditions?.every(checkConditionParams)); 108 }); 109 110 it('should rewire math expressions', () => { 111 const queries: AlertQuery[] = [dataSource, mathExpression]; 112 const rewiredQueries = queriesWithUpdatedReferences(queries, 'A', 'Query A'); 113 114 const queryModel = rewiredQueries[1].model as ExpressionQuery; 115 116 expect(queryModel.expression).toBe('abs(${Query A}) + ${Query A}'); 117 }); 118 119 it('should rewire reduce expressions', () => { 120 const queries: AlertQuery[] = [dataSource, reduceExpression]; 121 const rewiredQueries = queriesWithUpdatedReferences(queries, 'A', 'C'); 122 123 const queryModel = rewiredQueries[1].model as ExpressionQuery; 124 expect(queryModel.expression).toBe('C'); 125 }); 126 127 it('should rewire resample expressions', () => { 128 const queries: AlertQuery[] = [dataSource, resampleExpression]; 129 const rewiredQueries = queriesWithUpdatedReferences(queries, 'A', 'C'); 130 131 const queryModel = rewiredQueries[1].model as ExpressionQuery; 132 expect(queryModel.expression).toBe('C'); 133 }); 134 135 it('should rewire multiple expressions', () => { 136 const queries: AlertQuery[] = [dataSource, mathExpression, resampleExpression]; 137 const rewiredQueries = queriesWithUpdatedReferences(queries, 'A', 'C'); 138 139 expect(rewiredQueries[1].model as ExpressionQuery).toHaveProperty('expression', 'abs(${C}) + ${C}'); 140 expect(rewiredQueries[2].model as ExpressionQuery).toHaveProperty('expression', 'C'); 141 }); 142 143 it('should skip if refs are identical', () => { 144 const queries: AlertQuery[] = [dataSource, reduceExpression, mathExpression]; 145 const rewiredQueries = queriesWithUpdatedReferences(queries, 'A', 'A'); 146 147 expect(rewiredQueries[0]).toEqual(queries[0]); 148 expect(rewiredQueries[1]).toEqual(queries[1]); 149 expect(rewiredQueries[2]).toEqual(queries[2]); 150 }); 151 152 it('should not rewire non-referencing expressions', () => { 153 const dataSource1 = { ...dataSource, refId: 'Q1' }; 154 const dataSource2 = { ...dataSource, refId: 'Q2' }; 155 const condition1 = { 156 ...classicCondition, 157 refId: 'A', 158 model: { 159 ...classicCondition.model, 160 conditions: [ 161 { 162 ...classicCondition.model.conditions[0], 163 query: { params: ['Q1'] }, 164 }, 165 ], 166 }, 167 }; 168 const condition2 = { ...reduceExpression, refId: 'B', model: { ...reduceExpression.model, expression: 'Q1' } }; 169 const condition3 = { ...mathExpression, refId: 'C', model: { ...mathExpression.model, expression: '${Q1}' } }; 170 171 const queries: AlertQuery[] = [dataSource1, dataSource2, condition1, condition2, condition3]; 172 const rewiredQueries = queriesWithUpdatedReferences(queries, 'Q2', 'Q3'); 173 174 expect(rewiredQueries[0]).toEqual(queries[0]); 175 expect(rewiredQueries[1]).toEqual(queries[1]); 176 expect(rewiredQueries[2]).toEqual(queries[2]); 177 expect(rewiredQueries[3]).toEqual(queries[3]); 178 expect(rewiredQueries[4]).toEqual(queries[4]); 179 }); 180 }); 181 182 describe('updateMathExpressionRefs', () => { 183 it('should rewire refs without brackets', () => { 184 expect(updateMathExpressionRefs('abs($Foo) + $Foo', 'Foo', 'Bar')).toBe('abs(${Bar}) + ${Bar}'); 185 }); 186 it('should rewire refs with brackets', () => { 187 expect(updateMathExpressionRefs('abs(${Foo}) + $Foo', 'Foo', 'Bar')).toBe('abs(${Bar}) + ${Bar}'); 188 }); 189 it('should not rewire refs with partial variable match', () => { 190 expect(updateMathExpressionRefs('$A3 + $B', 'A', 'C')).toBe('$A3 + $B'); 191 }); 192 }); 193}); 194 195describe('checkForPathSeparator', () => { 196 it('should not allow strings with /', () => { 197 expect(checkForPathSeparator('foo / bar')).not.toBe(true); 198 expect(typeof checkForPathSeparator('foo / bar')).toBe('string'); 199 }); 200 it('should not allow strings with \\', () => { 201 expect(checkForPathSeparator('foo \\ bar')).not.toBe(true); 202 expect(typeof checkForPathSeparator('foo \\ bar')).toBe('string'); 203 }); 204 it('should allow anything without / or \\', () => { 205 expect(checkForPathSeparator('foo bar')).toBe(true); 206 }); 207}); 208