use TableData for timeseries in react

This commit is contained in:
ryan 2019-03-07 12:13:38 -08:00
parent 56682cb1eb
commit abf015ace2
9 changed files with 95 additions and 80 deletions

View File

@ -53,12 +53,9 @@ export interface TimeSeriesVMs {
length: number; length: number;
} }
interface Column { export interface Column {
text: string; text: string; // name
title?: string; type?: 'time' | 'number' | 'string' | 'object';
type?: string;
sort?: boolean;
desc?: boolean;
filterable?: boolean; filterable?: boolean;
unit?: string; unit?: string;
} }
@ -67,5 +64,4 @@ export interface TableData {
columns: Column[]; columns: Column[];
rows: any[]; rows: any[];
type: string; type: string;
columnMap: any;
} }

View File

@ -1,12 +1,12 @@
import { ComponentClass } from 'react'; import { ComponentClass } from 'react';
import { TimeSeries, LoadingState, TableData } from './data'; import { LoadingState, TableData } from './data';
import { TimeRange } from './time'; import { TimeRange } from './time';
import { ScopedVars } from './datasource'; import { ScopedVars } from './datasource';
export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string; export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string;
export interface PanelProps<T = any> { export interface PanelProps<T = any> {
panelData: PanelData; data?: TableData[];
timeRange: TimeRange; timeRange: TimeRange;
loading: LoadingState; loading: LoadingState;
options: T; options: T;
@ -16,11 +16,6 @@ export interface PanelProps<T = any> {
replaceVariables: InterpolateFunction; replaceVariables: InterpolateFunction;
} }
export interface PanelData {
timeSeries?: TimeSeries[];
tableData?: TableData;
}
export interface PanelEditorProps<T = any> { export interface PanelEditorProps<T = any> {
options: T; options: T;
onOptionsChange: (options: T) => void; onOptionsChange: (options: T) => void;

View File

@ -4,17 +4,19 @@ import isNumber from 'lodash/isNumber';
import { colors } from './colors'; import { colors } from './colors';
// Types // Types
import { TimeSeries, TimeSeriesVMs, NullValueMode, TimeSeriesValue } from '../types'; import { TimeSeriesVMs, NullValueMode, TimeSeriesValue, TableData } from '../types';
interface Options { interface Options {
timeSeries: TimeSeries[]; data: TableData[];
xColumn: number; // Time
yColumn: number; // Value
nullValueMode: NullValueMode; nullValueMode: NullValueMode;
} }
export function processTimeSeries({ timeSeries, nullValueMode }: Options): TimeSeriesVMs { export function processTimeSeries({ data, xColumn, yColumn, nullValueMode }: Options): TimeSeriesVMs {
const vmSeries = timeSeries.map((item, index) => { const vmSeries = data.map((item, index) => {
const colorIndex = index % colors.length; const colorIndex = index % colors.length;
const label = item.target; const label = item.columns[yColumn].text;
const result = []; const result = [];
// stat defaults // stat defaults
@ -42,9 +44,9 @@ export function processTimeSeries({ timeSeries, nullValueMode }: Options): TimeS
let previousValue = 0; let previousValue = 0;
let previousDeltaUp = true; let previousDeltaUp = true;
for (let i = 0; i < item.datapoints.length; i++) { for (let i = 0; i < item.rows.length; i++) {
currentValue = item.datapoints[i][0]; currentValue = item.rows[i][yColumn];
currentTime = item.datapoints[i][1]; currentTime = item.rows[i][xColumn];
if (typeof currentTime !== 'number') { if (typeof currentTime !== 'number') {
continue; continue;
@ -95,7 +97,7 @@ export function processTimeSeries({ timeSeries, nullValueMode }: Options): TimeS
if (previousValue > currentValue) { if (previousValue > currentValue) {
// counter reset // counter reset
previousDeltaUp = false; previousDeltaUp = false;
if (i === item.datapoints.length - 1) { if (i === item.rows.length - 1) {
// reset on last // reset on last
delta += currentValue; delta += currentValue;
} }

View File

@ -1,17 +1,15 @@
import _ from 'lodash'; import _ from 'lodash';
import { Column, TableData } from '@grafana/ui';
interface Column { // This class mutates and uses the extra column fields
text: string; interface ColumnEX extends Column {
title?: string; title?: string;
type?: string;
sort?: boolean; sort?: boolean;
desc?: boolean; desc?: boolean;
filterable?: boolean;
unit?: string;
} }
export default class TableModel { export default class TableModel implements TableData {
columns: Column[]; columns: ColumnEX[];
rows: any[]; rows: any[];
type: string; type: string;
columnMap: any; columnMap: any;

View File

@ -11,16 +11,16 @@ import {
DataQueryResponse, DataQueryResponse,
DataQueryError, DataQueryError,
LoadingState, LoadingState,
PanelData,
TableData, TableData,
TimeRange, TimeRange,
TimeSeries,
ScopedVars, ScopedVars,
} from '@grafana/ui'; } from '@grafana/ui';
import { toTableData } from '../utils/panel';
interface RenderProps { interface RenderProps {
loading: LoadingState; loading: LoadingState;
panelData: PanelData; data: TableData[];
} }
export interface Props { export interface Props {
@ -44,7 +44,7 @@ export interface State {
isFirstLoad: boolean; isFirstLoad: boolean;
loading: LoadingState; loading: LoadingState;
response: DataQueryResponse; response: DataQueryResponse;
panelData: PanelData; data?: TableData[];
} }
export class DataPanel extends Component<Props, State> { export class DataPanel extends Component<Props, State> {
@ -64,7 +64,6 @@ export class DataPanel extends Component<Props, State> {
response: { response: {
data: [], data: [],
}, },
panelData: {},
isFirstLoad: true, isFirstLoad: true,
}; };
} }
@ -146,10 +145,12 @@ export class DataPanel extends Component<Props, State> {
onDataResponse(resp); onDataResponse(resp);
} }
const data = toTableData(resp.data);
console.log('Converted:', data);
this.setState({ this.setState({
loading: LoadingState.Done, loading: LoadingState.Done,
response: resp, response: resp,
panelData: this.getPanelData(resp), data,
isFirstLoad: false, isFirstLoad: false,
}); });
} catch (err) { } catch (err) {
@ -172,23 +173,9 @@ export class DataPanel extends Component<Props, State> {
} }
}; };
getPanelData(response: DataQueryResponse) {
if (response.data.length > 0 && (response.data[0] as TableData).type === 'table') {
return {
tableData: response.data[0] as TableData,
timeSeries: null,
};
}
return {
timeSeries: response.data as TimeSeries[],
tableData: null,
};
}
render() { render() {
const { queries } = this.props; const { queries } = this.props;
const { loading, isFirstLoad, panelData } = this.state; const { loading, isFirstLoad, data } = this.state;
// do not render component until we have first data // do not render component until we have first data
if (isFirstLoad && (loading === LoadingState.Loading || loading === LoadingState.NotStarted)) { if (isFirstLoad && (loading === LoadingState.Loading || loading === LoadingState.NotStarted)) {
@ -203,10 +190,12 @@ export class DataPanel extends Component<Props, State> {
); );
} }
console.log('RENDER', data);
return ( return (
<> <>
{loading === LoadingState.Loading && this.renderLoadingState()} {loading === LoadingState.Loading && this.renderLoadingState()}
{this.props.children({ loading, panelData })} {this.props.children({ loading, data })}
</> </>
); );
} }

View File

@ -18,7 +18,7 @@ import { profiler } from 'app/core/profiler';
// Types // Types
import { DashboardModel, PanelModel } from '../state'; import { DashboardModel, PanelModel } from '../state';
import { PanelPlugin } from 'app/types'; import { PanelPlugin } from 'app/types';
import { DataQueryResponse, TimeRange, LoadingState, PanelData, DataQueryError } from '@grafana/ui'; import { DataQueryResponse, TimeRange, LoadingState, TableData, DataQueryError } from '@grafana/ui';
import { ScopedVars } from '@grafana/ui'; import { ScopedVars } from '@grafana/ui';
import variables from 'sass/_variables.generated.scss'; import variables from 'sass/_variables.generated.scss';
@ -142,7 +142,7 @@ export class PanelChrome extends PureComponent<Props, State> {
return this.hasPanelSnapshot ? snapshotDataToPanelData(this.props.panel) : null; return this.hasPanelSnapshot ? snapshotDataToPanelData(this.props.panel) : null;
} }
renderPanelPlugin(loading: LoadingState, panelData: PanelData, width: number, height: number): JSX.Element { renderPanelPlugin(loading: LoadingState, data: TableData[], width: number, height: number): JSX.Element {
const { panel, plugin } = this.props; const { panel, plugin } = this.props;
const { timeRange, renderCounter } = this.state; const { timeRange, renderCounter } = this.state;
const PanelComponent = plugin.exports.reactPanel.panel; const PanelComponent = plugin.exports.reactPanel.panel;
@ -157,7 +157,7 @@ export class PanelChrome extends PureComponent<Props, State> {
<div className="panel-content"> <div className="panel-content">
<PanelComponent <PanelComponent
loading={loading} loading={loading}
panelData={panelData} data={data}
timeRange={timeRange} timeRange={timeRange}
options={panel.getOptions(plugin.exports.reactPanel.defaults)} options={panel.getOptions(plugin.exports.reactPanel.defaults)}
width={width - 2 * variables.panelhorizontalpadding} width={width - 2 * variables.panelhorizontalpadding}
@ -188,8 +188,8 @@ export class PanelChrome extends PureComponent<Props, State> {
onDataResponse={this.onDataResponse} onDataResponse={this.onDataResponse}
onError={this.onDataError} onError={this.onDataError}
> >
{({ loading, panelData }) => { {({ loading, data }) => {
return this.renderPanelPlugin(loading, panelData, width, height); return this.renderPanelPlugin(loading, data, width, height);
}} }}
</DataPanel> </DataPanel>
) : ( ) : (

View File

@ -4,8 +4,7 @@ import store from 'app/core/store';
// Models // Models
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
import { PanelModel } from 'app/features/dashboard/state/PanelModel'; import { PanelModel } from 'app/features/dashboard/state/PanelModel';
import { PanelData, TimeRange, TimeSeries } from '@grafana/ui'; import { TableData, TimeRange, TimeSeries } from '@grafana/ui';
import { TableData } from '@grafana/ui/src';
// Utils // Utils
import { isString as _isString } from 'lodash'; import { isString as _isString } from 'lodash';
@ -173,16 +172,37 @@ export function getResolution(panel: PanelModel): number {
const isTimeSeries = (data: any): data is TimeSeries => data && data.hasOwnProperty('datapoints'); const isTimeSeries = (data: any): data is TimeSeries => data && data.hasOwnProperty('datapoints');
const isTableData = (data: any): data is TableData => data && data.hasOwnProperty('columns'); const isTableData = (data: any): data is TableData => data && data.hasOwnProperty('columns');
export const snapshotDataToPanelData = (panel: PanelModel): PanelData => { export const snapshotDataToPanelData = (panel: PanelModel): TableData[] => {
const snapshotData = panel.snapshotData; return toTableData(panel.snapshotData);
if (isTimeSeries(snapshotData[0])) { };
return {
timeSeries: snapshotData, export const toTableData = (results: any[]): TableData[] => {
} as PanelData; if (!results) {
} else if (isTableData(snapshotData[0])) { return [];
return { }
tableData: snapshotData[0], return results.map(data => {
} as PanelData; if (isTableData(data)) {
} return data as TableData;
throw new Error('snapshotData is invalid:' + snapshotData.toString()); }
if (isTimeSeries(data)) {
const ts = data as TimeSeries;
return {
type: 'timeseries',
columns: [
{
text: ts.target,
unit: ts.unit,
type: 'number', // Is this really true?
},
{
text: 'time',
type: 'time',
},
],
rows: ts.datapoints,
} as TableData;
}
console.warn('Can not convert', data);
throw new Error('Unsupported data format');
});
}; };

View File

@ -22,29 +22,39 @@ export class GaugePanel extends Component<Props, State> {
this.state = { this.state = {
value: this.findValue(props), value: this.findValue(props),
}; };
console.log('CONSTRUCTOR!', this.props.data);
} }
componentDidUpdate(prevProps: Props) { componentDidUpdate(prevProps: Props) {
if (this.props.panelData !== prevProps.panelData) { console.log('UPDATE', this.props.data);
if (this.props.data !== prevProps.data) {
this.setState({ value: this.findValue(this.props) }); this.setState({ value: this.findValue(this.props) });
} }
} }
findValue(props: Props): number | null { findValue(props: Props): number | null {
const { panelData, options } = props; const { data, options } = props;
const { valueOptions } = options; const { valueOptions } = options;
if (panelData.timeSeries) { console.log('FIND VALUE', data);
if (data) {
// For now, assume timeseries defaults
const xColumn = 1; // time
const yColumn = 0; // value
const vmSeries = processTimeSeries({ const vmSeries = processTimeSeries({
timeSeries: panelData.timeSeries, data,
xColumn,
yColumn,
nullValueMode: NullValueMode.Null, nullValueMode: NullValueMode.Null,
}); });
console.log('GOT', vmSeries);
if (vmSeries[0]) { if (vmSeries[0]) {
return vmSeries[0].stats[valueOptions.stat]; return vmSeries[0].stats[valueOptions.stat];
} }
} else if (panelData.tableData) {
return panelData.tableData.rows[0].find(prop => prop > 0);
} }
return null; return null;
} }

View File

@ -16,13 +16,18 @@ interface Props extends PanelProps<Options> {}
export class GraphPanel extends PureComponent<Props> { export class GraphPanel extends PureComponent<Props> {
render() { render() {
const { panelData, timeRange, width, height } = this.props; const { data, timeRange, width, height } = this.props;
const { showLines, showBars, showPoints } = this.props.options; const { showLines, showBars, showPoints } = this.props.options;
let vmSeries: TimeSeriesVMs; let vmSeries: TimeSeriesVMs;
if (panelData.timeSeries) { if (data) {
// For now, assume timeseries defaults
const xColumn = 1; // time
const yColumn = 0; // value
vmSeries = processTimeSeries({ vmSeries = processTimeSeries({
timeSeries: panelData.timeSeries, data,
xColumn,
yColumn,
nullValueMode: NullValueMode.Ignore, nullValueMode: NullValueMode.Ignore,
}); });
} }