VizLegend: Implement natural sort, simplify, optimize (#78570)

This commit is contained in:
Leon Sorokin
2023-11-27 16:39:43 -06:00
committed by GitHub
parent e9573a613d
commit b022ddeee8
2 changed files with 63 additions and 47 deletions
+6
View File
@@ -924,6 +924,12 @@ exports[`better eslint`] = {
"packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx:5381": [
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"]
],
"packages/grafana-ui/src/components/VizLegend/VizLegendTable.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Do not use any type assertions.", "3"]
],
"packages/grafana-ui/src/components/VizLegend/types.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
@@ -1,14 +1,16 @@
import { css, cx } from '@emotion/css';
import { orderBy } from 'lodash';
import React from 'react';
import { DisplayValue, GrafanaTheme2 } from '@grafana/data';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '../../themes/ThemeContext';
import { Icon } from '../Icon/Icon';
import { LegendTableItem } from './VizLegendTableItem';
import { VizLegendTableProps } from './types';
import { VizLegendItem, VizLegendTableProps } from './types';
const nameSortKey = 'Name';
const naturalCompare = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare;
/**
* @internal
@@ -27,42 +29,53 @@ export const VizLegendTable = <T extends unknown>({
isSortable,
}: VizLegendTableProps<T>): JSX.Element => {
const styles = useStyles2(getStyles);
const stats: Record<string, DisplayValue> = {};
const nameSortKey = 'Name';
const header: Record<string, string> = {};
if (isSortable) {
// placeholder displayValue for Name
stats[nameSortKey] = { description: 'name', numeric: 0, text: '' };
header[nameSortKey] = '';
}
for (const item of items) {
if (item.getDisplayValues) {
for (const displayValue of item.getDisplayValues()) {
stats[displayValue.title ?? '?'] = displayValue;
header[displayValue.title ?? '?'] = displayValue.description ?? '';
}
}
}
const sortedItems = sortKey
? orderBy(
items,
(item) => {
if (sortKey === nameSortKey) {
return item.label;
}
if (sortKey != null) {
let itemVals = new Map<VizLegendItem, string | number>();
if (item.getDisplayValues) {
const stat = item.getDisplayValues().filter((stat) => stat.title === sortKey)[0];
items.forEach((item) => {
if (sortKey === nameSortKey) {
itemVals.set(item, item.label);
} else if (item.getDisplayValues) {
const stat = item.getDisplayValues().find((stat) => stat.title === sortKey);
const val = stat == null || Number.isNaN(stat.numeric) ? -Infinity : stat.numeric;
itemVals.set(item, val);
}
});
if (stat) {
return isNaN(stat.numeric) ? -Infinity : stat.numeric;
}
}
return undefined;
},
sortDesc ? 'desc' : 'asc'
)
: items;
let sortMult = sortDesc ? -1 : 1;
if (sortKey === nameSortKey) {
// string sort
items.sort((a, b) => {
let aVal = itemVals.get(a) as string;
let bVal = itemVals.get(b) as string;
return sortMult * naturalCompare(aVal, bVal);
});
} else {
// numeric sort
items.sort((a, b) => {
let aVal = itemVals.get(a) as number;
let bVal = itemVals.get(b) as number;
return sortMult * (aVal - bVal);
});
}
}
if (!itemRenderer) {
/* eslint-disable-next-line react/display-name */
@@ -83,29 +96,26 @@ export const VizLegendTable = <T extends unknown>({
<thead>
<tr>
{!isSortable && <th></th>}
{Object.keys(stats).map((columnTitle) => {
const displayValue = stats[columnTitle];
return (
<th
title={displayValue.description}
key={columnTitle}
className={cx(styles.header, onToggleSort && styles.headerSortable, isSortable && styles.nameHeader, {
[styles.withIcon]: sortKey === columnTitle,
})}
onClick={() => {
if (onToggleSort) {
onToggleSort(columnTitle);
}
}}
>
{columnTitle}
{sortKey === columnTitle && <Icon size="xs" name={sortDesc ? 'angle-down' : 'angle-up'} />}
</th>
);
})}
{Object.keys(header).map((columnTitle) => (
<th
title={header[columnTitle]}
key={columnTitle}
className={cx(styles.header, onToggleSort && styles.headerSortable, isSortable && styles.nameHeader, {
[styles.withIcon]: sortKey === columnTitle,
})}
onClick={() => {
if (onToggleSort) {
onToggleSort(columnTitle);
}
}}
>
{columnTitle}
{sortKey === columnTitle && <Icon size="xs" name={sortDesc ? 'angle-down' : 'angle-up'} />}
</th>
))}
</tr>
</thead>
<tbody>{sortedItems.map(itemRenderer!)}</tbody>
<tbody>{items.map(itemRenderer!)}</tbody>
</table>
);
};