From 774b7267df6f583895dda6d3c22a02b654120faa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Thu, 22 Aug 2019 11:10:20 +0200 Subject: [PATCH] Explore: Fixes so Show context shows results again (#18675) * Fix: Fixes so Show context shows results again Fixes: #18656 * Refactor: Removes unused import that made it thrugh the pre-commit somehow --- .../src/utils/dataFrameHelper.test.ts | 28 ++++++- .../grafana-data/src/utils/dataFrameHelper.ts | 5 +- .../explore/LogRowContextProvider.test.ts | 74 +++++++++++++++++++ .../explore/LogRowContextProvider.tsx | 38 +++++++--- 4 files changed, 131 insertions(+), 14 deletions(-) create mode 100644 public/app/features/explore/LogRowContextProvider.test.ts diff --git a/packages/grafana-data/src/utils/dataFrameHelper.test.ts b/packages/grafana-data/src/utils/dataFrameHelper.test.ts index a84b1deec00..89385f066bc 100644 --- a/packages/grafana-data/src/utils/dataFrameHelper.test.ts +++ b/packages/grafana-data/src/utils/dataFrameHelper.test.ts @@ -1,4 +1,4 @@ -import { FieldType, DataFrameDTO, FieldDTO } from '../types/index'; +import { DataFrameDTO, FieldDTO, FieldType } from '../types'; import { DataFrameHelper } from './dataFrameHelper'; describe('dataFrameHelper', () => { @@ -87,3 +87,29 @@ describe('FieldCache', () => { expect(fieldCache.getFieldByName('null')).toBeUndefined(); }); }); + +describe('reverse', () => { + describe('when called with a DataFrame', () => { + it('then it should reverse the order of values in all fields', () => { + const frame: DataFrameDTO = { + fields: [ + { name: 'time', type: FieldType.time, values: [100, 200, 300] }, + { name: 'name', type: FieldType.string, values: ['a', 'b', 'c'] }, + { name: 'value', type: FieldType.number, values: [1, 2, 3] }, + ], + }; + + const helper = new DataFrameHelper(frame); + + expect(helper.getFieldByName('time')!.values.toArray()).toEqual([100, 200, 300]); + expect(helper.getFieldByName('name')!.values.toArray()).toEqual(['a', 'b', 'c']); + expect(helper.getFieldByName('value')!.values.toArray()).toEqual([1, 2, 3]); + + helper.reverse(); + + expect(helper.getFieldByName('time')!.values.toArray()).toEqual([300, 200, 100]); + expect(helper.getFieldByName('name')!.values.toArray()).toEqual(['c', 'b', 'a']); + expect(helper.getFieldByName('value')!.values.toArray()).toEqual([3, 2, 1]); + }); + }); +}); diff --git a/packages/grafana-data/src/utils/dataFrameHelper.ts b/packages/grafana-data/src/utils/dataFrameHelper.ts index 57985c47f39..5732d5c240f 100644 --- a/packages/grafana-data/src/utils/dataFrameHelper.ts +++ b/packages/grafana-data/src/utils/dataFrameHelper.ts @@ -44,10 +44,7 @@ export class DataFrameHelper implements DataFrame { */ reverse() { for (const f of this.fields) { - if (isArray(f.values)) { - const arr = f.values as any[]; - arr.reverse(); - } + f.values.toArray().reverse(); } } diff --git a/public/app/features/explore/LogRowContextProvider.test.ts b/public/app/features/explore/LogRowContextProvider.test.ts new file mode 100644 index 00000000000..d43387b18a7 --- /dev/null +++ b/public/app/features/explore/LogRowContextProvider.test.ts @@ -0,0 +1,74 @@ +import { DataFrameHelper, FieldType, LogRowModel } from '@grafana/data'; +import { getRowContexts } from './LogRowContextProvider'; + +describe('getRowContexts', () => { + describe('when called with a DataFrame and results are returned', () => { + it('then the result should be in correct format', async () => { + const firstResult = new DataFrameHelper({ + refId: 'B', + labels: {}, + fields: [ + { name: 'ts', type: FieldType.time, values: [3, 2, 1] }, + { name: 'line', type: FieldType.string, values: ['3', '2', '1'] }, + ], + }); + const secondResult = new DataFrameHelper({ + refId: 'B', + labels: {}, + fields: [ + { name: 'ts', type: FieldType.time, values: [6, 5, 4] }, + { name: 'line', type: FieldType.string, values: ['6', '5', '4'] }, + ], + }); + const row: LogRowModel = { + entry: '4', + labels: null, + hasAnsi: false, + raw: '4', + logLevel: null, + timeEpochMs: 4, + timeFromNow: '', + timeLocal: '', + timeUtc: '', + timestamp: '4', + }; + + const getRowContext = jest + .fn() + .mockResolvedValueOnce({ data: [firstResult] }) + .mockResolvedValueOnce({ data: [secondResult] }); + + const result = await getRowContexts(getRowContext, row, 10); + + expect(result).toEqual({ data: [[['3', '2', '1']], [['6', '5', '4']]], errors: [null, null] }); + }); + }); + + describe('when called with a DataFrame and errors occur', () => { + it('then the result should be in correct format', async () => { + const firstError = new Error('Error 1'); + const secondError = new Error('Error 2'); + const row: LogRowModel = { + entry: '4', + labels: null, + hasAnsi: false, + raw: '4', + logLevel: null, + timeEpochMs: 4, + timeFromNow: '', + timeLocal: '', + timeUtc: '', + timestamp: '4', + }; + + const getRowContext = jest + .fn() + .mockRejectedValueOnce(firstError) + .mockRejectedValueOnce(secondError); + + const result = await getRowContexts(getRowContext, row, 10); + + expect(result).toEqual({ data: [[], []], errors: ['Error 1', 'Error 2'] }); + }); + }); +}); diff --git a/public/app/features/explore/LogRowContextProvider.tsx b/public/app/features/explore/LogRowContextProvider.tsx index 08bddaa9f90..775934be943 100644 --- a/public/app/features/explore/LogRowContextProvider.tsx +++ b/public/app/features/explore/LogRowContextProvider.tsx @@ -1,5 +1,5 @@ import { DataQueryResponse, DataQueryError } from '@grafana/ui'; -import { LogRowModel } from '@grafana/data'; +import { LogRowModel, toDataFrame, Field } from '@grafana/data'; import { useState, useEffect } from 'react'; import flatten from 'lodash/flatten'; import useAsync from 'react-use/lib/useAsync'; @@ -47,19 +47,39 @@ export const getRowContexts = async ( const results: Array = await Promise.all(promises.map(p => p.catch(e => e))); return { - data: results.map((result, index) => { + data: results.map(result => { const dataResult: DataQueryResponse = result as DataQueryResponse; if (!dataResult.data) { return []; } - // We need to filter out the row we're basing our search from because of how start/end params work in Loki API - // see https://github.com/grafana/loki/issues/597#issuecomment-506408980 - // the alternative to create our own add 1 nanosecond method to the a timestamp string would be quite complex - return dataResult.data.map(series => { - const filteredRows = series.rows.filter((r: any) => r[0] !== row.timestamp); - return filteredRows.map((row: any) => row[1]); - }); + const data: any[] = []; + for (let index = 0; index < dataResult.data.length; index++) { + const dataFrame = toDataFrame(dataResult.data[index]); + const timestampField: Field = dataFrame.fields.filter(field => field.name === 'ts')[0]; + + for (let fieldIndex = 0; fieldIndex < timestampField.values.length; fieldIndex++) { + const timestamp = timestampField.values.get(fieldIndex); + + // We need to filter out the row we're basing our search from because of how start/end params work in Loki API + // see https://github.com/grafana/loki/issues/597#issuecomment-506408980 + // the alternative to create our own add 1 nanosecond method to the a timestamp string would be quite complex + if (timestamp === row.timestamp) { + continue; + } + + const lineField: Field = dataFrame.fields.filter(field => field.name === 'line')[0]; + const line = lineField.values.get(fieldIndex); // assuming that both fields have same length + + if (data.length === 0) { + data[0] = [line]; + } else { + data[0].push(line); + } + } + } + + return data; }), errors: results.map(result => { const errorResult: DataQueryError = result as DataQueryError;