Browse Source

more options in storybook

ryan 6 years ago
parent
commit
0446fb9171

+ 32 - 6
packages/grafana-ui/src/components/TableReducePicker/TableReducePicker.story.tsx

@@ -4,6 +4,7 @@ import { storiesOf } from '@storybook/react';
 import { action } from '@storybook/addon-actions';
 import { action } from '@storybook/addon-actions';
 import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
 import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
 import { TableReducePicker } from './TableReducePicker';
 import { TableReducePicker } from './TableReducePicker';
+import { text, boolean } from '@storybook/addon-knobs';
 
 
 interface State {
 interface State {
   reducers: string[];
   reducers: string[];
@@ -13,17 +14,33 @@ export class WrapperWithState extends PureComponent<any, State> {
   constructor(props: any) {
   constructor(props: any) {
     super(props);
     super(props);
     this.state = {
     this.state = {
-      reducers: [], // nothing?
+      reducers: this.toReducersArray(props.initialReducers),
     };
     };
   }
   }
 
 
+  toReducersArray = (txt: string): string[] => {
+    return txt.split(',').map(v => v.trim());
+  };
+
+  componentDidUpdate(prevProps: any) {
+    const { initialReducers } = this.props;
+    if (initialReducers !== prevProps.initialReducers) {
+      this.setState({ reducers: this.toReducersArray(initialReducers) });
+    }
+  }
+
   render() {
   render() {
+    const { placeholder, defaultReducer, allowMultiple } = this.props;
+    const { reducers } = this.state;
+
     return (
     return (
       <TableReducePicker
       <TableReducePicker
-        {...this.props}
-        reducers={this.state.reducers}
+        placeholder={placeholder}
+        defaultReducer={defaultReducer}
+        allowMultiple={allowMultiple}
+        reducers={reducers}
         onChange={(reducers: string[]) => {
         onChange={(reducers: string[]) => {
-          action('Reduce')(reducers);
+          action('Picked:')(reducers);
           this.setState({ reducers });
           this.setState({ reducers });
         }}
         }}
       />
       />
@@ -33,10 +50,19 @@ export class WrapperWithState extends PureComponent<any, State> {
 
 
 const story = storiesOf('UI/TableReducePicker', module);
 const story = storiesOf('UI/TableReducePicker', module);
 story.addDecorator(withCenteredStory);
 story.addDecorator(withCenteredStory);
-story.add('default', () => {
+story.add('picker', () => {
+  const placeholder = text('Placeholder Text', '');
+  const defaultReducer = text('Default Reducer', '');
+  const allowMultiple = boolean('Allow Multiple', false);
+  const initialReducers = text('Initial Reducers', '');
   return (
   return (
     <div>
     <div>
-      <WrapperWithState />
+      <WrapperWithState
+        placeholder={placeholder}
+        defaultReducer={defaultReducer}
+        allowMultiple={allowMultiple}
+        initialReducers={initialReducers}
+      />
     </div>
     </div>
   );
   );
 });
 });

+ 30 - 16
packages/grafana-ui/src/components/TableReducePicker/TableReducePicker.tsx

@@ -8,14 +8,38 @@ import { getTableReducers } from '../../utils/tableReducer';
 import { SelectOptionItem } from '../Select/Select';
 import { SelectOptionItem } from '../Select/Select';
 
 
 interface Props {
 interface Props {
+  placeholder?: string;
   onChange: (reducers: string[]) => void;
   onChange: (reducers: string[]) => void;
   reducers: string[];
   reducers: string[];
   width?: number;
   width?: number;
+  allowMultiple?: boolean;
+  defaultReducer?: string;
 }
 }
 
 
 export class TableReducePicker extends PureComponent<Props> {
 export class TableReducePicker extends PureComponent<Props> {
   static defaultProps = {
   static defaultProps = {
     width: 12,
     width: 12,
+    allowMultiple: false,
+  };
+
+  componentDidMount() {
+    console.log('MOUNT!', this);
+    this.checkInput();
+  }
+
+  componentDidUpdate(prevProps: Props) {
+    console.log('UPDATE', this);
+    this.checkInput();
+  }
+
+  checkInput = () => {
+    const { reducers, allowMultiple, defaultReducer, onChange } = this.props;
+    if (!allowMultiple && reducers.length > 1) {
+      onChange(reducers.slice(0, 1));
+    }
+    if (defaultReducer && reducers.length < 1) {
+      onChange([defaultReducer]);
+    }
   };
   };
 
 
   onSelectionChange = (item: SelectOptionItem) => {
   onSelectionChange = (item: SelectOptionItem) => {
@@ -28,28 +52,18 @@ export class TableReducePicker extends PureComponent<Props> {
   };
   };
 
 
   render() {
   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 { width, reducers, allowMultiple, defaultReducer, placeholder } = this.props;
+    const current = getTableReducers(reducers);
 
 
-    const current = reducerOptions.filter(options => reducers.includes(options.value));
     return (
     return (
       <Select
       <Select
         width={width}
         width={width}
         value={current}
         value={current}
-        isMulti={true}
+        isClearable={!defaultReducer}
+        isMulti={allowMultiple}
         isSearchable={true}
         isSearchable={true}
-        options={reducerOptions}
-        placeholder="Choose"
+        options={getTableReducers()}
+        placeholder={placeholder}
         onChange={this.onSelectionChange}
         onChange={this.onSelectionChange}
       />
       />
     );
     );

+ 101 - 53
packages/grafana-ui/src/utils/tableReducer.ts

@@ -3,22 +3,44 @@ 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 */
-type TableReducer = (data: TableData, columnIndexes: number[], ignoreNulls: boolean, nullAsZero: boolean) => any[];
+export enum TableReducerID {
+  sum = 'sum',
+  max = 'max',
+  min = 'min',
+  logmin = 'logmin',
+  mean = 'mean',
+  last = 'last',
+  first = 'first',
+  count = 'count',
+  range = 'range',
+  diff = 'diff',
+
+  allIsZero = 'allIsZero',
+  allIsNull = 'allIsNull',
+}
 
 
-/** Information about the reducing(stats) functions */
+/** Information about the reducing(stats) functions  */
 export interface TableReducerInfo {
 export interface TableReducerInfo {
-  key: string;
-  name: string;
+  value: string; // The ID - value maps directly to select component
+  label: string; // The name - label
   description: string;
   description: string;
+  alias?: string; // optional secondary key.  'avg' vs 'mean', 'total' vs 'sum'
+
+  // Internal details
+  emptyInputResult?: any; // typically null, but some things like 'count' & 'sum' should be zero
   standard: boolean; // The most common stats can all be calculated in a single pass
   standard: boolean; // The most common stats can all be calculated in a single pass
   reducer?: TableReducer;
   reducer?: TableReducer;
-  alias?: string; // optional secondary key.  'avg' vs 'mean'
 }
 }
 
 
 /** Get a list of the known reducing functions */
 /** Get a list of the known reducing functions */
-export function getTableReducers(): TableReducerInfo[] {
-  return reducers;
+export function getTableReducers(ids?: string[]): TableReducerInfo[] {
+  if (!hasBuiltIndex) {
+    getById(TableReducerID.sum); // Force the registry to load
+  }
+  if (ids === null || ids === undefined) {
+    return listOfReducers;
+  }
+  return ids.map(id => getById(id));
 }
 }
 
 
 export interface TableReducerOptions {
 export interface TableReducerOptions {
@@ -34,39 +56,22 @@ export function reduceTableData(data: TableData, options: TableReducerOptions):
   const ignoreNulls = options.nullValueMode === NullValueMode.Ignore;
   const ignoreNulls = options.nullValueMode === NullValueMode.Ignore;
   const nullAsZero = options.nullValueMode === NullValueMode.AsZero;
   const nullAsZero = options.nullValueMode === NullValueMode.AsZero;
 
 
+  const queue = getTableReducers(options.stats);
+
   // Return early for empty tables
   // Return early for empty tables
+  // This lets the concrete implementations assume at least one row
   if (!data.rows || data.rows.length < 1) {
   if (!data.rows || data.rows.length < 1) {
-    const val = nullAsZero ? 0 : null;
-    const rows = [indexes.map(v => val)];
-    return options.stats.map(stat => {
+    return queue.map(stat => {
       return {
       return {
         columns,
         columns,
-        rows,
+        rows: [indexes.map(v => stat.emptyInputResult)],
         type: 'table',
         type: 'table',
         columnMap: {},
         columnMap: {},
       };
       };
     });
     });
   }
   }
 
 
-  if (registry == null) {
-    registry = new Map<string, TableReducerInfo>();
-    reducers.forEach(calc => {
-      registry!.set(calc.key, calc);
-      if (calc.alias) {
-        registry!.set(calc.alias, calc);
-      }
-    });
-  }
-
-  const queue = options.stats.map(key => {
-    const c = registry!.get(key);
-    if (!c) {
-      throw new Error('Unknown stats calculator: ' + key);
-    }
-    return c;
-  });
-
-  // Avoid the standard calculator if possible
+  // Avoid calculating all the standard stats if possible
   if (queue.length === 1 && queue[0].reducer) {
   if (queue.length === 1 && queue[0].reducer) {
     return [
     return [
       {
       {
@@ -82,7 +87,7 @@ export function reduceTableData(data: TableData, options: TableReducerOptions):
   const standard = standardStatsReducer(data, indexes, ignoreNulls, nullAsZero);
   const standard = standardStatsReducer(data, indexes, ignoreNulls, nullAsZero);
   return queue.map(calc => {
   return queue.map(calc => {
     const values = calc.standard
     const values = calc.standard
-      ? standard.map((s: any) => s[calc.key])
+      ? standard.map((s: any) => s[calc.value])
       : calc.reducer!(data, indexes, ignoreNulls, nullAsZero);
       : calc.reducer!(data, indexes, ignoreNulls, nullAsZero);
     return {
     return {
       columns,
       columns,
@@ -99,6 +104,70 @@ export function reduceTableData(data: TableData, options: TableReducerOptions):
 //
 //
 // ------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------
 
 
+type TableReducer = (data: TableData, columnIndexes: number[], ignoreNulls: boolean, nullAsZero: boolean) => any[];
+
+// private registry of all reducers
+interface TableReducerIndex {
+  [id: string]: TableReducerInfo;
+}
+const listOfReducers: TableReducerInfo[] = [];
+const index: TableReducerIndex = {};
+let hasBuiltIndex = false;
+
+function getById(id: string): TableReducerInfo {
+  if (!hasBuiltIndex) {
+    [
+      {
+        value: TableReducerID.last,
+        label: 'Last',
+        description: 'Last Value (current)',
+        standard: true,
+        alias: 'current',
+        reducer: getLastRow,
+      },
+      { value: TableReducerID.first, label: 'First', description: 'First Value', standard: true, reducer: getFirstRow },
+      { value: TableReducerID.min, label: 'Min', description: 'Minimum Value', standard: true },
+      { value: TableReducerID.max, label: 'Max', description: 'Maximum Value', standard: true },
+      { value: TableReducerID.mean, label: 'Mean', description: 'Average Value', standard: true, alias: 'avg' },
+      {
+        value: TableReducerID.sum,
+        label: 'Total',
+        description: 'The sum of all values',
+        standard: true,
+        alias: 'total',
+      },
+      { value: TableReducerID.count, label: 'Count', description: 'Value Count', standard: true },
+      {
+        value: TableReducerID.range,
+        label: 'Range',
+        description: 'Difference between minimum and maximum values',
+        standard: true,
+      },
+      {
+        value: TableReducerID.diff,
+        label: 'Difference',
+        description: 'Difference between first and last values',
+        standard: true,
+      },
+    ].forEach(calc => {
+      const { value, alias } = calc;
+      if (index.hasOwnProperty(value)) {
+        console.warn('Duplicate Reducer', value, calc, index);
+      }
+      index[value] = calc;
+      if (alias) {
+        if (index.hasOwnProperty(alias)) {
+          console.warn('Duplicate Reducer (alias)', alias, calc, index);
+        }
+        index[alias] = calc;
+      }
+      listOfReducers.push(calc);
+    });
+    hasBuiltIndex = true;
+  }
+  return index[id];
+}
+
 /**
 /**
  * This will return an array of valid indexes and throw an error if invalid request
  * This will return an array of valid indexes and throw an error if invalid request
  */
  */
@@ -132,27 +201,6 @@ interface StandardStats {
   allIsNull: boolean;
   allIsNull: boolean;
 }
 }
 
 
-const reducers: TableReducerInfo[] = [
-  { key: 'sum', alias: 'total', name: 'Total', description: 'The sum of all values', standard: true },
-  { key: 'min', name: 'Min', description: 'Minimum Value', standard: true },
-  { key: 'max', name: 'Max', description: 'Maximum Value', standard: true },
-  { key: 'mean', name: 'Mean', description: 'Average Value', standard: true, alias: 'avg' },
-  { 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: '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 },
-];
-
-let registry: Map<string, TableReducerInfo> | null = null;
-
 function standardStatsReducer(
 function standardStatsReducer(
   data: TableData,
   data: TableData,
   columnIndexes: number[],
   columnIndexes: number[],