Jelajahi Sumber

add more functions and tests

ryan 6 tahun lalu
induk
melakukan
7e56514c5a

+ 21 - 5
packages/grafana-ui/src/components/StatsPicker/StatsPicker.tsx

@@ -36,10 +36,10 @@ export class StatsPicker extends PureComponent<Props> {
 
     const current = getStatsCalculators(stats);
     if (current.length !== stats.length) {
-      const found = current.map(v => v.value);
+      const found = current.map(v => v.id);
       const notFound = difference(stats, found);
       console.warn('Unknown stats', notFound, stats);
-      onChange(current.map(stat => stat.value));
+      onChange(current.map(stat => stat.id));
     }
 
     // Make sure there is only one
@@ -65,15 +65,31 @@ export class StatsPicker extends PureComponent<Props> {
 
   render() {
     const { width, stats, allowMultiple, defaultStat, placeholder } = this.props;
-    const current = getStatsCalculators(stats);
+    const options = getStatsCalculators().map(s => {
+      return {
+        value: s.id,
+        label: s.name,
+        desctipiton: s.description,
+      };
+    });
+
+    const value: SelectOptionItem[] = [];
+    stats.forEach(s => {
+      const o = options.find(v => v.value === s);
+      if (o) {
+        value.push(o);
+      }
+    });
+
+    //getStatsCalculators(stats);
     return (
       <Select
         width={width}
-        value={current}
+        value={value}
         isClearable={!defaultStat}
         isMulti={allowMultiple}
         isSearchable={true}
-        options={getStatsCalculators()}
+        options={options}
         placeholder={placeholder}
         onChange={this.onSelectionChange}
       />

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

@@ -32,14 +32,14 @@ describe('Stats Calculators', () => {
     const stats = getStatsCalculators(names);
     expect(stats.length).toBe(2);
 
-    const found = stats.map(v => v.value);
+    const found = stats.map(v => v.id);
     const notFound = _.difference(names, found);
     expect(notFound.length).toBe(2);
 
     expect(notFound[0]).toBe('not a stat');
   });
 
-  it('should calculate stats', () => {
+  it('should calculate basic stats', () => {
     const stats = calculateStats({
       data: basicTable,
       columnIndex: 0,
@@ -60,16 +60,22 @@ describe('Stats Calculators', () => {
     const stats = calculateStats({
       data: basicTable,
       columnIndex: 0,
-      stats: ['first', 'last', 'mean'],
+      stats: ['first'],
     });
 
-    // First
+    // Should do the simple version that just looks up value
+    expect(Object.keys(stats).length).toEqual(1);
     expect(stats.first).toEqual(10);
+  });
 
-    // Last
-    expect(stats.last).toEqual(20);
+  it('should get non standard stats', () => {
+    const stats = calculateStats({
+      data: basicTable,
+      columnIndex: 0,
+      stats: [StatID.distinctCount, StatID.changeCount],
+    });
 
-    // Mean
-    expect(stats.mean).toEqual(15);
+    expect(stats.distinctCount).toEqual(2);
+    expect(stats.changeCount).toEqual(1);
   });
 });

+ 104 - 34
packages/grafana-ui/src/utils/statsCalculator.ts

@@ -17,6 +17,9 @@ export enum StatID {
   delta = 'delta',
   step = 'step',
 
+  changeCount = 'changeCount',
+  distinctCount = 'distinctCount',
+
   allIsZero = 'allIsZero',
   allIsNull = 'allIsNull',
 }
@@ -29,9 +32,10 @@ export interface ColumnStats {
 type StatCalculator = (data: TableData, columnIndex: number, ignoreNulls: boolean, nullAsZero: boolean) => ColumnStats;
 
 export interface StatCalculatorInfo {
-  value: string; // The ID - value maps directly to select component
-  label: string; // The name - label for Select component
+  id: string;
+  name: string;
   description: string;
+
   alias?: string; // optional secondary key.  'avg' vs 'mean', 'total' vs 'sum'
 
   // Internal details
@@ -83,7 +87,7 @@ export function calculateStats(options: CalculateStatsOptions): ColumnStats {
   if (!data.rows || data.rows.length < 1) {
     const stats = {} as ColumnStats;
     queue.forEach(stat => {
-      stats[stat.value] = stat.emptyInputResult !== null ? stat.emptyInputResult : null;
+      stats[stat.id] = stat.emptyInputResult !== null ? stat.emptyInputResult : null;
     });
     return stats;
   }
@@ -93,13 +97,13 @@ 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(data, columnIndex, ignoreNulls, nullAsZero)];
+    return queue[0].calculator(data, columnIndex, ignoreNulls, nullAsZero);
   }
 
   // For now everything can use the standard stats
   let values = standardStatsStat(data, columnIndex, ignoreNulls, nullAsZero);
   queue.forEach(calc => {
-    if (!values.hasOwnProperty(calc.value) && calc.calculator) {
+    if (!values.hasOwnProperty(calc.id) && calc.calculator) {
       values = {
         ...values,
         ...calc.calculator(data, columnIndex, ignoreNulls, nullAsZero),
@@ -127,75 +131,89 @@ function getById(id: string): StatCalculatorInfo | undefined {
   if (!hasBuiltIndex) {
     [
       {
-        value: StatID.last,
-        label: 'Last',
+        id: StatID.last,
+        name: 'Last',
         description: 'Last Value (current)',
         standard: true,
         alias: 'current',
-        stat: calculateLast,
+        calculator: calculateLast,
       },
-      { value: StatID.first, label: 'First', description: 'First Value', standard: true, stat: calculateFirst },
-      { value: StatID.min, label: 'Min', description: 'Minimum Value', standard: true },
-      { value: StatID.max, label: 'Max', description: 'Maximum Value', standard: true },
-      { value: StatID.mean, label: 'Mean', description: 'Average Value', standard: true, alias: 'avg' },
+      { id: StatID.first, name: 'First', description: 'First Value', standard: true, calculator: calculateFirst },
+      { id: StatID.min, name: 'Min', description: 'Minimum Value', standard: true },
+      { id: StatID.max, name: 'Max', description: 'Maximum Value', standard: true },
+      { id: StatID.mean, name: 'Mean', description: 'Average Value', standard: true },
       {
-        value: StatID.sum,
-        label: 'Total',
+        id: StatID.sum,
+        name: 'Total',
         description: 'The sum of all values',
         emptyInputResult: 0,
         standard: true,
         alias: 'total',
       },
       {
-        value: StatID.count,
-        label: 'Count',
+        id: StatID.count,
+        name: 'Count',
         description: 'Number of values in response',
         emptyInputResult: 0,
         standard: true,
       },
       {
-        value: StatID.range,
-        label: 'Range',
+        id: StatID.range,
+        name: 'Range',
         description: 'Difference between minimum and maximum values',
         standard: true,
       },
       {
-        value: StatID.delta,
-        label: 'Delta',
+        id: StatID.delta,
+        name: 'Delta',
         description: 'Cumulative change in value (??? help not really sure ???)',
         standard: true,
       },
       {
-        value: StatID.step,
-        label: 'Step',
+        id: StatID.step,
+        name: 'Step',
         description: 'Minimum interval between values',
         standard: true,
       },
       {
-        value: StatID.diff,
-        label: 'Difference',
+        id: StatID.diff,
+        name: 'Difference',
         description: 'Difference between first and last values',
         standard: true,
       },
       {
-        value: StatID.logmin,
-        label: 'Min (above zero)',
+        id: StatID.logmin,
+        name: 'Min (above zero)',
         description: 'Used for log min scale',
         standard: true,
       },
-    ].forEach(calc => {
-      const { value, alias } = calc;
-      if (index.hasOwnProperty(value)) {
-        console.warn('Duplicate Stat', value, calc, index);
+      {
+        id: StatID.changeCount,
+        name: 'Change Count',
+        description: 'Number of times the value changes',
+        standard: false,
+        calculator: calculateChangeCount,
+      },
+      {
+        id: StatID.distinctCount,
+        name: 'Distinct Count',
+        description: 'Number of distinct values',
+        standard: false,
+        calculator: calculateDistinctCount,
+      },
+    ].forEach(info => {
+      const { id, alias } = info;
+      if (index.hasOwnProperty(id)) {
+        console.warn('Duplicate Stat', id, info, index);
       }
-      index[value] = calc;
+      index[id] = info;
       if (alias) {
         if (index.hasOwnProperty(alias)) {
-          console.warn('Duplicate Stat (alias)', alias, calc, index);
+          console.warn('Duplicate Stat (alias)', alias, info, index);
         }
-        index[alias] = calc;
+        index[alias] = info;
       }
-      listOfStats.push(calc);
+      listOfStats.push(info);
     });
     hasBuiltIndex = true;
   }
@@ -324,9 +342,61 @@ function standardStatsStat(
 }
 
 function calculateFirst(data: TableData, columnIndex: number, ignoreNulls: boolean, nullAsZero: boolean): ColumnStats {
+  console.log('FIRST', data);
   return { first: data.rows[0][columnIndex] };
 }
 
 function calculateLast(data: TableData, columnIndex: number, ignoreNulls: boolean, nullAsZero: boolean): ColumnStats {
   return { last: data.rows[data.rows.length - 1][columnIndex] };
 }
+
+function calculateChangeCount(
+  data: TableData,
+  columnIndex: number,
+  ignoreNulls: boolean,
+  nullAsZero: boolean
+): ColumnStats {
+  let count = 0;
+  let first = true;
+  let last: any = null;
+  for (let i = 0; i < data.rows.length; i++) {
+    let currentValue = data.rows[i][columnIndex];
+    if (currentValue === null) {
+      if (ignoreNulls) {
+        continue;
+      }
+      if (nullAsZero) {
+        currentValue = 0;
+      }
+    }
+    if (!first && last !== currentValue) {
+      count++;
+    }
+    first = false;
+    last = currentValue;
+  }
+
+  return { changeCount: count };
+}
+
+function calculateDistinctCount(
+  data: TableData,
+  columnIndex: 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];
+    if (currentValue === null) {
+      if (ignoreNulls) {
+        continue;
+      }
+      if (nullAsZero) {
+        currentValue = 0;
+      }
+    }
+    distinct.add(currentValue);
+  }
+  return { distinctCount: distinct.size };
+}