1import { of } from 'rxjs'; 2import { queryBuilder } from '../shared/testing/builders'; 3import { FieldType, toDataFrame } from '@grafana/data'; 4import { updateVariableOptions } from './reducer'; 5import { areMetricFindValues, toMetricFindValues, updateOptionsState, validateVariableSelection } from './operators'; 6 7describe('operators', () => { 8 beforeEach(() => { 9 jest.clearAllMocks(); 10 }); 11 12 describe('validateVariableSelection', () => { 13 describe('when called', () => { 14 it('then the correct observable should be created', async () => { 15 const variable = queryBuilder().withId('query').build(); 16 const dispatch = jest.fn().mockResolvedValue({}); 17 const observable = of(undefined).pipe(validateVariableSelection({ variable, dispatch })); 18 19 await expect(observable).toEmitValuesWith((received) => { 20 expect(received[0]).toEqual({}); 21 expect(dispatch).toHaveBeenCalledTimes(1); 22 }); 23 }); 24 }); 25 }); 26 27 describe('updateOptionsState', () => { 28 describe('when called', () => { 29 it('then the correct observable should be created', async () => { 30 const variable = queryBuilder().withId('query').build(); 31 const dispatch = jest.fn(); 32 const getTemplatedRegexFunc = jest.fn().mockReturnValue('getTemplatedRegexFunc result'); 33 34 const observable = of([{ text: 'A' }]).pipe(updateOptionsState({ variable, dispatch, getTemplatedRegexFunc })); 35 36 await expect(observable).toEmitValuesWith((received) => { 37 const value = received[0]; 38 expect(value).toEqual(undefined); 39 expect(getTemplatedRegexFunc).toHaveBeenCalledTimes(1); 40 expect(dispatch).toHaveBeenCalledTimes(1); 41 expect(dispatch).toHaveBeenCalledWith( 42 updateVariableOptions({ 43 id: 'query', 44 type: 'query', 45 data: { results: [{ text: 'A' }], templatedRegex: 'getTemplatedRegexFunc result' }, 46 }) 47 ); 48 }); 49 }); 50 }); 51 }); 52 53 describe('toMetricFindValues', () => { 54 const frameWithTextField = toDataFrame({ 55 fields: [{ name: 'text', type: FieldType.string, values: ['A', 'B', 'C'] }], 56 }); 57 const frameWithValueField = toDataFrame({ 58 fields: [{ name: 'value', type: FieldType.string, values: ['A', 'B', 'C'] }], 59 }); 60 const frameWithTextAndValueField = toDataFrame({ 61 fields: [ 62 { name: 'text', type: FieldType.string, values: ['TA', 'TB', 'TC'] }, 63 { name: 'value', type: FieldType.string, values: ['VA', 'VB', 'VC'] }, 64 ], 65 }); 66 const frameWithAStringField = toDataFrame({ 67 fields: [{ name: 'label', type: FieldType.string, values: ['A', 'B', 'C'] }], 68 }); 69 const frameWithExpandableField = toDataFrame({ 70 fields: [ 71 { name: 'label', type: FieldType.string, values: ['A', 'B', 'C'] }, 72 { name: 'expandable', type: FieldType.boolean, values: [true, false, true] }, 73 ], 74 }); 75 76 // it.each wouldn't work here as we need the done callback 77 [ 78 { series: null, expected: [] }, 79 { series: undefined, expected: [] }, 80 { series: [], expected: [] }, 81 { series: [{ text: '' }], expected: [{ text: '' }] }, 82 { series: [{ value: '' }], expected: [{ value: '' }] }, 83 { 84 series: [frameWithTextField], 85 expected: [ 86 { text: 'A', value: 'A' }, 87 { text: 'B', value: 'B' }, 88 { text: 'C', value: 'C' }, 89 ], 90 }, 91 { 92 series: [frameWithValueField], 93 expected: [ 94 { text: 'A', value: 'A' }, 95 { text: 'B', value: 'B' }, 96 { text: 'C', value: 'C' }, 97 ], 98 }, 99 { 100 series: [frameWithTextAndValueField], 101 expected: [ 102 { text: 'TA', value: 'VA' }, 103 { text: 'TB', value: 'VB' }, 104 { text: 'TC', value: 'VC' }, 105 ], 106 }, 107 { 108 series: [frameWithAStringField], 109 expected: [ 110 { text: 'A', value: 'A' }, 111 { text: 'B', value: 'B' }, 112 { text: 'C', value: 'C' }, 113 ], 114 }, 115 { 116 series: [frameWithExpandableField], 117 expected: [ 118 { text: 'A', value: 'A', expandable: true }, 119 { text: 'B', value: 'B', expandable: false }, 120 { text: 'C', value: 'C', expandable: true }, 121 ], 122 }, 123 ].map((scenario) => { 124 it(`when called with series:${JSON.stringify(scenario.series, null, 0)}`, async () => { 125 const { series, expected } = scenario; 126 const panelData: any = { series }; 127 const observable = of(panelData).pipe(toMetricFindValues()); 128 129 await expect(observable).toEmitValuesWith((received) => { 130 const value = received[0]; 131 expect(value).toEqual(expected); 132 }); 133 }); 134 }); 135 136 describe('when called without metric find values and string fields', () => { 137 it('then the observable throws', async () => { 138 const frameWithTimeField = toDataFrame({ 139 fields: [{ name: 'time', type: FieldType.time, values: [1, 2, 3] }], 140 }); 141 142 const panelData: any = { series: [frameWithTimeField] }; 143 const observable = of(panelData).pipe(toMetricFindValues()); 144 145 await expect(observable).toEmitValuesWith((received) => { 146 const value = received[0]; 147 expect(value).toEqual(new Error("Couldn't find any field of type string in the results.")); 148 }); 149 }); 150 }); 151 }); 152}); 153 154describe('areMetricFindValues', () => { 155 const frame = toDataFrame({ 156 fields: [{ name: 'text', type: FieldType.number, values: [1] }], 157 }); 158 159 it.each` 160 values | expected 161 ${null} | ${false} 162 ${undefined} | ${false} 163 ${[frame]} | ${false} 164 ${[{ text: () => {} }]} | ${false} 165 ${[{ text: { foo: 1 } }]} | ${false} 166 ${[{ text: Symbol('foo') }]} | ${false} 167 ${[{ text: true }]} | ${false} 168 ${[{ text: null }]} | ${true} 169 ${[{ value: null }]} | ${true} 170 ${[]} | ${true} 171 ${[{ text: '' }]} | ${true} 172 ${[{ Text: '' }]} | ${true} 173 ${[{ value: '' }]} | ${true} 174 ${[{ Value: '' }]} | ${true} 175 ${[{ text: '', value: '' }]} | ${true} 176 ${[{ Text: '', Value: '' }]} | ${true} 177 ${[{ text: 1 }]} | ${true} 178 ${[{ Text: 1 }]} | ${true} 179 ${[{ value: 1 }]} | ${true} 180 ${[{ Value: 1 }]} | ${true} 181 ${[{ text: 1, value: 1 }]} | ${true} 182 ${[{ Text: 1, Value: 1 }]} | ${true} 183 `('when called with values:$values', ({ values, expected }) => { 184 expect(areMetricFindValues(values)).toBe(expected); 185 }); 186}); 187