mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge pull request #15864 from ryantxu/all-data-as-table
Use TableData for all data in react
This commit is contained in:
@@ -40,8 +40,6 @@ export function makeDummyTable(columnCount: number, rowCount: number): TableData
|
||||
const suffix = (rowId + 1).toString();
|
||||
return Array.from(new Array(columnCount), (x, colId) => columnIndexToLeter(colId) + suffix);
|
||||
}),
|
||||
type: 'table',
|
||||
columnMap: {},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -51,27 +51,13 @@ export enum NullValueMode {
|
||||
export type TimeSeriesVMs = TimeSeriesVM[];
|
||||
|
||||
export interface Column {
|
||||
text: string;
|
||||
title?: string;
|
||||
type?: string;
|
||||
sort?: boolean;
|
||||
desc?: boolean;
|
||||
filterable?: boolean;
|
||||
text: string; // The column name
|
||||
type?: 'time' | 'number' | 'string' | 'object'; // not used anywhere? can we remove?
|
||||
filterable?: boolean; // currently only set by elasticsearch, and used in the table panel
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
export interface TableData {
|
||||
columns: Column[];
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { ComponentClass } from 'react';
|
||||
import { TimeSeries, LoadingState, TableData } from './data';
|
||||
import { LoadingState, TableData } from './data';
|
||||
import { TimeRange } from './time';
|
||||
import { ScopedVars } from './datasource';
|
||||
|
||||
export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string;
|
||||
|
||||
export interface PanelProps<T = any> {
|
||||
panelData: PanelData;
|
||||
data?: TableData[];
|
||||
timeRange: TimeRange;
|
||||
loading: LoadingState;
|
||||
options: T;
|
||||
@@ -16,11 +16,6 @@ export interface PanelProps<T = any> {
|
||||
replaceVariables: InterpolateFunction;
|
||||
}
|
||||
|
||||
export interface PanelData {
|
||||
timeSeries?: TimeSeries[];
|
||||
tableData?: TableData;
|
||||
}
|
||||
|
||||
export interface PanelEditorProps<T = any> {
|
||||
options: T;
|
||||
onOptionsChange: (options: T) => void;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
exports[`processTableData basic processing should generate a header and fix widths 1`] = `
|
||||
Object {
|
||||
"columnMap": Object {},
|
||||
"columns": Array [
|
||||
Object {
|
||||
"text": "Column 1",
|
||||
@@ -31,13 +30,11 @@ Object {
|
||||
null,
|
||||
],
|
||||
],
|
||||
"type": "table",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`processTableData basic processing should read header and two rows 1`] = `
|
||||
Object {
|
||||
"columnMap": Object {},
|
||||
"columns": Array [
|
||||
Object {
|
||||
"text": "a",
|
||||
@@ -61,6 +58,5 @@ Object {
|
||||
6,
|
||||
],
|
||||
],
|
||||
"type": "table",
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export * from './processTimeSeries';
|
||||
export * from './singlestat';
|
||||
export * from './processTableData';
|
||||
export * from './valueFormats/valueFormats';
|
||||
export * from './colors';
|
||||
export * from './namedColorsPalette';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { parseCSV } from './processTableData';
|
||||
import { parseCSV, toTableData } from './processTableData';
|
||||
|
||||
describe('processTableData', () => {
|
||||
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([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import isNumber from 'lodash/isNumber';
|
||||
import Papa, { ParseError, ParseMeta } from 'papaparse';
|
||||
|
||||
// Types
|
||||
import { TableData, Column } from '../types';
|
||||
import { TableData, Column, TimeSeries } from '../types';
|
||||
|
||||
// Subset of all parse options
|
||||
export interface TableParseOptions {
|
||||
@@ -70,8 +70,6 @@ export function matchRowSizes(table: TableData): TableData {
|
||||
return {
|
||||
columns,
|
||||
rows: fixedRows,
|
||||
type: table.type,
|
||||
columnMap: table.columnMap,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -118,8 +116,6 @@ export function parseCSV(text: string, options?: TableParseOptions, details?: Ta
|
||||
return {
|
||||
columns: [],
|
||||
rows: [],
|
||||
type: 'table',
|
||||
columnMap: {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -130,11 +126,48 @@ export function parseCSV(text: string, options?: TableParseOptions, details?: Ta
|
||||
return matchRowSizes({
|
||||
columns: makeColumns(header),
|
||||
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 {
|
||||
if (isNumber(sortIndex)) {
|
||||
const copy = {
|
||||
|
||||
@@ -4,17 +4,36 @@ import isNumber from 'lodash/isNumber';
|
||||
import { colors } from './colors';
|
||||
|
||||
// Types
|
||||
import { TimeSeries, TimeSeriesVMs, NullValueMode, TimeSeriesValue } from '../types';
|
||||
import { TimeSeriesVMs, NullValueMode, TimeSeriesValue, TableData } from '../types';
|
||||
|
||||
interface Options {
|
||||
timeSeries: TimeSeries[];
|
||||
data: TableData[];
|
||||
xColumn?: number; // Time (or null to guess)
|
||||
yColumn?: number; // Value (or null to guess)
|
||||
nullValueMode: NullValueMode;
|
||||
}
|
||||
|
||||
export function processTimeSeries({ timeSeries, nullValueMode }: Options): TimeSeriesVMs {
|
||||
const vmSeries = timeSeries.map((item, index) => {
|
||||
// NOTE: this should move to processTableData.ts
|
||||
// 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 label = item.target;
|
||||
const label = item.columns[yColumn].text;
|
||||
const result = [];
|
||||
|
||||
// stat defaults
|
||||
@@ -42,9 +61,9 @@ export function processTimeSeries({ timeSeries, nullValueMode }: Options): TimeS
|
||||
let previousValue = 0;
|
||||
let previousDeltaUp = true;
|
||||
|
||||
for (let i = 0; i < item.datapoints.length; i++) {
|
||||
currentValue = item.datapoints[i][0];
|
||||
currentTime = item.datapoints[i][1];
|
||||
for (let i = 0; i < item.rows.length; i++) {
|
||||
currentValue = item.rows[i][yColumn];
|
||||
currentTime = item.rows[i][xColumn];
|
||||
|
||||
if (typeof currentTime !== 'number') {
|
||||
continue;
|
||||
@@ -95,7 +114,7 @@ export function processTimeSeries({ timeSeries, nullValueMode }: Options): TimeS
|
||||
if (previousValue > currentValue) {
|
||||
// counter reset
|
||||
previousDeltaUp = false;
|
||||
if (i === item.datapoints.length - 1) {
|
||||
if (i === item.rows.length - 1) {
|
||||
// reset on last
|
||||
delta += currentValue;
|
||||
}
|
||||
|
||||
@@ -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 [];
|
||||
}
|
||||
Reference in New Issue
Block a user