WIP: Use data frames in explore

pull/18859/head
Torkel Ödegaard 6 years ago
parent 7985aa1e57
commit 8272dc87ab
  1. 3
      public/app/core/logs_model.ts
  2. 2
      public/app/features/dashboard/state/PanelQueryState.ts
  3. 7
      public/app/features/explore/state/reducers.ts
  4. 880
      public/app/features/explore/utils/ResultProcessor.test.ts
  5. 203
      public/app/features/explore/utils/ResultProcessor.ts
  6. 29
      public/app/plugins/datasource/prometheus/datasource.ts
  7. 4
      public/app/plugins/panel/graph2/GraphPanelController.tsx
  8. 16
      public/app/plugins/panel/graph2/getGraphSeriesModel.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 },
{

@ -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,

@ -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,

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

@ -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[]) || [];
}
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) {
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 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) {
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;
// };
}

@ -181,24 +181,6 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
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<PromQuery, PromOptions>
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<PromQuery, PromOptions>
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);

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

@ -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 { GraphLegendEditorLegendOptions } from './GraphLegendEditor';
export const getGraphSeriesModel = (
data: PanelData,
dataFrames: DataFrame[],
seriesOptions: SeriesOptions,
graphOptions: GraphOptions,
legendOptions: GraphLegendEditorLegendOptions
@ -18,7 +26,7 @@ export const getGraphSeriesModel = (
},
});
for (const series of data.series) {
for (const series of dataFrames) {
const { timeField } = getTimeField(series);
if (!timeField) {
continue;

Loading…
Cancel
Save