Преглед изворни кода

adding simple widget to pick the reducer

ryan пре 6 година
родитељ
комит
9016c18088

+ 42 - 0
packages/grafana-ui/src/components/TableReducePicker/TableReducePicker.story.tsx

@@ -0,0 +1,42 @@
+import React, { PureComponent } from 'react';
+
+import { storiesOf } from '@storybook/react';
+import { action } from '@storybook/addon-actions';
+import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
+import { TableReducePicker } from './TableReducePicker';
+
+interface State {
+  reducers: string[];
+}
+
+export class WrapperWithState extends PureComponent<any, State> {
+  constructor(props: any) {
+    super(props);
+    this.state = {
+      reducers: [], // nothing?
+    };
+  }
+
+  render() {
+    return (
+      <TableReducePicker
+        {...this.props}
+        reducers={this.state.reducers}
+        onChange={(reducers: string[]) => {
+          action('Reduce')(reducers);
+          this.setState({ reducers });
+        }}
+      />
+    );
+  }
+}
+
+const story = storiesOf('UI/TableReducePicker', module);
+story.addDecorator(withCenteredStory);
+story.add('default', () => {
+  return (
+    <div>
+      <WrapperWithState />
+    </div>
+  );
+});

+ 57 - 0
packages/grafana-ui/src/components/TableReducePicker/TableReducePicker.tsx

@@ -0,0 +1,57 @@
+import React, { PureComponent } from 'react';
+
+import isArray from 'lodash/isArray';
+
+import { Select } from '../index';
+
+import { getTableReducers } from '../../utils/tableReducer';
+import { SelectOptionItem } from '../Select/Select';
+
+interface Props {
+  onChange: (reducers: string[]) => void;
+  reducers: string[];
+  width?: number;
+}
+
+export class TableReducePicker extends PureComponent<Props> {
+  static defaultProps = {
+    width: 12,
+  };
+
+  onSelectionChange = (item: SelectOptionItem) => {
+    const { onChange } = this.props;
+    if (isArray(item)) {
+      onChange(item.map(v => v.value));
+    } else {
+      onChange([item.value]);
+    }
+  };
+
+  render() {
+    const { width, reducers } = this.props;
+
+    const allReducers = getTableReducers();
+
+    // Need to transform the data structure to work well with Select
+    const reducerOptions = allReducers.map(info => {
+      return {
+        label: info.name,
+        value: info.key,
+        description: info.description,
+      };
+    });
+
+    const current = reducerOptions.filter(options => reducers.includes(options.value));
+    return (
+      <Select
+        width={width}
+        value={current}
+        isMulti={true}
+        isSearchable={true}
+        options={reducerOptions}
+        placeholder="Choose"
+        onChange={this.onSelectionChange}
+      />
+    );
+  }
+}

+ 39 - 7
packages/grafana-ui/src/utils/tableReducer.test.ts

@@ -2,17 +2,49 @@ import { parseCSV } from './processTableData';
 import { reduceTableData } from './tableReducer';
 import { reduceTableData } from './tableReducer';
 
 
 describe('Table Reducer', () => {
 describe('Table Reducer', () => {
-  it('should calculate average', () => {
-    const table = parseCSV('a,b,c\n1,2,3\n4,5,6');
+  const basicTable = parseCSV('a,b,c\n10,20,30\n20,30,40');
 
 
-    const reduced = reduceTableData(table, {
-      stats: ['last'],
+  it('should calculate stats', () => {
+    const reduced = reduceTableData(basicTable, {
+      columnIndexes: [0, 1],
+      stats: ['first', 'last', 'mean'],
     });
     });
 
 
+    expect(reduced.length).toBe(3);
+
+    // First
+    expect(reduced[0].rows[0]).toEqual([10, 20]);
+
+    // Last
+    expect(reduced[1].rows[0]).toEqual([20, 30]);
+
+    // Mean
+    expect(reduced[2].rows[0]).toEqual([15, 25]);
+  });
+
+  it('should support a single stat also', () => {
+    // First
+    let reduced = reduceTableData(basicTable, {
+      columnIndexes: [0, 1],
+      stats: ['first'],
+    });
     expect(reduced.length).toBe(1);
     expect(reduced.length).toBe(1);
-    expect(reduced[0].rows.length).toBe(1);
-    expect(reduced[0].rows[0]).toEqual(table.rows[1]);
+    expect(reduced[0].rows[0]).toEqual([10, 20]);
 
 
-    console.log('REDUCE', reduced[0].rows);
+    // Last
+    reduced = reduceTableData(basicTable, {
+      columnIndexes: [0, 1],
+      stats: ['last'],
+    });
+    expect(reduced.length).toBe(1);
+    expect(reduced[0].rows[0]).toEqual([20, 30]);
+
+    // Mean
+    reduced = reduceTableData(basicTable, {
+      columnIndexes: [0, 1],
+      stats: ['mean'],
+    });
+    expect(reduced.length).toBe(1);
+    expect(reduced[0].rows[0]).toEqual([15, 25]);
   });
   });
 });
 });

