From 8272dc87abed9c3253e990b9c4f4e21860648689 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 3 Sep 2019 12:09:52 +0200 Subject: [PATCH 1/5] WIP: Use data frames in explore --- public/app/core/logs_model.ts | 3 +- .../dashboard/state/PanelQueryState.ts | 2 +- public/app/features/explore/state/reducers.ts | 7 +- .../explore/utils/ResultProcessor.test.ts | 880 +++++++++--------- .../features/explore/utils/ResultProcessor.ts | 203 ++-- .../datasource/prometheus/datasource.ts | 29 +- .../panel/graph2/GraphPanelController.tsx | 4 +- .../panel/graph2/getGraphSeriesModel.ts | 16 +- 8 files changed, 538 insertions(+), 606 deletions(-) diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index 3f5bb62417e..6024987a2fa 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -16,7 +16,6 @@ import { LogsMetaKind, LogsDedupStrategy, GraphSeriesXY, - LoadingState, dateTime, toUtc, NullValueMode, @@ -193,7 +192,7 @@ export function dataFrameToLogsModel(dataFrame: DataFrame[], intervalMs: number) logsModel.series = makeSeriesForLogs(logsModel.rows, intervalMs); } else { logsModel.series = getGraphSeriesModel( - { series: metricSeries, state: LoadingState.Done }, + metricSeries, {}, { showBars: true, showLines: false, showPoints: false }, { diff --git a/public/app/features/dashboard/state/PanelQueryState.ts b/public/app/features/dashboard/state/PanelQueryState.ts index 47e8df6ff66..45a0891a9ff 100644 --- a/public/app/features/dashboard/state/PanelQueryState.ts +++ b/public/app/features/dashboard/state/PanelQueryState.ts @@ -274,7 +274,7 @@ export class PanelQueryState { return { state: done ? LoadingState.Done : LoadingState.Streaming, - series, // Union of series from response and all streams + series: this.sendFrames ? getProcessedDataFrames(series) : [], legacy: this.sendLegacy ? translateToLegacyData(series) : undefined, request: { ...this.request, diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index b775fd1f5e9..048005a19aa 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -598,13 +598,10 @@ export const processQueryResponse = ( } const latency = request.endTime - request.startTime; - - // temporary hack until we switch to PanelData, Loki already converts to DataFrame so using legacy will destroy the format - const isLokiDataSource = state.datasourceInstance.meta.name === 'Loki'; - const processor = new ResultProcessor(state, replacePreviousResults, isLokiDataSource ? series : legacy); + const processor = new ResultProcessor(state, replacePreviousResults, series); // For Angular editors - state.eventBridge.emit('data-received', processor.getRawData()); + state.eventBridge.emit('data-received', legacy); return { ...state, diff --git a/public/app/features/explore/utils/ResultProcessor.test.ts b/public/app/features/explore/utils/ResultProcessor.test.ts index 67954ce697c..df8f571fb11 100644 --- a/public/app/features/explore/utils/ResultProcessor.test.ts +++ b/public/app/features/explore/utils/ResultProcessor.test.ts @@ -1,440 +1,440 @@ -jest.mock('@grafana/data/src/utils/moment_wrapper', () => ({ - dateTime: (ts: any) => { - return { - valueOf: () => ts, - fromNow: () => 'fromNow() jest mocked', - format: (fmt: string) => 'format() jest mocked', - }; - }, - toUtc: (ts: any) => { - return { - format: (fmt: string) => 'format() jest mocked', - }; - }, -})); - -import { ResultProcessor } from './ResultProcessor'; -import { ExploreItemState, ExploreMode } from 'app/types/explore'; -import TableModel from 'app/core/table_model'; -import { TimeSeries, LogRowModel, LogsMetaItem, GraphSeriesXY } from '@grafana/data'; - -const testContext = (options: any = {}) => { - const response = [ - { - target: 'A-series', - alias: 'A-series', - datapoints: [[39.91264531864214, 1559038518831], [40.35179822906545, 1559038519831]], - refId: 'A', - }, - { - columns: [ - { - text: 'Time', - }, - { - text: 'Message', - }, - { - text: 'Description', - }, - { - text: 'Value', - }, - ], - rows: [ - [1559038518831, 'This is a message', 'Description', 23.1], - [1559038519831, 'This is a message', 'Description', 23.1], - ], - refId: 'B', - }, - ]; - const defaultOptions = { - mode: ExploreMode.Metrics, - replacePreviousResults: true, - result: { data: response }, - graphResult: [] as TimeSeries[], - tableResult: new TableModel(), - logsResult: { hasUniqueLabels: false, rows: [] as LogRowModel[] }, - }; - const combinedOptions = { ...defaultOptions, ...options }; - const state = ({ - mode: combinedOptions.mode, - graphResult: combinedOptions.graphResult, - tableResult: combinedOptions.tableResult, - logsResult: combinedOptions.logsResult, - queryIntervals: { intervalMs: 10 }, - } as any) as ExploreItemState; - const resultProcessor = new ResultProcessor(state, combinedOptions.replacePreviousResults, combinedOptions.result); - - return { - result: combinedOptions.result, - resultProcessor, - }; -}; - -describe('ResultProcessor', () => { - describe('constructed without result', () => { - describe('when calling getRawData', () => { - it('then it should return an empty array', () => { - const { resultProcessor } = testContext({ result: null }); - const theResult = resultProcessor.getRawData(); - - expect(theResult).toEqual([]); - }); - }); - - describe('when calling getGraphResult', () => { - it('then it should return an empty array', () => { - const { resultProcessor } = testContext({ result: null }); - const theResult = resultProcessor.getGraphResult(); - - expect(theResult).toEqual([]); - }); - }); - - describe('when calling getTableResult', () => { - it('then it should return an empty TableModel', () => { - const { resultProcessor } = testContext({ result: null }); - const theResult = resultProcessor.getTableResult(); - - expect(theResult).toEqual(new TableModel()); - }); - }); - - describe('when calling getLogsResult', () => { - it('then it should return null', () => { - const { resultProcessor } = testContext({ result: null }); - const theResult = resultProcessor.getLogsResult(); - - expect(theResult).toBeNull(); - }); - }); - }); - - describe('constructed with a result that is a DataQueryResponse', () => { - describe('when calling getRawData', () => { - it('then it should return result.data', () => { - const { result, resultProcessor } = testContext(); - const theResult = resultProcessor.getRawData(); - - expect(theResult).toEqual(result.data); - }); - }); - - describe('when calling getGraphResult', () => { - it('then it should return correct graph result', () => { - const { resultProcessor } = testContext(); - const theResult = resultProcessor.getGraphResult(); - - expect(theResult).toEqual([ - { - label: 'A-series', - color: '#7EB26D', - data: [[1559038518831, 39.91264531864214], [1559038519831, 40.35179822906545]], - info: undefined, - isVisible: true, - yAxis: { - index: 1, - }, - }, - ]); - }); - }); - - describe('when calling getTableResult', () => { - it('then it should return correct table result', () => { - const { resultProcessor } = testContext(); - const theResult = resultProcessor.getTableResult(); - - expect(theResult).toEqual({ - columnMap: {}, - columns: [{ text: 'Time' }, { text: 'Message' }, { text: 'Description' }, { text: 'Value' }], - rows: [ - [1559038518831, 'This is a message', 'Description', 23.1], - [1559038519831, 'This is a message', 'Description', 23.1], - ], - type: 'table', - }); - }); - }); - - describe('when calling getLogsResult', () => { - it('then it should return correct logs result', () => { - const { resultProcessor } = testContext({ mode: ExploreMode.Logs, observerResponse: null }); - const theResult = resultProcessor.getLogsResult(); - - console.log(JSON.stringify(theResult)); - - expect(theResult).toEqual({ - hasUniqueLabels: false, - meta: [], - rows: [ - { - entry: 'This is a message', - hasAnsi: false, - labels: undefined, - logLevel: 'unknown', - raw: 'This is a message', - searchWords: [] as string[], - timeEpochMs: 1559038519831, - timeFromNow: 'fromNow() jest mocked', - timeLocal: 'format() jest mocked', - timeUtc: 'format() jest mocked', - timestamp: 1559038519831, - uniqueLabels: {}, - }, - { - entry: 'This is a message', - hasAnsi: false, - labels: undefined, - logLevel: 'unknown', - raw: 'This is a message', - searchWords: [] as string[], - timeEpochMs: 1559038518831, - timeFromNow: 'fromNow() jest mocked', - timeLocal: 'format() jest mocked', - timeUtc: 'format() jest mocked', - timestamp: 1559038518831, - uniqueLabels: {}, - }, - ], - series: [ - { - label: 'A-series', - color: '#7EB26D', - data: [[1559038518831, 39.91264531864214], [1559038519831, 40.35179822906545]], - info: undefined, - isVisible: true, - yAxis: { - index: 1, - }, - }, - ], - }); - }); - }); - }); - - describe('constructed with result that is a DataQueryResponse and merging with previous results', () => { - describe('when calling getRawData', () => { - it('then it should return result.data', () => { - const { result, resultProcessor } = testContext(); - const theResult = resultProcessor.getRawData(); - - expect(theResult).toEqual(result.data); - }); - }); - - describe('when calling getGraphResult', () => { - it('then it should return correct graph result', () => { - const { resultProcessor } = testContext({ - replacePreviousResults: false, - graphResult: [ - { - label: 'A-series', - color: '#7EB26D', - data: [[1558038518831, 19.91264531864214], [1558038518831, 20.35179822906545]], - info: undefined, - isVisible: true, - yAxis: { - index: 1, - }, - }, - ], - }); - const theResult = resultProcessor.getGraphResult(); - - expect(theResult).toEqual([ - { - label: 'A-series', - color: '#7EB26D', - data: [ - [1558038518831, 19.91264531864214], - [1558038518831, 20.35179822906545], - [1559038518831, 39.91264531864214], - [1559038519831, 40.35179822906545], - ], - info: undefined, - isVisible: true, - yAxis: { - index: 1, - }, - }, - ]); - }); - }); - - describe('when calling getTableResult', () => { - it('then it should return correct table result', () => { - const { resultProcessor } = testContext({ - replacePreviousResults: false, - tableResult: { - columnMap: {}, - columns: [{ text: 'Time' }, { text: 'Message' }, { text: 'Description' }, { text: 'Value' }], - rows: [ - [1558038518831, 'This is a previous message 1', 'Previous Description 1', 21.1], - [1558038519831, 'This is a previous message 2', 'Previous Description 2', 22.1], - ], - type: 'table', - }, - }); - const theResult = resultProcessor.getTableResult(); - - expect(theResult).toEqual({ - columnMap: {}, - columns: [{ text: 'Time' }, { text: 'Message' }, { text: 'Description' }, { text: 'Value' }], - rows: [ - [1558038518831, 'This is a previous message 1', 'Previous Description 1', 21.1], - [1558038519831, 'This is a previous message 2', 'Previous Description 2', 22.1], - [1559038518831, 'This is a message', 'Description', 23.1], - [1559038519831, 'This is a message', 'Description', 23.1], - ], - type: 'table', - }); - }); - }); - - describe('when calling getLogsResult', () => { - it('then it should return correct logs result', () => { - const { resultProcessor } = testContext({ - mode: ExploreMode.Logs, - replacePreviousResults: false, - logsResult: { - hasUniqueLabels: false, - meta: [], - rows: [ - { - entry: 'This is a previous message 1', - fresh: true, - hasAnsi: false, - labels: { cluster: 'some-cluster' }, - logLevel: 'unknown', - raw: 'This is a previous message 1', - searchWords: [] as string[], - timeEpochMs: 1558038519831, - timeFromNow: 'fromNow() jest mocked', - timeLocal: 'format() jest mocked', - timeUtc: 'format() jest mocked', - timestamp: 1558038519831, - uniqueLabels: {}, - }, - { - entry: 'This is a previous message 2', - fresh: true, - hasAnsi: false, - labels: { cluster: 'some-cluster' }, - logLevel: 'unknown', - raw: 'This is a previous message 2', - searchWords: [] as string[], - timeEpochMs: 1558038518831, - timeFromNow: 'fromNow() jest mocked', - timeLocal: 'format() jest mocked', - timeUtc: 'format() jest mocked', - timestamp: 1558038518831, - uniqueLabels: {}, - }, - ], - series: [ - { - label: 'A-series', - color: '#7EB26D', - data: [[1558038518831, 37.91264531864214], [1558038519831, 38.35179822906545]], - info: undefined, - isVisible: true, - yAxis: { - index: 1, - }, - }, - ], - }, - }); - const theResult = resultProcessor.getLogsResult(); - const expected = { - hasUniqueLabels: false, - meta: [] as LogsMetaItem[], - rows: [ - { - entry: 'This is a previous message 1', - fresh: false, - hasAnsi: false, - labels: { cluster: 'some-cluster' }, - logLevel: 'unknown', - raw: 'This is a previous message 1', - searchWords: [] as string[], - timeEpochMs: 1558038519831, - timeFromNow: 'fromNow() jest mocked', - timeLocal: 'format() jest mocked', - timeUtc: 'format() jest mocked', - timestamp: 1558038519831, - uniqueLabels: {}, - }, - { - entry: 'This is a previous message 2', - fresh: false, - hasAnsi: false, - labels: { cluster: 'some-cluster' }, - logLevel: 'unknown', - raw: 'This is a previous message 2', - searchWords: [] as string[], - timeEpochMs: 1558038518831, - timeFromNow: 'fromNow() jest mocked', - timeLocal: 'format() jest mocked', - timeUtc: 'format() jest mocked', - timestamp: 1558038518831, - uniqueLabels: {}, - }, - { - entry: 'This is a message', - fresh: true, - hasAnsi: false, - labels: undefined, - logLevel: 'unknown', - raw: 'This is a message', - searchWords: [] as string[], - timeEpochMs: 1559038519831, - timeFromNow: 'fromNow() jest mocked', - timeLocal: 'format() jest mocked', - timeUtc: 'format() jest mocked', - timestamp: 1559038519831, - uniqueLabels: {}, - }, - { - entry: 'This is a message', - fresh: true, - hasAnsi: false, - labels: undefined, - logLevel: 'unknown', - raw: 'This is a message', - searchWords: [] as string[], - timeEpochMs: 1559038518831, - timeFromNow: 'fromNow() jest mocked', - timeLocal: 'format() jest mocked', - timeUtc: 'format() jest mocked', - timestamp: 1559038518831, - uniqueLabels: {}, - }, - ], - series: [ - { - label: 'A-series', - color: '#7EB26D', - data: [ - [1558038518831, 37.91264531864214], - [1558038519831, 38.35179822906545], - [1559038518831, 39.91264531864214], - [1559038519831, 40.35179822906545], - ], - info: undefined, - isVisible: true, - yAxis: { - index: 1, - }, - } as GraphSeriesXY, - ], - }; - - expect(theResult).toEqual(expected); - }); - }); - }); -}); +// jest.mock('@grafana/data/src/utils/moment_wrapper', () => ({ +// dateTime: (ts: any) => { +// return { +// valueOf: () => ts, +// fromNow: () => 'fromNow() jest mocked', +// format: (fmt: string) => 'format() jest mocked', +// }; +// }, +// toUtc: (ts: any) => { +// return { +// format: (fmt: string) => 'format() jest mocked', +// }; +// }, +// })); +// +// import { ResultProcessor } from './ResultProcessor'; +// import { ExploreItemState, ExploreMode } from 'app/types/explore'; +// import TableModel from 'app/core/table_model'; +// import { TimeSeries, LogRowModel, LogsMetaItem, GraphSeriesXY } from '@grafana/data'; +// +// const testContext = (options: any = {}) => { +// const response = [ +// { +// target: 'A-series', +// alias: 'A-series', +// datapoints: [[39.91264531864214, 1559038518831], [40.35179822906545, 1559038519831]], +// refId: 'A', +// }, +// { +// columns: [ +// { +// text: 'Time', +// }, +// { +// text: 'Message', +// }, +// { +// text: 'Description', +// }, +// { +// text: 'Value', +// }, +// ], +// rows: [ +// [1559038518831, 'This is a message', 'Description', 23.1], +// [1559038519831, 'This is a message', 'Description', 23.1], +// ], +// refId: 'B', +// }, +// ]; +// const defaultOptions = { +// mode: ExploreMode.Metrics, +// replacePreviousResults: true, +// result: { data: response }, +// graphResult: [] as TimeSeries[], +// tableResult: new TableModel(), +// logsResult: { hasUniqueLabels: false, rows: [] as LogRowModel[] }, +// }; +// const combinedOptions = { ...defaultOptions, ...options }; +// const state = ({ +// mode: combinedOptions.mode, +// graphResult: combinedOptions.graphResult, +// tableResult: combinedOptions.tableResult, +// logsResult: combinedOptions.logsResult, +// queryIntervals: { intervalMs: 10 }, +// } as any) as ExploreItemState; +// const resultProcessor = new ResultProcessor(state, combinedOptions.replacePreviousResults, combinedOptions.result); +// +// return { +// result: combinedOptions.result, +// resultProcessor, +// }; +// }; +// +// describe('ResultProcessor', () => { +// describe('constructed without result', () => { +// describe('when calling getRawData', () => { +// it('then it should return an empty array', () => { +// const { resultProcessor } = testContext({ result: null }); +// const theResult = resultProcessor.getRawData(); +// +// expect(theResult).toEqual([]); +// }); +// }); +// +// describe('when calling getGraphResult', () => { +// it('then it should return an empty array', () => { +// const { resultProcessor } = testContext({ result: null }); +// const theResult = resultProcessor.getGraphResult(); +// +// expect(theResult).toEqual([]); +// }); +// }); +// +// describe('when calling getTableResult', () => { +// it('then it should return an empty TableModel', () => { +// const { resultProcessor } = testContext({ result: null }); +// const theResult = resultProcessor.getTableResult(); +// +// expect(theResult).toEqual(new TableModel()); +// }); +// }); +// +// describe('when calling getLogsResult', () => { +// it('then it should return null', () => { +// const { resultProcessor } = testContext({ result: null }); +// const theResult = resultProcessor.getLogsResult(); +// +// expect(theResult).toBeNull(); +// }); +// }); +// }); +// +// describe('constructed with a result that is a DataQueryResponse', () => { +// describe('when calling getRawData', () => { +// it('then it should return result.data', () => { +// const { result, resultProcessor } = testContext(); +// const theResult = resultProcessor.getRawData(); +// +// expect(theResult).toEqual(result.data); +// }); +// }); +// +// describe('when calling getGraphResult', () => { +// it('then it should return correct graph result', () => { +// const { resultProcessor } = testContext(); +// const theResult = resultProcessor.getGraphResult(); +// +// expect(theResult).toEqual([ +// { +// label: 'A-series', +// color: '#7EB26D', +// data: [[1559038518831, 39.91264531864214], [1559038519831, 40.35179822906545]], +// info: undefined, +// isVisible: true, +// yAxis: { +// index: 1, +// }, +// }, +// ]); +// }); +// }); +// +// describe('when calling getTableResult', () => { +// it('then it should return correct table result', () => { +// const { resultProcessor } = testContext(); +// const theResult = resultProcessor.getTableResult(); +// +// expect(theResult).toEqual({ +// columnMap: {}, +// columns: [{ text: 'Time' }, { text: 'Message' }, { text: 'Description' }, { text: 'Value' }], +// rows: [ +// [1559038518831, 'This is a message', 'Description', 23.1], +// [1559038519831, 'This is a message', 'Description', 23.1], +// ], +// type: 'table', +// }); +// }); +// }); +// +// describe('when calling getLogsResult', () => { +// it('then it should return correct logs result', () => { +// const { resultProcessor } = testContext({ mode: ExploreMode.Logs, observerResponse: null }); +// const theResult = resultProcessor.getLogsResult(); +// +// console.log(JSON.stringify(theResult)); +// +// expect(theResult).toEqual({ +// hasUniqueLabels: false, +// meta: [], +// rows: [ +// { +// entry: 'This is a message', +// hasAnsi: false, +// labels: undefined, +// logLevel: 'unknown', +// raw: 'This is a message', +// searchWords: [] as string[], +// timeEpochMs: 1559038519831, +// timeFromNow: 'fromNow() jest mocked', +// timeLocal: 'format() jest mocked', +// timeUtc: 'format() jest mocked', +// timestamp: 1559038519831, +// uniqueLabels: {}, +// }, +// { +// entry: 'This is a message', +// hasAnsi: false, +// labels: undefined, +// logLevel: 'unknown', +// raw: 'This is a message', +// searchWords: [] as string[], +// timeEpochMs: 1559038518831, +// timeFromNow: 'fromNow() jest mocked', +// timeLocal: 'format() jest mocked', +// timeUtc: 'format() jest mocked', +// timestamp: 1559038518831, +// uniqueLabels: {}, +// }, +// ], +// series: [ +// { +// label: 'A-series', +// color: '#7EB26D', +// data: [[1559038518831, 39.91264531864214], [1559038519831, 40.35179822906545]], +// info: undefined, +// isVisible: true, +// yAxis: { +// index: 1, +// }, +// }, +// ], +// }); +// }); +// }); +// }); +// +// describe('constructed with result that is a DataQueryResponse and merging with previous results', () => { +// describe('when calling getRawData', () => { +// it('then it should return result.data', () => { +// const { result, resultProcessor } = testContext(); +// const theResult = resultProcessor.getRawData(); +// +// expect(theResult).toEqual(result.data); +// }); +// }); +// +// describe('when calling getGraphResult', () => { +// it('then it should return correct graph result', () => { +// const { resultProcessor } = testContext({ +// replacePreviousResults: false, +// graphResult: [ +// { +// label: 'A-series', +// color: '#7EB26D', +// data: [[1558038518831, 19.91264531864214], [1558038518831, 20.35179822906545]], +// info: undefined, +// isVisible: true, +// yAxis: { +// index: 1, +// }, +// }, +// ], +// }); +// const theResult = resultProcessor.getGraphResult(); +// +// expect(theResult).toEqual([ +// { +// label: 'A-series', +// color: '#7EB26D', +// data: [ +// [1558038518831, 19.91264531864214], +// [1558038518831, 20.35179822906545], +// [1559038518831, 39.91264531864214], +// [1559038519831, 40.35179822906545], +// ], +// info: undefined, +// isVisible: true, +// yAxis: { +// index: 1, +// }, +// }, +// ]); +// }); +// }); +// +// describe('when calling getTableResult', () => { +// it('then it should return correct table result', () => { +// const { resultProcessor } = testContext({ +// replacePreviousResults: false, +// tableResult: { +// columnMap: {}, +// columns: [{ text: 'Time' }, { text: 'Message' }, { text: 'Description' }, { text: 'Value' }], +// rows: [ +// [1558038518831, 'This is a previous message 1', 'Previous Description 1', 21.1], +// [1558038519831, 'This is a previous message 2', 'Previous Description 2', 22.1], +// ], +// type: 'table', +// }, +// }); +// const theResult = resultProcessor.getTableResult(); +// +// expect(theResult).toEqual({ +// columnMap: {}, +// columns: [{ text: 'Time' }, { text: 'Message' }, { text: 'Description' }, { text: 'Value' }], +// rows: [ +// [1558038518831, 'This is a previous message 1', 'Previous Description 1', 21.1], +// [1558038519831, 'This is a previous message 2', 'Previous Description 2', 22.1], +// [1559038518831, 'This is a message', 'Description', 23.1], +// [1559038519831, 'This is a message', 'Description', 23.1], +// ], +// type: 'table', +// }); +// }); +// }); +// +// describe('when calling getLogsResult', () => { +// it('then it should return correct logs result', () => { +// const { resultProcessor } = testContext({ +// mode: ExploreMode.Logs, +// replacePreviousResults: false, +// logsResult: { +// hasUniqueLabels: false, +// meta: [], +// rows: [ +// { +// entry: 'This is a previous message 1', +// fresh: true, +// hasAnsi: false, +// labels: { cluster: 'some-cluster' }, +// logLevel: 'unknown', +// raw: 'This is a previous message 1', +// searchWords: [] as string[], +// timeEpochMs: 1558038519831, +// timeFromNow: 'fromNow() jest mocked', +// timeLocal: 'format() jest mocked', +// timeUtc: 'format() jest mocked', +// timestamp: 1558038519831, +// uniqueLabels: {}, +// }, +// { +// entry: 'This is a previous message 2', +// fresh: true, +// hasAnsi: false, +// labels: { cluster: 'some-cluster' }, +// logLevel: 'unknown', +// raw: 'This is a previous message 2', +// searchWords: [] as string[], +// timeEpochMs: 1558038518831, +// timeFromNow: 'fromNow() jest mocked', +// timeLocal: 'format() jest mocked', +// timeUtc: 'format() jest mocked', +// timestamp: 1558038518831, +// uniqueLabels: {}, +// }, +// ], +// series: [ +// { +// label: 'A-series', +// color: '#7EB26D', +// data: [[1558038518831, 37.91264531864214], [1558038519831, 38.35179822906545]], +// info: undefined, +// isVisible: true, +// yAxis: { +// index: 1, +// }, +// }, +// ], +// }, +// }); +// const theResult = resultProcessor.getLogsResult(); +// const expected = { +// hasUniqueLabels: false, +// meta: [] as LogsMetaItem[], +// rows: [ +// { +// entry: 'This is a previous message 1', +// fresh: false, +// hasAnsi: false, +// labels: { cluster: 'some-cluster' }, +// logLevel: 'unknown', +// raw: 'This is a previous message 1', +// searchWords: [] as string[], +// timeEpochMs: 1558038519831, +// timeFromNow: 'fromNow() jest mocked', +// timeLocal: 'format() jest mocked', +// timeUtc: 'format() jest mocked', +// timestamp: 1558038519831, +// uniqueLabels: {}, +// }, +// { +// entry: 'This is a previous message 2', +// fresh: false, +// hasAnsi: false, +// labels: { cluster: 'some-cluster' }, +// logLevel: 'unknown', +// raw: 'This is a previous message 2', +// searchWords: [] as string[], +// timeEpochMs: 1558038518831, +// timeFromNow: 'fromNow() jest mocked', +// timeLocal: 'format() jest mocked', +// timeUtc: 'format() jest mocked', +// timestamp: 1558038518831, +// uniqueLabels: {}, +// }, +// { +// entry: 'This is a message', +// fresh: true, +// hasAnsi: false, +// labels: undefined, +// logLevel: 'unknown', +// raw: 'This is a message', +// searchWords: [] as string[], +// timeEpochMs: 1559038519831, +// timeFromNow: 'fromNow() jest mocked', +// timeLocal: 'format() jest mocked', +// timeUtc: 'format() jest mocked', +// timestamp: 1559038519831, +// uniqueLabels: {}, +// }, +// { +// entry: 'This is a message', +// fresh: true, +// hasAnsi: false, +// labels: undefined, +// logLevel: 'unknown', +// raw: 'This is a message', +// searchWords: [] as string[], +// timeEpochMs: 1559038518831, +// timeFromNow: 'fromNow() jest mocked', +// timeLocal: 'format() jest mocked', +// timeUtc: 'format() jest mocked', +// timestamp: 1559038518831, +// uniqueLabels: {}, +// }, +// ], +// series: [ +// { +// label: 'A-series', +// color: '#7EB26D', +// data: [ +// [1558038518831, 37.91264531864214], +// [1558038519831, 38.35179822906545], +// [1559038518831, 39.91264531864214], +// [1559038519831, 40.35179822906545], +// ], +// info: undefined, +// isVisible: true, +// yAxis: { +// index: 1, +// }, +// } as GraphSeriesXY, +// ], +// }; +// +// expect(theResult).toEqual(expected); +// }); +// }); +// }); +// }); diff --git a/public/app/features/explore/utils/ResultProcessor.ts b/public/app/features/explore/utils/ResultProcessor.ts index 75c557bcaa7..0211790b101 100644 --- a/public/app/features/explore/utils/ResultProcessor.ts +++ b/public/app/features/explore/utils/ResultProcessor.ts @@ -1,85 +1,55 @@ -import { DataQueryResponse, DataQueryResponseData } from '@grafana/ui'; - -import { - TableData, - isTableData, - LogsModel, - toDataFrame, - guessFieldTypes, - TimeSeries, - GraphSeriesXY, - LoadingState, -} from '@grafana/data'; +import { TableData, LogsModel, TimeSeries, GraphSeriesXY, DataFrame } from '@grafana/data'; import { ExploreItemState, ExploreMode } from 'app/types/explore'; -import { getProcessedDataFrames } from 'app/features/dashboard/state/PanelQueryState'; import TableModel, { mergeTablesIntoModel } from 'app/core/table_model'; import { sortLogsResult, refreshIntervalToSortOrder } from 'app/core/utils/explore'; import { dataFrameToLogsModel } from 'app/core/logs_model'; import { getGraphSeriesModel } from 'app/plugins/panel/graph2/getGraphSeriesModel'; export class ResultProcessor { - private rawData: DataQueryResponseData[] = []; - private metrics: TimeSeries[] = []; - private tables: TableData[] = []; - constructor( private state: ExploreItemState, private replacePreviousResults: boolean, - result?: DataQueryResponse | DataQueryResponseData[] - ) { - if (result && result.hasOwnProperty('data')) { - this.rawData = (result as DataQueryResponse).data; - } else { - this.rawData = (result as DataQueryResponseData[]) || []; - } + private dataFrames: DataFrame[] + ) {} - if (this.state.mode !== ExploreMode.Metrics) { - return; - } - - for (let index = 0; index < this.rawData.length; index++) { - const res: any = this.rawData[index]; - const isTable = isTableData(res); - if (isTable) { - this.tables.push(res); - } else { - this.metrics.push(res); - } - } - } - - getRawData = (): any[] => { - return this.rawData; - }; - - getGraphResult = (): GraphSeriesXY[] => { + getGraphResult(): GraphSeriesXY[] { if (this.state.mode !== ExploreMode.Metrics) { return []; } - const newResults = this.createGraphSeries(this.metrics); - return this.mergeGraphResults(newResults, this.state.graphResult); - }; + const onlyTimeSeries = this.dataFrames.filter(series => series.fields.length === 2); - getTableResult = (): TableModel => { + return getGraphSeriesModel( + onlyTimeSeries, + {}, + { showBars: false, showLines: true, showPoints: false }, + { asTable: false, isVisible: true, placement: 'under' } + ); + } + + getTableResult(): TableModel { if (this.state.mode !== ExploreMode.Metrics) { return new TableModel(); } - const prevTableResults: any[] | TableModel = this.state.tableResult || []; - const tablesToMerge = this.replacePreviousResults ? this.tables : [].concat(prevTableResults, this.tables); + return new TableModel(); + // const tables = this.panelData.series.map(frame => { + // }); + // const prevTableResults: any[] | TableModel = this.state.tableResult || []; + // const tablesToMerge = this.replacePreviousResults ? this.tables : [].concat(prevTableResults, this.tables); + // + // return mergeTablesIntoModel(new TableModel(), ...tablesToMerge); + } - return mergeTablesIntoModel(new TableModel(), ...tablesToMerge); - }; - - getLogsResult = (): LogsModel => { + getLogsResult(): LogsModel { if (this.state.mode !== ExploreMode.Logs) { return null; } + const graphInterval = this.state.queryIntervals.intervalMs; - const dataFrame = this.rawData.map(result => guessFieldTypes(toDataFrame(result))); - const newResults = this.rawData ? dataFrameToLogsModel(dataFrame, graphInterval) : null; + + const newResults = dataFrameToLogsModel(this.dataFrames, graphInterval); const sortOrder = refreshIntervalToSortOrder(this.state.refreshInterval); const sortedNewResults = sortLogsResult(newResults, sortOrder); @@ -94,7 +64,6 @@ export class ResultProcessor { const prevLogsResult: LogsModel = this.state.logsResult || { hasUniqueLabels: false, rows: [] }; const sortedLogResult = sortLogsResult(prevLogsResult, sortOrder); const rowsInState = sortedLogResult.rows; - const seriesInState = sortedLogResult.series || []; const processedRows = []; for (const row of rowsInState) { @@ -104,78 +73,60 @@ export class ResultProcessor { processedRows.push({ ...row, fresh: true }); } - const processedSeries = this.mergeGraphResults(sortedNewResults.series, seriesInState); - const slice = -1000; const rows = processedRows.slice(slice); - const series = processedSeries.slice(slice); + const series = sortedNewResults.series.slice(slice); return { ...sortedNewResults, rows, series }; - }; + } - private createGraphSeries = (rawData: any[]) => { - const dataFrames = getProcessedDataFrames(rawData); - const graphSeries = getGraphSeriesModel( - { series: dataFrames, state: LoadingState.Done }, - {}, - { showBars: false, showLines: true, showPoints: false }, - { - asTable: false, - isVisible: true, - placement: 'under', - } - ); - - return graphSeries; - }; - - private isSameGraphSeries = (a: GraphSeriesXY, b: GraphSeriesXY) => { - if (a.hasOwnProperty('label') && b.hasOwnProperty('label')) { - const aValue = a.label; - const bValue = b.label; - if (aValue !== undefined && bValue !== undefined && aValue === bValue) { - return true; - } - } - - return false; - }; - - private mergeGraphResults = (newResults: GraphSeriesXY[], prevResults: GraphSeriesXY[]): GraphSeriesXY[] => { - if (!prevResults || prevResults.length === 0 || this.replacePreviousResults) { - return newResults; // Hack before we use GraphSeriesXY instead - } - - const results: GraphSeriesXY[] = prevResults.slice() as GraphSeriesXY[]; - - // update existing results - for (let index = 0; index < results.length; index++) { - const prevResult = results[index]; - for (const newResult of newResults) { - const isSame = this.isSameGraphSeries(prevResult, newResult); - - if (isSame) { - prevResult.data = prevResult.data.concat(newResult.data); - break; - } - } - } - - // add new results - for (const newResult of newResults) { - let isNew = true; - for (const prevResult of results) { - const isSame = this.isSameGraphSeries(prevResult, newResult); - if (isSame) { - isNew = false; - break; - } - } - - if (isNew) { - results.push(newResult); - } - } - return results; - }; + // private isSameGraphSeries = (a: GraphSeriesXY, b: GraphSeriesXY) => { + // if (a.hasOwnProperty('label') && b.hasOwnProperty('label')) { + // const aValue = a.label; + // const bValue = b.label; + // if (aValue !== undefined && bValue !== undefined && aValue === bValue) { + // return true; + // } + // } + // + // return false; + // }; + // + // private mergeGraphResults = (newResults: GraphSeriesXY[], prevResults: GraphSeriesXY[]): GraphSeriesXY[] => { + // if (!prevResults || prevResults.length === 0 || this.replacePreviousResults) { + // return newResults; // Hack before we use GraphSeriesXY instead + // } + // + // const results: GraphSeriesXY[] = prevResults.slice() as GraphSeriesXY[]; + // + // // update existing results + // for (let index = 0; index < results.length; index++) { + // const prevResult = results[index]; + // for (const newResult of newResults) { + // const isSame = this.isSameGraphSeries(prevResult, newResult); + // + // if (isSame) { + // prevResult.data = prevResult.data.concat(newResult.data); + // break; + // } + // } + // } + // + // // add new results + // for (const newResult of newResults) { + // let isNew = true; + // for (const prevResult of results) { + // const isSame = this.isSameGraphSeries(prevResult, newResult); + // if (isSame) { + // isNew = false; + // break; + // } + // } + // + // if (isNew) { + // results.push(newResult); + // } + // } + // return results; + // }; } diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 300c6e1d85e..0e9c46b574d 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -181,24 +181,6 @@ export class PrometheusDatasource extends DataSourceApi activeTargets: PromQuery[], end: number ) => { - // Because we want to get run instant and TimeSeries Prom queries in parallel but this isn't actually streaming - // we need to stop/cancel each posted event with a stop stream event (see below) to the observer so that the - // PanelQueryState stops the stream - const getStopState = (state: DataStreamState): DataStreamState => ({ - ...state, - state: LoadingState.Done, - request: { ...options, requestId: 'done' }, - }); - - const startLoadingEvent: DataStreamState = { - key: `prometheus-loading_indicator`, - state: LoadingState.Loading, - request: options, - data: [], - unsubscribe: () => undefined, - }; - - observer(startLoadingEvent); // Starts the loading indicator const lastTimeSeriesQuery = queries.filter(query => !query.instant).pop(); for (let index = 0; index < queries.length; index++) { @@ -220,19 +202,13 @@ export class PrometheusDatasource extends DataSourceApi const data = this.processResult(response, query, target, queries.length); const state: DataStreamState = { key: `prometheus-${target.refId}`, - state: LoadingState.Loading, + state: LoadingState.Done, request: options, data, unsubscribe: () => undefined, }; - const states = [state, getStopState(state)]; - - if (target.refId === lastTimeSeriesQuery.refId && target.expr === lastTimeSeriesQuery.expr) { - states.push(getStopState(startLoadingEvent)); // Stops the loading indicator - } - - return states; + return [state]; }), catchError(err => { const error = this.handleErrors(err, target); @@ -306,6 +282,7 @@ export class PrometheusDatasource extends DataSourceApi this.runObserverQueries(options, observer, queries, activeTargets, end); return this.$q.when({ data: [] }) as Promise<{ data: any }>; } + const allQueryPromise = _.map(queries, query => { if (query.instant) { return this.performInstantQuery(query, end); diff --git a/public/app/plugins/panel/graph2/GraphPanelController.tsx b/public/app/plugins/panel/graph2/GraphPanelController.tsx index f1ae18edce8..eede30143c8 100644 --- a/public/app/plugins/panel/graph2/GraphPanelController.tsx +++ b/public/app/plugins/panel/graph2/GraphPanelController.tsx @@ -35,7 +35,7 @@ export class GraphPanelController extends React.Component Date: Tue, 3 Sep 2019 12:36:21 +0200 Subject: [PATCH 2/5] Explore: everything seems to be working again --- .../features/explore/utils/ResultProcessor.ts | 99 ++++++++----------- .../datasource/prometheus/datasource.ts | 2 - 2 files changed, 41 insertions(+), 60 deletions(-) diff --git a/public/app/features/explore/utils/ResultProcessor.ts b/public/app/features/explore/utils/ResultProcessor.ts index 0211790b101..4a1872662c5 100644 --- a/public/app/features/explore/utils/ResultProcessor.ts +++ b/public/app/features/explore/utils/ResultProcessor.ts @@ -1,4 +1,4 @@ -import { TableData, LogsModel, TimeSeries, GraphSeriesXY, DataFrame } from '@grafana/data'; +import { LogsModel, GraphSeriesXY, DataFrame, FieldType } from '@grafana/data'; import { ExploreItemState, ExploreMode } from 'app/types/explore'; import TableModel, { mergeTablesIntoModel } from 'app/core/table_model'; @@ -33,13 +33,46 @@ export class ResultProcessor { return new TableModel(); } - return new TableModel(); - // const tables = this.panelData.series.map(frame => { - // }); - // const prevTableResults: any[] | TableModel = this.state.tableResult || []; - // const tablesToMerge = this.replacePreviousResults ? this.tables : [].concat(prevTableResults, this.tables); - // - // return mergeTablesIntoModel(new TableModel(), ...tablesToMerge); + // For now ignore time series + // We can change this later, just need to figure out how to + // Ignore time series only for prometheus + const onlyTables = this.dataFrames.filter(frame => { + if (frame.fields.length === 2) { + if (frame.fields[1].type === FieldType.time) { + return false; + } + } + return true; + }); + + const tables = onlyTables.map(frame => { + const { fields } = frame; + const fieldCount = fields.length; + const rowCount = fields[0].values.length; + + const columns = fields.map(field => ({ + text: field.name, + type: field.type, + filterable: field.config.filterable, + })); + + const rows: any[][] = []; + for (let i = 0; i < rowCount; i++) { + const row: any[] = []; + for (let j = 0; j < fieldCount; j++) { + row.push(frame.fields[j].values.get(i)); + } + rows.push(row); + } + + return new TableModel({ + columns, + rows, + meta: frame.meta, + }); + }); + + return mergeTablesIntoModel(new TableModel(), ...tables); } getLogsResult(): LogsModel { @@ -79,54 +112,4 @@ export class ResultProcessor { return { ...sortedNewResults, rows, series }; } - - // private isSameGraphSeries = (a: GraphSeriesXY, b: GraphSeriesXY) => { - // if (a.hasOwnProperty('label') && b.hasOwnProperty('label')) { - // const aValue = a.label; - // const bValue = b.label; - // if (aValue !== undefined && bValue !== undefined && aValue === bValue) { - // return true; - // } - // } - // - // return false; - // }; - // - // private mergeGraphResults = (newResults: GraphSeriesXY[], prevResults: GraphSeriesXY[]): GraphSeriesXY[] => { - // if (!prevResults || prevResults.length === 0 || this.replacePreviousResults) { - // return newResults; // Hack before we use GraphSeriesXY instead - // } - // - // const results: GraphSeriesXY[] = prevResults.slice() as GraphSeriesXY[]; - // - // // update existing results - // for (let index = 0; index < results.length; index++) { - // const prevResult = results[index]; - // for (const newResult of newResults) { - // const isSame = this.isSameGraphSeries(prevResult, newResult); - // - // if (isSame) { - // prevResult.data = prevResult.data.concat(newResult.data); - // break; - // } - // } - // } - // - // // add new results - // for (const newResult of newResults) { - // let isNew = true; - // for (const prevResult of results) { - // const isSame = this.isSameGraphSeries(prevResult, newResult); - // if (isSame) { - // isNew = false; - // break; - // } - // } - // - // if (isNew) { - // results.push(newResult); - // } - // } - // return results; - // }; } diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 0e9c46b574d..de506023151 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -181,8 +181,6 @@ export class PrometheusDatasource extends DataSourceApi activeTargets: PromQuery[], end: number ) => { - const lastTimeSeriesQuery = queries.filter(query => !query.instant).pop(); - for (let index = 0; index < queries.length; index++) { const query = queries[index]; const target = activeTargets[index]; From 443e8d8daa359802b8873005fa91d9321ed2175e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 3 Sep 2019 14:31:34 +0200 Subject: [PATCH 3/5] Reworked ResultProcessor tests --- .../explore/utils/ResultProcessor.test.ts | 813 ++++++++---------- 1 file changed, 373 insertions(+), 440 deletions(-) diff --git a/public/app/features/explore/utils/ResultProcessor.test.ts b/public/app/features/explore/utils/ResultProcessor.test.ts index df8f571fb11..b1459c11955 100644 --- a/public/app/features/explore/utils/ResultProcessor.test.ts +++ b/public/app/features/explore/utils/ResultProcessor.test.ts @@ -1,440 +1,373 @@ -// jest.mock('@grafana/data/src/utils/moment_wrapper', () => ({ -// dateTime: (ts: any) => { -// return { -// valueOf: () => ts, -// fromNow: () => 'fromNow() jest mocked', -// format: (fmt: string) => 'format() jest mocked', -// }; -// }, -// toUtc: (ts: any) => { -// return { -// format: (fmt: string) => 'format() jest mocked', -// }; -// }, -// })); -// -// import { ResultProcessor } from './ResultProcessor'; -// import { ExploreItemState, ExploreMode } from 'app/types/explore'; -// import TableModel from 'app/core/table_model'; -// import { TimeSeries, LogRowModel, LogsMetaItem, GraphSeriesXY } from '@grafana/data'; -// -// const testContext = (options: any = {}) => { -// const response = [ -// { -// target: 'A-series', -// alias: 'A-series', -// datapoints: [[39.91264531864214, 1559038518831], [40.35179822906545, 1559038519831]], -// refId: 'A', -// }, -// { -// columns: [ -// { -// text: 'Time', -// }, -// { -// text: 'Message', -// }, -// { -// text: 'Description', -// }, -// { -// text: 'Value', -// }, -// ], -// rows: [ -// [1559038518831, 'This is a message', 'Description', 23.1], -// [1559038519831, 'This is a message', 'Description', 23.1], -// ], -// refId: 'B', -// }, -// ]; -// const defaultOptions = { -// mode: ExploreMode.Metrics, -// replacePreviousResults: true, -// result: { data: response }, -// graphResult: [] as TimeSeries[], -// tableResult: new TableModel(), -// logsResult: { hasUniqueLabels: false, rows: [] as LogRowModel[] }, -// }; -// const combinedOptions = { ...defaultOptions, ...options }; -// const state = ({ -// mode: combinedOptions.mode, -// graphResult: combinedOptions.graphResult, -// tableResult: combinedOptions.tableResult, -// logsResult: combinedOptions.logsResult, -// queryIntervals: { intervalMs: 10 }, -// } as any) as ExploreItemState; -// const resultProcessor = new ResultProcessor(state, combinedOptions.replacePreviousResults, combinedOptions.result); -// -// return { -// result: combinedOptions.result, -// resultProcessor, -// }; -// }; -// -// describe('ResultProcessor', () => { -// describe('constructed without result', () => { -// describe('when calling getRawData', () => { -// it('then it should return an empty array', () => { -// const { resultProcessor } = testContext({ result: null }); -// const theResult = resultProcessor.getRawData(); -// -// expect(theResult).toEqual([]); -// }); -// }); -// -// describe('when calling getGraphResult', () => { -// it('then it should return an empty array', () => { -// const { resultProcessor } = testContext({ result: null }); -// const theResult = resultProcessor.getGraphResult(); -// -// expect(theResult).toEqual([]); -// }); -// }); -// -// describe('when calling getTableResult', () => { -// it('then it should return an empty TableModel', () => { -// const { resultProcessor } = testContext({ result: null }); -// const theResult = resultProcessor.getTableResult(); -// -// expect(theResult).toEqual(new TableModel()); -// }); -// }); -// -// describe('when calling getLogsResult', () => { -// it('then it should return null', () => { -// const { resultProcessor } = testContext({ result: null }); -// const theResult = resultProcessor.getLogsResult(); -// -// expect(theResult).toBeNull(); -// }); -// }); -// }); -// -// describe('constructed with a result that is a DataQueryResponse', () => { -// describe('when calling getRawData', () => { -// it('then it should return result.data', () => { -// const { result, resultProcessor } = testContext(); -// const theResult = resultProcessor.getRawData(); -// -// expect(theResult).toEqual(result.data); -// }); -// }); -// -// describe('when calling getGraphResult', () => { -// it('then it should return correct graph result', () => { -// const { resultProcessor } = testContext(); -// const theResult = resultProcessor.getGraphResult(); -// -// expect(theResult).toEqual([ -// { -// label: 'A-series', -// color: '#7EB26D', -// data: [[1559038518831, 39.91264531864214], [1559038519831, 40.35179822906545]], -// info: undefined, -// isVisible: true, -// yAxis: { -// index: 1, -// }, -// }, -// ]); -// }); -// }); -// -// describe('when calling getTableResult', () => { -// it('then it should return correct table result', () => { -// const { resultProcessor } = testContext(); -// const theResult = resultProcessor.getTableResult(); -// -// expect(theResult).toEqual({ -// columnMap: {}, -// columns: [{ text: 'Time' }, { text: 'Message' }, { text: 'Description' }, { text: 'Value' }], -// rows: [ -// [1559038518831, 'This is a message', 'Description', 23.1], -// [1559038519831, 'This is a message', 'Description', 23.1], -// ], -// type: 'table', -// }); -// }); -// }); -// -// describe('when calling getLogsResult', () => { -// it('then it should return correct logs result', () => { -// const { resultProcessor } = testContext({ mode: ExploreMode.Logs, observerResponse: null }); -// const theResult = resultProcessor.getLogsResult(); -// -// console.log(JSON.stringify(theResult)); -// -// expect(theResult).toEqual({ -// hasUniqueLabels: false, -// meta: [], -// rows: [ -// { -// entry: 'This is a message', -// hasAnsi: false, -// labels: undefined, -// logLevel: 'unknown', -// raw: 'This is a message', -// searchWords: [] as string[], -// timeEpochMs: 1559038519831, -// timeFromNow: 'fromNow() jest mocked', -// timeLocal: 'format() jest mocked', -// timeUtc: 'format() jest mocked', -// timestamp: 1559038519831, -// uniqueLabels: {}, -// }, -// { -// entry: 'This is a message', -// hasAnsi: false, -// labels: undefined, -// logLevel: 'unknown', -// raw: 'This is a message', -// searchWords: [] as string[], -// timeEpochMs: 1559038518831, -// timeFromNow: 'fromNow() jest mocked', -// timeLocal: 'format() jest mocked', -// timeUtc: 'format() jest mocked', -// timestamp: 1559038518831, -// uniqueLabels: {}, -// }, -// ], -// series: [ -// { -// label: 'A-series', -// color: '#7EB26D', -// data: [[1559038518831, 39.91264531864214], [1559038519831, 40.35179822906545]], -// info: undefined, -// isVisible: true, -// yAxis: { -// index: 1, -// }, -// }, -// ], -// }); -// }); -// }); -// }); -// -// describe('constructed with result that is a DataQueryResponse and merging with previous results', () => { -// describe('when calling getRawData', () => { -// it('then it should return result.data', () => { -// const { result, resultProcessor } = testContext(); -// const theResult = resultProcessor.getRawData(); -// -// expect(theResult).toEqual(result.data); -// }); -// }); -// -// describe('when calling getGraphResult', () => { -// it('then it should return correct graph result', () => { -// const { resultProcessor } = testContext({ -// replacePreviousResults: false, -// graphResult: [ -// { -// label: 'A-series', -// color: '#7EB26D', -// data: [[1558038518831, 19.91264531864214], [1558038518831, 20.35179822906545]], -// info: undefined, -// isVisible: true, -// yAxis: { -// index: 1, -// }, -// }, -// ], -// }); -// const theResult = resultProcessor.getGraphResult(); -// -// expect(theResult).toEqual([ -// { -// label: 'A-series', -// color: '#7EB26D', -// data: [ -// [1558038518831, 19.91264531864214], -// [1558038518831, 20.35179822906545], -// [1559038518831, 39.91264531864214], -// [1559038519831, 40.35179822906545], -// ], -// info: undefined, -// isVisible: true, -// yAxis: { -// index: 1, -// }, -// }, -// ]); -// }); -// }); -// -// describe('when calling getTableResult', () => { -// it('then it should return correct table result', () => { -// const { resultProcessor } = testContext({ -// replacePreviousResults: false, -// tableResult: { -// columnMap: {}, -// columns: [{ text: 'Time' }, { text: 'Message' }, { text: 'Description' }, { text: 'Value' }], -// rows: [ -// [1558038518831, 'This is a previous message 1', 'Previous Description 1', 21.1], -// [1558038519831, 'This is a previous message 2', 'Previous Description 2', 22.1], -// ], -// type: 'table', -// }, -// }); -// const theResult = resultProcessor.getTableResult(); -// -// expect(theResult).toEqual({ -// columnMap: {}, -// columns: [{ text: 'Time' }, { text: 'Message' }, { text: 'Description' }, { text: 'Value' }], -// rows: [ -// [1558038518831, 'This is a previous message 1', 'Previous Description 1', 21.1], -// [1558038519831, 'This is a previous message 2', 'Previous Description 2', 22.1], -// [1559038518831, 'This is a message', 'Description', 23.1], -// [1559038519831, 'This is a message', 'Description', 23.1], -// ], -// type: 'table', -// }); -// }); -// }); -// -// describe('when calling getLogsResult', () => { -// it('then it should return correct logs result', () => { -// const { resultProcessor } = testContext({ -// mode: ExploreMode.Logs, -// replacePreviousResults: false, -// logsResult: { -// hasUniqueLabels: false, -// meta: [], -// rows: [ -// { -// entry: 'This is a previous message 1', -// fresh: true, -// hasAnsi: false, -// labels: { cluster: 'some-cluster' }, -// logLevel: 'unknown', -// raw: 'This is a previous message 1', -// searchWords: [] as string[], -// timeEpochMs: 1558038519831, -// timeFromNow: 'fromNow() jest mocked', -// timeLocal: 'format() jest mocked', -// timeUtc: 'format() jest mocked', -// timestamp: 1558038519831, -// uniqueLabels: {}, -// }, -// { -// entry: 'This is a previous message 2', -// fresh: true, -// hasAnsi: false, -// labels: { cluster: 'some-cluster' }, -// logLevel: 'unknown', -// raw: 'This is a previous message 2', -// searchWords: [] as string[], -// timeEpochMs: 1558038518831, -// timeFromNow: 'fromNow() jest mocked', -// timeLocal: 'format() jest mocked', -// timeUtc: 'format() jest mocked', -// timestamp: 1558038518831, -// uniqueLabels: {}, -// }, -// ], -// series: [ -// { -// label: 'A-series', -// color: '#7EB26D', -// data: [[1558038518831, 37.91264531864214], [1558038519831, 38.35179822906545]], -// info: undefined, -// isVisible: true, -// yAxis: { -// index: 1, -// }, -// }, -// ], -// }, -// }); -// const theResult = resultProcessor.getLogsResult(); -// const expected = { -// hasUniqueLabels: false, -// meta: [] as LogsMetaItem[], -// rows: [ -// { -// entry: 'This is a previous message 1', -// fresh: false, -// hasAnsi: false, -// labels: { cluster: 'some-cluster' }, -// logLevel: 'unknown', -// raw: 'This is a previous message 1', -// searchWords: [] as string[], -// timeEpochMs: 1558038519831, -// timeFromNow: 'fromNow() jest mocked', -// timeLocal: 'format() jest mocked', -// timeUtc: 'format() jest mocked', -// timestamp: 1558038519831, -// uniqueLabels: {}, -// }, -// { -// entry: 'This is a previous message 2', -// fresh: false, -// hasAnsi: false, -// labels: { cluster: 'some-cluster' }, -// logLevel: 'unknown', -// raw: 'This is a previous message 2', -// searchWords: [] as string[], -// timeEpochMs: 1558038518831, -// timeFromNow: 'fromNow() jest mocked', -// timeLocal: 'format() jest mocked', -// timeUtc: 'format() jest mocked', -// timestamp: 1558038518831, -// uniqueLabels: {}, -// }, -// { -// entry: 'This is a message', -// fresh: true, -// hasAnsi: false, -// labels: undefined, -// logLevel: 'unknown', -// raw: 'This is a message', -// searchWords: [] as string[], -// timeEpochMs: 1559038519831, -// timeFromNow: 'fromNow() jest mocked', -// timeLocal: 'format() jest mocked', -// timeUtc: 'format() jest mocked', -// timestamp: 1559038519831, -// uniqueLabels: {}, -// }, -// { -// entry: 'This is a message', -// fresh: true, -// hasAnsi: false, -// labels: undefined, -// logLevel: 'unknown', -// raw: 'This is a message', -// searchWords: [] as string[], -// timeEpochMs: 1559038518831, -// timeFromNow: 'fromNow() jest mocked', -// timeLocal: 'format() jest mocked', -// timeUtc: 'format() jest mocked', -// timestamp: 1559038518831, -// uniqueLabels: {}, -// }, -// ], -// series: [ -// { -// label: 'A-series', -// color: '#7EB26D', -// data: [ -// [1558038518831, 37.91264531864214], -// [1558038519831, 38.35179822906545], -// [1559038518831, 39.91264531864214], -// [1559038519831, 40.35179822906545], -// ], -// info: undefined, -// isVisible: true, -// yAxis: { -// index: 1, -// }, -// } as GraphSeriesXY, -// ], -// }; -// -// expect(theResult).toEqual(expected); -// }); -// }); -// }); -// }); +jest.mock('@grafana/data/src/utils/moment_wrapper', () => ({ + dateTime: (ts: any) => { + return { + valueOf: () => ts, + fromNow: () => 'fromNow() jest mocked', + format: (fmt: string) => 'format() jest mocked', + }; + }, + toUtc: (ts: any) => { + return { + format: (fmt: string) => 'format() jest mocked', + }; + }, +})); + +import { ResultProcessor } from './ResultProcessor'; +import { ExploreItemState, ExploreMode } from 'app/types/explore'; +import TableModel from 'app/core/table_model'; +import { + TimeSeries, + LogRowModel, + LogsMetaItem, + GraphSeriesXY, + MutableDataFrame, + toDataFrame, + FieldType, +} from '@grafana/data'; + +const testContext = (options: any = {}) => { + const timeSeries = toDataFrame({ + name: 'A-series', + refId: 'A', + fields: [ + { name: 'A-series', type: FieldType.number, values: [4, 5, 6] }, + { name: 'time', type: FieldType.time, values: [100, 200, 300] }, + ], + }); + + const table = toDataFrame({ + name: 'table-res', + refId: 'A', + fields: [ + { name: 'value', type: FieldType.number, values: [4, 5, 6] }, + { name: 'time', type: FieldType.time, values: [100, 200, 300] }, + { name: 'message', type: FieldType.string, values: ['this is a message', 'second message', 'third'] }, + ], + }); + + const defaultOptions = { + mode: ExploreMode.Metrics, + replacePreviousResults: true, + dataFrames: [timeSeries, table], + graphResult: [] as TimeSeries[], + tableResult: new TableModel(), + logsResult: { hasUniqueLabels: false, rows: [] as LogRowModel[] }, + }; + + const combinedOptions = { ...defaultOptions, ...options }; + + const state = ({ + mode: combinedOptions.mode, + graphResult: combinedOptions.graphResult, + tableResult: combinedOptions.tableResult, + logsResult: combinedOptions.logsResult, + queryIntervals: { intervalMs: 10 }, + } as any) as ExploreItemState; + + const resultProcessor = new ResultProcessor( + state, + combinedOptions.replacePreviousResults, + combinedOptions.dataFrames + ); + + return { + dataFrames: combinedOptions.dataFrames, + resultProcessor, + }; +}; + +describe('ResultProcessor', () => { + describe('constructed without result', () => { + describe('when calling getGraphResult', () => { + it('then it should return an empty array', () => { + const { resultProcessor } = testContext({ dataFrames: [] }); + const theResult = resultProcessor.getGraphResult(); + + expect(theResult).toEqual([]); + }); + }); + + describe('when calling getTableResult', () => { + it('then it should return an empty TableModel', () => { + const { resultProcessor } = testContext({ dataFrames: [] }); + const theResult = resultProcessor.getTableResult(); + + expect(theResult).toEqual(new TableModel()); + }); + }); + + describe('when calling getLogsResult', () => { + it('then it should return null', () => { + const { resultProcessor } = testContext({ dataFrames: [] }); + const theResult = resultProcessor.getLogsResult(); + + expect(theResult).toBeNull(); + }); + }); + }); + + describe('constructed with a result that is a DataQueryResponse', () => { + describe('when calling getGraphResult', () => { + it('then it should return correct graph result', () => { + const { resultProcessor } = testContext(); + const theResult = resultProcessor.getGraphResult(); + + expect(theResult).toEqual([ + { + label: 'A-series', + color: '#7EB26D', + data: [[100, 4], [200, 5], [300, 6]], + info: undefined, + isVisible: true, + yAxis: { + index: 1, + }, + }, + ]); + }); + }); + + describe('when calling getTableResult', () => { + it('then it should return correct table result', () => { + const { resultProcessor } = testContext(); + const theResult = resultProcessor.getTableResult(); + + expect(theResult).toEqual({ + columnMap: {}, + columns: [ + { text: 'value', type: 'number', filterable: undefined }, + { text: 'time', type: 'time', filterable: undefined }, + { text: 'message', type: 'string', filterable: undefined }, + ], + rows: [[4, 100, 'this is a message'], [5, 200, 'second message'], [6, 300, 'third']], + type: 'table', + }); + }); + }); + + describe('when calling getLogsResult', () => { + it('then it should return correct logs result', () => { + const { resultProcessor } = testContext({ mode: ExploreMode.Logs }); + const theResult = resultProcessor.getLogsResult(); + + expect(theResult).toEqual({ + hasUniqueLabels: false, + meta: [], + rows: [ + { + entry: 'third', + hasAnsi: false, + labels: undefined, + logLevel: 'unknown', + raw: 'third', + searchWords: [] as string[], + timeEpochMs: 300, + timeFromNow: 'fromNow() jest mocked', + timeLocal: 'format() jest mocked', + timeUtc: 'format() jest mocked', + timestamp: 300, + uniqueLabels: {}, + }, + { + entry: 'second message', + hasAnsi: false, + labels: undefined, + logLevel: 'unknown', + raw: 'second message', + searchWords: [] as string[], + timeEpochMs: 200, + timeFromNow: 'fromNow() jest mocked', + timeLocal: 'format() jest mocked', + timeUtc: 'format() jest mocked', + timestamp: 200, + uniqueLabels: {}, + }, + { + entry: 'this is a message', + hasAnsi: false, + labels: undefined, + logLevel: 'unknown', + raw: 'this is a message', + searchWords: [] as string[], + timeEpochMs: 100, + timeFromNow: 'fromNow() jest mocked', + timeLocal: 'format() jest mocked', + timeUtc: 'format() jest mocked', + timestamp: 100, + uniqueLabels: {}, + }, + ], + series: [ + { + label: 'A-series', + color: '#7EB26D', + data: [[100, 4], [200, 5], [300, 6]], + info: undefined, + isVisible: true, + yAxis: { + index: 1, + }, + }, + ], + }); + }); + }); + }); + + describe('constructed with result that is a DataQueryResponse and merging with previous results', () => { + describe('when calling getLogsResult', () => { + it('then it should return correct logs result', () => { + const { resultProcessor } = testContext({ + mode: ExploreMode.Logs, + replacePreviousResults: false, + logsResult: { + hasUniqueLabels: false, + meta: [], + rows: [ + { + entry: 'This is a previous message 1', + fresh: true, + hasAnsi: false, + labels: { cluster: 'some-cluster' }, + logLevel: 'unknown', + raw: 'This is a previous message 1', + searchWords: [] as string[], + timeEpochMs: 1558038519831, + timeFromNow: 'fromNow() jest mocked', + timeLocal: 'format() jest mocked', + timeUtc: 'format() jest mocked', + timestamp: 1558038519831, + uniqueLabels: {}, + }, + { + entry: 'This is a previous message 2', + fresh: true, + hasAnsi: false, + labels: { cluster: 'some-cluster' }, + logLevel: 'unknown', + raw: 'This is a previous message 2', + searchWords: [] as string[], + timeEpochMs: 1558038518831, + timeFromNow: 'fromNow() jest mocked', + timeLocal: 'format() jest mocked', + timeUtc: 'format() jest mocked', + timestamp: 1558038518831, + uniqueLabels: {}, + }, + ], + series: [ + { + label: 'A-series', + color: '#7EB26D', + data: [[1558038518831, 37.91264531864214], [1558038519831, 38.35179822906545]], + info: undefined, + isVisible: true, + yAxis: { + index: 1, + }, + }, + ], + }, + }); + + const theResult = resultProcessor.getLogsResult(); + const expected = { + hasUniqueLabels: false, + meta: [] as LogsMetaItem[], + rows: [ + { + entry: 'This is a previous message 1', + fresh: false, + hasAnsi: false, + labels: { cluster: 'some-cluster' }, + logLevel: 'unknown', + raw: 'This is a previous message 1', + searchWords: [] as string[], + timeEpochMs: 1558038519831, + timeFromNow: 'fromNow() jest mocked', + timeLocal: 'format() jest mocked', + timeUtc: 'format() jest mocked', + timestamp: 1558038519831, + uniqueLabels: {}, + }, + { + entry: 'This is a previous message 2', + fresh: false, + hasAnsi: false, + labels: { cluster: 'some-cluster' }, + logLevel: 'unknown', + raw: 'This is a previous message 2', + searchWords: [] as string[], + timeEpochMs: 1558038518831, + timeFromNow: 'fromNow() jest mocked', + timeLocal: 'format() jest mocked', + timeUtc: 'format() jest mocked', + timestamp: 1558038518831, + uniqueLabels: {}, + }, + { + entry: 'third', + fresh: true, + hasAnsi: false, + labels: undefined, + logLevel: 'unknown', + raw: 'third', + searchWords: [] as string[], + timeEpochMs: 300, + timeFromNow: 'fromNow() jest mocked', + timeLocal: 'format() jest mocked', + timeUtc: 'format() jest mocked', + timestamp: 300, + uniqueLabels: {}, + }, + { + entry: 'second message', + fresh: true, + hasAnsi: false, + labels: undefined, + logLevel: 'unknown', + raw: 'second message', + searchWords: [] as string[], + timeEpochMs: 200, + timeFromNow: 'fromNow() jest mocked', + timeLocal: 'format() jest mocked', + timeUtc: 'format() jest mocked', + timestamp: 200, + uniqueLabels: {}, + }, + { + entry: 'this is a message', + fresh: true, + hasAnsi: false, + labels: undefined, + logLevel: 'unknown', + raw: 'this is a message', + searchWords: [] as string[], + timeEpochMs: 100, + timeFromNow: 'fromNow() jest mocked', + timeLocal: 'format() jest mocked', + timeUtc: 'format() jest mocked', + timestamp: 100, + uniqueLabels: {}, + }, + ], + series: [ + { + label: 'A-series', + color: '#7EB26D', + data: [[100, 4], [200, 5], [300, 6]], + info: undefined, + isVisible: true, + yAxis: { + index: 1, + }, + } as GraphSeriesXY, + ], + }; + + expect(theResult).toEqual(expected); + }); + }); + }); +}); From 7cec38347012300cef73340b2ca128e1c05c52cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 3 Sep 2019 15:24:55 +0200 Subject: [PATCH 4/5] Fixed unit test --- .../app/features/explore/utils/ResultProcessor.test.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/public/app/features/explore/utils/ResultProcessor.test.ts b/public/app/features/explore/utils/ResultProcessor.test.ts index b1459c11955..75efb92061b 100644 --- a/public/app/features/explore/utils/ResultProcessor.test.ts +++ b/public/app/features/explore/utils/ResultProcessor.test.ts @@ -16,15 +16,7 @@ jest.mock('@grafana/data/src/utils/moment_wrapper', () => ({ import { ResultProcessor } from './ResultProcessor'; import { ExploreItemState, ExploreMode } from 'app/types/explore'; import TableModel from 'app/core/table_model'; -import { - TimeSeries, - LogRowModel, - LogsMetaItem, - GraphSeriesXY, - MutableDataFrame, - toDataFrame, - FieldType, -} from '@grafana/data'; +import { TimeSeries, LogRowModel, LogsMetaItem, GraphSeriesXY, toDataFrame, FieldType } from '@grafana/data'; const testContext = (options: any = {}) => { const timeSeries = toDataFrame({ From 289a33bf5b0c1d3f23e19de9beddc911fe05e98b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 3 Sep 2019 15:57:00 +0200 Subject: [PATCH 5/5] Updated is time series test --- .../features/explore/utils/ResultProcessor.ts | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/public/app/features/explore/utils/ResultProcessor.ts b/public/app/features/explore/utils/ResultProcessor.ts index 4a1872662c5..956593b7a75 100644 --- a/public/app/features/explore/utils/ResultProcessor.ts +++ b/public/app/features/explore/utils/ResultProcessor.ts @@ -18,7 +18,7 @@ export class ResultProcessor { return []; } - const onlyTimeSeries = this.dataFrames.filter(series => series.fields.length === 2); + const onlyTimeSeries = this.dataFrames.filter(isTimeSeries); return getGraphSeriesModel( onlyTimeSeries, @@ -36,14 +36,7 @@ export class ResultProcessor { // For now ignore time series // We can change this later, just need to figure out how to // Ignore time series only for prometheus - const onlyTables = this.dataFrames.filter(frame => { - if (frame.fields.length === 2) { - if (frame.fields[1].type === FieldType.time) { - return false; - } - } - return true; - }); + const onlyTables = this.dataFrames.filter(frame => !isTimeSeries(frame)); const tables = onlyTables.map(frame => { const { fields } = frame; @@ -113,3 +106,13 @@ export class ResultProcessor { return { ...sortedNewResults, rows, series }; } } + +export function isTimeSeries(frame: DataFrame): boolean { + if (frame.fields.length === 2) { + if (frame.fields[1].type === FieldType.time) { + return true; + } + } + + return false; +}