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