Table: Remove hardcoded assumption of __nestedFrames field name (#115117)

* Table: Remove hardcoded assumption of __nestedFrames field name

* E2E for nested tables

* Apply suggestion from @fastfrwrd
This commit is contained in:
Paul Marbach
2025-12-12 21:57:47 +00:00
committed by GitHub
parent cfc5d96c34
commit 359d097154
8 changed files with 73 additions and 20 deletions
@@ -343,6 +343,33 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
// TODO -- saving for another day.
});
test('Tests nested table expansion', async ({ gotoDashboardPage, selectors, page }) => {
const dashboardPage = await gotoDashboardPage({
uid: DASHBOARD_UID,
queryParams: new URLSearchParams({ editPanel: '4' }),
});
await expect(
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Nested tables'))
).toBeVisible();
await waitForTableLoad(page);
await expect(page.locator('[role="row"]')).toHaveCount(3); // header + 2 rows
const firstRowExpander = dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Visualization.TableNG.RowExpander)
.first();
await firstRowExpander.click();
await expect(page.locator('[role="row"]')).not.toHaveCount(3); // more rows are present now, it is dynamic tho.
// TODO: test sorting
await firstRowExpander.click();
await expect(page.locator('[role="row"]')).toHaveCount(3); // back to original state
});
test('Tests tooltip interactions', async ({ gotoDashboardPage, selectors }) => {
const dashboardPage = await gotoDashboardPage({
uid: DASHBOARD_UID,
-5
View File
@@ -804,11 +804,6 @@
"count": 2
}
},
"packages/grafana-ui/src/components/Table/TableNG/utils.ts": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
"packages/grafana-ui/src/components/Table/TableRT/Filter.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
@@ -499,6 +499,9 @@ export const versionedComponents = {
},
},
TableNG: {
RowExpander: {
'12.4.0': 'data-testid tableng row expander',
},
Filters: {
HeaderButton: {
'12.1.0': 'data-testid tableng header filter',
@@ -154,8 +154,18 @@ export function TableNG(props: TableNGProps) {
const resizeHandler = useColumnResize(onColumnResize);
const rows = useMemo(() => frameToRecords(data), [data]);
const hasNestedFrames = useMemo(() => getIsNestedTable(data.fields), [data]);
const nestedFramesFieldName = useMemo(() => {
if (!hasNestedFrames) {
return;
}
const firstNestedField = data.fields.find((f) => f.type === FieldType.nestedFrames);
if (!firstNestedField) {
return;
}
return getDisplayName(firstNestedField);
}, [data, hasNestedFrames]);
const rows = useMemo(() => frameToRecords(data, nestedFramesFieldName), [data, nestedFramesFieldName]);
const getTextColorForBackground = useMemo(() => memoize(_getTextColorForBackground, { maxSize: 1000 }), []);
const {
@@ -374,7 +384,11 @@ export function TableNG(props: TableNGProps) {
return null;
}
const expandedRecords = applySort(frameToRecords(nestedData), nestedData.fields, sortColumns);
const expandedRecords = applySort(
frameToRecords(nestedData, nestedFramesFieldName),
nestedData.fields,
sortColumns
);
if (!expandedRecords.length) {
return (
<div className={styles.noDataNested}>
@@ -398,7 +412,7 @@ export function TableNG(props: TableNGProps) {
width: COLUMN.EXPANDER_WIDTH,
minWidth: COLUMN.EXPANDER_WIDTH,
}),
[commonDataGridProps, data.fields.length, expandedRows, sortColumns, styles]
[commonDataGridProps, data.fields.length, expandedRows, sortColumns, styles, nestedFramesFieldName]
);
const fromFields = useCallback(
@@ -1,6 +1,7 @@
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { t } from '@grafana/i18n';
import { useStyles2 } from '../../../../themes/ThemeContext';
@@ -16,13 +17,21 @@ export function RowExpander({ onCellExpand, isExpanded }: RowExpanderNGProps) {
}
}
return (
<div role="button" tabIndex={0} className={styles.expanderCell} onClick={onCellExpand} onKeyDown={handleKeyDown}>
<div
role="button"
tabIndex={0}
className={styles.expanderCell}
onClick={onCellExpand}
onKeyDown={handleKeyDown}
data-testid={selectors.components.Panels.Visualization.TableNG.RowExpander}
>
<Icon
aria-label={
isExpanded
? t('grafana-ui.row-expander-ng.aria-label-collapse', 'Collapse row')
: t('grafana-ui.row-expander.aria-label-expand', 'Expand row')
}
aria-expanded={isExpanded}
name={isExpanded ? 'angle-down' : 'angle-right'}
size="lg"
/>
@@ -79,7 +79,6 @@ export interface TableRow {
// Nested table properties
data?: DataFrame;
__nestedFrames?: DataFrame[];
__expanded?: boolean; // For row expansion state
// Generic typing for column values
@@ -262,7 +261,7 @@ export type TableCellStyles = (theme: GrafanaTheme2, options: TableCellStyleOpti
export type Comparator = (a: TableCellValue, b: TableCellValue) => number;
// Type for converting a DataFrame into an array of TableRows
export type FrameToRowsConverter = (frame: DataFrame) => TableRow[];
export type FrameToRowsConverter = (frame: DataFrame, nestedFramesFieldName?: string) => TableRow[];
// Type for mapping column names to their field types
export type ColumnTypes = Record<string, FieldType>;
@@ -675,10 +675,12 @@ export function applySort(
/**
* @internal
*/
export const frameToRecords = (frame: DataFrame): TableRow[] => {
export const frameToRecords = (frame: DataFrame, nestedFramesFieldName?: string): TableRow[] => {
const fnBody = `
const rows = Array(frame.length);
const values = frame.fields.map(f => f.values);
const hasNestedFrames = '${nestedFramesFieldName ?? ''}'.length > 0;
let rowCount = 0;
for (let i = 0; i < frame.length; i++) {
rows[rowCount] = {
@@ -686,11 +688,14 @@ export const frameToRecords = (frame: DataFrame): TableRow[] => {
__index: i,
${frame.fields.map((field, fieldIdx) => `${JSON.stringify(getDisplayName(field))}: values[${fieldIdx}][i]`).join(',')}
};
rowCount += 1;
if (rows[rowCount-1]['__nestedFrames']){
const childFrame = rows[rowCount-1]['__nestedFrames'];
rows[rowCount] = {__depth: 1, __index: i, data: childFrame[0]}
rowCount += 1;
rowCount++;
if (hasNestedFrames) {
const childFrame = rows[rowCount-1][${JSON.stringify(nestedFramesFieldName)}];
if (childFrame){
rows[rowCount] = {__depth: 1, __index: i, data: childFrame[0]}
rowCount++;
}
}
}
return rows;
@@ -698,8 +703,9 @@ export const frameToRecords = (frame: DataFrame): TableRow[] => {
// Creates a function that converts a DataFrame into an array of TableRows
// Uses new Function() for performance as it's faster than creating rows using loops
const convert = new Function('frame', fnBody) as FrameToRowsConverter;
return convert(frame);
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const convert = new Function('frame', 'nestedFramesFieldName', fnBody) as FrameToRowsConverter;
return convert(frame, nestedFramesFieldName);
};
/* ----------------------------- Data grid comparator ---------------------------- */
@@ -911,7 +911,7 @@ const traceSubFrame = (
subFrame.add(transformSpanToTraceData(span, spanSet, trace));
});
return subFrame;
return toDataFrame(subFrame);
};
interface TraceTableData {