diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/HeaderCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/HeaderCell.tsx index 462baacf508..5c53de65886 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/HeaderCell.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/HeaderCell.tsx @@ -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; @@ -46,16 +47,17 @@ const HeaderCell: React.FC = ({ const headerRef = useRef(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; }); } diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/TableCellNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/TableCellNG.tsx index b6faecf4e25..e3ec36ec11b 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/TableCellNG.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/TableCellNG.tsx @@ -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 (
diff --git a/packages/grafana-ui/src/components/Table/TableNG/Filter/utils.ts b/packages/grafana-ui/src/components/Table/TableNG/Filter/utils.ts index 41f77a188f4..862ba9856c6 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Filter/utils.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/Filter/utils.ts @@ -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; diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx index 38c3904a034..bc2f1e384d7 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx @@ -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((acc, { name, type }) => ({ ...acc, [name]: type }), {}), + () => props.data.fields.reduce((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 = {}; // 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>((acc, { config, name }) => { - if (config?.custom?.cellOptions?.type) { - acc[name] = config.custom.cellOptions.type; + return props.data.fields.reduce>((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): 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 ) diff --git a/packages/grafana-ui/src/components/Table/TableNG/types.ts b/packages/grafana-ui/src/components/Table/TableNG/types.ts index b5a8457395b..473f535f609 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/types.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/types.ts @@ -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 { diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.ts index a9c5908dda4..e1dbcd527ce 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/utils.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/utils.ts @@ -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; +};