Commit 77b3da3e by Ryan McKinley Committed by Torkel Ödegaard

refactor(data models): Renamed TableData to SeriesData (#16185)

parent c7d10826
...@@ -4,7 +4,7 @@ import { Table } from './Table'; ...@@ -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) { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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> { ...@@ -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> { ...@@ -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> { ...@@ -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> { ...@@ -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> { ...@@ -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> { ...@@ -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'; ...@@ -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 { ...@@ -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 { ...@@ -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'; ...@@ -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', () => { ...@@ -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'; ...@@ -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', () => { ...@@ -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);
}} }}
/> />
......
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> { ...@@ -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} &nbsp; Rows:{table.rows.length}, Columns:{table.fields.length} &nbsp;
{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>
......
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'); ...@@ -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[] = [ ...@@ -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[] = [ ...@@ -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[] = [ ...@@ -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[] = [ ...@@ -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[] = [ ...@@ -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 { ...@@ -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 { ...@@ -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;
......
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;
......
// 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 { ...@@ -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 [
......
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', () => { ...@@ -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', () => { ...@@ -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'; ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 ...@@ -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 ...@@ -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 { ...@@ -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; ...@@ -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 | ...@@ -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 | ...@@ -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 => { ...@@ -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', () => { ...@@ -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', () => { ...@@ -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', () => { ...@@ -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', () => { ...@@ -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],
}); });
......
// 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 { ...@@ -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[] { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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( ...@@ -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( ...@@ -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( ...@@ -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( ...@@ -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 { ...@@ -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 {
......
// 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', () => { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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> { ...@@ -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'; ...@@ -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> { ...@@ -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;
......
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 { ...@@ -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 => {
......
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 { ...@@ -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 @@ ...@@ -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> { ...@@ -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> { ...@@ -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'; ...@@ -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 ...@@ -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,
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment