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:
@@ -343,6 +343,33 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
|
|||||||
// TODO -- saving for another day.
|
// 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 }) => {
|
test('Tests tooltip interactions', async ({ gotoDashboardPage, selectors }) => {
|
||||||
const dashboardPage = await gotoDashboardPage({
|
const dashboardPage = await gotoDashboardPage({
|
||||||
uid: DASHBOARD_UID,
|
uid: DASHBOARD_UID,
|
||||||
|
|||||||
@@ -804,11 +804,6 @@
|
|||||||
"count": 2
|
"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": {
|
"packages/grafana-ui/src/components/Table/TableRT/Filter.tsx": {
|
||||||
"@typescript-eslint/no-explicit-any": {
|
"@typescript-eslint/no-explicit-any": {
|
||||||
"count": 1
|
"count": 1
|
||||||
|
|||||||
@@ -499,6 +499,9 @@ export const versionedComponents = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
TableNG: {
|
TableNG: {
|
||||||
|
RowExpander: {
|
||||||
|
'12.4.0': 'data-testid tableng row expander',
|
||||||
|
},
|
||||||
Filters: {
|
Filters: {
|
||||||
HeaderButton: {
|
HeaderButton: {
|
||||||
'12.1.0': 'data-testid tableng header filter',
|
'12.1.0': 'data-testid tableng header filter',
|
||||||
|
|||||||
@@ -154,8 +154,18 @@ export function TableNG(props: TableNGProps) {
|
|||||||
|
|
||||||
const resizeHandler = useColumnResize(onColumnResize);
|
const resizeHandler = useColumnResize(onColumnResize);
|
||||||
|
|
||||||
const rows = useMemo(() => frameToRecords(data), [data]);
|
|
||||||
const hasNestedFrames = useMemo(() => getIsNestedTable(data.fields), [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 getTextColorForBackground = useMemo(() => memoize(_getTextColorForBackground, { maxSize: 1000 }), []);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -374,7 +384,11 @@ export function TableNG(props: TableNGProps) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const expandedRecords = applySort(frameToRecords(nestedData), nestedData.fields, sortColumns);
|
const expandedRecords = applySort(
|
||||||
|
frameToRecords(nestedData, nestedFramesFieldName),
|
||||||
|
nestedData.fields,
|
||||||
|
sortColumns
|
||||||
|
);
|
||||||
if (!expandedRecords.length) {
|
if (!expandedRecords.length) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.noDataNested}>
|
<div className={styles.noDataNested}>
|
||||||
@@ -398,7 +412,7 @@ export function TableNG(props: TableNGProps) {
|
|||||||
width: COLUMN.EXPANDER_WIDTH,
|
width: COLUMN.EXPANDER_WIDTH,
|
||||||
minWidth: 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(
|
const fromFields = useCallback(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { t } from '@grafana/i18n';
|
import { t } from '@grafana/i18n';
|
||||||
|
|
||||||
import { useStyles2 } from '../../../../themes/ThemeContext';
|
import { useStyles2 } from '../../../../themes/ThemeContext';
|
||||||
@@ -16,13 +17,21 @@ export function RowExpander({ onCellExpand, isExpanded }: RowExpanderNGProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
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
|
<Icon
|
||||||
aria-label={
|
aria-label={
|
||||||
isExpanded
|
isExpanded
|
||||||
? t('grafana-ui.row-expander-ng.aria-label-collapse', 'Collapse row')
|
? t('grafana-ui.row-expander-ng.aria-label-collapse', 'Collapse row')
|
||||||
: t('grafana-ui.row-expander.aria-label-expand', 'Expand row')
|
: t('grafana-ui.row-expander.aria-label-expand', 'Expand row')
|
||||||
}
|
}
|
||||||
|
aria-expanded={isExpanded}
|
||||||
name={isExpanded ? 'angle-down' : 'angle-right'}
|
name={isExpanded ? 'angle-down' : 'angle-right'}
|
||||||
size="lg"
|
size="lg"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ export interface TableRow {
|
|||||||
|
|
||||||
// Nested table properties
|
// Nested table properties
|
||||||
data?: DataFrame;
|
data?: DataFrame;
|
||||||
__nestedFrames?: DataFrame[];
|
|
||||||
__expanded?: boolean; // For row expansion state
|
__expanded?: boolean; // For row expansion state
|
||||||
|
|
||||||
// Generic typing for column values
|
// Generic typing for column values
|
||||||
@@ -262,7 +261,7 @@ export type TableCellStyles = (theme: GrafanaTheme2, options: TableCellStyleOpti
|
|||||||
export type Comparator = (a: TableCellValue, b: TableCellValue) => number;
|
export type Comparator = (a: TableCellValue, b: TableCellValue) => number;
|
||||||
|
|
||||||
// Type for converting a DataFrame into an array of TableRows
|
// 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
|
// Type for mapping column names to their field types
|
||||||
export type ColumnTypes = Record<string, FieldType>;
|
export type ColumnTypes = Record<string, FieldType>;
|
||||||
|
|||||||
@@ -675,10 +675,12 @@ export function applySort(
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export const frameToRecords = (frame: DataFrame): TableRow[] => {
|
export const frameToRecords = (frame: DataFrame, nestedFramesFieldName?: string): TableRow[] => {
|
||||||
const fnBody = `
|
const fnBody = `
|
||||||
const rows = Array(frame.length);
|
const rows = Array(frame.length);
|
||||||
const values = frame.fields.map(f => f.values);
|
const values = frame.fields.map(f => f.values);
|
||||||
|
const hasNestedFrames = '${nestedFramesFieldName ?? ''}'.length > 0;
|
||||||
|
|
||||||
let rowCount = 0;
|
let rowCount = 0;
|
||||||
for (let i = 0; i < frame.length; i++) {
|
for (let i = 0; i < frame.length; i++) {
|
||||||
rows[rowCount] = {
|
rows[rowCount] = {
|
||||||
@@ -686,11 +688,14 @@ export const frameToRecords = (frame: DataFrame): TableRow[] => {
|
|||||||
__index: i,
|
__index: i,
|
||||||
${frame.fields.map((field, fieldIdx) => `${JSON.stringify(getDisplayName(field))}: values[${fieldIdx}][i]`).join(',')}
|
${frame.fields.map((field, fieldIdx) => `${JSON.stringify(getDisplayName(field))}: values[${fieldIdx}][i]`).join(',')}
|
||||||
};
|
};
|
||||||
rowCount += 1;
|
rowCount++;
|
||||||
if (rows[rowCount-1]['__nestedFrames']){
|
|
||||||
const childFrame = rows[rowCount-1]['__nestedFrames'];
|
if (hasNestedFrames) {
|
||||||
rows[rowCount] = {__depth: 1, __index: i, data: childFrame[0]}
|
const childFrame = rows[rowCount-1][${JSON.stringify(nestedFramesFieldName)}];
|
||||||
rowCount += 1;
|
if (childFrame){
|
||||||
|
rows[rowCount] = {__depth: 1, __index: i, data: childFrame[0]}
|
||||||
|
rowCount++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rows;
|
return rows;
|
||||||
@@ -698,8 +703,9 @@ export const frameToRecords = (frame: DataFrame): TableRow[] => {
|
|||||||
|
|
||||||
// Creates a function that converts a DataFrame into an array of TableRows
|
// 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
|
// Uses new Function() for performance as it's faster than creating rows using loops
|
||||||
const convert = new Function('frame', fnBody) as FrameToRowsConverter;
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
return convert(frame);
|
const convert = new Function('frame', 'nestedFramesFieldName', fnBody) as FrameToRowsConverter;
|
||||||
|
return convert(frame, nestedFramesFieldName);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ----------------------------- Data grid comparator ---------------------------- */
|
/* ----------------------------- Data grid comparator ---------------------------- */
|
||||||
|
|||||||
@@ -911,7 +911,7 @@ const traceSubFrame = (
|
|||||||
subFrame.add(transformSpanToTraceData(span, spanSet, trace));
|
subFrame.add(transformSpanToTraceData(span, spanSet, trace));
|
||||||
});
|
});
|
||||||
|
|
||||||
return subFrame;
|
return toDataFrame(subFrame);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface TraceTableData {
|
interface TraceTableData {
|
||||||
|
|||||||
Reference in New Issue
Block a user