mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
adding simple widget to pick the reducer
This commit is contained in:
parent
b7963f8e87
commit
9016c18088
@ -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>
|
||||
);
|
||||
});
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
@ -2,17 +2,49 @@ import { parseCSV } from './processTableData';
|
||||
import { reduceTableData } from './tableReducer';
|
||||
|
||||
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(1);
|
||||
expect(reduced[0].rows.length).toBe(1);
|
||||
expect(reduced[0].rows[0]).toEqual(table.rows[1]);
|
||||
expect(reduced.length).toBe(3);
|
||||
|
||||
console.log('REDUCE', reduced[0].rows);
|
||||
// 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[0].rows[0]).toEqual([10, 20]);
|
||||
|
||||
// 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]);
|
||||
});
|
||||
});
|
||||
|
@ -4,12 +4,7 @@ import isNumber from 'lodash/isNumber';
|
||||
import { TableData, NullValueMode } from '../types/index';
|
||||
|
||||
/** 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 */
|
||||
export interface TableReducerInfo {
|
||||
@ -33,6 +28,26 @@ export interface TableReducerOptions {
|
||||
}
|
||||
|
||||
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) {
|
||||
registry = new Map<string, TableReducerInfo>();
|
||||
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 c = registry!.get(key);
|
||||
if (!c) {
|
||||
@ -128,8 +137,15 @@ const reducers: TableReducerInfo[] = [
|
||||
{ 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 },
|
||||
{ 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: '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 },
|
||||
@ -235,3 +251,13 @@ function standardStatsReducer(
|
||||
|
||||
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]);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user