Merge pull request #15864 from ryantxu/all-data-as-table

Use TableData for all data in react
This commit is contained in:
Torkel Ödegaard 2019-03-20 09:15:06 +01:00 committed by GitHub
commit abd8948478
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 165 additions and 158 deletions

View File

@ -40,8 +40,6 @@ export function makeDummyTable(columnCount: number, rowCount: number): TableData
const suffix = (rowId + 1).toString(); const suffix = (rowId + 1).toString();
return Array.from(new Array(columnCount), (x, colId) => columnIndexToLeter(colId) + suffix); return Array.from(new Array(columnCount), (x, colId) => columnIndexToLeter(colId) + suffix);
}), }),
type: 'table',
columnMap: {},
}; };
} }

View File

@ -51,27 +51,13 @@ export enum NullValueMode {
export type TimeSeriesVMs = TimeSeriesVM[]; export type TimeSeriesVMs = TimeSeriesVM[];
export interface Column { export interface Column {
text: string; text: string; // The column name
title?: string; type?: 'time' | 'number' | 'string' | 'object'; // not used anywhere? can we remove?
type?: string; filterable?: boolean; // currently only set by elasticsearch, and used in the table panel
sort?: boolean;
desc?: boolean;
filterable?: boolean;
unit?: string; unit?: string;
} }
export interface TableData { export interface TableData {
columns: Column[]; columns: Column[];
rows: any[]; rows: any[];
type: string;
columnMap: any;
}
export type SingleStatValue = number | string | null;
/*
* So we can add meta info like tags & series name
*/
export interface SingleStatValueInfo {
value: SingleStatValue;
} }

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

@ -2,7 +2,6 @@
exports[`processTableData basic processing should generate a header and fix widths 1`] = ` exports[`processTableData basic processing should generate a header and fix widths 1`] = `
Object { Object {
"columnMap": Object {},
"columns": Array [ "columns": Array [
Object { Object {
"text": "Column 1", "text": "Column 1",
@ -31,13 +30,11 @@ Object {
null, null,
], ],
], ],
"type": "table",
} }
`; `;
exports[`processTableData basic processing should read header and two rows 1`] = ` exports[`processTableData basic processing should read header and two rows 1`] = `
Object { Object {
"columnMap": Object {},
"columns": Array [ "columns": Array [
Object { Object {
"text": "a", "text": "a",
@ -61,6 +58,5 @@ Object {
6, 6,
], ],
], ],
"type": "table",
} }
`; `;

View File

@ -1,5 +1,5 @@
export * from './processTimeSeries'; export * from './processTimeSeries';
export * from './singlestat'; export * from './processTableData';
export * from './valueFormats/valueFormats'; export * from './valueFormats/valueFormats';
export * from './colors'; export * from './colors';
export * from './namedColorsPalette'; export * from './namedColorsPalette';

View File

@ -1,4 +1,4 @@
import { parseCSV } from './processTableData'; import { parseCSV, toTableData } from './processTableData';
describe('processTableData', () => { describe('processTableData', () => {
describe('basic processing', () => { describe('basic processing', () => {
@ -18,3 +18,41 @@ describe('processTableData', () => {
}); });
}); });
}); });
describe('toTableData', () => {
it('converts timeseries to table skipping nulls', () => {
const input1 = {
target: 'Field Name',
datapoints: [[100, 1], [200, 2]],
};
const input2 = {
// without target
target: '',
datapoints: [[100, 1], [200, 2]],
};
const data = toTableData([null, input1, input2, null, null]);
expect(data.length).toBe(2);
expect(data[0].columns[0].text).toBe(input1.target);
expect(data[0].rows).toBe(input1.datapoints);
// Default name
expect(data[1].columns[0].text).toEqual('Value');
});
it('keeps tableData unchanged', () => {
const input = {
columns: [{ text: 'A' }, { text: 'B' }, { text: 'C' }],
rows: [[100, 'A', 1], [200, 'B', 2], [300, 'C', 3]],
};
const data = toTableData([null, input, null, null]);
expect(data.length).toBe(1);
expect(data[0]).toBe(input);
});
it('supports null values OK', () => {
expect(toTableData([null, null, null, null])).toEqual([]);
expect(toTableData(undefined)).toEqual([]);
expect(toTableData((null as unknown) as any[])).toEqual([]);
expect(toTableData([])).toEqual([]);
});
});

View File

@ -3,7 +3,7 @@ import isNumber from 'lodash/isNumber';
import Papa, { ParseError, ParseMeta } from 'papaparse'; import Papa, { ParseError, ParseMeta } from 'papaparse';
// Types // Types
import { TableData, Column } from '../types'; import { TableData, Column, TimeSeries } from '../types';
// Subset of all parse options // Subset of all parse options
export interface TableParseOptions { export interface TableParseOptions {
@ -70,8 +70,6 @@ export function matchRowSizes(table: TableData): TableData {
return { return {
columns, columns,
rows: fixedRows, rows: fixedRows,
type: table.type,
columnMap: table.columnMap,
}; };
} }
@ -118,8 +116,6 @@ export function parseCSV(text: string, options?: TableParseOptions, details?: Ta
return { return {
columns: [], columns: [],
rows: [], rows: [],
type: 'table',
columnMap: {},
}; };
} }
@ -130,11 +126,48 @@ export function parseCSV(text: string, options?: TableParseOptions, details?: Ta
return matchRowSizes({ return matchRowSizes({
columns: makeColumns(header), columns: makeColumns(header),
rows: results.data, rows: results.data,
type: 'table',
columnMap: {},
}); });
} }
function convertTimeSeriesToTableData(timeSeries: TimeSeries): TableData {
return {
columns: [
{
text: timeSeries.target || 'Value',
unit: timeSeries.unit,
},
{
text: 'Time',
type: 'time',
unit: 'dateTimeAsIso',
},
],
rows: timeSeries.datapoints,
};
}
export const isTableData = (data: any): data is TableData => data && data.hasOwnProperty('columns');
export const toTableData = (results?: any[]): TableData[] => {
if (!results) {
return [];
}
return results
.filter(d => !!d)
.map(data => {
if (data.hasOwnProperty('columns')) {
return data as TableData;
}
if (data.hasOwnProperty('datapoints')) {
return convertTimeSeriesToTableData(data);
}
// TODO, try to convert JSON to table?
console.warn('Can not convert', data);
throw new Error('Unsupported data format');
});
};
export function sortTableData(data: TableData, sortIndex?: number, reverse = false): TableData { export function sortTableData(data: TableData, sortIndex?: number, reverse = false): TableData {
if (isNumber(sortIndex)) { if (isNumber(sortIndex)) {
const copy = { const copy = {

View File

@ -4,17 +4,36 @@ 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 (or null to guess)
yColumn?: number; // Value (or null to guess)
nullValueMode: NullValueMode; nullValueMode: NullValueMode;
} }
export function processTimeSeries({ timeSeries, nullValueMode }: Options): TimeSeriesVMs { // NOTE: this should move to processTableData.ts
const vmSeries = timeSeries.map((item, index) => { // I left it as is so the merge changes are more clear.
export function processTimeSeries({ data, xColumn, yColumn, nullValueMode }: Options): TimeSeriesVMs {
const vmSeries = data.map((item, index) => {
if (!isNumber(xColumn)) {
xColumn = 1; // Default timeseries colum. TODO, find first time field!
}
if (!isNumber(yColumn)) {
yColumn = 0; // TODO, find first non-time field
}
// TODO? either % or throw error?
if (xColumn >= item.columns.length) {
throw new Error('invalid colum: ' + xColumn);
}
if (yColumn >= item.columns.length) {
throw new Error('invalid colum: ' + yColumn);
}
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 +61,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 +114,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,33 +0,0 @@
import { PanelData, NullValueMode, SingleStatValueInfo } from '../types';
import { processTimeSeries } from './processTimeSeries';
export interface SingleStatProcessingOptions {
panelData: PanelData;
stat: string;
}
//
// This is a temporary thing, waiting for a better data model and maybe unification between time series & table data
//
export function processSingleStatPanelData(options: SingleStatProcessingOptions): SingleStatValueInfo[] {
const { panelData, stat } = options;
if (panelData.timeSeries) {
const timeSeries = processTimeSeries({
timeSeries: panelData.timeSeries,
nullValueMode: NullValueMode.Null,
});
return timeSeries.map((series, index) => {
const value = stat !== 'name' ? series.stats[stat] : series.label;
return {
value: value,
};
});
} else if (panelData.tableData) {
throw { message: 'Panel data not supported' };
}
return [];
}

View File

@ -1,17 +1,18 @@
import _ from 'lodash'; import _ from 'lodash';
import { Column, TableData } from '@grafana/ui';
interface Column { /**
text: string; * Extends the standard Column class with variables that get
* mutated in the angular table panel.
*/
interface MutableColumn 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: MutableColumn[];
rows: any[]; rows: any[];
type: string; type: string;
columnMap: any; columnMap: any;

View File

@ -11,16 +11,15 @@ import {
DataQueryResponse, DataQueryResponse,
DataQueryError, DataQueryError,
LoadingState, LoadingState,
PanelData,
TableData, TableData,
TimeRange, TimeRange,
TimeSeries,
ScopedVars, ScopedVars,
toTableData,
} from '@grafana/ui'; } from '@grafana/ui';
interface RenderProps { interface RenderProps {
loading: LoadingState; loading: LoadingState;
panelData: PanelData; data: TableData[];
} }
export interface Props { export interface Props {
@ -44,7 +43,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 +63,6 @@ export class DataPanel extends Component<Props, State> {
response: { response: {
data: [], data: [],
}, },
panelData: {},
isFirstLoad: true, isFirstLoad: true,
}; };
} }
@ -149,7 +147,7 @@ export class DataPanel extends Component<Props, State> {
this.setState({ this.setState({
loading: LoadingState.Done, loading: LoadingState.Done,
response: resp, response: resp,
panelData: this.getPanelData(resp), data: toTableData(resp.data),
isFirstLoad: false, isFirstLoad: false,
}); });
} catch (err) { } catch (err) {
@ -172,23 +170,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)) {
@ -206,7 +190,7 @@ export class DataPanel extends Component<Props, State> {
return ( return (
<> <>
{loading === LoadingState.Loading && this.renderLoadingState()} {loading === LoadingState.Loading && this.renderLoadingState()}
{this.props.children({ loading, panelData })} {this.props.children({ loading, data })}
</> </>
); );
} }

View File

@ -11,7 +11,7 @@ import { DataPanel } from './DataPanel';
import ErrorBoundary from '../../../core/components/ErrorBoundary/ErrorBoundary'; import ErrorBoundary from '../../../core/components/ErrorBoundary/ErrorBoundary';
// Utils // Utils
import { applyPanelTimeOverrides, snapshotDataToPanelData } from 'app/features/dashboard/utils/panel'; import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel';
import { PANEL_HEADER_HEIGHT } from 'app/core/constants'; import { PANEL_HEADER_HEIGHT } from 'app/core/constants';
import { profiler } from 'app/core/profiler'; import { profiler } from 'app/core/profiler';
import config from 'app/core/config'; import config from 'app/core/config';
@ -19,7 +19,7 @@ import config from 'app/core/config';
// 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, toTableData } from '@grafana/ui';
import { ScopedVars } from '@grafana/ui'; import { ScopedVars } from '@grafana/ui';
import templateSrv from 'app/features/templating/template_srv'; import templateSrv from 'app/features/templating/template_srv';
@ -139,10 +139,10 @@ export class PanelChrome extends PureComponent<Props, State> {
} }
get getDataForPanel() { get getDataForPanel() {
return this.hasPanelSnapshot ? snapshotDataToPanelData(this.props.panel) : null; return this.hasPanelSnapshot ? toTableData(this.props.panel.snapshotData) : 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 * config.theme.panelPadding.horizontal} width={width - 2 * config.theme.panelPadding.horizontal}
@ -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 { TimeRange } from '@grafana/ui';
import { TableData } from '@grafana/ui/src';
// Utils // Utils
import { isString as _isString } from 'lodash'; import { isString as _isString } from 'lodash';
@ -170,19 +169,3 @@ export function getResolution(panel: PanelModel): number {
return panel.maxDataPoints ? panel.maxDataPoints : Math.ceil(width * (panel.gridPos.w / 24)); return panel.maxDataPoints ? panel.maxDataPoints : Math.ceil(width * (panel.gridPos.w / 24));
} }
const isTimeSeries = (data: any): data is TimeSeries => data && data.hasOwnProperty('datapoints');
const isTableData = (data: any): data is TableData => data && data.hasOwnProperty('columns');
export const snapshotDataToPanelData = (panel: PanelModel): PanelData => {
const snapshotData = panel.snapshotData;
if (isTimeSeries(snapshotData[0])) {
return {
timeSeries: snapshotData,
} as PanelData;
} else if (isTableData(snapshotData[0])) {
return {
tableData: snapshotData[0],
} as PanelData;
}
throw new Error('snapshotData is invalid:' + snapshotData.toString());
};

View File

@ -32,14 +32,14 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
}; };
render() { render() {
const { height, width, options, panelData, renderCounter } = this.props; const { height, width, options, data, renderCounter } = this.props;
return ( return (
<ProcessedValuesRepeater <ProcessedValuesRepeater
getProcessedValues={this.getProcessedValues} getProcessedValues={this.getProcessedValues}
renderValue={this.renderValue} renderValue={this.renderValue}
width={width} width={width}
height={height} height={height}
source={panelData} source={data}
renderCounter={renderCounter} renderCounter={renderCounter}
orientation={options.orientation} orientation={options.orientation}
/> />

View File

@ -37,14 +37,14 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
}; };
render() { render() {
const { height, width, options, panelData, renderCounter } = this.props; const { height, width, options, data, renderCounter } = this.props;
return ( return (
<ProcessedValuesRepeater <ProcessedValuesRepeater
getProcessedValues={this.getProcessedValues} getProcessedValues={this.getProcessedValues}
renderValue={this.renderValue} renderValue={this.renderValue}
width={width} width={width}
height={height} height={height}
source={panelData} source={data}
renderCounter={renderCounter} renderCounter={renderCounter}
orientation={options.orientation} orientation={options.orientation}
/> />

View File

@ -16,13 +16,13 @@ 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) {
vmSeries = processTimeSeries({ vmSeries = processTimeSeries({
timeSeries: panelData.timeSeries, data,
nullValueMode: NullValueMode.Ignore, nullValueMode: NullValueMode.Ignore,
}); });
} }

View File

@ -8,7 +8,7 @@ import kbn from 'app/core/utils/kbn';
import config from 'app/core/config'; import config from 'app/core/config';
import TimeSeries from 'app/core/time_series2'; import TimeSeries from 'app/core/time_series2';
import { MetricsPanelCtrl } from 'app/plugins/sdk'; import { MetricsPanelCtrl } from 'app/plugins/sdk';
import { GrafanaThemeType, getValueFormat, getColorFromHexRgbOrName } from '@grafana/ui'; import { GrafanaThemeType, getValueFormat, getColorFromHexRgbOrName, isTableData } from '@grafana/ui';
class SingleStatCtrl extends MetricsPanelCtrl { class SingleStatCtrl extends MetricsPanelCtrl {
static templateUrl = 'module.html'; static templateUrl = 'module.html';
@ -112,7 +112,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
scopedVars: _.extend({}, this.panel.scopedVars), scopedVars: _.extend({}, this.panel.scopedVars),
}; };
if (dataList.length > 0 && dataList[0].type === 'table') { if (dataList.length > 0 && isTableData(dataList[0])) {
this.dataType = 'table'; this.dataType = 'table';
const tableData = dataList.map(this.tableHandler.bind(this)); const tableData = dataList.map(this.tableHandler.bind(this));
this.setTableValues(tableData, data); this.setTableValues(tableData, data);

View File

@ -4,27 +4,33 @@ import React, { PureComponent, CSSProperties } from 'react';
// Types // Types
import { SingleStatOptions, SingleStatBaseOptions } from './types'; import { SingleStatOptions, SingleStatBaseOptions } from './types';
import { processSingleStatPanelData, DisplayValue, PanelProps } from '@grafana/ui'; import { DisplayValue, PanelProps, processTimeSeries, NullValueMode } from '@grafana/ui';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
import { getDisplayProcessor } from '@grafana/ui'; import { getDisplayProcessor } from '@grafana/ui';
import { ProcessedValuesRepeater } from './ProcessedValuesRepeater'; import { ProcessedValuesRepeater } from './ProcessedValuesRepeater';
export const getSingleStatValues = (props: PanelProps<SingleStatBaseOptions>): DisplayValue[] => { export const getSingleStatValues = (props: PanelProps<SingleStatBaseOptions>): DisplayValue[] => {
const { panelData, replaceVariables, options } = props; const { data, replaceVariables, options } = props;
const { valueOptions, valueMappings } = options; const { valueOptions, valueMappings } = options;
const { unit, decimals, stat } = valueOptions;
const processor = getDisplayProcessor({ const processor = getDisplayProcessor({
unit: valueOptions.unit, unit,
decimals: valueOptions.decimals, decimals,
mappings: valueMappings, mappings: valueMappings,
thresholds: options.thresholds, thresholds: options.thresholds,
prefix: replaceVariables(valueOptions.prefix), prefix: replaceVariables(valueOptions.prefix),
suffix: replaceVariables(valueOptions.suffix), suffix: replaceVariables(valueOptions.suffix),
theme: config.theme, theme: config.theme,
}); });
return processSingleStatPanelData({
panelData: panelData, return processTimeSeries({
stat: valueOptions.stat, data,
}).map(stat => processor(stat.value)); nullValueMode: NullValueMode.Null,
}).map((series, index) => {
const value = stat !== 'name' ? series.stats[stat] : series.label;
return processor(value);
});
}; };
export class SingleStatPanel extends PureComponent<PanelProps<SingleStatOptions>> { export class SingleStatPanel extends PureComponent<PanelProps<SingleStatOptions>> {
@ -49,14 +55,14 @@ export class SingleStatPanel extends PureComponent<PanelProps<SingleStatOptions>
}; };
render() { render() {
const { height, width, options, panelData, renderCounter } = this.props; const { height, width, options, data, renderCounter } = this.props;
return ( return (
<ProcessedValuesRepeater <ProcessedValuesRepeater
getProcessedValues={this.getProcessedValues} getProcessedValues={this.getProcessedValues}
renderValue={this.renderValue} renderValue={this.renderValue}
width={width} width={width}
height={height} height={height}
source={panelData} source={data}
renderCounter={renderCounter} renderCounter={renderCounter}
orientation={options.orientation} orientation={options.orientation}
/> />

View File

@ -6,6 +6,7 @@ import { transformDataToTable } from './transformers';
import { tablePanelEditor } from './editor'; import { tablePanelEditor } from './editor';
import { columnOptionsTab } from './column_options'; import { columnOptionsTab } from './column_options';
import { TableRenderer } from './renderer'; import { TableRenderer } from './renderer';
import { isTableData } from '@grafana/ui';
class TablePanelCtrl extends MetricsPanelCtrl { class TablePanelCtrl extends MetricsPanelCtrl {
static templateUrl = 'module.html'; static templateUrl = 'module.html';
@ -104,7 +105,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
// automatically correct transform mode based on data // automatically correct transform mode based on data
if (this.dataRaw && this.dataRaw.length) { if (this.dataRaw && this.dataRaw.length) {
if (this.dataRaw[0].type === 'table') { if (isTableData(this.dataRaw[0])) {
this.panel.transform = 'table'; this.panel.transform = 'table';
} else { } else {
if (this.dataRaw[0].type === 'docs') { if (this.dataRaw[0].type === 'docs') {

View File

@ -14,15 +14,15 @@ export class TablePanel extends Component<Props> {
} }
render() { render() {
const { panelData, options } = this.props; const { data, options } = this.props;
if (!panelData || !panelData.tableData) { if (data.length < 1) {
return <div>No Table Data...</div>; return <div>No Table Data...</div>;
} }
return ( return (
<ThemeContext.Consumer> <ThemeContext.Consumer>
{theme => <Table {...this.props} {...options} theme={theme} data={panelData.tableData} />} {theme => <Table {...this.props} {...options} theme={theme} data={data[0]} />}
</ThemeContext.Consumer> </ThemeContext.Consumer>
); );
} }