[release-12.0.1] TableNG: Fix sparkline sorting + column key config (#104955)
TableNG: Fix sparkline sorting + column key config (#104008)
* fix: sorting sparkline cells
* fix: prevent same cell name issues
* chore: clean
* chore: bring back nuked sparkline fix
* chore: mob feedback
* chore: use getDisplayName function
* chore: missed two!!
* chore: improved betterer results
---------
Co-authored-by: Adela Almasan <adela.almasan@grafana.com>
(cherry picked from commit 72932e10b2)
This commit is contained in:
@@ -10,6 +10,7 @@ import { getFieldTypeIcon } from '../../../../types';
|
||||
import { Icon } from '../../../Icon/Icon';
|
||||
import { Filter } from '../Filter/Filter';
|
||||
import { TableColumnResizeActionCallback, FilterType, TableRow, TableSummaryRow } from '../types';
|
||||
import { getDisplayName } from '../utils';
|
||||
|
||||
interface HeaderCellProps {
|
||||
column: Column<TableRow, TableSummaryRow>;
|
||||
@@ -46,16 +47,17 @@ const HeaderCell: React.FC<HeaderCellProps> = ({
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const filterable = field.config?.custom?.filterable ?? false;
|
||||
const displayName = getDisplayName(field);
|
||||
|
||||
let isColumnFilterable = filterable;
|
||||
if (field.config.custom?.filterable !== filterable) {
|
||||
isColumnFilterable = field.config.custom?.filterable || false;
|
||||
}
|
||||
// we have to remove/reset the filter if the column is not filterable
|
||||
if (!isColumnFilterable && filter[field.name]) {
|
||||
if (!isColumnFilterable && filter[displayName]) {
|
||||
setFilter((filter: FilterType) => {
|
||||
const newFilter = { ...filter };
|
||||
delete newFilter[field.name];
|
||||
delete newFilter[displayName];
|
||||
return newFilter;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
FILTER_OUT_OPERATOR,
|
||||
TableCellNGProps,
|
||||
} from '../types';
|
||||
import { getCellColors, getTextAlign } from '../utils';
|
||||
import { getCellColors, getDisplayName, getTextAlign } from '../utils';
|
||||
|
||||
import { ActionsCell } from './ActionsCell';
|
||||
import AutoCell from './AutoCell';
|
||||
@@ -49,6 +49,7 @@ export function TableCellNG(props: TableCellNGProps) {
|
||||
} = props;
|
||||
|
||||
const cellInspect = field.config?.custom?.inspect ?? false;
|
||||
const displayName = getDisplayName(field);
|
||||
|
||||
const { config: fieldConfig } = field;
|
||||
const defaultCellOptions: TableAutoCellOptions = { type: TableCellDisplayMode.Auto };
|
||||
@@ -171,15 +172,23 @@ export function TableCellNG(props: TableCellNGProps) {
|
||||
|
||||
const onFilterFor = useCallback(() => {
|
||||
if (onCellFilterAdded) {
|
||||
onCellFilterAdded({ key: field.name, operator: FILTER_FOR_OPERATOR, value: String(value ?? '') });
|
||||
onCellFilterAdded({
|
||||
key: displayName,
|
||||
operator: FILTER_FOR_OPERATOR,
|
||||
value: String(value ?? ''),
|
||||
});
|
||||
}
|
||||
}, [field.name, onCellFilterAdded, value]);
|
||||
}, [displayName, onCellFilterAdded, value]);
|
||||
|
||||
const onFilterOut = useCallback(() => {
|
||||
if (onCellFilterAdded) {
|
||||
onCellFilterAdded({ key: field.name, operator: FILTER_OUT_OPERATOR, value: String(value ?? '') });
|
||||
onCellFilterAdded({
|
||||
key: displayName,
|
||||
operator: FILTER_OUT_OPERATOR,
|
||||
value: String(value ?? ''),
|
||||
});
|
||||
}
|
||||
}, [field.name, onCellFilterAdded, value]);
|
||||
}, [displayName, onCellFilterAdded, value]);
|
||||
|
||||
return (
|
||||
<div ref={divWidthRef} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} className={styles.cell}>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Field, formattedValueToString, SelectableValue } from '@grafana/data';
|
||||
|
||||
import { getDisplayName } from '../utils';
|
||||
|
||||
export function calculateUniqueFieldValues(rows: any[], field?: Field) {
|
||||
if (!field || rows.length === 0) {
|
||||
return {};
|
||||
@@ -9,7 +11,7 @@ export function calculateUniqueFieldValues(rows: any[], field?: Field) {
|
||||
|
||||
for (let index = 0; index < rows.length; index++) {
|
||||
const row = rows[index];
|
||||
const fieldValue = row[field.name];
|
||||
const fieldValue = row[getDisplayName(field)];
|
||||
const displayValue = field.display ? field.display(fieldValue) : fieldValue;
|
||||
const value = field.display ? formattedValueToString(displayValue) : displayValue;
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
getCellHeightCalculator,
|
||||
getComparator,
|
||||
getDefaultRowHeight,
|
||||
getDisplayName,
|
||||
getFooterItemNG,
|
||||
getFooterStyles,
|
||||
getIsNestedTable,
|
||||
@@ -196,7 +197,7 @@ export function TableNG(props: TableNGProps) {
|
||||
|
||||
// Create a map of column key to column type
|
||||
const columnTypes = useMemo(
|
||||
() => props.data.fields.reduce<ColumnTypes>((acc, { name, type }) => ({ ...acc, [name]: type }), {}),
|
||||
() => props.data.fields.reduce<ColumnTypes>((acc, field) => ({ ...acc, [getDisplayName(field)]: field.type }), {}),
|
||||
[props.data.fields]
|
||||
);
|
||||
|
||||
@@ -204,7 +205,10 @@ export function TableNG(props: TableNGProps) {
|
||||
const textWraps = useMemo(
|
||||
() =>
|
||||
props.data.fields.reduce<{ [key: string]: boolean }>(
|
||||
(acc, { name, config }) => ({ ...acc, [name]: config?.custom?.cellOptions?.wrapText ?? false }),
|
||||
(acc, field) => ({
|
||||
...acc,
|
||||
[getDisplayName(field)]: field.config?.custom?.cellOptions?.wrapText ?? false,
|
||||
}),
|
||||
{}
|
||||
),
|
||||
[props.data.fields]
|
||||
@@ -218,12 +222,13 @@ export function TableNG(props: TableNGProps) {
|
||||
const widths: Record<string, number> = {};
|
||||
|
||||
// Set default widths from field config if they exist
|
||||
props.data.fields.forEach(({ name, config }) => {
|
||||
const configWidth = config?.custom?.width;
|
||||
props.data.fields.forEach((field) => {
|
||||
const displayName = getDisplayName(field);
|
||||
const configWidth = field.config?.custom?.width;
|
||||
const totalWidth = typeof configWidth === 'number' ? configWidth : COLUMN.DEFAULT_WIDTH;
|
||||
// subtract out padding and 1px right border
|
||||
const contentWidth = totalWidth - 2 * TABLE.CELL_PADDING - 1;
|
||||
widths[name] = contentWidth;
|
||||
widths[displayName] = contentWidth;
|
||||
});
|
||||
|
||||
// Measure actual widths if available
|
||||
@@ -243,9 +248,9 @@ export function TableNG(props: TableNGProps) {
|
||||
}, [props.data.fields]);
|
||||
|
||||
const fieldDisplayType = useMemo(() => {
|
||||
return props.data.fields.reduce<Record<string, TableCellDisplayMode>>((acc, { config, name }) => {
|
||||
if (config?.custom?.cellOptions?.type) {
|
||||
acc[name] = config.custom.cellOptions.type;
|
||||
return props.data.fields.reduce<Record<string, TableCellDisplayMode>>((acc, field) => {
|
||||
if (field.config?.custom?.cellOptions?.type) {
|
||||
acc[getDisplayName(field)] = field.config.custom.cellOptions.type;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
@@ -264,7 +269,9 @@ export function TableNG(props: TableNGProps) {
|
||||
);
|
||||
|
||||
const getDisplayedValue = (row: TableRow, key: string) => {
|
||||
const field = props.data.fields.find((field) => field.name === key)!;
|
||||
const field = props.data.fields.find((field) => {
|
||||
return getDisplayName(field) === key;
|
||||
})!;
|
||||
const displayedValue = formattedValueToString(field.display!(row[key]));
|
||||
return displayedValue;
|
||||
};
|
||||
@@ -763,7 +770,7 @@ export function mapFrameToDataGrid({
|
||||
return;
|
||||
}
|
||||
const fieldTableOptions: TableFieldOptionsType = field.config.custom || {};
|
||||
const key = field.name;
|
||||
const key = getDisplayName(field);
|
||||
const justifyColumnContent = getTextAlign(field);
|
||||
const footerStyles = getFooterStyles(justifyColumnContent);
|
||||
|
||||
@@ -779,7 +786,7 @@ export function mapFrameToDataGrid({
|
||||
key,
|
||||
name: field.name,
|
||||
field,
|
||||
cellClass: textWraps[field.name] ? styles.cellWrapped : styles.cell,
|
||||
cellClass: textWraps[getDisplayName(field)] ? styles.cellWrapped : styles.cell,
|
||||
renderCell: (props: RenderCellProps<TableRow, TableSummaryRow>): JSX.Element => {
|
||||
const { row, rowIdx } = props;
|
||||
const cellType = field.config?.custom?.cellOptions?.type ?? TableCellDisplayMode.Auto;
|
||||
@@ -806,7 +813,7 @@ export function mapFrameToDataGrid({
|
||||
defaultLineHeight,
|
||||
defaultRowHeight,
|
||||
TABLE.CELL_PADDING,
|
||||
textWraps[field.name],
|
||||
textWraps[getDisplayName(field)],
|
||||
field,
|
||||
cellType
|
||||
)
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
ActionModel,
|
||||
InterpolateFunction,
|
||||
FieldType,
|
||||
DataFrameWithValue,
|
||||
} from '@grafana/data';
|
||||
import { TableCellOptions, TableCellHeight, TableFieldOptions } from '@grafana/schema';
|
||||
|
||||
@@ -66,6 +67,7 @@ export type TableCellValue =
|
||||
| Date // FieldType.time
|
||||
| DataFrame // For nested data
|
||||
| DataFrame[] // For nested frames
|
||||
| DataFrameWithValue // For sparklines
|
||||
| undefined; // For undefined values
|
||||
|
||||
export interface TableRow {
|
||||
|
||||
@@ -313,7 +313,7 @@ export function getFooterItemNG(rows: TableRow[], field: Field, options: TableFo
|
||||
const value = reduceField({
|
||||
field: {
|
||||
...field,
|
||||
values: rows.map((row) => row[field.name]),
|
||||
values: rows.map((row) => row[getDisplayName(field)]),
|
||||
},
|
||||
reducers: options.reducer,
|
||||
})[calc];
|
||||
@@ -470,7 +470,7 @@ export const frameToRecords = (frame: DataFrame): TableRow[] => {
|
||||
rows[rowCount] = {
|
||||
__depth: 0,
|
||||
__index: i,
|
||||
${frame.fields.map((field, fieldIdx) => `${JSON.stringify(field.name)}: values[${fieldIdx}][i]`).join(',')}
|
||||
${frame.fields.map((field, fieldIdx) => `${JSON.stringify(getDisplayName(field))}: values[${fieldIdx}][i]`).join(',')}
|
||||
};
|
||||
rowCount += 1;
|
||||
if (rows[rowCount-1]['Nested frames']){
|
||||
@@ -520,6 +520,12 @@ export interface MapFrameToGridOptions extends TableNGProps {
|
||||
const compare = new Intl.Collator('en', { sensitivity: 'base', numeric: true }).compare;
|
||||
export function getComparator(sortColumnType: FieldType): Comparator {
|
||||
switch (sortColumnType) {
|
||||
// Handle sorting for frame type fields (sparklines)
|
||||
case FieldType.frame:
|
||||
return (a, b) => {
|
||||
// @ts-ignore The values are DataFrameWithValue
|
||||
return (a?.value ?? 0) - (b?.value ?? 0);
|
||||
};
|
||||
case FieldType.time:
|
||||
case FieldType.number:
|
||||
case FieldType.boolean:
|
||||
@@ -594,3 +600,7 @@ export function migrateTableDisplayModeToCellOptions(displayMode: TableCellDispl
|
||||
/** Returns true if the DataFrame contains nested frames */
|
||||
export const getIsNestedTable = (dataFrame: DataFrame): boolean =>
|
||||
dataFrame.fields.some(({ type }) => type === FieldType.nestedFrames);
|
||||
|
||||
export const getDisplayName = (field: Field): string => {
|
||||
return field.state?.displayName ?? field.name;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user