From 03951c00cb110a04d4ed8bf881a7e0322b432847 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 28 Mar 2019 10:21:53 -0700 Subject: [PATCH] Feat: Angular panels & SeriesData to Table/TimeSeries (#16266) Similar to how the react panels make sure all data is SeriesData before passing it to the react panels, this makes sure SeriesData is converted to TableData|TimeSeries before passing it to the angular panels. It also changes the typing to encourage using SeriesData for future development --- packages/grafana-ui/src/types/datasource.ts | 7 +++- .../src/utils/processSeriesData.test.ts | 41 ++++++++++++++++++- .../grafana-ui/src/utils/processSeriesData.ts | 26 ++++++++++++ .../app/features/panel/metrics_panel_ctrl.ts | 10 ++++- 4 files changed, 80 insertions(+), 4 deletions(-) diff --git a/packages/grafana-ui/src/types/datasource.ts b/packages/grafana-ui/src/types/datasource.ts index 0b0d5def1f6..15011de9836 100644 --- a/packages/grafana-ui/src/types/datasource.ts +++ b/packages/grafana-ui/src/types/datasource.ts @@ -2,7 +2,12 @@ import { TimeRange, RawTimeRange } from './time'; import { PluginMeta } from './plugin'; import { TableData, TimeSeries, SeriesData } from './data'; -export type DataQueryResponseData = TimeSeries | TableData | SeriesData | any; +/** + * Starting in v6.2 SeriesData can represent both TimeSeries and TableData + */ +export type LegacyResponseData = TimeSeries | TableData | any; + +export type DataQueryResponseData = SeriesData | LegacyResponseData; export interface DataQueryResponse { data: DataQueryResponseData[]; diff --git a/packages/grafana-ui/src/utils/processSeriesData.test.ts b/packages/grafana-ui/src/utils/processSeriesData.test.ts index 1767a02d728..03b1a889db2 100644 --- a/packages/grafana-ui/src/utils/processSeriesData.test.ts +++ b/packages/grafana-ui/src/utils/processSeriesData.test.ts @@ -1,5 +1,12 @@ -import { toSeriesData, guessFieldTypes, guessFieldTypeFromValue } from './processSeriesData'; -import { FieldType } from '../types/data'; +import { + isSeriesData, + toLegacyResponseData, + isTableData, + toSeriesData, + guessFieldTypes, + guessFieldTypeFromValue, +} from './processSeriesData'; +import { FieldType, TimeSeries } from '../types/data'; import moment from 'moment'; describe('toSeriesData', () => { @@ -63,3 +70,33 @@ describe('toSeriesData', () => { expect(norm.fields[3].type).toBe(FieldType.time); // based on name }); }); + +describe('SerisData backwards compatibility', () => { + it('converts TimeSeries to series and back again', () => { + const timeseries = { + target: 'Field Name', + datapoints: [[100, 1], [200, 2]], + }; + const series = toSeriesData(timeseries); + expect(isSeriesData(timeseries)).toBeFalsy(); + expect(isSeriesData(series)).toBeTruthy(); + + const roundtrip = toLegacyResponseData(series) as TimeSeries; + expect(isSeriesData(roundtrip)).toBeFalsy(); + expect(roundtrip.target).toBe(timeseries.target); + }); + + it('converts TableData to series and back again', () => { + const table = { + columns: [{ text: 'a', unit: 'ms' }, { text: 'b', unit: 'zz' }, { text: 'c', unit: 'yy' }], + rows: [[100, 1, 'a'], [200, 2, 'a']], + }; + const series = toSeriesData(table); + expect(isTableData(table)).toBeTruthy(); + expect(isSeriesData(series)).toBeTruthy(); + + const roundtrip = toLegacyResponseData(series) as TimeSeries; + expect(isTableData(roundtrip)).toBeTruthy(); + expect(roundtrip).toMatchObject(table); + }); +}); diff --git a/packages/grafana-ui/src/utils/processSeriesData.ts b/packages/grafana-ui/src/utils/processSeriesData.ts index e0cd065d386..437b9b36579 100644 --- a/packages/grafana-ui/src/utils/processSeriesData.ts +++ b/packages/grafana-ui/src/utils/processSeriesData.ts @@ -157,6 +157,32 @@ export const toSeriesData = (data: any): SeriesData => { throw new Error('Unsupported data format'); }; +export const toLegacyResponseData = (series: SeriesData): TimeSeries | TableData => { + const { fields, rows } = series; + + if (fields.length === 2) { + const type = guessFieldTypeFromTable(series, 1); + if (type === FieldType.time) { + return { + target: fields[0].name || series.name, + datapoints: rows, + unit: fields[0].unit, + } as TimeSeries; + } + } + + return { + columns: fields.map(f => { + return { + text: f.name, + filterable: f.filterable, + unit: f.unit, + }; + }), + rows, + }; +}; + export function sortSeriesData(data: SeriesData, sortIndex?: number, reverse = false): SeriesData { if (isNumber(sortIndex)) { const copy = { diff --git a/public/app/features/panel/metrics_panel_ctrl.ts b/public/app/features/panel/metrics_panel_ctrl.ts index 3e217369b15..e596d4d3856 100644 --- a/public/app/features/panel/metrics_panel_ctrl.ts +++ b/public/app/features/panel/metrics_panel_ctrl.ts @@ -6,6 +6,7 @@ import { PanelCtrl } from 'app/features/panel/panel_ctrl'; import { getExploreUrl } from 'app/core/utils/explore'; import { applyPanelTimeOverrides, getResolution } from 'app/features/dashboard/utils/panel'; import { ContextSrv } from 'app/core/services/context_srv'; +import { toLegacyResponseData, isSeriesData } from '@grafana/ui'; class MetricsPanelCtrl extends PanelCtrl { scope: any; @@ -188,7 +189,14 @@ class MetricsPanelCtrl extends PanelCtrl { result = { data: [] }; } - this.events.emit('data-received', result.data); + // Make sure the data is TableData | TimeSeries + const data = result.data.map(v => { + if (isSeriesData(v)) { + return toLegacyResponseData(v); + } + return v; + }); + this.events.emit('data-received', data); } handleDataStream(stream) {