mirror of https://github.com/grafana/grafana.git
refactor(data models): Renamed TableData to SeriesData (#16185)
This commit is contained in:
parent
c7d108264d
commit
77b3da3e8b
|
|
@ -4,7 +4,7 @@ import { Table } from './Table';
|
||||||
import { getTheme } from '../../themes';
|
import { getTheme } from '../../themes';
|
||||||
|
|
||||||
import { migratedTestTable, migratedTestStyles, simpleTable } from './examples';
|
import { migratedTestTable, migratedTestStyles, simpleTable } from './examples';
|
||||||
import { ScopedVars, TableData, GrafanaThemeType } from '../../types/index';
|
import { ScopedVars, SeriesData, GrafanaThemeType } from '../../types/index';
|
||||||
import { withFullSizeStory } from '../../utils/storybook/withFullSizeStory';
|
import { withFullSizeStory } from '../../utils/storybook/withFullSizeStory';
|
||||||
import { number, boolean } from '@storybook/addon-knobs';
|
import { number, boolean } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
|
@ -29,11 +29,11 @@ export function columnIndexToLeter(column: number) {
|
||||||
return String.fromCharCode(A + c2);
|
return String.fromCharCode(A + c2);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeDummyTable(columnCount: number, rowCount: number): TableData {
|
export function makeDummyTable(columnCount: number, rowCount: number): SeriesData {
|
||||||
return {
|
return {
|
||||||
columns: Array.from(new Array(columnCount), (x, i) => {
|
fields: Array.from(new Array(columnCount), (x, i) => {
|
||||||
return {
|
return {
|
||||||
text: columnIndexToLeter(i),
|
name: columnIndexToLeter(i),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
rows: Array.from(new Array(rowCount), (x, rowId) => {
|
rows: Array.from(new Array(rowCount), (x, rowId) => {
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ import {
|
||||||
} from 'react-virtualized';
|
} from 'react-virtualized';
|
||||||
import { Themeable } from '../../types/theme';
|
import { Themeable } from '../../types/theme';
|
||||||
|
|
||||||
import { sortTableData } from '../../utils/processTableData';
|
import { sortSeriesData } from '../../utils/processTableData';
|
||||||
|
|
||||||
import { TableData, InterpolateFunction } from '@grafana/ui';
|
import { SeriesData, InterpolateFunction } from '@grafana/ui';
|
||||||
import {
|
import {
|
||||||
TableCellBuilder,
|
TableCellBuilder,
|
||||||
ColumnStyle,
|
ColumnStyle,
|
||||||
|
|
@ -25,7 +25,7 @@ import {
|
||||||
import { stringToJsRegex } from '../../utils/index';
|
import { stringToJsRegex } from '../../utils/index';
|
||||||
|
|
||||||
export interface Props extends Themeable {
|
export interface Props extends Themeable {
|
||||||
data: TableData;
|
data: SeriesData;
|
||||||
|
|
||||||
minColumnWidth: number;
|
minColumnWidth: number;
|
||||||
showHeader: boolean;
|
showHeader: boolean;
|
||||||
|
|
@ -43,7 +43,7 @@ export interface Props extends Themeable {
|
||||||
interface State {
|
interface State {
|
||||||
sortBy?: number;
|
sortBy?: number;
|
||||||
sortDirection?: SortDirectionType;
|
sortDirection?: SortDirectionType;
|
||||||
data: TableData;
|
data: SeriesData;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ColumnRenderInfo {
|
interface ColumnRenderInfo {
|
||||||
|
|
@ -108,17 +108,17 @@ export class Table extends Component<Props, State> {
|
||||||
// Update the data when data or sort changes
|
// Update the data when data or sort changes
|
||||||
if (dataChanged || sortBy !== prevState.sortBy || sortDirection !== prevState.sortDirection) {
|
if (dataChanged || sortBy !== prevState.sortBy || sortDirection !== prevState.sortDirection) {
|
||||||
this.scrollToTop = true;
|
this.scrollToTop = true;
|
||||||
this.setState({ data: sortTableData(data, sortBy, sortDirection === 'DESC') });
|
this.setState({ data: sortSeriesData(data, sortBy, sortDirection === 'DESC') });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Given the configuration, setup how each column gets rendered */
|
/** Given the configuration, setup how each column gets rendered */
|
||||||
initColumns(props: Props): ColumnRenderInfo[] {
|
initColumns(props: Props): ColumnRenderInfo[] {
|
||||||
const { styles, data, width, minColumnWidth } = props;
|
const { styles, data, width, minColumnWidth } = props;
|
||||||
const columnWidth = Math.max(width / data.columns.length, minColumnWidth);
|
const columnWidth = Math.max(width / data.fields.length, minColumnWidth);
|
||||||
|
|
||||||
return data.columns.map((col, index) => {
|
return data.fields.map((col, index) => {
|
||||||
let title = col.text;
|
let title = col.name;
|
||||||
let style: ColumnStyle | null = null; // ColumnStyle
|
let style: ColumnStyle | null = null; // ColumnStyle
|
||||||
|
|
||||||
// Find the style based on the text
|
// Find the style based on the text
|
||||||
|
|
@ -159,7 +159,7 @@ export class Table extends Component<Props, State> {
|
||||||
this.setState({ sortBy: sort, sortDirection: dir });
|
this.setState({ sortBy: sort, sortDirection: dir });
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Converts the grid coordinates to TableData coordinates */
|
/** Converts the grid coordinates to SeriesData coordinates */
|
||||||
getCellRef = (rowIndex: number, columnIndex: number): DataIndex => {
|
getCellRef = (rowIndex: number, columnIndex: number): DataIndex => {
|
||||||
const { showHeader, rotate } = this.props;
|
const { showHeader, rotate } = this.props;
|
||||||
const rowOffset = showHeader ? -1 : 0;
|
const rowOffset = showHeader ? -1 : 0;
|
||||||
|
|
@ -187,17 +187,17 @@ export class Table extends Component<Props, State> {
|
||||||
const { columnIndex, rowIndex, style } = cell.props;
|
const { columnIndex, rowIndex, style } = cell.props;
|
||||||
const { column } = this.getCellRef(rowIndex, columnIndex);
|
const { column } = this.getCellRef(rowIndex, columnIndex);
|
||||||
|
|
||||||
let col = data.columns[column];
|
let col = data.fields[column];
|
||||||
const sorting = sortBy === column;
|
const sorting = sortBy === column;
|
||||||
if (!col) {
|
if (!col) {
|
||||||
col = {
|
col = {
|
||||||
text: '??' + columnIndex + '???',
|
name: '??' + columnIndex + '???',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="gf-table-header" style={style} onClick={() => this.onCellClick(rowIndex, columnIndex)}>
|
<div className="gf-table-header" style={style} onClick={() => this.onCellClick(rowIndex, columnIndex)}>
|
||||||
{col.text}
|
{col.name}
|
||||||
{sorting && <SortIndicator sortDirection={sortDirection} />}
|
{sorting && <SortIndicator sortDirection={sortDirection} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -217,7 +217,7 @@ export class Table extends Component<Props, State> {
|
||||||
const { data } = this.state;
|
const { data } = this.state;
|
||||||
|
|
||||||
const isHeader = row < 0;
|
const isHeader = row < 0;
|
||||||
const rowData = isHeader ? data.columns : data.rows[row];
|
const rowData = isHeader ? data.fields : data.rows[row];
|
||||||
const value = rowData ? rowData[column] : '';
|
const value = rowData ? rowData[column] : '';
|
||||||
const builder = isHeader ? this.headerBuilder : this.getTableCellBuilder(column);
|
const builder = isHeader ? this.headerBuilder : this.getTableCellBuilder(column);
|
||||||
|
|
||||||
|
|
@ -226,7 +226,7 @@ export class Table extends Component<Props, State> {
|
||||||
{builder({
|
{builder({
|
||||||
value,
|
value,
|
||||||
row: rowData,
|
row: rowData,
|
||||||
column: data.columns[column],
|
column: data.fields[column],
|
||||||
table: this,
|
table: this,
|
||||||
props,
|
props,
|
||||||
})}
|
})}
|
||||||
|
|
@ -242,7 +242,7 @@ export class Table extends Component<Props, State> {
|
||||||
const { showHeader, fixedHeader, fixedColumns, rotate, width, height } = this.props;
|
const { showHeader, fixedHeader, fixedColumns, rotate, width, height } = this.props;
|
||||||
const { data } = this.state;
|
const { data } = this.state;
|
||||||
|
|
||||||
let columnCount = data.columns.length;
|
let columnCount = data.fields.length;
|
||||||
let rowCount = data.rows.length + (showHeader ? 1 : 0);
|
let rowCount = data.rows.length + (showHeader ? 1 : 0);
|
||||||
|
|
||||||
let fixedColumnCount = Math.min(fixedColumns, columnCount);
|
let fixedColumnCount = Math.min(fixedColumns, columnCount);
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,12 @@ import { Table, Props } from './Table';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { ValueFormatter } from '../../utils/index';
|
import { ValueFormatter } from '../../utils/index';
|
||||||
import { GrafanaTheme } from '../../types/theme';
|
import { GrafanaTheme } from '../../types/theme';
|
||||||
import { getValueFormat, getColorFromHexRgbOrName, Column } from '@grafana/ui';
|
import { getValueFormat, getColorFromHexRgbOrName, Field } from '@grafana/ui';
|
||||||
import { InterpolateFunction } from '../../types/panel';
|
import { InterpolateFunction } from '../../types/panel';
|
||||||
|
|
||||||
export interface TableCellBuilderOptions {
|
export interface TableCellBuilderOptions {
|
||||||
value: any;
|
value: any;
|
||||||
column?: Column;
|
column?: Field;
|
||||||
row?: any[];
|
row?: any[];
|
||||||
table?: Table;
|
table?: Table;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
@ -74,7 +74,7 @@ export interface ColumnStyle {
|
||||||
// private replaceVariables: InterpolateFunction,
|
// private replaceVariables: InterpolateFunction,
|
||||||
// private fmt?:ValueFormatter) {
|
// private fmt?:ValueFormatter) {
|
||||||
|
|
||||||
export function getCellBuilder(schema: Column, style: ColumnStyle | null, props: Props): TableCellBuilder {
|
export function getCellBuilder(schema: Field, style: ColumnStyle | null, props: Props): TableCellBuilder {
|
||||||
if (!style) {
|
if (!style) {
|
||||||
return simpleCellBuilder;
|
return simpleCellBuilder;
|
||||||
}
|
}
|
||||||
|
|
@ -154,12 +154,12 @@ class CellBuilderWithStyle {
|
||||||
private mapper: ValueMapper,
|
private mapper: ValueMapper,
|
||||||
private style: ColumnStyle,
|
private style: ColumnStyle,
|
||||||
private theme: GrafanaTheme,
|
private theme: GrafanaTheme,
|
||||||
private column: Column,
|
private column: Field,
|
||||||
private replaceVariables: InterpolateFunction,
|
private replaceVariables: InterpolateFunction,
|
||||||
private fmt?: ValueFormatter
|
private fmt?: ValueFormatter
|
||||||
) {
|
) {
|
||||||
//
|
//
|
||||||
console.log('COLUMN', column.text, theme);
|
console.log('COLUMN', column.name, theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
getColorForValue = (value: any): string | null => {
|
getColorForValue = (value: any): string | null => {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import TableInputCSV from './TableInputCSV';
|
import TableInputCSV from './TableInputCSV';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
import { TableData } from '../../types/data';
|
import { SeriesData } from '../../types/data';
|
||||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||||
|
|
||||||
const TableInputStories = storiesOf('UI/Table/Input', module);
|
const TableInputStories = storiesOf('UI/Table/Input', module);
|
||||||
|
|
@ -15,7 +15,7 @@ TableInputStories.add('default', () => {
|
||||||
<div style={{ width: '90%', height: '90vh' }}>
|
<div style={{ width: '90%', height: '90vh' }}>
|
||||||
<TableInputCSV
|
<TableInputCSV
|
||||||
text={'a,b,c\n1,2,3'}
|
text={'a,b,c\n1,2,3'}
|
||||||
onTableParsed={(table: TableData, text: string) => {
|
onTableParsed={(table: SeriesData, text: string) => {
|
||||||
console.log('Table', table, text);
|
console.log('Table', table, text);
|
||||||
action('Table')(table, text);
|
action('Table')(table, text);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
|
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import TableInputCSV from './TableInputCSV';
|
import TableInputCSV from './TableInputCSV';
|
||||||
import { TableData } from '../../types/data';
|
import { SeriesData } from '../../types/data';
|
||||||
|
|
||||||
describe('TableInputCSV', () => {
|
describe('TableInputCSV', () => {
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
||||||
|
|
@ -10,7 +10,7 @@ describe('TableInputCSV', () => {
|
||||||
.create(
|
.create(
|
||||||
<TableInputCSV
|
<TableInputCSV
|
||||||
text={'a,b,c\n1,2,3'}
|
text={'a,b,c\n1,2,3'}
|
||||||
onTableParsed={(table: TableData, text: string) => {
|
onTableParsed={(table: SeriesData, text: string) => {
|
||||||
// console.log('Table:', table, 'from:', text);
|
// console.log('Table:', table, 'from:', text);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { parseCSV, TableParseOptions, TableParseDetails } from '../../utils/processTableData';
|
import { parseCSV, TableParseOptions, TableParseDetails } from '../../utils/processTableData';
|
||||||
import { TableData } from '../../types/data';
|
import { SeriesData } from '../../types/data';
|
||||||
import { AutoSizer } from 'react-virtualized';
|
import { AutoSizer } from 'react-virtualized';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
options?: TableParseOptions;
|
options?: TableParseOptions;
|
||||||
text: string;
|
text: string;
|
||||||
onTableParsed: (table: TableData, text: string) => void;
|
onTableParsed: (table: SeriesData, text: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
text: string;
|
text: string;
|
||||||
table: TableData;
|
table: SeriesData;
|
||||||
details: TableParseDetails;
|
details: TableParseDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,7 +82,7 @@ class TableInputCSV extends React.PureComponent<Props, State> {
|
||||||
<div className="gf-table-input-csv" style={{ width, height }}>
|
<div className="gf-table-input-csv" style={{ width, height }}>
|
||||||
<textarea placeholder="Enter CSV here..." value={this.state.text} onChange={this.onTextChange} />
|
<textarea placeholder="Enter CSV here..." value={this.state.text} onChange={this.onTextChange} />
|
||||||
<footer onClick={this.onFooterClicked} className={footerClassNames}>
|
<footer onClick={this.onFooterClicked} className={footerClassNames}>
|
||||||
Rows:{table.rows.length}, Columns:{table.columns.length}
|
Rows:{table.rows.length}, Columns:{table.fields.length}
|
||||||
{hasErrors ? <i className="fa fa-exclamation-triangle" /> : <i className="fa fa-check-circle" />}
|
{hasErrors ? <i className="fa fa-exclamation-triangle" /> : <i className="fa fa-check-circle" />}
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { TableData } from '../../types/data';
|
import { SeriesData } from '../../types/data';
|
||||||
import { ColumnStyle } from './TableCellBuilder';
|
import { ColumnStyle } from './TableCellBuilder';
|
||||||
|
|
||||||
import { getColorDefinitionByName } from '@grafana/ui';
|
import { getColorDefinitionByName } from '@grafana/ui';
|
||||||
|
|
@ -7,23 +7,23 @@ const SemiDarkOrange = getColorDefinitionByName('semi-dark-orange');
|
||||||
|
|
||||||
export const migratedTestTable = {
|
export const migratedTestTable = {
|
||||||
type: 'table',
|
type: 'table',
|
||||||
columns: [
|
fields: [
|
||||||
{ text: 'Time' },
|
{ name: 'Time' },
|
||||||
{ text: 'Value' },
|
{ name: 'Value' },
|
||||||
{ text: 'Colored' },
|
{ name: 'Colored' },
|
||||||
{ text: 'Undefined' },
|
{ name: 'Undefined' },
|
||||||
{ text: 'String' },
|
{ name: 'String' },
|
||||||
{ text: 'United', unit: 'bps' },
|
{ name: 'United', unit: 'bps' },
|
||||||
{ text: 'Sanitized' },
|
{ name: 'Sanitized' },
|
||||||
{ text: 'Link' },
|
{ name: 'Link' },
|
||||||
{ text: 'Array' },
|
{ name: 'Array' },
|
||||||
{ text: 'Mapping' },
|
{ name: 'Mapping' },
|
||||||
{ text: 'RangeMapping' },
|
{ name: 'RangeMapping' },
|
||||||
{ text: 'MappingColored' },
|
{ name: 'MappingColored' },
|
||||||
{ text: 'RangeMappingColored' },
|
{ name: 'RangeMappingColored' },
|
||||||
],
|
],
|
||||||
rows: [[1388556366666, 1230, 40, undefined, '', '', 'my.host.com', 'host1', ['value1', 'value2'], 1, 2, 1, 2]],
|
rows: [[1388556366666, 1230, 40, undefined, '', '', 'my.host.com', 'host1', ['value1', 'value2'], 1, 2, 1, 2]],
|
||||||
} as TableData;
|
} as SeriesData;
|
||||||
|
|
||||||
export const migratedTestStyles: ColumnStyle[] = [
|
export const migratedTestStyles: ColumnStyle[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -87,19 +87,19 @@ export const migratedTestStyles: ColumnStyle[] = [
|
||||||
valueMaps: [
|
valueMaps: [
|
||||||
{
|
{
|
||||||
value: '1',
|
value: '1',
|
||||||
text: 'on',
|
name: 'on',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: '0',
|
value: '0',
|
||||||
text: 'off',
|
name: 'off',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'HELLO WORLD',
|
value: 'HELLO WORLD',
|
||||||
text: 'HELLO GRAFANA',
|
name: 'HELLO GRAFANA',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'value1, value2',
|
value: 'value1, value2',
|
||||||
text: 'value3, value4',
|
name: 'value3, value4',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -111,12 +111,12 @@ export const migratedTestStyles: ColumnStyle[] = [
|
||||||
{
|
{
|
||||||
from: '1',
|
from: '1',
|
||||||
to: '3',
|
to: '3',
|
||||||
text: 'on',
|
name: 'on',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: '3',
|
from: '3',
|
||||||
to: '6',
|
to: '6',
|
||||||
text: 'off',
|
name: 'off',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -127,11 +127,11 @@ export const migratedTestStyles: ColumnStyle[] = [
|
||||||
valueMaps: [
|
valueMaps: [
|
||||||
{
|
{
|
||||||
value: '1',
|
value: '1',
|
||||||
text: 'on',
|
name: 'on',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: '0',
|
value: '0',
|
||||||
text: 'off',
|
name: 'off',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
colorMode: 'value',
|
colorMode: 'value',
|
||||||
|
|
@ -146,12 +146,12 @@ export const migratedTestStyles: ColumnStyle[] = [
|
||||||
{
|
{
|
||||||
from: '1',
|
from: '1',
|
||||||
to: '3',
|
to: '3',
|
||||||
text: 'on',
|
name: 'on',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: '3',
|
from: '3',
|
||||||
to: '6',
|
to: '6',
|
||||||
text: 'off',
|
name: 'off',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
colorMode: 'value',
|
colorMode: 'value',
|
||||||
|
|
@ -162,6 +162,6 @@ export const migratedTestStyles: ColumnStyle[] = [
|
||||||
|
|
||||||
export const simpleTable = {
|
export const simpleTable = {
|
||||||
type: 'table',
|
type: 'table',
|
||||||
columns: [{ text: 'First' }, { text: 'Second' }, { text: 'Third' }],
|
columns: [{ name: 'First' }, { name: 'Second' }, { name: 'Third' }],
|
||||||
rows: [[701, 205, 305], [702, 206, 301], [703, 207, 304]],
|
rows: [[701, 205, 305], [702, 206, 301], [703, 207, 304]],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,44 @@ export enum LoadingState {
|
||||||
Error = 'Error',
|
Error = 'Error',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum FieldType {
|
||||||
|
time = 'time', // or date
|
||||||
|
number = 'number',
|
||||||
|
string = 'string',
|
||||||
|
boolean = 'boolean',
|
||||||
|
other = 'other', // Object, Array, etc
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Field {
|
||||||
|
name: string; // The column name
|
||||||
|
type?: FieldType;
|
||||||
|
filterable?: boolean;
|
||||||
|
unit?: string;
|
||||||
|
dateFormat?: string; // Source data format
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Tags {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesData {
|
||||||
|
name?: string;
|
||||||
|
fields: Field[];
|
||||||
|
rows: any[][];
|
||||||
|
tags?: Tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Column {
|
||||||
|
text: string; // For a Column, the 'text' is the field name
|
||||||
|
filterable?: boolean;
|
||||||
|
unit?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableData {
|
||||||
|
columns: Column[];
|
||||||
|
rows: any[][];
|
||||||
|
}
|
||||||
|
|
||||||
export type TimeSeriesValue = number | null;
|
export type TimeSeriesValue = number | null;
|
||||||
|
|
||||||
export type TimeSeriesPoints = TimeSeriesValue[][];
|
export type TimeSeriesPoints = TimeSeriesValue[][];
|
||||||
|
|
@ -33,33 +71,6 @@ export enum NullValueMode {
|
||||||
/** View model projection of many time series */
|
/** View model projection of many time series */
|
||||||
export type TimeSeriesVMs = TimeSeriesVM[];
|
export type TimeSeriesVMs = TimeSeriesVM[];
|
||||||
|
|
||||||
export enum ColumnType {
|
|
||||||
time = 'time', // or date
|
|
||||||
number = 'number',
|
|
||||||
string = 'string',
|
|
||||||
boolean = 'boolean',
|
|
||||||
other = 'other', // Object, Array, etc
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Column {
|
|
||||||
text: string; // The column name
|
|
||||||
type?: ColumnType;
|
|
||||||
filterable?: boolean;
|
|
||||||
unit?: string;
|
|
||||||
dateFormat?: string; // Source data format
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Tags {
|
|
||||||
[key: string]: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TableData {
|
|
||||||
name?: string;
|
|
||||||
columns: Column[];
|
|
||||||
rows: any[][];
|
|
||||||
tags?: Tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AnnotationEvent {
|
export interface AnnotationEvent {
|
||||||
annotation?: any;
|
annotation?: any;
|
||||||
dashboardId?: number;
|
dashboardId?: number;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { ComponentClass } from 'react';
|
import { ComponentClass } from 'react';
|
||||||
import { LoadingState, TableData } from './data';
|
import { LoadingState, SeriesData } 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> {
|
||||||
data?: TableData[];
|
data?: SeriesData[];
|
||||||
timeRange: TimeRange;
|
timeRange: TimeRange;
|
||||||
loading: LoadingState;
|
loading: LoadingState;
|
||||||
options: T;
|
options: T;
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`processTableData basic processing should generate a header and fix widths 1`] = `
|
exports[`processSeriesData basic processing should generate a header and fix widths 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"columns": Array [
|
"fields": Array [
|
||||||
Object {
|
Object {
|
||||||
"text": "Column 1",
|
"name": "Field 1",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"text": "Column 2",
|
"name": "Field 2",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"text": "Column 3",
|
"name": "Field 3",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"rows": Array [
|
"rows": Array [
|
||||||
|
|
@ -33,17 +33,17 @@ Object {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`processTableData basic processing should read header and two rows 1`] = `
|
exports[`processSeriesData basic processing should read header and two rows 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"columns": Array [
|
"fields": Array [
|
||||||
Object {
|
Object {
|
||||||
"text": "a",
|
"name": "a",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"text": "b",
|
"name": "b",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"text": "c",
|
"name": "c",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"rows": Array [
|
"rows": Array [
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { parseCSV, toTableData, guessColumnTypes, guessColumnTypeFromValue } from './processTableData';
|
import { parseCSV, toSeriesData, guessFieldTypes, guessFieldTypeFromValue } from './processTableData';
|
||||||
import { ColumnType } from '../types/data';
|
import { FieldType } from '../types/data';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
describe('processTableData', () => {
|
describe('processSeriesData', () => {
|
||||||
describe('basic processing', () => {
|
describe('basic processing', () => {
|
||||||
it('should read header and two rows', () => {
|
it('should read header and two rows', () => {
|
||||||
const text = 'a,b,c\n1,2,3\n4,5,6';
|
const text = 'a,b,c\n1,2,3\n4,5,6';
|
||||||
|
|
@ -21,14 +21,14 @@ describe('processTableData', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('toTableData', () => {
|
describe('toSeriesData', () => {
|
||||||
it('converts timeseries to table ', () => {
|
it('converts timeseries to table ', () => {
|
||||||
const input1 = {
|
const input1 = {
|
||||||
target: 'Field Name',
|
target: 'Field Name',
|
||||||
datapoints: [[100, 1], [200, 2]],
|
datapoints: [[100, 1], [200, 2]],
|
||||||
};
|
};
|
||||||
let table = toTableData(input1);
|
let table = toSeriesData(input1);
|
||||||
expect(table.columns[0].text).toBe(input1.target);
|
expect(table.fields[0].name).toBe(input1.target);
|
||||||
expect(table.rows).toBe(input1.datapoints);
|
expect(table.rows).toBe(input1.datapoints);
|
||||||
|
|
||||||
// Should fill a default name if target is empty
|
// Should fill a default name if target is empty
|
||||||
|
|
@ -37,48 +37,48 @@ describe('toTableData', () => {
|
||||||
target: '',
|
target: '',
|
||||||
datapoints: [[100, 1], [200, 2]],
|
datapoints: [[100, 1], [200, 2]],
|
||||||
};
|
};
|
||||||
table = toTableData(input2);
|
table = toSeriesData(input2);
|
||||||
expect(table.columns[0].text).toEqual('Value');
|
expect(table.fields[0].name).toEqual('Value');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('keeps tableData unchanged', () => {
|
it('keeps tableData unchanged', () => {
|
||||||
const input = {
|
const input = {
|
||||||
columns: [{ text: 'A' }, { text: 'B' }, { text: 'C' }],
|
fields: [{ text: 'A' }, { text: 'B' }, { text: 'C' }],
|
||||||
rows: [[100, 'A', 1], [200, 'B', 2], [300, 'C', 3]],
|
rows: [[100, 'A', 1], [200, 'B', 2], [300, 'C', 3]],
|
||||||
};
|
};
|
||||||
const table = toTableData(input);
|
const table = toSeriesData(input);
|
||||||
expect(table).toBe(input);
|
expect(table).toBe(input);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Guess Colum Types from value', () => {
|
it('Guess Colum Types from value', () => {
|
||||||
expect(guessColumnTypeFromValue(1)).toBe(ColumnType.number);
|
expect(guessFieldTypeFromValue(1)).toBe(FieldType.number);
|
||||||
expect(guessColumnTypeFromValue(1.234)).toBe(ColumnType.number);
|
expect(guessFieldTypeFromValue(1.234)).toBe(FieldType.number);
|
||||||
expect(guessColumnTypeFromValue(3.125e7)).toBe(ColumnType.number);
|
expect(guessFieldTypeFromValue(3.125e7)).toBe(FieldType.number);
|
||||||
expect(guessColumnTypeFromValue(true)).toBe(ColumnType.boolean);
|
expect(guessFieldTypeFromValue(true)).toBe(FieldType.boolean);
|
||||||
expect(guessColumnTypeFromValue(false)).toBe(ColumnType.boolean);
|
expect(guessFieldTypeFromValue(false)).toBe(FieldType.boolean);
|
||||||
expect(guessColumnTypeFromValue(new Date())).toBe(ColumnType.time);
|
expect(guessFieldTypeFromValue(new Date())).toBe(FieldType.time);
|
||||||
expect(guessColumnTypeFromValue(moment())).toBe(ColumnType.time);
|
expect(guessFieldTypeFromValue(moment())).toBe(FieldType.time);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Guess Colum Types from strings', () => {
|
it('Guess Colum Types from strings', () => {
|
||||||
expect(guessColumnTypeFromValue('1')).toBe(ColumnType.number);
|
expect(guessFieldTypeFromValue('1')).toBe(FieldType.number);
|
||||||
expect(guessColumnTypeFromValue('1.234')).toBe(ColumnType.number);
|
expect(guessFieldTypeFromValue('1.234')).toBe(FieldType.number);
|
||||||
expect(guessColumnTypeFromValue('3.125e7')).toBe(ColumnType.number);
|
expect(guessFieldTypeFromValue('3.125e7')).toBe(FieldType.number);
|
||||||
expect(guessColumnTypeFromValue('True')).toBe(ColumnType.boolean);
|
expect(guessFieldTypeFromValue('True')).toBe(FieldType.boolean);
|
||||||
expect(guessColumnTypeFromValue('FALSE')).toBe(ColumnType.boolean);
|
expect(guessFieldTypeFromValue('FALSE')).toBe(FieldType.boolean);
|
||||||
expect(guessColumnTypeFromValue('true')).toBe(ColumnType.boolean);
|
expect(guessFieldTypeFromValue('true')).toBe(FieldType.boolean);
|
||||||
expect(guessColumnTypeFromValue('xxxx')).toBe(ColumnType.string);
|
expect(guessFieldTypeFromValue('xxxx')).toBe(FieldType.string);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Guess Colum Types from table', () => {
|
it('Guess Colum Types from table', () => {
|
||||||
const table = {
|
const table = {
|
||||||
columns: [{ text: 'A (number)' }, { text: 'B (strings)' }, { text: 'C (nulls)' }, { text: 'Time' }],
|
fields: [{ name: 'A (number)' }, { name: 'B (strings)' }, { name: 'C (nulls)' }, { name: 'Time' }],
|
||||||
rows: [[123, null, null, '2000'], [null, 'Hello', null, 'XXX']],
|
rows: [[123, null, null, '2000'], [null, 'Hello', null, 'XXX']],
|
||||||
};
|
};
|
||||||
const norm = guessColumnTypes(table);
|
const norm = guessFieldTypes(table);
|
||||||
expect(norm.columns[0].type).toBe(ColumnType.number);
|
expect(norm.fields[0].type).toBe(FieldType.number);
|
||||||
expect(norm.columns[1].type).toBe(ColumnType.string);
|
expect(norm.fields[1].type).toBe(FieldType.string);
|
||||||
expect(norm.columns[2].type).toBeUndefined();
|
expect(norm.fields[2].type).toBeUndefined();
|
||||||
expect(norm.columns[3].type).toBe(ColumnType.time); // based on name
|
expect(norm.fields[3].type).toBe(FieldType.time); // based on name
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import moment from 'moment';
|
||||||
import Papa, { ParseError, ParseMeta } from 'papaparse';
|
import Papa, { ParseError, ParseMeta } from 'papaparse';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { TableData, Column, TimeSeries, ColumnType } from '../types';
|
import { SeriesData, Field, TimeSeries, FieldType, TableData } from '../types';
|
||||||
|
|
||||||
// Subset of all parse options
|
// Subset of all parse options
|
||||||
export interface TableParseOptions {
|
export interface TableParseOptions {
|
||||||
|
|
@ -31,12 +31,12 @@ export interface TableParseDetails {
|
||||||
* @returns a new table that has equal length rows, or the same
|
* @returns a new table that has equal length rows, or the same
|
||||||
* table if no changes were needed
|
* table if no changes were needed
|
||||||
*/
|
*/
|
||||||
export function matchRowSizes(table: TableData): TableData {
|
export function matchRowSizes(table: SeriesData): SeriesData {
|
||||||
const { rows } = table;
|
const { rows } = table;
|
||||||
let { columns } = table;
|
let { fields } = table;
|
||||||
|
|
||||||
let sameSize = true;
|
let sameSize = true;
|
||||||
let size = columns.length;
|
let size = fields.length;
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
if (size !== row.length) {
|
if (size !== row.length) {
|
||||||
sameSize = false;
|
sameSize = false;
|
||||||
|
|
@ -47,13 +47,13 @@ export function matchRowSizes(table: TableData): TableData {
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pad Columns
|
// Pad Fields
|
||||||
if (size !== columns.length) {
|
if (size !== fields.length) {
|
||||||
const diff = size - columns.length;
|
const diff = size - fields.length;
|
||||||
columns = [...columns];
|
fields = [...fields];
|
||||||
for (let i = 0; i < diff; i++) {
|
for (let i = 0; i < diff; i++) {
|
||||||
columns.push({
|
fields.push({
|
||||||
text: 'Column ' + (columns.length + 1),
|
name: 'Field ' + (fields.length + 1),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -72,30 +72,30 @@ export function matchRowSizes(table: TableData): TableData {
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
columns,
|
fields,
|
||||||
rows: fixedRows,
|
rows: fixedRows,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeColumns(values: any[]): Column[] {
|
function makeFields(values: any[]): Field[] {
|
||||||
return values.map((value, index) => {
|
return values.map((value, index) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
value = 'Column ' + (index + 1);
|
value = 'Field ' + (index + 1);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
text: value.toString().trim(),
|
name: value.toString().trim(),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert CSV text into a valid TableData object
|
* Convert CSV text into a valid SeriesData object
|
||||||
*
|
*
|
||||||
* @param text
|
* @param text
|
||||||
* @param options
|
* @param options
|
||||||
* @param details, if exists the result will be filled with debugging details
|
* @param details, if exists the result will be filled with debugging details
|
||||||
*/
|
*/
|
||||||
export function parseCSV(text: string, options?: TableParseOptions, details?: TableParseDetails): TableData {
|
export function parseCSV(text: string, options?: TableParseOptions, details?: TableParseDetails): SeriesData {
|
||||||
const results = Papa.parse(text, { ...options, dynamicTyping: true, skipEmptyLines: true });
|
const results = Papa.parse(text, { ...options, dynamicTyping: true, skipEmptyLines: true });
|
||||||
const { data, meta, errors } = results;
|
const { data, meta, errors } = results;
|
||||||
|
|
||||||
|
|
@ -118,7 +118,7 @@ export function parseCSV(text: string, options?: TableParseOptions, details?: Ta
|
||||||
details.errors = errors;
|
details.errors = errors;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
columns: [],
|
fields: [],
|
||||||
rows: [],
|
rows: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -128,22 +128,35 @@ export function parseCSV(text: string, options?: TableParseOptions, details?: Ta
|
||||||
const header = headerIsNotFirstLine ? [] : results.data.shift();
|
const header = headerIsNotFirstLine ? [] : results.data.shift();
|
||||||
|
|
||||||
return matchRowSizes({
|
return matchRowSizes({
|
||||||
columns: makeColumns(header),
|
fields: makeFields(header),
|
||||||
rows: results.data,
|
rows: results.data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertTimeSeriesToTableData(timeSeries: TimeSeries): TableData {
|
function convertTableToSeriesData(table: TableData): SeriesData {
|
||||||
|
return {
|
||||||
|
// rename the 'text' to 'name' field
|
||||||
|
fields: table.columns.map(c => {
|
||||||
|
const { text, ...field } = c;
|
||||||
|
const f = field as Field;
|
||||||
|
f.name = text;
|
||||||
|
return f;
|
||||||
|
}),
|
||||||
|
rows: table.rows,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertTimeSeriesToSeriesData(timeSeries: TimeSeries): SeriesData {
|
||||||
return {
|
return {
|
||||||
name: timeSeries.target,
|
name: timeSeries.target,
|
||||||
columns: [
|
fields: [
|
||||||
{
|
{
|
||||||
text: timeSeries.target || 'Value',
|
name: timeSeries.target || 'Value',
|
||||||
unit: timeSeries.unit,
|
unit: timeSeries.unit,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Time',
|
name: 'Time',
|
||||||
type: ColumnType.time,
|
type: FieldType.time,
|
||||||
unit: 'dateTimeAsIso',
|
unit: 'dateTimeAsIso',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -151,10 +164,10 @@ function convertTimeSeriesToTableData(timeSeries: TimeSeries): TableData {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFirstTimeColumn = (table: TableData): number => {
|
export const getFirstTimeField = (table: SeriesData): number => {
|
||||||
const { columns } = table;
|
const { fields } = table;
|
||||||
for (let i = 0; i < columns.length; i++) {
|
for (let i = 0; i < fields.length; i++) {
|
||||||
if (columns[i].type === ColumnType.time) {
|
if (fields[i].type === FieldType.time) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -170,45 +183,45 @@ const NUMBER = /^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i;
|
||||||
*
|
*
|
||||||
* TODO: better Date/Time support! Look for standard date strings?
|
* TODO: better Date/Time support! Look for standard date strings?
|
||||||
*/
|
*/
|
||||||
export function guessColumnTypeFromValue(v: any): ColumnType {
|
export function guessFieldTypeFromValue(v: any): FieldType {
|
||||||
if (isNumber(v)) {
|
if (isNumber(v)) {
|
||||||
return ColumnType.number;
|
return FieldType.number;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isString(v)) {
|
if (isString(v)) {
|
||||||
if (NUMBER.test(v)) {
|
if (NUMBER.test(v)) {
|
||||||
return ColumnType.number;
|
return FieldType.number;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v === 'true' || v === 'TRUE' || v === 'True' || v === 'false' || v === 'FALSE' || v === 'False') {
|
if (v === 'true' || v === 'TRUE' || v === 'True' || v === 'false' || v === 'FALSE' || v === 'False') {
|
||||||
return ColumnType.boolean;
|
return FieldType.boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ColumnType.string;
|
return FieldType.string;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isBoolean(v)) {
|
if (isBoolean(v)) {
|
||||||
return ColumnType.boolean;
|
return FieldType.boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v instanceof Date || v instanceof moment) {
|
if (v instanceof Date || v instanceof moment) {
|
||||||
return ColumnType.time;
|
return FieldType.time;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ColumnType.other;
|
return FieldType.other;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Looks at the data to guess the column type. This ignores any existing setting
|
* Looks at the data to guess the column type. This ignores any existing setting
|
||||||
*/
|
*/
|
||||||
function guessColumnTypeFromTable(table: TableData, index: number): ColumnType | undefined {
|
function guessFieldTypeFromTable(table: SeriesData, index: number): FieldType | undefined {
|
||||||
const column = table.columns[index];
|
const column = table.fields[index];
|
||||||
|
|
||||||
// 1. Use the column name to guess
|
// 1. Use the column name to guess
|
||||||
if (column.text) {
|
if (column.name) {
|
||||||
const name = column.text.toLowerCase();
|
const name = column.name.toLowerCase();
|
||||||
if (name === 'date' || name === 'time') {
|
if (name === 'date' || name === 'time') {
|
||||||
return ColumnType.time;
|
return FieldType.time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,7 +229,7 @@ function guessColumnTypeFromTable(table: TableData, index: number): ColumnType |
|
||||||
for (let i = 0; i < table.rows.length; i++) {
|
for (let i = 0; i < table.rows.length; i++) {
|
||||||
const v = table.rows[i][index];
|
const v = table.rows[i][index];
|
||||||
if (v !== null) {
|
if (v !== null) {
|
||||||
return guessColumnTypeFromValue(v);
|
return guessFieldTypeFromValue(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,20 +241,20 @@ function guessColumnTypeFromTable(table: TableData, index: number): ColumnType |
|
||||||
* @returns a table Returns a copy of the table with the best guess for each column type
|
* @returns a table Returns a copy of the table with the best guess for each column type
|
||||||
* If the table already has column types defined, they will be used
|
* If the table already has column types defined, they will be used
|
||||||
*/
|
*/
|
||||||
export const guessColumnTypes = (table: TableData): TableData => {
|
export const guessFieldTypes = (table: SeriesData): SeriesData => {
|
||||||
for (let i = 0; i < table.columns.length; i++) {
|
for (let i = 0; i < table.fields.length; i++) {
|
||||||
if (!table.columns[i].type) {
|
if (!table.fields[i].type) {
|
||||||
// Somethign is missing a type return a modified copy
|
// Somethign is missing a type return a modified copy
|
||||||
return {
|
return {
|
||||||
...table,
|
...table,
|
||||||
columns: table.columns.map((column, index) => {
|
fields: table.fields.map((column, index) => {
|
||||||
if (column.type) {
|
if (column.type) {
|
||||||
return column;
|
return column;
|
||||||
}
|
}
|
||||||
// Replace it with a calculated version
|
// Replace it with a calculated version
|
||||||
return {
|
return {
|
||||||
...column,
|
...column,
|
||||||
type: guessColumnTypeFromTable(table, index),
|
type: guessFieldTypeFromTable(table, index),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
@ -251,21 +264,26 @@ export const guessColumnTypes = (table: TableData): TableData => {
|
||||||
return table;
|
return table;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isTableData = (data: any): data is TableData => data && data.hasOwnProperty('columns');
|
export const isTableData = (data: any): data is SeriesData => data && data.hasOwnProperty('columns');
|
||||||
|
|
||||||
export const toTableData = (data: any): TableData => {
|
export const isSeriesData = (data: any): data is SeriesData => data && data.hasOwnProperty('fields');
|
||||||
if (data.hasOwnProperty('columns')) {
|
|
||||||
return data as TableData;
|
export const toSeriesData = (data: any): SeriesData => {
|
||||||
|
if (data.hasOwnProperty('fields')) {
|
||||||
|
return data as SeriesData;
|
||||||
}
|
}
|
||||||
if (data.hasOwnProperty('datapoints')) {
|
if (data.hasOwnProperty('datapoints')) {
|
||||||
return convertTimeSeriesToTableData(data);
|
return convertTimeSeriesToSeriesData(data);
|
||||||
|
}
|
||||||
|
if (data.hasOwnProperty('columns')) {
|
||||||
|
return convertTableToSeriesData(data);
|
||||||
}
|
}
|
||||||
// TODO, try to convert JSON/Array to table?
|
// TODO, try to convert JSON/Array to table?
|
||||||
console.warn('Can not convert', data);
|
console.warn('Can not convert', data);
|
||||||
throw new Error('Unsupported data format');
|
throw new Error('Unsupported data format');
|
||||||
};
|
};
|
||||||
|
|
||||||
export function sortTableData(data: TableData, sortIndex?: number, reverse = false): TableData {
|
export function sortSeriesData(data: SeriesData, sortIndex?: number, reverse = false): SeriesData {
|
||||||
if (isNumber(sortIndex)) {
|
if (isNumber(sortIndex)) {
|
||||||
const copy = {
|
const copy = {
|
||||||
...data,
|
...data,
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,8 @@ describe('Stats Calculators', () => {
|
||||||
|
|
||||||
it('should calculate basic stats', () => {
|
it('should calculate basic stats', () => {
|
||||||
const stats = calculateStats({
|
const stats = calculateStats({
|
||||||
table: basicTable,
|
series: basicTable,
|
||||||
columnIndex: 0,
|
fieldIndex: 0,
|
||||||
stats: ['first', 'last', 'mean'],
|
stats: ['first', 'last', 'mean'],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -58,8 +58,8 @@ describe('Stats Calculators', () => {
|
||||||
|
|
||||||
it('should support a single stat also', () => {
|
it('should support a single stat also', () => {
|
||||||
const stats = calculateStats({
|
const stats = calculateStats({
|
||||||
table: basicTable,
|
series: basicTable,
|
||||||
columnIndex: 0,
|
fieldIndex: 0,
|
||||||
stats: ['first'],
|
stats: ['first'],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -70,8 +70,8 @@ describe('Stats Calculators', () => {
|
||||||
|
|
||||||
it('should get non standard stats', () => {
|
it('should get non standard stats', () => {
|
||||||
const stats = calculateStats({
|
const stats = calculateStats({
|
||||||
table: basicTable,
|
series: basicTable,
|
||||||
columnIndex: 0,
|
fieldIndex: 0,
|
||||||
stats: [StatID.distinctCount, StatID.changeCount],
|
stats: [StatID.distinctCount, StatID.changeCount],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -81,8 +81,8 @@ describe('Stats Calculators', () => {
|
||||||
|
|
||||||
it('should calculate step', () => {
|
it('should calculate step', () => {
|
||||||
const stats = calculateStats({
|
const stats = calculateStats({
|
||||||
table: { columns: [{ text: 'A' }], rows: [[100], [200], [300], [400]] },
|
series: { fields: [{ name: 'A' }], rows: [[100], [200], [300], [400]] },
|
||||||
columnIndex: 0,
|
fieldIndex: 0,
|
||||||
stats: [StatID.step, StatID.delta],
|
stats: [StatID.step, StatID.delta],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Libraries
|
// Libraries
|
||||||
import isNumber from 'lodash/isNumber';
|
import isNumber from 'lodash/isNumber';
|
||||||
|
|
||||||
import { TableData, NullValueMode } from '../types/index';
|
import { SeriesData, NullValueMode } from '../types/index';
|
||||||
|
|
||||||
export enum StatID {
|
export enum StatID {
|
||||||
sum = 'sum',
|
sum = 'sum',
|
||||||
|
|
@ -29,7 +29,7 @@ export interface ColumnStats {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal function
|
// Internal function
|
||||||
type StatCalculator = (table: TableData, columnIndex: number, ignoreNulls: boolean, nullAsZero: boolean) => ColumnStats;
|
type StatCalculator = (data: SeriesData, fieldIndex: number, ignoreNulls: boolean, nullAsZero: boolean) => ColumnStats;
|
||||||
|
|
||||||
export interface StatCalculatorInfo {
|
export interface StatCalculatorInfo {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -64,8 +64,8 @@ export function getStatsCalculators(ids?: string[]): StatCalculatorInfo[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CalculateStatsOptions {
|
export interface CalculateStatsOptions {
|
||||||
table: TableData;
|
series: SeriesData;
|
||||||
columnIndex: number;
|
fieldIndex: number;
|
||||||
stats: string[]; // The stats to calculate
|
stats: string[]; // The stats to calculate
|
||||||
nullValueMode?: NullValueMode;
|
nullValueMode?: NullValueMode;
|
||||||
}
|
}
|
||||||
|
|
@ -74,7 +74,7 @@ export interface CalculateStatsOptions {
|
||||||
* @returns an object with a key for each selected stat
|
* @returns an object with a key for each selected stat
|
||||||
*/
|
*/
|
||||||
export function calculateStats(options: CalculateStatsOptions): ColumnStats {
|
export function calculateStats(options: CalculateStatsOptions): ColumnStats {
|
||||||
const { table, columnIndex, stats, nullValueMode } = options;
|
const { series, fieldIndex, stats, nullValueMode } = options;
|
||||||
|
|
||||||
if (!stats || stats.length < 1) {
|
if (!stats || stats.length < 1) {
|
||||||
return {};
|
return {};
|
||||||
|
|
@ -82,9 +82,9 @@ export function calculateStats(options: CalculateStatsOptions): ColumnStats {
|
||||||
|
|
||||||
const queue = getStatsCalculators(stats);
|
const queue = getStatsCalculators(stats);
|
||||||
|
|
||||||
// Return early for empty tables
|
// Return early for empty series
|
||||||
// This lets the concrete implementations assume at least one row
|
// This lets the concrete implementations assume at least one row
|
||||||
if (!table.rows || table.rows.length < 1) {
|
if (!series.rows || series.rows.length < 1) {
|
||||||
const stats = {} as ColumnStats;
|
const stats = {} as ColumnStats;
|
||||||
for (const stat of queue) {
|
for (const stat of queue) {
|
||||||
stats[stat.id] = stat.emptyInputResult !== null ? stat.emptyInputResult : null;
|
stats[stat.id] = stat.emptyInputResult !== null ? stat.emptyInputResult : null;
|
||||||
|
|
@ -97,16 +97,16 @@ export function calculateStats(options: CalculateStatsOptions): ColumnStats {
|
||||||
|
|
||||||
// Avoid calculating all the standard stats if possible
|
// Avoid calculating all the standard stats if possible
|
||||||
if (queue.length === 1 && queue[0].calculator) {
|
if (queue.length === 1 && queue[0].calculator) {
|
||||||
return queue[0].calculator(table, columnIndex, ignoreNulls, nullAsZero);
|
return queue[0].calculator(series, fieldIndex, ignoreNulls, nullAsZero);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For now everything can use the standard stats
|
// For now everything can use the standard stats
|
||||||
let values = standardStatsStat(table, columnIndex, ignoreNulls, nullAsZero);
|
let values = standardStatsStat(series, fieldIndex, ignoreNulls, nullAsZero);
|
||||||
for (const calc of queue) {
|
for (const calc of queue) {
|
||||||
if (!values.hasOwnProperty(calc.id) && calc.calculator) {
|
if (!values.hasOwnProperty(calc.id) && calc.calculator) {
|
||||||
values = {
|
values = {
|
||||||
...values,
|
...values,
|
||||||
...calc.calculator(table, columnIndex, ignoreNulls, nullAsZero),
|
...calc.calculator(series, fieldIndex, ignoreNulls, nullAsZero),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -223,8 +223,8 @@ function getById(id: string): StatCalculatorInfo | undefined {
|
||||||
}
|
}
|
||||||
|
|
||||||
function standardStatsStat(
|
function standardStatsStat(
|
||||||
data: TableData,
|
data: SeriesData,
|
||||||
columnIndex: number,
|
fieldIndex: number,
|
||||||
ignoreNulls: boolean,
|
ignoreNulls: boolean,
|
||||||
nullAsZero: boolean
|
nullAsZero: boolean
|
||||||
): ColumnStats {
|
): ColumnStats {
|
||||||
|
|
@ -250,7 +250,7 @@ function standardStatsStat(
|
||||||
} as ColumnStats;
|
} as ColumnStats;
|
||||||
|
|
||||||
for (let i = 0; i < data.rows.length; i++) {
|
for (let i = 0; i < data.rows.length; i++) {
|
||||||
let currentValue = data.rows[i][columnIndex];
|
let currentValue = data.rows[i][fieldIndex];
|
||||||
|
|
||||||
if (currentValue === null) {
|
if (currentValue === null) {
|
||||||
if (ignoreNulls) {
|
if (ignoreNulls) {
|
||||||
|
|
@ -345,17 +345,17 @@ function standardStatsStat(
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateFirst(data: TableData, columnIndex: number, ignoreNulls: boolean, nullAsZero: boolean): ColumnStats {
|
function calculateFirst(data: SeriesData, fieldIndex: number, ignoreNulls: boolean, nullAsZero: boolean): ColumnStats {
|
||||||
return { first: data.rows[0][columnIndex] };
|
return { first: data.rows[0][fieldIndex] };
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateLast(data: TableData, columnIndex: number, ignoreNulls: boolean, nullAsZero: boolean): ColumnStats {
|
function calculateLast(data: SeriesData, fieldIndex: number, ignoreNulls: boolean, nullAsZero: boolean): ColumnStats {
|
||||||
return { last: data.rows[data.rows.length - 1][columnIndex] };
|
return { last: data.rows[data.rows.length - 1][fieldIndex] };
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateChangeCount(
|
function calculateChangeCount(
|
||||||
data: TableData,
|
data: SeriesData,
|
||||||
columnIndex: number,
|
fieldIndex: number,
|
||||||
ignoreNulls: boolean,
|
ignoreNulls: boolean,
|
||||||
nullAsZero: boolean
|
nullAsZero: boolean
|
||||||
): ColumnStats {
|
): ColumnStats {
|
||||||
|
|
@ -363,7 +363,7 @@ function calculateChangeCount(
|
||||||
let first = true;
|
let first = true;
|
||||||
let last: any = null;
|
let last: any = null;
|
||||||
for (let i = 0; i < data.rows.length; i++) {
|
for (let i = 0; i < data.rows.length; i++) {
|
||||||
let currentValue = data.rows[i][columnIndex];
|
let currentValue = data.rows[i][fieldIndex];
|
||||||
if (currentValue === null) {
|
if (currentValue === null) {
|
||||||
if (ignoreNulls) {
|
if (ignoreNulls) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -383,14 +383,14 @@ function calculateChangeCount(
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateDistinctCount(
|
function calculateDistinctCount(
|
||||||
data: TableData,
|
data: SeriesData,
|
||||||
columnIndex: number,
|
fieldIndex: number,
|
||||||
ignoreNulls: boolean,
|
ignoreNulls: boolean,
|
||||||
nullAsZero: boolean
|
nullAsZero: boolean
|
||||||
): ColumnStats {
|
): ColumnStats {
|
||||||
const distinct = new Set<any>();
|
const distinct = new Set<any>();
|
||||||
for (let i = 0; i < data.rows.length; i++) {
|
for (let i = 0; i < data.rows.length; i++) {
|
||||||
let currentValue = data.rows[i][columnIndex];
|
let currentValue = data.rows[i][fieldIndex];
|
||||||
if (currentValue === null) {
|
if (currentValue === null) {
|
||||||
if (ignoreNulls) {
|
if (ignoreNulls) {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ interface MutableColumn extends Column {
|
||||||
title?: string;
|
title?: string;
|
||||||
sort?: boolean;
|
sort?: boolean;
|
||||||
desc?: boolean;
|
desc?: boolean;
|
||||||
|
type?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class TableModel implements TableData {
|
export default class TableModel implements TableData {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Library
|
// Library
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { DataPanel, getProcessedTableData } from './DataPanel';
|
import { DataPanel, getProcessedSeriesData } from './DataPanel';
|
||||||
|
|
||||||
describe('DataPanel', () => {
|
describe('DataPanel', () => {
|
||||||
let dataPanel: DataPanel;
|
let dataPanel: DataPanel;
|
||||||
|
|
@ -34,27 +34,27 @@ describe('DataPanel', () => {
|
||||||
target: '',
|
target: '',
|
||||||
datapoints: [[100, 1], [200, 2]],
|
datapoints: [[100, 1], [200, 2]],
|
||||||
};
|
};
|
||||||
const data = getProcessedTableData([null, input1, input2, null, null]);
|
const data = getProcessedSeriesData([null, input1, input2, null, null]);
|
||||||
expect(data.length).toBe(2);
|
expect(data.length).toBe(2);
|
||||||
expect(data[0].columns[0].text).toBe(input1.target);
|
expect(data[0].fields[0].name).toBe(input1.target);
|
||||||
expect(data[0].rows).toBe(input1.datapoints);
|
expect(data[0].rows).toBe(input1.datapoints);
|
||||||
|
|
||||||
// Default name
|
// Default name
|
||||||
expect(data[1].columns[0].text).toEqual('Value');
|
expect(data[1].fields[0].name).toEqual('Value');
|
||||||
|
|
||||||
// Every colun should have a name and a type
|
// Every colun should have a name and a type
|
||||||
for (const table of data) {
|
for (const table of data) {
|
||||||
for (const column of table.columns) {
|
for (const column of table.fields) {
|
||||||
expect(column.text).toBeDefined();
|
expect(column.name).toBeDefined();
|
||||||
expect(column.type).toBeDefined();
|
expect(column.type).toBeDefined();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('supports null values from query OK', () => {
|
it('supports null values from query OK', () => {
|
||||||
expect(getProcessedTableData([null, null, null, null])).toEqual([]);
|
expect(getProcessedSeriesData([null, null, null, null])).toEqual([]);
|
||||||
expect(getProcessedTableData(undefined)).toEqual([]);
|
expect(getProcessedSeriesData(undefined)).toEqual([]);
|
||||||
expect(getProcessedTableData((null as unknown) as any[])).toEqual([]);
|
expect(getProcessedSeriesData((null as unknown) as any[])).toEqual([]);
|
||||||
expect(getProcessedTableData([])).toEqual([]);
|
expect(getProcessedSeriesData([])).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,16 +11,16 @@ import {
|
||||||
DataQueryResponse,
|
DataQueryResponse,
|
||||||
DataQueryError,
|
DataQueryError,
|
||||||
LoadingState,
|
LoadingState,
|
||||||
TableData,
|
SeriesData,
|
||||||
TimeRange,
|
TimeRange,
|
||||||
ScopedVars,
|
ScopedVars,
|
||||||
toTableData,
|
toSeriesData,
|
||||||
guessColumnTypes,
|
guessFieldTypes,
|
||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
|
|
||||||
interface RenderProps {
|
interface RenderProps {
|
||||||
loading: LoadingState;
|
loading: LoadingState;
|
||||||
data: TableData[];
|
data: SeriesData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
data?: TableData[];
|
data?: SeriesData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -52,18 +52,18 @@ export interface State {
|
||||||
*
|
*
|
||||||
* This is also used by PanelChrome for snapshot support
|
* This is also used by PanelChrome for snapshot support
|
||||||
*/
|
*/
|
||||||
export function getProcessedTableData(results?: any[]): TableData[] {
|
export function getProcessedSeriesData(results?: any[]): SeriesData[] {
|
||||||
if (!results) {
|
if (!results) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const tables: TableData[] = [];
|
const series: SeriesData[] = [];
|
||||||
for (const r of results) {
|
for (const r of results) {
|
||||||
if (r) {
|
if (r) {
|
||||||
tables.push(guessColumnTypes(toTableData(r)));
|
series.push(guessFieldTypes(toSeriesData(r)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tables;
|
return series;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DataPanel extends Component<Props, State> {
|
export class DataPanel extends Component<Props, State> {
|
||||||
|
|
@ -167,7 +167,7 @@ export class DataPanel extends Component<Props, State> {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: LoadingState.Done,
|
loading: LoadingState.Done,
|
||||||
response: resp,
|
response: resp,
|
||||||
data: getProcessedTableData(resp.data),
|
data: getProcessedSeriesData(resp.data),
|
||||||
isFirstLoad: false,
|
isFirstLoad: false,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,12 @@ 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, TableData, DataQueryError } from '@grafana/ui';
|
import { DataQueryResponse, TimeRange, LoadingState, DataQueryError, SeriesData } 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';
|
||||||
|
|
||||||
import { getProcessedTableData } from './DataPanel';
|
import { getProcessedSeriesData } from './DataPanel';
|
||||||
|
|
||||||
const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
|
const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
|
||||||
|
|
||||||
|
|
@ -141,10 +141,10 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
get getDataForPanel() {
|
get getDataForPanel() {
|
||||||
return this.hasPanelSnapshot ? getProcessedTableData(this.props.panel.snapshotData) : null;
|
return this.hasPanelSnapshot ? getProcessedSeriesData(this.props.panel.snapshotData) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPanelPlugin(loading: LoadingState, data: TableData[], width: number, height: number): JSX.Element {
|
renderPanelPlugin(loading: LoadingState, data: SeriesData[], 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;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import TableModel from 'app/core/table_model';
|
import TableModel from 'app/core/table_model';
|
||||||
import { ColumnType } from '@grafana/ui';
|
import { FieldType } from '@grafana/ui';
|
||||||
|
|
||||||
export default class InfluxSeries {
|
export default class InfluxSeries {
|
||||||
series: any;
|
series: any;
|
||||||
|
|
@ -157,7 +157,7 @@ export default class InfluxSeries {
|
||||||
// Check that the first column is indeed 'time'
|
// Check that the first column is indeed 'time'
|
||||||
if (series.columns[0] === 'time') {
|
if (series.columns[0] === 'time') {
|
||||||
// Push this now before the tags and with the right type
|
// Push this now before the tags and with the right type
|
||||||
table.columns.push({ text: 'Time', type: ColumnType.time });
|
table.columns.push({ text: 'Time', type: FieldType.time });
|
||||||
j++;
|
j++;
|
||||||
}
|
}
|
||||||
_.each(_.keys(series.tags), key => {
|
_.each(_.keys(series.tags), key => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import TableModel from 'app/core/table_model';
|
import TableModel from 'app/core/table_model';
|
||||||
import { TimeSeries, ColumnType } from '@grafana/ui';
|
import { TimeSeries, FieldType } from '@grafana/ui';
|
||||||
|
|
||||||
export class ResultTransformer {
|
export class ResultTransformer {
|
||||||
constructor(private templateSrv) {}
|
constructor(private templateSrv) {}
|
||||||
|
|
@ -98,7 +98,7 @@ export class ResultTransformer {
|
||||||
|
|
||||||
// Sort metric labels, create columns for them and record their index
|
// Sort metric labels, create columns for them and record their index
|
||||||
const sortedLabels = _.keys(metricLabels).sort();
|
const sortedLabels = _.keys(metricLabels).sort();
|
||||||
table.columns.push({ text: 'Time', type: ColumnType.time });
|
table.columns.push({ text: 'Time', type: FieldType.time });
|
||||||
_.each(sortedLabels, (label, labelIndex) => {
|
_.each(sortedLabels, (label, labelIndex) => {
|
||||||
metricLabels[label] = labelIndex + 1;
|
metricLabels[label] = labelIndex + 1;
|
||||||
table.columns.push({ text: label, filterable: true });
|
table.columns.push({ text: label, filterable: true });
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
import { Graph, PanelProps, NullValueMode, colors, TimeSeriesVMs, ColumnType, getFirstTimeColumn } from '@grafana/ui';
|
import { Graph, PanelProps, NullValueMode, colors, TimeSeriesVMs, FieldType, getFirstTimeField } from '@grafana/ui';
|
||||||
import { Options } from './types';
|
import { Options } from './types';
|
||||||
import { getFlotPairs } from '@grafana/ui/src/utils/flotPairs';
|
import { getFlotPairs } from '@grafana/ui/src/utils/flotPairs';
|
||||||
|
|
||||||
|
|
@ -15,16 +15,16 @@ export class GraphPanel extends PureComponent<Props> {
|
||||||
|
|
||||||
const vmSeries: TimeSeriesVMs = [];
|
const vmSeries: TimeSeriesVMs = [];
|
||||||
for (const table of data) {
|
for (const table of data) {
|
||||||
const timeColumn = getFirstTimeColumn(table);
|
const timeColumn = getFirstTimeField(table);
|
||||||
if (timeColumn < 0) {
|
if (timeColumn < 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < table.columns.length; i++) {
|
for (let i = 0; i < table.fields.length; i++) {
|
||||||
const column = table.columns[i];
|
const column = table.fields[i];
|
||||||
|
|
||||||
// Show all numeric columns
|
// Show all numeric columns
|
||||||
if (column.type === ColumnType.number) {
|
if (column.type === FieldType.number) {
|
||||||
// Use external calculator just to make sure it works :)
|
// Use external calculator just to make sure it works :)
|
||||||
const points = getFlotPairs({
|
const points = getFlotPairs({
|
||||||
rows: table.rows,
|
rows: table.rows,
|
||||||
|
|
@ -34,7 +34,7 @@ export class GraphPanel extends PureComponent<Props> {
|
||||||
});
|
});
|
||||||
|
|
||||||
vmSeries.push({
|
vmSeries.push({
|
||||||
label: column.text,
|
label: column.name,
|
||||||
data: points,
|
data: points,
|
||||||
color: colors[vmSeries.length % colors.length],
|
color: colors[vmSeries.length % colors.length],
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import React, { PureComponent, CSSProperties } from 'react';
|
||||||
// Types
|
// Types
|
||||||
import { SingleStatOptions, SingleStatBaseOptions } from './types';
|
import { SingleStatOptions, SingleStatBaseOptions } from './types';
|
||||||
|
|
||||||
import { DisplayValue, PanelProps, NullValueMode, ColumnType, calculateStats } from '@grafana/ui';
|
import { DisplayValue, PanelProps, NullValueMode, FieldType, calculateStats } 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';
|
||||||
|
|
@ -26,19 +26,19 @@ export const getSingleStatValues = (props: PanelProps<SingleStatBaseOptions>): D
|
||||||
|
|
||||||
const values: DisplayValue[] = [];
|
const values: DisplayValue[] = [];
|
||||||
|
|
||||||
for (const table of data) {
|
for (const series of data) {
|
||||||
if (stat === 'name') {
|
if (stat === 'name') {
|
||||||
values.push(display(table.name));
|
values.push(display(series.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < table.columns.length; i++) {
|
for (let i = 0; i < series.fields.length; i++) {
|
||||||
const column = table.columns[i];
|
const column = series.fields[i];
|
||||||
|
|
||||||
// Show all columns that are not 'time'
|
// Show all columns that are not 'time'
|
||||||
if (column.type === ColumnType.number) {
|
if (column.type === FieldType.number) {
|
||||||
const stats = calculateStats({
|
const stats = calculateStats({
|
||||||
table,
|
series,
|
||||||
columnIndex: i,
|
fieldIndex: i,
|
||||||
stats: [stat], // The stats to calculate
|
stats: [stat], // The stats to calculate
|
||||||
nullValueMode: NullValueMode.Null,
|
nullValueMode: NullValueMode.Null,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue