1import { DataQuery, toDataFrameDTO, DataFrame } from '@grafana/data';
2import { FetchError, FetchResponse } from 'src/services';
3import { BackendDataSourceResponse, toDataQueryResponse, toTestingStatus } from './queryResponse';
4
5const resp = ({
6  data: {
7    results: {
8      A: {
9        frames: [
10          {
11            schema: {
12              refId: 'A',
13              fields: [
14                { name: 'time', type: 'time', typeInfo: { frame: 'time.Time', nullable: true } },
15                { name: 'A-series', type: 'number', typeInfo: { frame: 'float64', nullable: true } },
16              ],
17            },
18            data: {
19              values: [
20                [1611767228473, 1611767240473, 1611767252473, 1611767264473, 1611767276473, 1611767288473],
21                [1, 20, 90, 30, 5, 0],
22              ],
23            },
24          },
25        ],
26      },
27      B: {
28        frames: [
29          {
30            schema: {
31              refId: 'B',
32              fields: [
33                { name: 'time', type: 'time', typeInfo: { frame: 'time.Time', nullable: true } },
34                { name: 'B-series', type: 'number', typeInfo: { frame: 'float64', nullable: true } },
35              ],
36            },
37            data: {
38              values: [
39                [1611767228473, 1611767240473, 1611767252473, 1611767264473, 1611767276473, 1611767288473],
40                [1, 20, 90, 30, 5, 0],
41              ],
42            },
43          },
44        ],
45      },
46    },
47  },
48} as any) as FetchResponse<BackendDataSourceResponse>;
49
50const resWithError = ({
51  data: {
52    results: {
53      A: {
54        error: 'Hello Error',
55        frames: [
56          {
57            schema: {
58              fields: [{ name: 'numbers', type: 'number' }],
59              meta: {
60                notices: [
61                  {
62                    severity: 2,
63                    text: 'Text',
64                  },
65                ],
66              },
67            },
68            data: {
69              values: [[1, 3]],
70            },
71          },
72        ],
73      },
74    },
75  },
76} as any) as FetchResponse<BackendDataSourceResponse>;
77
78const emptyResults = {
79  data: { results: { '': { refId: '' } } },
80};
81
82describe('Query Response parser', () => {
83  test('should parse output with dataframe', () => {
84    const res = toDataQueryResponse(resp);
85    const frames = res.data;
86    expect(frames).toHaveLength(2);
87    expect(frames[0].refId).toEqual('A');
88    expect(frames[1].refId).toEqual('B');
89
90    const norm = frames.map((f) => toDataFrameDTO(f));
91    expect(norm).toMatchInlineSnapshot(`
92      Array [
93        Object {
94          "fields": Array [
95            Object {
96              "config": Object {},
97              "labels": undefined,
98              "name": "time",
99              "type": "time",
100              "values": Array [
101                1611767228473,
102                1611767240473,
103                1611767252473,
104                1611767264473,
105                1611767276473,
106                1611767288473,
107              ],
108            },
109            Object {
110              "config": Object {},
111              "labels": undefined,
112              "name": "A-series",
113              "type": "number",
114              "values": Array [
115                1,
116                20,
117                90,
118                30,
119                5,
120                0,
121              ],
122            },
123          ],
124          "meta": undefined,
125          "name": undefined,
126          "refId": "A",
127        },
128        Object {
129          "fields": Array [
130            Object {
131              "config": Object {},
132              "labels": undefined,
133              "name": "time",
134              "type": "time",
135              "values": Array [
136                1611767228473,
137                1611767240473,
138                1611767252473,
139                1611767264473,
140                1611767276473,
141                1611767288473,
142              ],
143            },
144            Object {
145              "config": Object {},
146              "labels": undefined,
147              "name": "B-series",
148              "type": "number",
149              "values": Array [
150                1,
151                20,
152                90,
153                30,
154                5,
155                0,
156              ],
157            },
158          ],
159          "meta": undefined,
160          "name": undefined,
161          "refId": "B",
162        },
163      ]
164    `);
165  });
166
167  test('should parse output with dataframe in order of queries', () => {
168    const queries: DataQuery[] = [{ refId: 'B' }, { refId: 'A' }];
169    const res = toDataQueryResponse(resp, queries);
170    const frames = res.data;
171    expect(frames).toHaveLength(2);
172    expect(frames[0].refId).toEqual('B');
173    expect(frames[1].refId).toEqual('A');
174
175    const norm = frames.map((f) => toDataFrameDTO(f));
176    expect(norm).toMatchInlineSnapshot(`
177      Array [
178        Object {
179          "fields": Array [
180            Object {
181              "config": Object {},
182              "labels": undefined,
183              "name": "time",
184              "type": "time",
185              "values": Array [
186                1611767228473,
187                1611767240473,
188                1611767252473,
189                1611767264473,
190                1611767276473,
191                1611767288473,
192              ],
193            },
194            Object {
195              "config": Object {},
196              "labels": undefined,
197              "name": "B-series",
198              "type": "number",
199              "values": Array [
200                1,
201                20,
202                90,
203                30,
204                5,
205                0,
206              ],
207            },
208          ],
209          "meta": undefined,
210          "name": undefined,
211          "refId": "B",
212        },
213        Object {
214          "fields": Array [
215            Object {
216              "config": Object {},
217              "labels": undefined,
218              "name": "time",
219              "type": "time",
220              "values": Array [
221                1611767228473,
222                1611767240473,
223                1611767252473,
224                1611767264473,
225                1611767276473,
226                1611767288473,
227              ],
228            },
229            Object {
230              "config": Object {},
231              "labels": undefined,
232              "name": "A-series",
233              "type": "number",
234              "values": Array [
235                1,
236                20,
237                90,
238                30,
239                5,
240                0,
241              ],
242            },
243          ],
244          "meta": undefined,
245          "name": undefined,
246          "refId": "A",
247        },
248      ]
249    `);
250  });
251
252  test('processEmptyResults', () => {
253    const frames = toDataQueryResponse(emptyResults).data;
254    expect(frames.length).toEqual(0);
255  });
256
257  test('keeps query order', () => {
258    const resp = {
259      data: {
260        results: {
261          X: {
262            series: [{ name: 'Requests/s', points: [[13.594958983547151, 1611839862951]] }] as any,
263          },
264          B: {
265            series: [{ name: 'Requests/s', points: [[13.594958983547151, 1611839862951]] }] as any,
266          },
267          A: {
268            series: [{ name: 'Requests/s', points: [[13.594958983547151, 1611839862951]] }] as any,
269          },
270        },
271      },
272    };
273
274    const queries: DataQuery[] = [{ refId: 'A' }, { refId: 'B' }];
275
276    const ids = (toDataQueryResponse(resp, queries).data as DataFrame[]).map((f) => f.refId);
277    expect(ids).toEqual(['A', 'B']);
278  });
279
280  test('resultWithError', () => {
281    // Generated from:
282    // qdr.Responses[q.GetRefID()] = backend.DataResponse{
283    //   Error: fmt.Errorf("an Error: %w", fmt.Errorf("another error")),
284    //   Frames: data.Frames{
285    //     {
286    //       Fields: data.Fields{data.NewField("numbers", nil, []float64{1, 3})},
287    //       Meta: &data.FrameMeta{
288    //         Notices: []data.Notice{
289    //           {
290    //             Severity: data.NoticeSeverityError,
291    //             Text:     "Text",
292    //           },
293    //         },
294    //       },
295    //     },
296    //   },
297    // }
298    const res = toDataQueryResponse(resWithError);
299    expect(res.error).toMatchInlineSnapshot(`
300      Object {
301        "message": "Hello Error",
302        "refId": "A",
303      }
304    `);
305
306    const norm = res.data.map((f) => toDataFrameDTO(f));
307    expect(norm).toMatchInlineSnapshot(`
308      Array [
309        Object {
310          "fields": Array [
311            Object {
312              "config": Object {},
313              "labels": undefined,
314              "name": "numbers",
315              "type": "number",
316              "values": Array [
317                1,
318                3,
319              ],
320            },
321          ],
322          "meta": Object {
323            "notices": Array [
324              Object {
325                "severity": 2,
326                "text": "Text",
327              },
328            ],
329          },
330          "name": undefined,
331          "refId": "A",
332        },
333      ]
334    `);
335  });
336
337  describe('should convert to TestingStatus', () => {
338    test('from api/ds/query generic errors', () => {
339      const result = toTestingStatus({ status: 500, data: { message: 'message', error: 'error' } } as FetchError);
340      expect(result).toMatchObject({
341        status: 'error',
342        message: 'message',
343        details: { message: 'error' },
344      });
345    });
346    test('from api/ds/query result errors', () => {
347      const result = toTestingStatus({
348        status: 400,
349        data: {
350          results: {
351            A: {
352              error: 'error',
353            },
354          },
355        },
356      } as FetchError);
357      expect(result).toMatchObject({
358        status: 'error',
359        message: 'error',
360      });
361    });
362    test('unknown errors', () => {
363      expect(() => {
364        toTestingStatus({ status: 503, data: 'Fatal Error' } as FetchError);
365      }).toThrow();
366
367      expect(() => {
368        toTestingStatus({ status: 503, data: {} } as FetchError);
369      }).toThrow();
370
371      expect(() => {
372        toTestingStatus({ status: 503 } as FetchError);
373      }).toThrow();
374    });
375  });
376});
377