瀏覽代碼

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

Ryan McKinley 6 年之前
父節點
當前提交
77b3da3e8b

+ 4 - 4
packages/grafana-ui/src/components/Table/Table.story.tsx

@@ -4,7 +4,7 @@ import { Table } from './Table';
 import { getTheme } from '../../themes';
 
 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 { number, boolean } from '@storybook/addon-knobs';
 
@@ -29,11 +29,11 @@ export function columnIndexToLeter(column: number) {
   return String.fromCharCode(A + c2);
 }
 
-export function makeDummyTable(columnCount: number, rowCount: number): TableData {
+export function makeDummyTable(columnCount: number, rowCount: number): SeriesData {
   return {
-    columns: Array.from(new Array(columnCount), (x, i) => {
+    fields: Array.from(new Array(columnCount), (x, i) => {
       return {
-        text: columnIndexToLeter(i),
+        name: columnIndexToLeter(i),
       };
     }),
     rows: Array.from(new Array(rowCount), (x, rowId) => {

+ 15 - 15
packages/grafana-ui/src/components/Table/Table.tsx

@@ -12,9 +12,9 @@ import {
 } from 'react-virtualized';
 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 {
   TableCellBuilder,
   ColumnStyle,
@@ -25,7 +25,7 @@ import {
 import { stringToJsRegex } from '../../utils/index';
 
 export interface Props extends Themeable {
-  data: TableData;
+  data: SeriesData;
 
   minColumnWidth: number;
   showHeader: boolean;
@@ -43,7 +43,7 @@ export interface Props extends Themeable {
 interface State {
   sortBy?: number;
   sortDirection?: SortDirectionType;
-  data: TableData;
+  data: SeriesData;
 }
 
 interface ColumnRenderInfo {
@@ -108,17 +108,17 @@ export class Table extends Component<Props, State> {
     // Update the data when data or sort changes
     if (dataChanged || sortBy !== prevState.sortBy || sortDirection !== prevState.sortDirection) {
       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 */
   initColumns(props: Props): ColumnRenderInfo[] {
     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) => {
-      let title = col.text;
+    return data.fields.map((col, index) => {
+      let title = col.name;
       let style: ColumnStyle | null = null; // ColumnStyle
 
       // Find the style based on the text
@@ -159,7 +159,7 @@ export class Table extends Component<Props, State> {
     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 => {
     const { showHeader, rotate } = this.props;
     const rowOffset = showHeader ? -1 : 0;
@@ -187,17 +187,17 @@ export class Table extends Component<Props, State> {
     const { columnIndex, rowIndex, style } = cell.props;
     const { column } = this.getCellRef(rowIndex, columnIndex);
 
-    let col = data.columns[column];
+    let col = data.fields[column];
     const sorting = sortBy === column;
     if (!col) {
       col = {
-        text: '??' + columnIndex + '???',
+        name: '??' + columnIndex + '???',
       };
     }
 
     return (
       <div className="gf-table-header" style={style} onClick={() => this.onCellClick(rowIndex, columnIndex)}>
-        {col.text}
+        {col.name}
         {sorting && <SortIndicator sortDirection={sortDirection} />}
       </div>
     );
@@ -217,7 +217,7 @@ export class Table extends Component<Props, State> {
     const { data } = this.state;
 
     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 builder = isHeader ? this.headerBuilder : this.getTableCellBuilder(column);
 
@@ -226,7 +226,7 @@ export class Table extends Component<Props, State> {
         {builder({
           value,
           row: rowData,
-          column: data.columns[column],
+          column: data.fields[column],
           table: this,
           props,
         })}
@@ -242,7 +242,7 @@ export class Table extends Component<Props, State> {
     const { showHeader, fixedHeader, fixedColumns, rotate, width, height } = this.props;
     const { data } = this.state;
 
-    let columnCount = data.columns.length;
+    let columnCount = data.fields.length;
     let rowCount = data.rows.length + (showHeader ? 1 : 0);
 
     let fixedColumnCount = Math.min(fixedColumns, columnCount);

+ 5 - 5
packages/grafana-ui/src/components/Table/TableCellBuilder.tsx

@@ -6,12 +6,12 @@ import { Table, Props } from './Table';
 import moment from 'moment';
 import { ValueFormatter } from '../../utils/index';
 import { GrafanaTheme } from '../../types/theme';
-import { getValueFormat, getColorFromHexRgbOrName, Column } from '@grafana/ui';
+import { getValueFormat, getColorFromHexRgbOrName, Field } from '@grafana/ui';
 import { InterpolateFunction } from '../../types/panel';
 
 export interface TableCellBuilderOptions {
   value: any;
-  column?: Column;
+  column?: Field;
   row?: any[];
   table?: Table;
   className?: string;
@@ -74,7 +74,7 @@ export interface ColumnStyle {
 // private replaceVariables: InterpolateFunction,
 // 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) {
     return simpleCellBuilder;
   }
@@ -154,12 +154,12 @@ class CellBuilderWithStyle {
     private mapper: ValueMapper,
     private style: ColumnStyle,
     private theme: GrafanaTheme,
-    private column: Column,
+    private column: Field,
     private replaceVariables: InterpolateFunction,
     private fmt?: ValueFormatter
   ) {
     //
-    console.log('COLUMN', column.text, theme);
+    console.log('COLUMN', column.name, theme);
   }
 
   getColorForValue = (value: any): string | null => {

+ 2 - 2
packages/grafana-ui/src/components/Table/TableInputCSV.story.tsx

@@ -3,7 +3,7 @@ import React from 'react';
 import { storiesOf } from '@storybook/react';
 import TableInputCSV from './TableInputCSV';
 import { action } from '@storybook/addon-actions';
-import { TableData } from '../../types/data';
+import { SeriesData } from '../../types/data';
 import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
 
 const TableInputStories = storiesOf('UI/Table/Input', module);
@@ -15,7 +15,7 @@ TableInputStories.add('default', () => {
     <div style={{ width: '90%', height: '90vh' }}>
       <TableInputCSV
         text={'a,b,c\n1,2,3'}
-        onTableParsed={(table: TableData, text: string) => {
+        onTableParsed={(table: SeriesData, text: string) => {
           console.log('Table', table, text);
           action('Table')(table, text);
         }}

+ 2 - 2
packages/grafana-ui/src/components/Table/TableInputCSV.test.tsx

@@ -2,7 +2,7 @@ import React from 'react';
 
 import renderer from 'react-test-renderer';
 import TableInputCSV from './TableInputCSV';
-import { TableData } from '../../types/data';
+import { SeriesData } from '../../types/data';
 
 describe('TableInputCSV', () => {
   it('renders correctly', () => {
@@ -10,7 +10,7 @@ describe('TableInputCSV', () => {
       .create(
         <TableInputCSV
           text={'a,b,c\n1,2,3'}
-          onTableParsed={(table: TableData, text: string) => {
+          onTableParsed={(table: SeriesData, text: string) => {
             // console.log('Table:', table, 'from:', text);
           }}
         />

+ 4 - 4
packages/grafana-ui/src/components/Table/TableInputCSV.tsx

@@ -1,18 +1,18 @@
 import React from 'react';
 import debounce from 'lodash/debounce';
 import { parseCSV, TableParseOptions, TableParseDetails } from '../../utils/processTableData';
-import { TableData } from '../../types/data';
+import { SeriesData } from '../../types/data';
 import { AutoSizer } from 'react-virtualized';
 
 interface Props {
   options?: TableParseOptions;
   text: string;
-  onTableParsed: (table: TableData, text: string) => void;
+  onTableParsed: (table: SeriesData, text: string) => void;
 }
 
 interface State {
   text: string;
-  table: TableData;
+  table: SeriesData;
   details: TableParseDetails;
 }
 
@@ -82,7 +82,7 @@ class TableInputCSV extends React.PureComponent<Props, State> {
           <div className="gf-table-input-csv" style={{ width, height }}>
             <textarea placeholder="Enter CSV here..." value={this.state.text} onChange={this.onTextChange} />
             <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" />}
             </footer>
           </div>

+ 27 - 27
packages/grafana-ui/src/components/Table/examples.ts

@@ -1,4 +1,4 @@
-import { TableData } from '../../types/data';
+import { SeriesData } from '../../types/data';
 import { ColumnStyle } from './TableCellBuilder';
 
 import { getColorDefinitionByName } from '@grafana/ui';
@@ -7,23 +7,23 @@ const SemiDarkOrange = getColorDefinitionByName('semi-dark-orange');
 
 export const migratedTestTable = {
   type: 'table',
-  columns: [
-    { text: 'Time' },
-    { text: 'Value' },
-    { text: 'Colored' },
-    { text: 'Undefined' },
-    { text: 'String' },
-    { text: 'United', unit: 'bps' },
-    { text: 'Sanitized' },
-    { text: 'Link' },
-    { text: 'Array' },
-    { text: 'Mapping' },
-    { text: 'RangeMapping' },
-    { text: 'MappingColored' },
-    { text: 'RangeMappingColored' },
+  fields: [
+    { name: 'Time' },
+    { name: 'Value' },
+    { name: 'Colored' },
+    { name: 'Undefined' },
+    { name: 'String' },
+    { name: 'United', unit: 'bps' },
+    { name: 'Sanitized' },
+    { name: 'Link' },
+    { name: 'Array' },
+    { name: 'Mapping' },
+    { name: 'RangeMapping' },
+    { name: 'MappingColored' },
+    { name: 'RangeMappingColored' },
   ],
   rows: [[1388556366666, 1230, 40, undefined, '', '', 'my.host.com', 'host1', ['value1', 'value2'], 1, 2, 1, 2]],
-} as TableData;
+} as SeriesData;
 
 export const migratedTestStyles: ColumnStyle[] = [
   {
@@ -87,19 +87,19 @@ export const migratedTestStyles: ColumnStyle[] = [
     valueMaps: [
       {
         value: '1',
-        text: 'on',
+        name: 'on',
       },
       {
         value: '0',
-        text: 'off',
+        name: 'off',
       },
       {
         value: 'HELLO WORLD',
-        text: 'HELLO GRAFANA',
+        name: 'HELLO GRAFANA',
       },
       {
         value: 'value1, value2',
-        text: 'value3, value4',
+        name: 'value3, value4',
       },
     ],
   },
@@ -111,12 +111,12 @@ export const migratedTestStyles: ColumnStyle[] = [
       {
         from: '1',
         to: '3',
-        text: 'on',
+        name: 'on',
       },
       {
         from: '3',
         to: '6',
-        text: 'off',
+        name: 'off',
       },
     ],
   },
@@ -127,11 +127,11 @@ export const migratedTestStyles: ColumnStyle[] = [
     valueMaps: [
       {
         value: '1',
-        text: 'on',
+        name: 'on',
       },
       {
         value: '0',
-        text: 'off',
+        name: 'off',
       },
     ],
     colorMode: 'value',
@@ -146,12 +146,12 @@ export const migratedTestStyles: ColumnStyle[] = [
       {
         from: '1',
         to: '3',
-        text: 'on',
+        name: 'on',
       },
       {
         from: '3',
         to: '6',
-        text: 'off',
+        name: 'off',
       },
     ],
     colorMode: 'value',
@@ -162,6 +162,6 @@ export const migratedTestStyles: ColumnStyle[] = [
 
 export const simpleTable = {
   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]],
 };

+ 38 - 27
packages/grafana-ui/src/types/data.ts

@@ -5,6 +5,44 @@ export enum LoadingState {
   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 TimeSeriesPoints = TimeSeriesValue[][];
@@ -33,33 +71,6 @@ export enum NullValueMode {
 /** View model projection of many time series */
 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 {
   annotation?: any;
   dashboardId?: number;

+ 2 - 2
packages/grafana-ui/src/types/panel.ts

@@ -1,12 +1,12 @@
 import { ComponentClass } from 'react';
-import { LoadingState, TableData } from './data';
+import { LoadingState, SeriesData } from './data';
 import { TimeRange } from './time';
 import { ScopedVars } from './datasource';
 
 export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string;
 
 export interface PanelProps<T = any> {
-  data?: TableData[];
+  data?: SeriesData[];
   timeRange: TimeRange;
   loading: LoadingState;
   options: T;

+ 10 - 10
packages/grafana-ui/src/utils/__snapshots__/processTableData.test.ts.snap

@@ -1,16 +1,16 @@
 // 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 {
-  "columns": Array [
+  "fields": Array [
     Object {
-      "text": "Column 1",
+      "name": "Field 1",
     },
     Object {
-      "text": "Column 2",
+      "name": "Field 2",
     },
     Object {
-      "text": "Column 3",
+      "name": "Field 3",
     },
   ],
   "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 {
-  "columns": Array [
+  "fields": Array [
     Object {
-      "text": "a",
+      "name": "a",
     },
     Object {
-      "text": "b",
+      "name": "b",
     },
     Object {
-      "text": "c",
+      "name": "c",
     },
   ],
   "rows": Array [

+ 30 - 30
packages/grafana-ui/src/utils/processTableData.test.ts

@@ -1,8 +1,8 @@
-import { parseCSV, toTableData, guessColumnTypes, guessColumnTypeFromValue } from './processTableData';
-import { ColumnType } from '../types/data';
+import { parseCSV, toSeriesData, guessFieldTypes, guessFieldTypeFromValue } from './processTableData';
+import { FieldType } from '../types/data';
 import moment from 'moment';
 
-describe('processTableData', () => {
+describe('processSeriesData', () => {
   describe('basic processing', () => {
     it('should read header and two rows', () => {
       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 ', () => {
     const input1 = {
       target: 'Field Name',
       datapoints: [[100, 1], [200, 2]],
     };
-    let table = toTableData(input1);
-    expect(table.columns[0].text).toBe(input1.target);
+    let table = toSeriesData(input1);
+    expect(table.fields[0].name).toBe(input1.target);
     expect(table.rows).toBe(input1.datapoints);
 
     // Should fill a default name if target is empty
@@ -37,48 +37,48 @@ describe('toTableData', () => {
       target: '',
       datapoints: [[100, 1], [200, 2]],
     };
-    table = toTableData(input2);
-    expect(table.columns[0].text).toEqual('Value');
+    table = toSeriesData(input2);
+    expect(table.fields[0].name).toEqual('Value');
   });
 
   it('keeps tableData unchanged', () => {
     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]],
     };
-    const table = toTableData(input);
+    const table = toSeriesData(input);
     expect(table).toBe(input);
   });
 
   it('Guess Colum Types from value', () => {
-    expect(guessColumnTypeFromValue(1)).toBe(ColumnType.number);
-    expect(guessColumnTypeFromValue(1.234)).toBe(ColumnType.number);
-    expect(guessColumnTypeFromValue(3.125e7)).toBe(ColumnType.number);
-    expect(guessColumnTypeFromValue(true)).toBe(ColumnType.boolean);
-    expect(guessColumnTypeFromValue(false)).toBe(ColumnType.boolean);
-    expect(guessColumnTypeFromValue(new Date())).toBe(ColumnType.time);
-    expect(guessColumnTypeFromValue(moment())).toBe(ColumnType.time);
+    expect(guessFieldTypeFromValue(1)).toBe(FieldType.number);
+    expect(guessFieldTypeFromValue(1.234)).toBe(FieldType.number);
+    expect(guessFieldTypeFromValue(3.125e7)).toBe(FieldType.number);
+    expect(guessFieldTypeFromValue(true)).toBe(FieldType.boolean);
+    expect(guessFieldTypeFromValue(false)).toBe(FieldType.boolean);
+    expect(guessFieldTypeFromValue(new Date())).toBe(FieldType.time);
+    expect(guessFieldTypeFromValue(moment())).toBe(FieldType.time);
   });
 
   it('Guess Colum Types from strings', () => {
-    expect(guessColumnTypeFromValue('1')).toBe(ColumnType.number);
-    expect(guessColumnTypeFromValue('1.234')).toBe(ColumnType.number);
-    expect(guessColumnTypeFromValue('3.125e7')).toBe(ColumnType.number);
-    expect(guessColumnTypeFromValue('True')).toBe(ColumnType.boolean);
-    expect(guessColumnTypeFromValue('FALSE')).toBe(ColumnType.boolean);
-    expect(guessColumnTypeFromValue('true')).toBe(ColumnType.boolean);
-    expect(guessColumnTypeFromValue('xxxx')).toBe(ColumnType.string);
+    expect(guessFieldTypeFromValue('1')).toBe(FieldType.number);
+    expect(guessFieldTypeFromValue('1.234')).toBe(FieldType.number);
+    expect(guessFieldTypeFromValue('3.125e7')).toBe(FieldType.number);
+    expect(guessFieldTypeFromValue('True')).toBe(FieldType.boolean);
+    expect(guessFieldTypeFromValue('FALSE')).toBe(FieldType.boolean);
+    expect(guessFieldTypeFromValue('true')).toBe(FieldType.boolean);
+    expect(guessFieldTypeFromValue('xxxx')).toBe(FieldType.string);
   });
 
   it('Guess Colum Types from 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']],
     };
-    const norm = guessColumnTypes(table);
-    expect(norm.columns[0].type).toBe(ColumnType.number);
-    expect(norm.columns[1].type).toBe(ColumnType.string);
-    expect(norm.columns[2].type).toBeUndefined();
-    expect(norm.columns[3].type).toBe(ColumnType.time); // based on name
+    const norm = guessFieldTypes(table);
+    expect(norm.fields[0].type).toBe(FieldType.number);
+    expect(norm.fields[1].type).toBe(FieldType.string);
+    expect(norm.fields[2].type).toBeUndefined();
+    expect(norm.fields[3].type).toBe(FieldType.time); // based on name
   });
 });

+ 70 - 52
packages/grafana-ui/src/utils/processTableData.ts

@@ -7,7 +7,7 @@ import moment from 'moment';
 import Papa, { ParseError, ParseMeta } from 'papaparse';
 
 // Types
-import { TableData, Column, TimeSeries, ColumnType } from '../types';
+import { SeriesData, Field, TimeSeries, FieldType, TableData } from '../types';
 
 // Subset of all parse options
 export interface TableParseOptions {
@@ -31,12 +31,12 @@ export interface TableParseDetails {
  * @returns a new table that has equal length rows, or the same
  * table if no changes were needed
  */
-export function matchRowSizes(table: TableData): TableData {
+export function matchRowSizes(table: SeriesData): SeriesData {
   const { rows } = table;
-  let { columns } = table;
+  let { fields } = table;
 
   let sameSize = true;
-  let size = columns.length;
+  let size = fields.length;
   rows.forEach(row => {
     if (size !== row.length) {
       sameSize = false;
@@ -47,13 +47,13 @@ export function matchRowSizes(table: TableData): TableData {
     return table;
   }
 
-  // Pad Columns
-  if (size !== columns.length) {
-    const diff = size - columns.length;
-    columns = [...columns];
+  // Pad Fields
+  if (size !== fields.length) {
+    const diff = size - fields.length;
+    fields = [...fields];
     for (let i = 0; i < diff; i++) {
-      columns.push({
-        text: 'Column ' + (columns.length + 1),
+      fields.push({
+        name: 'Field ' + (fields.length + 1),
       });
     }
   }
@@ -72,30 +72,30 @@ export function matchRowSizes(table: TableData): TableData {
   });
 
   return {
-    columns,
+    fields,
     rows: fixedRows,
   };
 }
 
-function makeColumns(values: any[]): Column[] {
+function makeFields(values: any[]): Field[] {
   return values.map((value, index) => {
     if (!value) {
-      value = 'Column ' + (index + 1);
+      value = 'Field ' + (index + 1);
     }
     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 options
  * @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 { data, meta, errors } = results;
 
@@ -118,7 +118,7 @@ export function parseCSV(text: string, options?: TableParseOptions, details?: Ta
       details.errors = errors;
     }
     return {
-      columns: [],
+      fields: [],
       rows: [],
     };
   }
@@ -128,22 +128,35 @@ export function parseCSV(text: string, options?: TableParseOptions, details?: Ta
   const header = headerIsNotFirstLine ? [] : results.data.shift();
 
   return matchRowSizes({
-    columns: makeColumns(header),
+    fields: makeFields(header),
     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 {
     name: timeSeries.target,
-    columns: [
+    fields: [
       {
-        text: timeSeries.target || 'Value',
+        name: timeSeries.target || 'Value',
         unit: timeSeries.unit,
       },
       {
-        text: 'Time',
-        type: ColumnType.time,
+        name: 'Time',
+        type: FieldType.time,
         unit: 'dateTimeAsIso',
       },
     ],
@@ -151,10 +164,10 @@ function convertTimeSeriesToTableData(timeSeries: TimeSeries): TableData {
   };
 }
 
-export const getFirstTimeColumn = (table: TableData): number => {
-  const { columns } = table;
-  for (let i = 0; i < columns.length; i++) {
-    if (columns[i].type === ColumnType.time) {
+export const getFirstTimeField = (table: SeriesData): number => {
+  const { fields } = table;
+  for (let i = 0; i < fields.length; i++) {
+    if (fields[i].type === FieldType.time) {
       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?
  */
-export function guessColumnTypeFromValue(v: any): ColumnType {
+export function guessFieldTypeFromValue(v: any): FieldType {
   if (isNumber(v)) {
-    return ColumnType.number;
+    return FieldType.number;
   }
 
   if (isString(v)) {
     if (NUMBER.test(v)) {
-      return ColumnType.number;
+      return FieldType.number;
     }
 
     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)) {
-    return ColumnType.boolean;
+    return FieldType.boolean;
   }
 
   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
  */
-function guessColumnTypeFromTable(table: TableData, index: number): ColumnType | undefined {
-  const column = table.columns[index];
+function guessFieldTypeFromTable(table: SeriesData, index: number): FieldType | undefined {
+  const column = table.fields[index];
 
   // 1. Use the column name to guess
-  if (column.text) {
-    const name = column.text.toLowerCase();
+  if (column.name) {
+    const name = column.name.toLowerCase();
     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++) {
     const v = table.rows[i][index];
     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
  * If the table already has column types defined, they will be used
  */
-export const guessColumnTypes = (table: TableData): TableData => {
-  for (let i = 0; i < table.columns.length; i++) {
-    if (!table.columns[i].type) {
+export const guessFieldTypes = (table: SeriesData): SeriesData => {
+  for (let i = 0; i < table.fields.length; i++) {
+    if (!table.fields[i].type) {
       // Somethign is missing a type return a modified copy
       return {
         ...table,
-        columns: table.columns.map((column, index) => {
+        fields: table.fields.map((column, index) => {
           if (column.type) {
             return column;
           }
           // Replace it with a calculated version
           return {
             ...column,
-            type: guessColumnTypeFromTable(table, index),
+            type: guessFieldTypeFromTable(table, index),
           };
         }),
       };
@@ -251,21 +264,26 @@ export const guessColumnTypes = (table: TableData): TableData => {
   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 => {
-  if (data.hasOwnProperty('columns')) {
-    return data as TableData;
+export const isSeriesData = (data: any): data is SeriesData => data && data.hasOwnProperty('fields');
+
+export const toSeriesData = (data: any): SeriesData => {
+  if (data.hasOwnProperty('fields')) {
+    return data as SeriesData;
   }
   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?
   console.warn('Can not convert', data);
   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)) {
     const copy = {
       ...data,

+ 8 - 8
packages/grafana-ui/src/utils/statsCalculator.test.ts

@@ -41,8 +41,8 @@ describe('Stats Calculators', () => {
 
   it('should calculate basic stats', () => {
     const stats = calculateStats({
-      table: basicTable,
-      columnIndex: 0,
+      series: basicTable,
+      fieldIndex: 0,
       stats: ['first', 'last', 'mean'],
     });
 
@@ -58,8 +58,8 @@ describe('Stats Calculators', () => {
 
   it('should support a single stat also', () => {
     const stats = calculateStats({
-      table: basicTable,
-      columnIndex: 0,
+      series: basicTable,
+      fieldIndex: 0,
       stats: ['first'],
     });
 
@@ -70,8 +70,8 @@ describe('Stats Calculators', () => {
 
   it('should get non standard stats', () => {
     const stats = calculateStats({
-      table: basicTable,
-      columnIndex: 0,
+      series: basicTable,
+      fieldIndex: 0,
       stats: [StatID.distinctCount, StatID.changeCount],
     });
 
@@ -81,8 +81,8 @@ describe('Stats Calculators', () => {
 
   it('should calculate step', () => {
     const stats = calculateStats({
-      table: { columns: [{ text: 'A' }], rows: [[100], [200], [300], [400]] },
-      columnIndex: 0,
+      series: { fields: [{ name: 'A' }], rows: [[100], [200], [300], [400]] },
+      fieldIndex: 0,
       stats: [StatID.step, StatID.delta],
     });
 

+ 23 - 23
packages/grafana-ui/src/utils/statsCalculator.ts

@@ -1,7 +1,7 @@
 // Libraries
 import isNumber from 'lodash/isNumber';
 
-import { TableData, NullValueMode } from '../types/index';
+import { SeriesData, NullValueMode } from '../types/index';
 
 export enum StatID {
   sum = 'sum',
@@ -29,7 +29,7 @@ export interface ColumnStats {
 }
 
 // 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 {
   id: string;
@@ -64,8 +64,8 @@ export function getStatsCalculators(ids?: string[]): StatCalculatorInfo[] {
 }
 
 export interface CalculateStatsOptions {
-  table: TableData;
-  columnIndex: number;
+  series: SeriesData;
+  fieldIndex: number;
   stats: string[]; // The stats to calculate
   nullValueMode?: NullValueMode;
 }
@@ -74,7 +74,7 @@ export interface CalculateStatsOptions {
  * @returns an object with a key for each selected stat
  */
 export function calculateStats(options: CalculateStatsOptions): ColumnStats {
-  const { table, columnIndex, stats, nullValueMode } = options;
+  const { series, fieldIndex, stats, nullValueMode } = options;
 
   if (!stats || stats.length < 1) {
     return {};
@@ -82,9 +82,9 @@ export function calculateStats(options: CalculateStatsOptions): ColumnStats {
 
   const queue = getStatsCalculators(stats);
 
-  // Return early for empty tables
+  // Return early for empty series
   // 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;
     for (const stat of queue) {
       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
   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
-  let values = standardStatsStat(table, columnIndex, ignoreNulls, nullAsZero);
+  let values = standardStatsStat(series, fieldIndex, ignoreNulls, nullAsZero);
   for (const calc of queue) {
     if (!values.hasOwnProperty(calc.id) && calc.calculator) {
       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(
-  data: TableData,
-  columnIndex: number,
+  data: SeriesData,
+  fieldIndex: number,
   ignoreNulls: boolean,
   nullAsZero: boolean
 ): ColumnStats {
@@ -250,7 +250,7 @@ function standardStatsStat(
   } as ColumnStats;
 
   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 (ignoreNulls) {
@@ -345,17 +345,17 @@ function standardStatsStat(
   return stats;
 }
 
-function calculateFirst(data: TableData, columnIndex: number, ignoreNulls: boolean, nullAsZero: boolean): ColumnStats {
-  return { first: data.rows[0][columnIndex] };
+function calculateFirst(data: SeriesData, fieldIndex: number, ignoreNulls: boolean, nullAsZero: boolean): ColumnStats {
+  return { first: data.rows[0][fieldIndex] };
 }
 
-function calculateLast(data: TableData, columnIndex: number, ignoreNulls: boolean, nullAsZero: boolean): ColumnStats {
-  return { last: data.rows[data.rows.length - 1][columnIndex] };
+function calculateLast(data: SeriesData, fieldIndex: number, ignoreNulls: boolean, nullAsZero: boolean): ColumnStats {
+  return { last: data.rows[data.rows.length - 1][fieldIndex] };
 }
 
 function calculateChangeCount(
-  data: TableData,
-  columnIndex: number,
+  data: SeriesData,
+  fieldIndex: number,
   ignoreNulls: boolean,
   nullAsZero: boolean
 ): ColumnStats {
@@ -363,7 +363,7 @@ function calculateChangeCount(
   let first = true;
   let last: any = null;
   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 (ignoreNulls) {
         continue;
@@ -383,14 +383,14 @@ function calculateChangeCount(
 }
 
 function calculateDistinctCount(
-  data: TableData,
-  columnIndex: number,
+  data: SeriesData,
+  fieldIndex: number,
   ignoreNulls: boolean,
   nullAsZero: boolean
 ): ColumnStats {
   const distinct = new Set<any>();
   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 (ignoreNulls) {
         continue;

+ 1 - 0
public/app/core/table_model.ts

@@ -9,6 +9,7 @@ interface MutableColumn extends Column {
   title?: string;
   sort?: boolean;
   desc?: boolean;
+  type?: string;
 }
 
 export default class TableModel implements TableData {

+ 10 - 10
public/app/features/dashboard/dashgrid/DataPanel.test.tsx

@@ -1,7 +1,7 @@
 // Library
 import React from 'react';
 
-import { DataPanel, getProcessedTableData } from './DataPanel';
+import { DataPanel, getProcessedSeriesData } from './DataPanel';
 
 describe('DataPanel', () => {
   let dataPanel: DataPanel;
@@ -34,27 +34,27 @@ describe('DataPanel', () => {
       target: '',
       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[0].columns[0].text).toBe(input1.target);
+    expect(data[0].fields[0].name).toBe(input1.target);
     expect(data[0].rows).toBe(input1.datapoints);
 
     // 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
     for (const table of data) {
-      for (const column of table.columns) {
-        expect(column.text).toBeDefined();
+      for (const column of table.fields) {
+        expect(column.name).toBeDefined();
         expect(column.type).toBeDefined();
       }
     }
   });
 
   it('supports null values from query OK', () => {
-    expect(getProcessedTableData([null, null, null, null])).toEqual([]);
-    expect(getProcessedTableData(undefined)).toEqual([]);
-    expect(getProcessedTableData((null as unknown) as any[])).toEqual([]);
-    expect(getProcessedTableData([])).toEqual([]);
+    expect(getProcessedSeriesData([null, null, null, null])).toEqual([]);
+    expect(getProcessedSeriesData(undefined)).toEqual([]);
+    expect(getProcessedSeriesData((null as unknown) as any[])).toEqual([]);
+    expect(getProcessedSeriesData([])).toEqual([]);
   });
 });

+ 10 - 10
public/app/features/dashboard/dashgrid/DataPanel.tsx

@@ -11,16 +11,16 @@ import {
   DataQueryResponse,
   DataQueryError,
   LoadingState,
-  TableData,
+  SeriesData,
   TimeRange,
   ScopedVars,
-  toTableData,
-  guessColumnTypes,
+  toSeriesData,
+  guessFieldTypes,
 } from '@grafana/ui';
 
 interface RenderProps {
   loading: LoadingState;
-  data: TableData[];
+  data: SeriesData[];
 }
 
 export interface Props {
@@ -44,7 +44,7 @@ export interface State {
   isFirstLoad: boolean;
   loading: LoadingState;
   response: DataQueryResponse;
-  data?: TableData[];
+  data?: SeriesData[];
 }
 
 /**
@@ -52,18 +52,18 @@ export interface State {
  *
  * This is also used by PanelChrome for snapshot support
  */
-export function getProcessedTableData(results?: any[]): TableData[] {
+export function getProcessedSeriesData(results?: any[]): SeriesData[] {
   if (!results) {
     return [];
   }
 
-  const tables: TableData[] = [];
+  const series: SeriesData[] = [];
   for (const r of results) {
     if (r) {
-      tables.push(guessColumnTypes(toTableData(r)));
+      series.push(guessFieldTypes(toSeriesData(r)));
     }
   }
-  return tables;
+  return series;
 }
 
 export class DataPanel extends Component<Props, State> {
@@ -167,7 +167,7 @@ export class DataPanel extends Component<Props, State> {
       this.setState({
         loading: LoadingState.Done,
         response: resp,
-        data: getProcessedTableData(resp.data),
+        data: getProcessedSeriesData(resp.data),
         isFirstLoad: false,
       });
     } catch (err) {

+ 4 - 4
public/app/features/dashboard/dashgrid/PanelChrome.tsx

@@ -19,12 +19,12 @@ import config from 'app/core/config';
 // Types
 import { DashboardModel, PanelModel } from '../state';
 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 templateSrv from 'app/features/templating/template_srv';
 
-import { getProcessedTableData } from './DataPanel';
+import { getProcessedSeriesData } from './DataPanel';
 
 const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
 
@@ -141,10 +141,10 @@ export class PanelChrome extends PureComponent<Props, State> {
   }
 
   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 { timeRange, renderCounter } = this.state;
     const PanelComponent = plugin.exports.reactPanel.panel;

+ 2 - 2
public/app/plugins/datasource/influxdb/influx_series.ts

@@ -1,6 +1,6 @@
 import _ from 'lodash';
 import TableModel from 'app/core/table_model';
-import { ColumnType } from '@grafana/ui';
+import { FieldType } from '@grafana/ui';
 
 export default class InfluxSeries {
   series: any;
@@ -157,7 +157,7 @@ export default class InfluxSeries {
         // Check that the first column is indeed 'time'
         if (series.columns[0] === 'time') {
           // 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++;
         }
         _.each(_.keys(series.tags), key => {

+ 2 - 2
public/app/plugins/datasource/prometheus/result_transformer.ts

@@ -1,6 +1,6 @@
 import _ from 'lodash';
 import TableModel from 'app/core/table_model';
-import { TimeSeries, ColumnType } from '@grafana/ui';
+import { TimeSeries, FieldType } from '@grafana/ui';
 
 export class ResultTransformer {
   constructor(private templateSrv) {}
@@ -98,7 +98,7 @@ export class ResultTransformer {
 
     // Sort metric labels, create columns for them and record their index
     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) => {
       metricLabels[label] = labelIndex + 1;
       table.columns.push({ text: label, filterable: true });

+ 6 - 6
public/app/plugins/panel/graph2/GraphPanel.tsx

@@ -2,7 +2,7 @@
 import _ from 'lodash';
 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 { getFlotPairs } from '@grafana/ui/src/utils/flotPairs';
 
@@ -15,16 +15,16 @@ export class GraphPanel extends PureComponent<Props> {
 
     const vmSeries: TimeSeriesVMs = [];
     for (const table of data) {
-      const timeColumn = getFirstTimeColumn(table);
+      const timeColumn = getFirstTimeField(table);
       if (timeColumn < 0) {
         continue;
       }
 
-      for (let i = 0; i < table.columns.length; i++) {
-        const column = table.columns[i];
+      for (let i = 0; i < table.fields.length; i++) {
+        const column = table.fields[i];
 
         // Show all numeric columns
-        if (column.type === ColumnType.number) {
+        if (column.type === FieldType.number) {
           // Use external calculator just to make sure it works :)
           const points = getFlotPairs({
             rows: table.rows,
@@ -34,7 +34,7 @@ export class GraphPanel extends PureComponent<Props> {
           });
 
           vmSeries.push({
-            label: column.text,
+            label: column.name,
             data: points,
             color: colors[vmSeries.length % colors.length],
 

+ 8 - 8
public/app/plugins/panel/singlestat2/SingleStatPanel.tsx

@@ -4,7 +4,7 @@ import React, { PureComponent, CSSProperties } from 'react';
 // 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 { getDisplayProcessor } from '@grafana/ui';
 import { ProcessedValuesRepeater } from './ProcessedValuesRepeater';
@@ -26,19 +26,19 @@ export const getSingleStatValues = (props: PanelProps<SingleStatBaseOptions>): D
 
   const values: DisplayValue[] = [];
 
-  for (const table of data) {
+  for (const series of data) {
     if (stat === 'name') {
-      values.push(display(table.name));
+      values.push(display(series.name));
     }
 
-    for (let i = 0; i < table.columns.length; i++) {
-      const column = table.columns[i];
+    for (let i = 0; i < series.fields.length; i++) {
+      const column = series.fields[i];
 
       // Show all columns that are not 'time'
-      if (column.type === ColumnType.number) {
+      if (column.type === FieldType.number) {
         const stats = calculateStats({
-          table,
-          columnIndex: i,
+          series,
+          fieldIndex: i,
           stats: [stat], // The stats to calculate
           nullValueMode: NullValueMode.Null,
         });