mirror of
https://github.com/grafana/grafana.git
synced 2025-02-15 10:03:33 -06:00
Table: Fixes sorting for number fields (#34722)
* Table: Fixes sorting for number fields * Refactor: changes after PR comments * Refactor: found a bug
This commit is contained in:
parent
13768da417
commit
37ab5ec7f4
@ -14,7 +14,7 @@ import {
|
|||||||
useTable,
|
useTable,
|
||||||
} from 'react-table';
|
} from 'react-table';
|
||||||
import { FixedSizeList } from 'react-window';
|
import { FixedSizeList } from 'react-window';
|
||||||
import { getColumns, sortCaseInsensitive } from './utils';
|
import { getColumns, sortCaseInsensitive, sortNumber } from './utils';
|
||||||
import {
|
import {
|
||||||
TableColumnResizeActionCallback,
|
TableColumnResizeActionCallback,
|
||||||
TableFilterActionCallback,
|
TableFilterActionCallback,
|
||||||
@ -153,7 +153,8 @@ export const Table: FC<Props> = memo((props: Props) => {
|
|||||||
stateReducer: stateReducer,
|
stateReducer: stateReducer,
|
||||||
initialState: getInitialState(initialSortBy, memoizedColumns),
|
initialState: getInitialState(initialSortBy, memoizedColumns),
|
||||||
sortTypes: {
|
sortTypes: {
|
||||||
'alphanumeric-insensitive': sortCaseInsensitive,
|
number: sortNumber, // should be replace with the builtin number when react-table is upgraded, see https://github.com/tannerlinsley/react-table/pull/3235
|
||||||
|
'alphanumeric-insensitive': sortCaseInsensitive, // should be replace with the builtin string when react-table is upgraded, see https://github.com/tannerlinsley/react-table/pull/3235
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[initialSortBy, memoizedColumns, memoizedData, resizable, stateReducer]
|
[initialSortBy, memoizedColumns, memoizedData, resizable, stateReducer]
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
getFilteredOptions,
|
getFilteredOptions,
|
||||||
getTextAlign,
|
getTextAlign,
|
||||||
rowToFieldValue,
|
rowToFieldValue,
|
||||||
|
sortNumber,
|
||||||
sortOptions,
|
sortOptions,
|
||||||
valuesToOptions,
|
valuesToOptions,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
@ -414,4 +415,61 @@ describe('Table utils', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('sortNumber', () => {
|
||||||
|
it.each`
|
||||||
|
a | b | expected
|
||||||
|
${{ values: [] }} | ${{ values: [] }} | ${0}
|
||||||
|
${{ values: [undefined] }} | ${{ values: [undefined] }} | ${0}
|
||||||
|
${{ values: [null] }} | ${{ values: [null] }} | ${0}
|
||||||
|
${{ values: [Number.POSITIVE_INFINITY] }} | ${{ values: [Number.POSITIVE_INFINITY] }} | ${0}
|
||||||
|
${{ values: [Number.NEGATIVE_INFINITY] }} | ${{ values: [Number.NEGATIVE_INFINITY] }} | ${0}
|
||||||
|
${{ values: [Number.POSITIVE_INFINITY] }} | ${{ values: [Number.NEGATIVE_INFINITY] }} | ${1}
|
||||||
|
${{ values: [Number.NEGATIVE_INFINITY] }} | ${{ values: [Number.POSITIVE_INFINITY] }} | ${-1}
|
||||||
|
${{ values: ['infinIty'] }} | ${{ values: ['infinIty'] }} | ${0}
|
||||||
|
${{ values: ['infinity'] }} | ${{ values: ['not infinity'] }} | ${0}
|
||||||
|
${{ values: [1] }} | ${{ values: [1] }} | ${0}
|
||||||
|
${{ values: [1.5] }} | ${{ values: [1.5] }} | ${0}
|
||||||
|
${{ values: [2] }} | ${{ values: [1] }} | ${1}
|
||||||
|
${{ values: [25] }} | ${{ values: [2.5] }} | ${1}
|
||||||
|
${{ values: [2.5] }} | ${{ values: [1.5] }} | ${1}
|
||||||
|
${{ values: [1] }} | ${{ values: [2] }} | ${-1}
|
||||||
|
${{ values: [2.5] }} | ${{ values: [25] }} | ${-1}
|
||||||
|
${{ values: [1.5] }} | ${{ values: [2.5] }} | ${-1}
|
||||||
|
${{ values: [1] }} | ${{ values: [] }} | ${1}
|
||||||
|
${{ values: [1] }} | ${{ values: [undefined] }} | ${1}
|
||||||
|
${{ values: [1] }} | ${{ values: [null] }} | ${1}
|
||||||
|
${{ values: [1] }} | ${{ values: [Number.POSITIVE_INFINITY] }} | ${-1}
|
||||||
|
${{ values: [1] }} | ${{ values: [Number.NEGATIVE_INFINITY] }} | ${1}
|
||||||
|
${{ values: [1] }} | ${{ values: ['infinIty'] }} | ${1}
|
||||||
|
${{ values: [-1] }} | ${{ values: ['infinIty'] }} | ${1}
|
||||||
|
${{ values: [] }} | ${{ values: [1] }} | ${-1}
|
||||||
|
${{ values: [undefined] }} | ${{ values: [1] }} | ${-1}
|
||||||
|
${{ values: [null] }} | ${{ values: [1] }} | ${-1}
|
||||||
|
${{ values: [Number.POSITIVE_INFINITY] }} | ${{ values: [1] }} | ${1}
|
||||||
|
${{ values: [Number.NEGATIVE_INFINITY] }} | ${{ values: [1] }} | ${-1}
|
||||||
|
${{ values: ['infinIty'] }} | ${{ values: [1] }} | ${-1}
|
||||||
|
${{ values: ['infinIty'] }} | ${{ values: [-1] }} | ${-1}
|
||||||
|
`("when called with a: '$a.toString', b: '$b.toString' then result should be '$expected'", ({ a, b, expected }) => {
|
||||||
|
expect(sortNumber(a, b, '0')).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('should have good performance', () => {
|
||||||
|
const ITERATIONS = 100000;
|
||||||
|
const a: any = { values: Array(ITERATIONS) };
|
||||||
|
const b: any = { values: Array(ITERATIONS) };
|
||||||
|
for (let i = 0; i < ITERATIONS; i++) {
|
||||||
|
a.values[i] = Math.random() * Date.now();
|
||||||
|
b.values[i] = Math.random() * Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = performance.now();
|
||||||
|
for (let i = 0; i < ITERATIONS; i++) {
|
||||||
|
sortNumber(a, b, i.toString(10));
|
||||||
|
}
|
||||||
|
const stop = performance.now();
|
||||||
|
const diff = stop - start;
|
||||||
|
expect(diff).toBeLessThanOrEqual(20);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -60,6 +60,7 @@ export function getColumns(data: DataFrame, availableWidth: number, columnMinWid
|
|||||||
const selectSortType = (type: FieldType): string => {
|
const selectSortType = (type: FieldType): string => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case FieldType.number:
|
case FieldType.number:
|
||||||
|
return 'number';
|
||||||
case FieldType.time:
|
case FieldType.time:
|
||||||
return 'basic';
|
return 'basic';
|
||||||
default:
|
default:
|
||||||
@ -208,3 +209,22 @@ export function getFilteredOptions(options: SelectableValue[], filterValues?: Se
|
|||||||
export function sortCaseInsensitive(a: Row<any>, b: Row<any>, id: string) {
|
export function sortCaseInsensitive(a: Row<any>, b: Row<any>, id: string) {
|
||||||
return String(a.values[id]).localeCompare(String(b.values[id]), undefined, { sensitivity: 'base' });
|
return String(a.values[id]).localeCompare(String(b.values[id]), undefined, { sensitivity: 'base' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sortNumber needs to have great performance as it is called a lot
|
||||||
|
export function sortNumber(rowA: Row<any>, rowB: Row<any>, id: string) {
|
||||||
|
const a = toNumber(rowA.values[id]);
|
||||||
|
const b = toNumber(rowB.values[id]);
|
||||||
|
return a === b ? 0 : a > b ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toNumber(value: any): number {
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === null || value === undefined || value === '' || isNaN(value)) {
|
||||||
|
return Number.NEGATIVE_INFINITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number(value);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user