+ 40 - 14
packages/grafana-ui/src/utils/tableReducer.ts

@@ -4,12 +4,7 @@ import isNumber from 'lodash/isNumber';
 import { TableData, NullValueMode } from '../types/index';
 import { TableData, NullValueMode } from '../types/index';
 
 
 /** Reduce each column in a table to a single value */
 /** Reduce each column in a table to a single value */
-export type TableReducer = (
-  data: TableData,
-  columnIndexes: number[],
-  ignoreNulls: boolean,
-  nullAsZero: boolean
-) => any[];
+type TableReducer = (data: TableData, columnIndexes: number[], ignoreNulls: boolean, nullAsZero: boolean) => any[];
 
 
 /** Information about the reducing(stats) functions */
 /** Information about the reducing(stats) functions */
 export interface TableReducerInfo {
 export interface TableReducerInfo {
@@ -33,6 +28,26 @@ export interface TableReducerOptions {
 }
 }
 
 
 export function reduceTableData(data: TableData, options: TableReducerOptions): TableData[] {
 export function reduceTableData(data: TableData, options: TableReducerOptions): TableData[] {
+  const indexes = verifyColumns(data, options);
+  const columns = indexes.map(v => data.columns[v]);
+
+  const ignoreNulls = options.nullValueMode === NullValueMode.Ignore;
+  const nullAsZero = options.nullValueMode === NullValueMode.AsZero;
+
+  // Return early for empty tables
+  if (!data.rows || data.rows.length < 1) {
+    const val = nullAsZero ? 0 : null;
+    const rows = [indexes.map(v => val)];
+    return options.stats.map(stat => {
+      return {
+        columns,
+        rows,
+        type: 'table',
+        columnMap: {},
+      };
+    });
+  }
+
   if (registry == null) {
   if (registry == null) {
     registry = new Map<string, TableReducerInfo>();
     registry = new Map<string, TableReducerInfo>();
     reducers.forEach(calc => {
     reducers.forEach(calc => {
@@ -43,12 +58,6 @@ export function reduceTableData(data: TableData, options: TableReducerOptions):
     });
     });
   }
   }
 
 
-  const indexes = verifyColumns(data, options);
-  const columns = indexes.map(v => data.columns[v]);
-
-  const ignoreNulls = options.nullValueMode === NullValueMode.Ignore;
-  const nullAsZero = options.nullValueMode === NullValueMode.AsZero;
-
   const queue = options.stats.map(key => {
   const queue = options.stats.map(key => {
     const c = registry!.get(key);
     const c = registry!.get(key);
     if (!c) {
     if (!c) {
@@ -128,8 +137,15 @@ const reducers: TableReducerInfo[] = [
   { key: 'min', name: 'Min', description: 'Minimum Value', standard: true },
   { key: 'min', name: 'Min', description: 'Minimum Value', standard: true },
   { key: 'max', name: 'Max', description: 'Maximum Value', standard: true },
   { key: 'max', name: 'Max', description: 'Maximum Value', standard: true },
   { key: 'mean', name: 'Mean', description: 'Average Value', standard: true, alias: 'avg' },
   { key: 'mean', name: 'Mean', description: 'Average Value', standard: true, alias: 'avg' },
-  { key: 'first', name: 'First', description: 'First Value', standard: true },
-  { key: 'last', name: 'Last', description: 'Last Value (current)', standard: true, alias: 'current' },
+  { key: 'first', name: 'First', description: 'First Value', standard: true, reducer: getFirstRow },
+  {
+    key: 'last',
+    name: 'Last',
+    description: 'Last Value (current)',
+    standard: true,
+    alias: 'current',
+    reducer: getLastRow,
+  },
   { key: 'count', name: 'Count', description: 'Value Count', standard: true },
   { key: 'count', name: 'Count', description: 'Value Count', standard: true },
   { key: 'range', name: 'Range', description: 'Difference between minimum and maximum values', standard: true },
   { key: 'range', name: 'Range', description: 'Difference between minimum and maximum values', standard: true },
   { key: 'diff', name: 'Difference', description: 'Difference between first and last values', standard: true },
   { key: 'diff', name: 'Difference', description: 'Difference between first and last values', standard: true },
@@ -235,3 +251,13 @@ function standardStatsReducer(
 
 
   return column;
   return column;
 }
 }
+
+function getFirstRow(data: TableData, columnIndexes: number[], ignoreNulls: boolean, nullAsZero: boolean): any[] {
+  const row = data.rows[0];
+  return columnIndexes.map(idx => row[idx]);
+}
+
+function getLastRow(data: TableData, columnIndexes: number[], ignoreNulls: boolean, nullAsZero: boolean): any[] {
+  const row = data.rows[data.rows.length - 1];
+  return columnIndexes.map(idx => row[idx]);
+}