Browse Source

WIP: Use data frames in explore

Torkel Ödegaard 6 years ago
parent
commit
8272dc87ab

+ 1 - 2
public/app/core/logs_model.ts

@@ -16,7 +16,6 @@ import {
   LogsMetaKind,
   LogsMetaKind,
   LogsDedupStrategy,
   LogsDedupStrategy,
   GraphSeriesXY,
   GraphSeriesXY,
-  LoadingState,
   dateTime,
   dateTime,
   toUtc,
   toUtc,
   NullValueMode,
   NullValueMode,
@@ -193,7 +192,7 @@ export function dataFrameToLogsModel(dataFrame: DataFrame[], intervalMs: number)
       logsModel.series = makeSeriesForLogs(logsModel.rows, intervalMs);
       logsModel.series = makeSeriesForLogs(logsModel.rows, intervalMs);
     } else {
     } else {
       logsModel.series = getGraphSeriesModel(
       logsModel.series = getGraphSeriesModel(
-        { series: metricSeries, state: LoadingState.Done },
+        metricSeries,
         {},
         {},
         { showBars: true, showLines: false, showPoints: false },
         { showBars: true, showLines: false, showPoints: false },
         {
         {

+ 1 - 1
public/app/features/dashboard/state/PanelQueryState.ts

@@ -274,7 +274,7 @@ export class PanelQueryState {
 
 
     return {
     return {
       state: done ? LoadingState.Done : LoadingState.Streaming,
       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,
       legacy: this.sendLegacy ? translateToLegacyData(series) : undefined,
       request: {
       request: {
         ...this.request,
         ...this.request,

+ 2 - 5
public/app/features/explore/state/reducers.ts

@@ -598,13 +598,10 @@ export const processQueryResponse = (
   }
   }
 
 
   const latency = request.endTime - request.startTime;
   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
   // For Angular editors
-  state.eventBridge.emit('data-received', processor.getRawData());
+  state.eventBridge.emit('data-received', legacy);
 
 
   return {
   return {
     ...state,
     ...state,

+ 440 - 440
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);
+//       });
+//     });
+//   });
+// });

+ 77 - 126
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 { ExploreItemState, ExploreMode } from 'app/types/explore';
-import { getProcessedDataFrames } from 'app/features/dashboard/state/PanelQueryState';
 import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
 import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
 import { sortLogsResult, refreshIntervalToSortOrder } from 'app/core/utils/explore';
 import { sortLogsResult, refreshIntervalToSortOrder } from 'app/core/utils/explore';
 import { dataFrameToLogsModel } from 'app/core/logs_model';
 import { dataFrameToLogsModel } from 'app/core/logs_model';
 import { getGraphSeriesModel } from 'app/plugins/panel/graph2/getGraphSeriesModel';
 import { getGraphSeriesModel } from 'app/plugins/panel/graph2/getGraphSeriesModel';
 
 
 export class ResultProcessor {
 export class ResultProcessor {
-  private rawData: DataQueryResponseData[] = [];
-  private metrics: TimeSeries[] = [];
-  private tables: TableData[] = [];
-
   constructor(
   constructor(
     private state: ExploreItemState,
     private state: ExploreItemState,
     private replacePreviousResults: boolean,
     private replacePreviousResults: boolean,
-    result?: DataQueryResponse | DataQueryResponseData[]
-  ) {
-    if (result && result.hasOwnProperty('data')) {
-      this.rawData = (result as DataQueryResponse).data;
-    } else {
-      this.rawData = (result as DataQueryResponseData[]) || [];
-    }
-
-    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;
-  };
+    private dataFrames: DataFrame[]
+  ) {}
 
 
-  getGraphResult = (): GraphSeriesXY[] => {
+  getGraphResult(): GraphSeriesXY[] {
     if (this.state.mode !== ExploreMode.Metrics) {
     if (this.state.mode !== ExploreMode.Metrics) {
       return [];
       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) {
     if (this.state.mode !== ExploreMode.Metrics) {
       return new TableModel();
       return new TableModel();
     }
     }
 
 
-    const prevTableResults: any[] | TableModel = this.state.tableResult || [];
-    const tablesToMerge = this.replacePreviousResults ? this.tables : [].concat(prevTableResults, this.tables);
-
-    return mergeTablesIntoModel(new TableModel(), ...tablesToMerge);
-  };
+    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);
+  }
 
 
-  getLogsResult = (): LogsModel => {
+  getLogsResult(): LogsModel {
     if (this.state.mode !== ExploreMode.Logs) {
     if (this.state.mode !== ExploreMode.Logs) {
       return null;
       return null;
     }
     }
+
     const graphInterval = this.state.queryIntervals.intervalMs;
     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 sortOrder = refreshIntervalToSortOrder(this.state.refreshInterval);
     const sortedNewResults = sortLogsResult(newResults, sortOrder);
     const sortedNewResults = sortLogsResult(newResults, sortOrder);
 
 
@@ -94,7 +64,6 @@ export class ResultProcessor {
     const prevLogsResult: LogsModel = this.state.logsResult || { hasUniqueLabels: false, rows: [] };
     const prevLogsResult: LogsModel = this.state.logsResult || { hasUniqueLabels: false, rows: [] };
     const sortedLogResult = sortLogsResult(prevLogsResult, sortOrder);
     const sortedLogResult = sortLogsResult(prevLogsResult, sortOrder);
     const rowsInState = sortedLogResult.rows;
     const rowsInState = sortedLogResult.rows;
-    const seriesInState = sortedLogResult.series || [];
 
 
     const processedRows = [];
     const processedRows = [];
     for (const row of rowsInState) {
     for (const row of rowsInState) {
@@ -104,78 +73,60 @@ export class ResultProcessor {
       processedRows.push({ ...row, fresh: true });
       processedRows.push({ ...row, fresh: true });
     }
     }
 
 
-    const processedSeries = this.mergeGraphResults(sortedNewResults.series, seriesInState);
-
     const slice = -1000;
     const slice = -1000;
     const rows = processedRows.slice(slice);
     const rows = processedRows.slice(slice);
-    const series = processedSeries.slice(slice);
+    const series = sortedNewResults.series.slice(slice);
 
 
     return { ...sortedNewResults, rows, series };
     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;
+  // };
 }
 }

+ 3 - 26
public/app/plugins/datasource/prometheus/datasource.ts

@@ -181,24 +181,6 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
     activeTargets: PromQuery[],
     activeTargets: PromQuery[],
     end: number
     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();
     const lastTimeSeriesQuery = queries.filter(query => !query.instant).pop();
 
 
     for (let index = 0; index < queries.length; index++) {
     for (let index = 0; index < queries.length; index++) {
@@ -220,19 +202,13 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
             const data = this.processResult(response, query, target, queries.length);
             const data = this.processResult(response, query, target, queries.length);
             const state: DataStreamState = {
             const state: DataStreamState = {
               key: `prometheus-${target.refId}`,
               key: `prometheus-${target.refId}`,
-              state: LoadingState.Loading,
+              state: LoadingState.Done,
               request: options,
               request: options,
               data,
               data,
               unsubscribe: () => undefined,
               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 => {
           catchError(err => {
             const error = this.handleErrors(err, target);
             const error = this.handleErrors(err, target);
@@ -306,6 +282,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
       this.runObserverQueries(options, observer, queries, activeTargets, end);
       this.runObserverQueries(options, observer, queries, activeTargets, end);
       return this.$q.when({ data: [] }) as Promise<{ data: any }>;
       return this.$q.when({ data: [] }) as Promise<{ data: any }>;
     }
     }
+
     const allQueryPromise = _.map(queries, query => {
     const allQueryPromise = _.map(queries, query => {
       if (query.instant) {
       if (query.instant) {
         return this.performInstantQuery(query, end);
         return this.performInstantQuery(query, end);

+ 2 - 2
public/app/plugins/panel/graph2/GraphPanelController.tsx

@@ -35,7 +35,7 @@ export class GraphPanelController extends React.Component<GraphPanelControllerPr
 
 
     this.state = {
     this.state = {
       graphSeriesModel: getGraphSeriesModel(
       graphSeriesModel: getGraphSeriesModel(
-        props.data,
+        props.data.series,
         props.options.series,
         props.options.series,
         props.options.graph,
         props.options.graph,
         props.options.legend
         props.options.legend
@@ -47,7 +47,7 @@ export class GraphPanelController extends React.Component<GraphPanelControllerPr
     return {
     return {
       ...state,
       ...state,
       graphSeriesModel: getGraphSeriesModel(
       graphSeriesModel: getGraphSeriesModel(
-        props.data,
+        props.data.series,
         props.options.series,
         props.options.series,
         props.options.graph,
         props.options.graph,
         props.options.legend
         props.options.legend

+ 12 - 4
public/app/plugins/panel/graph2/getGraphSeriesModel.ts

@@ -1,11 +1,19 @@
-import { colors, getFlotPairs, getColorFromHexRgbOrName, getDisplayProcessor, PanelData } from '@grafana/ui';
-import { NullValueMode, reduceField, FieldType, DisplayValue, GraphSeriesXY, getTimeField } from '@grafana/data';
+import { colors, getFlotPairs, getColorFromHexRgbOrName, getDisplayProcessor } from '@grafana/ui';
+import {
+  NullValueMode,
+  reduceField,
+  FieldType,
+  DisplayValue,
+  GraphSeriesXY,
+  getTimeField,
+  DataFrame,
+} from '@grafana/data';
 
 
 import { SeriesOptions, GraphOptions } from './types';
 import { SeriesOptions, GraphOptions } from './types';
 import { GraphLegendEditorLegendOptions } from './GraphLegendEditor';
 import { GraphLegendEditorLegendOptions } from './GraphLegendEditor';
 
 
 export const getGraphSeriesModel = (
 export const getGraphSeriesModel = (
-  data: PanelData,
+  dataFrames: DataFrame[],
   seriesOptions: SeriesOptions,
   seriesOptions: SeriesOptions,
   graphOptions: GraphOptions,
   graphOptions: GraphOptions,
   legendOptions: GraphLegendEditorLegendOptions
   legendOptions: GraphLegendEditorLegendOptions
@@ -18,7 +26,7 @@ export const getGraphSeriesModel = (
     },
     },
   });
   });
 
 
-  for (const series of data.series) {
+  for (const series of dataFrames) {
     const { timeField } = getTimeField(series);
     const { timeField } = getTimeField(series);
     if (!timeField) {
     if (!timeField) {
       continue;
       continue;