grafana/packages/grafana-ui/src/components/Table/Table.test.tsx

750 lines
25 KiB
TypeScript
Raw Normal View History

import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { applyFieldOverrides, createTheme, DataFrame, FieldType, toDataFrame } from '@grafana/data';
import { Icon } from '../Icon/Icon';
Table: Move library to react-data-grid (#102482) * Changes galore * Freedom 🗽 * Add feature flag * Latest changes * Basic auto cell type * Partially working bar-gauge * Brokenish but whatevs * Include the toggle doc * TableNG: Context menu (#94094) * feat(table-ng): context menu init commit * betterer * feat(table-ng): re-use contextmenu component * fix(table-ng): close context menu issue * TableNG: Sorting columns (#94200) feat(table-ng): sorting column * fix feature toggle conflict * TableNG: Sorting with custom table header (#95351) * TableNG: Header Toggle (#95310) * TableNG: Multi-column sorting (#95395) feat(table-ng): multi-sorting * TableNG: Column width options (#95426) * feat(table-ng): column width * mouse handle drag event * move resizing task * TableNG: Fix icon sorting direction (#95653) fix(table-ng): sorting icon direction * TableNG: Show table footer (#95313) * TableNG: Show table footer * Revert betterer * Update betterer * Incorporate reducer calculations into footer * Update imports in FooterRow * Use getFooterValue for summary cell render * TableNG: Min column width (#95657) * feat(table-ng): min column width * feat(table-ng): set a min width constant * TableNG: Column alignment (#95679) * feat(table-ng): column alignment * cleaning * feat(table-ng): header cell alignment * optimizations * feat(table-ng): footer cell alignment * calc counter * TableNG: use compiled fn for columns -> records conversion (#95914) * use compiled fn for columns -> records conversion * TableNG: Move key rev and fix width overrides (#95921) * meh * add index to records --------- Co-authored-by: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com> * TableNG: Sparkline Cell Parity (#95690) * sparkline value * todo * Remove unsued shallowField * Pass justifyContent to sparkline --------- Co-authored-by: drew08t <drew08@gmail.com> * TableNG: BarGauge cell updates (#95521) * fix bargauge cell * merge and fix props * cleanup imports * TableNG: Text wrapping (#96041) * feat(table-ng): fix long text cell width * feat(table-ng): fix long text cell width 2 * comment out column rowHeight * fix long text column width * fix types * fix types * naming * Check current header cell ref is defined for key * cleaning * make table re-render when data changed * eslint --------- Co-authored-by: drew08t <drew08@gmail.com> * TableNG: Text overflow (#96641) * feat(table-ng): text overflow * cleaning * TableNG: Fix footer for count (#96802) * TableNG: Table column filter (#96767) * feat(table-ng): add filter form --------- Co-authored-by: drew08t <drew08@gmail.com> Co-authored-by: Leon Sorokin <leeoniya@gmail.com> * TableNG: On column resize trigger (#97004) chore(table-ng): trigger on resize on text wrap only * TableNG: Improve sort performance (#97767) * TableNG: Improve sort performance * clean a bit * a bit more * Remove const that was breaking sort --------- Co-authored-by: Leon Sorokin <leeoniya@gmail.com> * TableNG: Fix sorting (#98141) fix(table-ng): sorting * TableNG: fix multi sorting (#98668) fix(table-ng): multi sorting * TableNG: Column re-size handler (#98901) * feat(table-ng): column re-size handler * TableNG: Fix footer calcs with no reducer (#99347) * TableNG: Update renderHeaderCell with filter dep (#99483) * TableNG: Updated styles for demo (#99530) * style proposal: table ng * chore: revert gauge cell custom stuff * TableNG: Cross-filter (#99459) * feat(table-ng): cross-filter * fix filter update issue * fix filter reset issue * Fix spacebar for filter input --------- Co-authored-by: drew08t <drew08@gmail.com> * TableNG: Filter perfomance optimization (#99620) fix(table-ng): filter performance optimization * TableNG: Refine styling closer to original table (#99625) * TableNG: Support groupToNestedTableTransform (#97134) * TableNG: Support groupToNestedTableTransform * Fix merge issues * Force refresh for now * Remove log * Fix some conflicts * Fix more conflicts * Help avoid clash with compiled frameToRecords keys * Make subtable height unconstrained * Support show field names in nested tables toggle * TableNG: Fix footer + some other misc updates (#99846) fix: footer fixes huzzah * TableNG: Styling - Update styling for cells (#99851) * fix(table-ng): bargauge inner width issue * TableNG: Move header cell component (#99844) * fix(table-ng): move header cell into separate file * Fix sub table --------- Co-authored-by: drew08t <drew08@gmail.com> * TableNG: Auto cell feature parity (#100095) * feat(table-ng): auto cell feature parity * TableNG: JSON cell implementation + hover fixes (#100152) * feat: tableNG json cell + auto fixes * chore: add comment * add justify content to json cell --------- Co-authored-by: Ihor Yeromin <yeryomin.igor@gmail.com> * TableNG: Fix cell hover issue (#100207) * fix(table-ng): cell hover issue * better commenting * TableNG: Text color cell (#100120) feat(table-ng): text color cell feature parity * TableNG: Image cell implementation (#100132) * feat: tableNG image cell * fix: incorporate justify-content correctly * chore: pass down cell options from fieldConfig --------- Co-authored-by: Ihor Yeromin <yeryomin.igor@gmail.com> * TableNG: Cell height performance improvement (#100544) * chore: perf improvement * chore: minor fix * Update packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx Co-authored-by: Leon Sorokin <leeoniya@gmail.com> * chore: fix betterer --------- Co-authored-by: Leon Sorokin <leeoniya@gmail.com> * TableNG: Add pagination (#100165) * TableNG: Add pagination * TableNG: Get collapsed icon state correct + update `rowHeight` (#100556) * fix: get collapsed icon state correct + update condition for calculating row height * chore: some cleanup! * chore: naming to avoid confusion with local state name * TableNG: Add support for `DataLinksCell` (#100459) * TableNG: Improve sub table styling (#100772) * Move files temporarily to fix conflicts * Fix feature flag conflicts * Move files back to cell dir * TableNG: Update inner height of bar gauge cell (#100996) * fix: change inner height of bar gauge cell * chore: move function to utils, cleanup * Remove testing line * TableNG: Add bottom border to column headers + fix footer styling (#101016) * feat: add bottom border to column headers for table parity * feat: summary row style fix * chore: remove redundant style --------- Co-authored-by: drew08t <drew08@gmail.com> * TableNG: Add support for `ActionsCell` (#101024) * TableNG: Cell hover styles + header resize handler indicator (#100770) * fix: tableNG styles * chore: clean up comments * chore: remove column header stuffz for now * fix: refactor to transform/translate + resize handler hover styling * chore: re-think approach - change a lot of things * chore: most recent iteration * chore: wait i like this better * chore: hoist into colors function + clean it up! * moar better * chore: define constants for clarity * chore: calculate rbga to rgb values given background color --------- Co-authored-by: drew08t <drew08@gmail.com> * TableNG: Fix scoll hover jumpy behavior (#101085) * fix(table-ng): hover scroll jumping * Account for panel padding during pagination --------- Co-authored-by: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com> Co-authored-by: drew08t <drew08@gmail.com> * TableNG: Fix imports (#101059) * fix(table-ng): clean imports Co-authored-by: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com> * TableNG: Sorted rows dependent upon filtered rows (#100985) TableNG: Improve multi-sort performance * TableNG: Fix sparkline width (#101164) fix(table-ng): sparkline width * TableNG: Type TableNG (#101257) * feat: type tableNG * chore: push betterer * chore: fix linter + why can't I have inline if statements... GRR! * fix: linter - props name got changed at some point... * feedback: data links prop consistency + json cell robustness * chore: remove unused rowIndex prop --------- Co-authored-by: drew08t <drew08@gmail.com> * TableNG: Add support for datalinks (#100769) Co-authored-by: drew08t <drew08@gmail.com> * Chore: Remove unused import (#102064) remove unused import * Update betterer * BarGauge: Remove z-index (#102220) fix(bargauge): remove z-index * TableNG: Refactor + testing (#102045) * feat: type tableNG * chore: push betterer * chore: fix linter + why can't I have inline if statements... GRR! * fix: linter - props name got changed at some point... * feedback: data links prop consistency + json cell robustness * feat: refactor + tests * chore: fix import lint errors * betterer * chore: fix image cell * chore: revert width function * add test * betterer * chore: fix sorting + add tests * chore: pr feedback --------- Co-authored-by: Ihor Yeromin <yeryomin.igor@gmail.com> Co-authored-by: drew08t <drew08@gmail.com> * TableNG: Fix table suggestion (#102497) fix: defensively guard against missing cellOptions * TableNG: Footer fields calc fix (#102487) * fix: respect footer fields calc selection * chore: add test * TableNG: Image cell hover fix (#102489) fix: image cell hover * TableNG: Persist scrollbars during re render (#102559) * TableNG: Persist scrollbars during re render * Update improved betterer * TableNG: Fix column width override (#102474) * fix(table): column width override * TableNG: Add support for crosshair share (#102410) * TableNG: Add support for crosshair share * Add tests * TableNG: Fix table ng tests (#102645) fix: cellType causing tests to fail * Remove empty file * TableNG: Update util tests (#102646) * TableNG: Add column type icon (#102686) * chore(table-ng): add column type icon * chore(table-ng): clean styling * Use core internationalization outside grafana ui * Import popover directly * Add count to grafana-ui locales * TableNG: Change feature flag to tableNextGen (#102814) Change feature flag to tableNextGen * TableNG: Add row colors (#102706) * chore(table-ng): add row colors * clean up * fix params * fix(table-ng): cell color background indexing --------- Co-authored-by: Kyle Cunningham <kyle@codeincarnate.com> Co-authored-by: Ihor Yeromin <yeryomin.igor@gmail.com> Co-authored-by: Adela Almasan <adela.almasan@grafana.com> Co-authored-by: Leon Sorokin <leeoniya@gmail.com> Co-authored-by: Adela Almasan <88068998+adela-almasan@users.noreply.github.com> Co-authored-by: Alex Spencer <52186778+alexjonspencer1@users.noreply.github.com>
2025-03-26 11:57:57 +08:00
import { Table } from './TableRT/Table';
import { CustomHeaderRendererProps, TableRTProps } from './types';
// mock transition styles to ensure consistent behaviour in unit tests
jest.mock('@floating-ui/react', () => ({
...jest.requireActual('@floating-ui/react'),
useTransitionStyles: () => ({
styles: {},
}),
}));
const dataFrameData = {
name: 'A',
fields: [
{
name: 'time',
type: FieldType.time,
values: [1609459200000, 1609470000000, 1609462800000, 1609466400000],
config: {
custom: {
filterable: false,
},
},
},
{
name: 'temperature',
type: FieldType.number,
values: [10, NaN, 11, 12],
config: {
custom: {
filterable: false,
headerComponent: (props: CustomHeaderRendererProps) => (
<span>
{props.defaultContent}
<Icon aria-label={'header-icon'} name={'ellipsis-v'} />
</span>
),
},
links: [
{
targetBlank: true,
title: 'Value link',
url: '${__value.text}',
},
],
},
},
{
name: 'img',
type: FieldType.string,
values: ['data:image/png;base64,1', 'data:image/png;base64,2', 'data:image/png;base64,3'],
config: {
custom: {
filterable: false,
displayMode: 'image',
},
links: [
{
targetBlank: true,
title: 'Image link',
url: '${__value.text}',
},
],
},
},
],
};
const fullDataFrame = toDataFrame(dataFrameData);
const emptyValuesDataFrame = toDataFrame({
...dataFrameData,
// Remove all values
fields: dataFrameData.fields.map((field) => ({ ...field, values: [] })),
});
function getDataFrame(dataFrame: DataFrame): DataFrame {
Table: Support display of multiple sub tables (#71953) * Add nested option to DataFrame. Refactor Table to use nested dataframes for sub-tables * Use nested frames for TraceQL response * debugging * Fix cell text and table position * Update getItemSize * noHeader size * Update sub table renderer * Update table container height * Cleanup and fix RawPrometheusContainer height * Update resultTransformer and docker script * Updates to TableContainer, resultTransformer after merge * Fixes for table pagination in dashboards * Cell height and show footer enhancement/fix * Sub table links * Update RawPrometheusContainer * Remove console log * Update tests * Update storybook * Remove Tempo demo * Store nested data in single field via its values * Move nested prop into custom * Tempo demo * Add field type & update incorrect logic * Update docker compose image for Tempo * Update packages/grafana-data/src/field/fieldOverrides.ts Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> * Simplify logic for getting nestedFrames and rendering sub tables * Update docs for table * Update nested table bg color * Lighten nested table bg color * Renames * Migrate frames using parentRowIndex and add deprecation notice * Update title * Align expander icon size between Table and interactive table * Table: Refactor out the expanded rows bits * fix spacing * Add line along left side for expanded rows * Disable hover row background when expanded --------- Co-authored-by: André Pereira <adrapereira@gmail.com> Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2023-08-10 19:33:46 +08:00
return applyOverrides(dataFrame);
}
function applyOverrides(dataFrame: DataFrame) {
const dataFrames = applyFieldOverrides({
data: [dataFrame],
fieldConfig: {
defaults: {},
overrides: [],
},
replaceVariables: (value, vars, _format) => {
return vars && value === '${__value.text}' ? '${__value.text} interpolation' : value;
},
timeZone: 'utc',
theme: createTheme(),
});
return dataFrames[0];
}
function getTestContext(propOverrides: Partial<TableRTProps> = {}) {
const onSortByChange = jest.fn();
const onCellFilterAdded = jest.fn();
const onColumnResize = jest.fn();
const props: TableRTProps = {
ariaLabel: 'aria-label',
data: getDataFrame(fullDataFrame),
height: 600,
width: 800,
onSortByChange,
onCellFilterAdded,
onColumnResize,
initialRowIndex: undefined,
};
Object.assign(props, propOverrides);
const { rerender } = render(<Table {...props} />);
return { rerender, onSortByChange, onCellFilterAdded, onColumnResize };
}
function getTable(): HTMLElement {
return screen.getAllByRole('table')[0];
}
function getFooter(): HTMLElement {
return screen.getByTestId('table-footer');
}
function getColumnHeader(name: string | RegExp): HTMLElement {
return within(getTable()).getByRole('columnheader', { name });
}
function getLinks(row: HTMLElement): HTMLElement[] {
return within(row).getAllByRole('link');
}
function getRowsData(rows: HTMLElement[]): Object[] {
let content = [];
for (let i = 1; i < rows.length; i++) {
const row = getLinks(rows[i])[0];
content.push({
time: within(rows[i]).getByText(/2021*/).textContent,
temperature: row.textContent,
link: row.getAttribute('href'),
});
}
return content;
}
describe('Table', () => {
describe('when mounted with EMPTY data', () => {
describe('and Standard Options `No value` value is NOT set', () => {
it('the default `no data` message should be displayed', () => {
getTestContext({ data: toDataFrame([]) });
expect(getTable()).toBeInTheDocument();
expect(screen.queryByRole('row')).not.toBeInTheDocument();
expect(screen.getByText(/No data/i)).toBeInTheDocument();
});
});
describe('and Standard Options `No value` value IS set', () => {
it('the `No value` Standard Options message should be displayed', () => {
const noValuesDisplayText = 'All healthy';
getTestContext({
data: toDataFrame([]),
fieldConfig: { defaults: { noValue: noValuesDisplayText }, overrides: [] },
});
expect(getTable()).toBeInTheDocument();
expect(screen.queryByRole('row')).not.toBeInTheDocument();
expect(screen.getByText(noValuesDisplayText)).toBeInTheDocument();
});
});
});
describe('when mounted with data', () => {
describe('but empty values', () => {
describe('and Standard Options `No value` value is NOT set', () => {
it('the default `no data` message should be displayed', () => {
getTestContext({ data: getDataFrame(emptyValuesDataFrame) });
expect(getTable()).toBeInTheDocument();
expect(screen.getByText(/No data/i)).toBeInTheDocument();
});
});
describe('and Standard Options `No value` value IS set', () => {
it('the `No value` Standard Options message should be displayed', () => {
const noValuesDisplayText = 'All healthy';
getTestContext({
data: getDataFrame(emptyValuesDataFrame),
fieldConfig: { defaults: { noValue: noValuesDisplayText }, overrides: [] },
});
expect(getTable()).toBeInTheDocument();
expect(screen.getByText(noValuesDisplayText)).toBeInTheDocument();
});
});
});
it('then correct rows should be rendered', () => {
getTestContext();
expect(getTable()).toBeInTheDocument();
expect(screen.getAllByRole('columnheader')).toHaveLength(3);
expect(getColumnHeader(/time/)).toBeInTheDocument();
expect(getColumnHeader(/temperature/)).toBeInTheDocument();
expect(getColumnHeader(/img/)).toBeInTheDocument();
const rows = within(getTable()).getAllByRole('row');
expect(rows).toHaveLength(5);
expect(getRowsData(rows)).toEqual([
{ time: '2021-01-01 00:00:00', temperature: '10', link: '${__value.text} interpolation' },
{ time: '2021-01-01 03:00:00', temperature: 'NaN', link: '${__value.text} interpolation' },
{ time: '2021-01-01 01:00:00', temperature: '11', link: '${__value.text} interpolation' },
{ time: '2021-01-01 02:00:00', temperature: '12', link: '${__value.text} interpolation' },
]);
});
});
describe('when mounted with footer', () => {
it('then footer should be displayed', () => {
const footerValues = ['a', 'b', 'c'];
getTestContext({ footerValues });
expect(getTable()).toBeInTheDocument();
expect(getFooter()).toBeInTheDocument();
});
});
describe('when sorting with column header', () => {
it('then correct rows should be rendered', async () => {
getTestContext();
await userEvent.click(within(getColumnHeader(/temperature/)).getByText(/temperature/i));
await userEvent.click(within(getColumnHeader(/temperature/)).getByText(/temperature/i));
const rows = within(getTable()).getAllByRole('row');
expect(rows).toHaveLength(5);
expect(getRowsData(rows)).toEqual([
{ time: '2021-01-01 02:00:00', temperature: '12', link: '${__value.text} interpolation' },
{ time: '2021-01-01 01:00:00', temperature: '11', link: '${__value.text} interpolation' },
{ time: '2021-01-01 00:00:00', temperature: '10', link: '${__value.text} interpolation' },
{ time: '2021-01-01 03:00:00', temperature: 'NaN', link: '${__value.text} interpolation' },
]);
});
});
describe('custom header', () => {
it('Should be rendered', async () => {
getTestContext();
await userEvent.click(within(getColumnHeader(/temperature/)).getByText(/temperature/i));
await userEvent.click(within(getColumnHeader(/temperature/)).getByText(/temperature/i));
const rows = within(getTable()).getAllByRole('row');
expect(rows).toHaveLength(5);
expect(within(rows[0]).getByLabelText('header-icon')).toBeInTheDocument();
});
});
describe('on filtering', () => {
it('the rows should be filtered', async () => {
getTestContext({
data: toDataFrame({
name: 'A',
fields: [
{
name: 'number',
type: FieldType.number,
values: [1, 1, 1, 2, 2, 3, 4, 5],
config: {
custom: {
filterable: true,
},
},
},
],
}),
});
expect(within(getTable()).getAllByRole('row')).toHaveLength(9);
await userEvent.click(within(getColumnHeader(/number/)).getByRole('button', { name: '' }));
await userEvent.click(screen.getByLabelText('1'));
await userEvent.click(screen.getByText('Ok'));
// 3 + header row
expect(within(getTable()).getAllByRole('row')).toHaveLength(4);
});
it('should redo footer calculations', async () => {
getTestContext({
footerOptions: { show: true, reducer: ['sum'] },
data: toDataFrame({
name: 'A',
fields: [
{
name: 'number',
type: FieldType.number,
values: [1, 1, 1, 2, 2],
config: {
custom: {
filterable: true,
},
},
},
],
}),
});
expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('7');
await userEvent.click(within(getColumnHeader(/number/)).getByRole('button', { name: '' }));
await userEvent.click(screen.getByLabelText('1'));
await userEvent.click(screen.getByText('Ok'));
expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('3');
});
it('should filter rows and recalculate footer values when multiple filter values are selected', async () => {
getTestContext({
footerOptions: { show: true, reducer: ['sum'] },
data: toDataFrame({
name: 'A',
fields: [
{
name: 'number',
type: FieldType.number,
values: [1, 1, 1, 2, 2, 3, 3],
config: {
custom: {
filterable: true,
},
},
},
],
}),
});
expect(within(getTable()).getAllByRole('row')).toHaveLength(8);
expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('13');
await userEvent.click(within(getColumnHeader(/number/)).getByRole('button', { name: '' }));
await userEvent.click(screen.getByLabelText('2'));
await userEvent.click(screen.getByLabelText('3'));
await userEvent.click(screen.getByText('Ok'));
//4 + header row
expect(within(getTable()).getAllByRole('row')).toHaveLength(5);
expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('10');
});
it('should reset when clear filters button is pressed', async () => {
getTestContext({
footerOptions: { show: true, reducer: ['sum'] },
data: toDataFrame({
name: 'A',
fields: [
{
name: 'number',
type: FieldType.number,
values: [1, 1, 1, 2, 2],
config: {
custom: {
filterable: true,
},
},
},
],
}),
});
await userEvent.click(within(getColumnHeader(/number/)).getByRole('button', { name: '' }));
await userEvent.click(screen.getByLabelText('1'));
await userEvent.click(screen.getByText('Ok'));
//3 + header row
expect(within(getTable()).getAllByRole('row')).toHaveLength(4);
expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('3');
await userEvent.click(within(getColumnHeader(/number/)).getByRole('button', { name: '' }));
await userEvent.click(screen.getByText('Clear filter'));
//5 + header row
expect(within(getTable()).getAllByRole('row')).toHaveLength(6);
expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('7');
});
});
describe('on data change', () => {
it('should redo footer value calculations', async () => {
const { rerender } = getTestContext({
footerOptions: { show: true, reducer: ['sum'] },
data: toDataFrame({
name: 'A',
fields: [
{
name: 'number',
type: FieldType.number,
values: [1, 1, 1, 2, 2],
config: {
custom: {
filterable: true,
},
},
},
],
}),
});
//5 + header row
expect(within(getTable()).getAllByRole('row')).toHaveLength(6);
expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('7');
const onSortByChange = jest.fn();
const onCellFilterAdded = jest.fn();
const onColumnResize = jest.fn();
const props: TableRTProps = {
ariaLabel: 'aria-label',
data: getDataFrame(fullDataFrame),
height: 600,
width: 800,
onSortByChange,
onCellFilterAdded,
onColumnResize,
};
const propOverrides = {
footerOptions: { show: true, reducer: ['sum'] },
data: toDataFrame({
name: 'A',
fields: [
{
name: 'number',
type: FieldType.number,
values: [1, 1, 1, 2],
config: {
custom: {
filterable: true,
},
},
},
],
}),
};
Object.assign(props, propOverrides);
rerender(<Table {...props} />);
//4 + header row
expect(within(getTable()).getAllByRole('row')).toHaveLength(5);
expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('5');
});
});
describe('on table footer disabled', () => {
it('should not show footer', async () => {
getTestContext({
footerOptions: { show: false, reducer: ['sum'] },
data: toDataFrame({
name: 'A',
fields: [
{
name: 'number',
type: FieldType.number,
values: [1, 1, 1, 2, 2],
config: {
custom: {
filterable: true,
},
},
},
],
}),
});
expect(() => screen.getByTestId('table-footer')).toThrow('Unable to find an element');
});
});
describe('on table footer enabled and count calculation selected', () => {
it('should show count of non-null values', async () => {
getTestContext({
footerOptions: { show: true, reducer: ['count'] },
data: toDataFrame({
name: 'A',
fields: [
{
name: 'number',
type: FieldType.number,
values: [1, 1, 1, 2, null],
config: {
custom: {
filterable: true,
},
},
},
],
}),
});
expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('4');
});
it('should show count of rows when `count rows` is selected', async () => {
getTestContext({
footerOptions: { show: true, reducer: ['count'], countRows: true },
data: toDataFrame({
name: 'A',
fields: [
{
name: 'number1',
type: FieldType.number,
values: [1, 1, 1, 2, null],
config: {
custom: {
filterable: true,
},
},
},
],
}),
});
expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('Count');
expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[1]).toHaveTextContent('5');
});
it('should show correct counts when turning `count rows` on and off', async () => {
const { rerender } = getTestContext({
footerOptions: { show: true, reducer: ['count'], countRows: true },
data: toDataFrame({
name: 'A',
fields: [
{
name: 'number1',
type: FieldType.number,
values: [1, 1, 1, 2, null],
config: {
custom: {
filterable: true,
},
},
},
],
}),
});
expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('Count');
expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[1]).toHaveTextContent('5');
const onSortByChange = jest.fn();
const onCellFilterAdded = jest.fn();
const onColumnResize = jest.fn();
const props: TableRTProps = {
ariaLabel: 'aria-label',
data: getDataFrame(fullDataFrame),
height: 600,
width: 800,
onSortByChange,
onCellFilterAdded,
onColumnResize,
};
const propOverrides = {
footerOptions: { show: true, reducer: ['count'], countRows: false },
data: toDataFrame({
name: 'A',
fields: [
{
name: 'number',
type: FieldType.number,
values: [1, 1, 1, 2, null],
config: {
custom: {
filterable: true,
},
},
},
],
}),
};
Object.assign(props, propOverrides);
rerender(<Table {...props} />);
expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('4');
});
});
Table: Support display of multiple sub tables (#71953) * Add nested option to DataFrame. Refactor Table to use nested dataframes for sub-tables * Use nested frames for TraceQL response * debugging * Fix cell text and table position * Update getItemSize * noHeader size * Update sub table renderer * Update table container height * Cleanup and fix RawPrometheusContainer height * Update resultTransformer and docker script * Updates to TableContainer, resultTransformer after merge * Fixes for table pagination in dashboards * Cell height and show footer enhancement/fix * Sub table links * Update RawPrometheusContainer * Remove console log * Update tests * Update storybook * Remove Tempo demo * Store nested data in single field via its values * Move nested prop into custom * Tempo demo * Add field type & update incorrect logic * Update docker compose image for Tempo * Update packages/grafana-data/src/field/fieldOverrides.ts Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> * Simplify logic for getting nestedFrames and rendering sub tables * Update docs for table * Update nested table bg color * Lighten nested table bg color * Renames * Migrate frames using parentRowIndex and add deprecation notice * Update title * Align expander icon size between Table and interactive table * Table: Refactor out the expanded rows bits * fix spacing * Add line along left side for expanded rows * Disable hover row background when expanded --------- Co-authored-by: André Pereira <adrapereira@gmail.com> Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2023-08-10 19:33:46 +08:00
describe('when mounted with nested data', () => {
beforeEach(() => {
const createNestedFrame = (idx: number) =>
Table: Support display of multiple sub tables (#71953) * Add nested option to DataFrame. Refactor Table to use nested dataframes for sub-tables * Use nested frames for TraceQL response * debugging * Fix cell text and table position * Update getItemSize * noHeader size * Update sub table renderer * Update table container height * Cleanup and fix RawPrometheusContainer height * Update resultTransformer and docker script * Updates to TableContainer, resultTransformer after merge * Fixes for table pagination in dashboards * Cell height and show footer enhancement/fix * Sub table links * Update RawPrometheusContainer * Remove console log * Update tests * Update storybook * Remove Tempo demo * Store nested data in single field via its values * Move nested prop into custom * Tempo demo * Add field type & update incorrect logic * Update docker compose image for Tempo * Update packages/grafana-data/src/field/fieldOverrides.ts Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> * Simplify logic for getting nestedFrames and rendering sub tables * Update docs for table * Update nested table bg color * Lighten nested table bg color * Renames * Migrate frames using parentRowIndex and add deprecation notice * Update title * Align expander icon size between Table and interactive table * Table: Refactor out the expanded rows bits * fix spacing * Add line along left side for expanded rows * Disable hover row background when expanded --------- Co-authored-by: André Pereira <adrapereira@gmail.com> Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2023-08-10 19:33:46 +08:00
applyOverrides(
toDataFrame({
Table: Support display of multiple sub tables (#71953) * Add nested option to DataFrame. Refactor Table to use nested dataframes for sub-tables * Use nested frames for TraceQL response * debugging * Fix cell text and table position * Update getItemSize * noHeader size * Update sub table renderer * Update table container height * Cleanup and fix RawPrometheusContainer height * Update resultTransformer and docker script * Updates to TableContainer, resultTransformer after merge * Fixes for table pagination in dashboards * Cell height and show footer enhancement/fix * Sub table links * Update RawPrometheusContainer * Remove console log * Update tests * Update storybook * Remove Tempo demo * Store nested data in single field via its values * Move nested prop into custom * Tempo demo * Add field type & update incorrect logic * Update docker compose image for Tempo * Update packages/grafana-data/src/field/fieldOverrides.ts Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> * Simplify logic for getting nestedFrames and rendering sub tables * Update docs for table * Update nested table bg color * Lighten nested table bg color * Renames * Migrate frames using parentRowIndex and add deprecation notice * Update title * Align expander icon size between Table and interactive table * Table: Refactor out the expanded rows bits * fix spacing * Add line along left side for expanded rows * Disable hover row background when expanded --------- Co-authored-by: André Pereira <adrapereira@gmail.com> Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2023-08-10 19:33:46 +08:00
name: `nested_frame${idx}`,
fields: [
{
Table: Support display of multiple sub tables (#71953) * Add nested option to DataFrame. Refactor Table to use nested dataframes for sub-tables * Use nested frames for TraceQL response * debugging * Fix cell text and table position * Update getItemSize * noHeader size * Update sub table renderer * Update table container height * Cleanup and fix RawPrometheusContainer height * Update resultTransformer and docker script * Updates to TableContainer, resultTransformer after merge * Fixes for table pagination in dashboards * Cell height and show footer enhancement/fix * Sub table links * Update RawPrometheusContainer * Remove console log * Update tests * Update storybook * Remove Tempo demo * Store nested data in single field via its values * Move nested prop into custom * Tempo demo * Add field type & update incorrect logic * Update docker compose image for Tempo * Update packages/grafana-data/src/field/fieldOverrides.ts Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> * Simplify logic for getting nestedFrames and rendering sub tables * Update docs for table * Update nested table bg color * Lighten nested table bg color * Renames * Migrate frames using parentRowIndex and add deprecation notice * Update title * Align expander icon size between Table and interactive table * Table: Refactor out the expanded rows bits * fix spacing * Add line along left side for expanded rows * Disable hover row background when expanded --------- Co-authored-by: André Pereira <adrapereira@gmail.com> Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2023-08-10 19:33:46 +08:00
name: `humidity_${idx}`,
type: FieldType.string,
values: [`3%_${idx}`, `17%_${idx}`],
},
Table: Support display of multiple sub tables (#71953) * Add nested option to DataFrame. Refactor Table to use nested dataframes for sub-tables * Use nested frames for TraceQL response * debugging * Fix cell text and table position * Update getItemSize * noHeader size * Update sub table renderer * Update table container height * Cleanup and fix RawPrometheusContainer height * Update resultTransformer and docker script * Updates to TableContainer, resultTransformer after merge * Fixes for table pagination in dashboards * Cell height and show footer enhancement/fix * Sub table links * Update RawPrometheusContainer * Remove console log * Update tests * Update storybook * Remove Tempo demo * Store nested data in single field via its values * Move nested prop into custom * Tempo demo * Add field type & update incorrect logic * Update docker compose image for Tempo * Update packages/grafana-data/src/field/fieldOverrides.ts Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> * Simplify logic for getting nestedFrames and rendering sub tables * Update docs for table * Update nested table bg color * Lighten nested table bg color * Renames * Migrate frames using parentRowIndex and add deprecation notice * Update title * Align expander icon size between Table and interactive table * Table: Refactor out the expanded rows bits * fix spacing * Add line along left side for expanded rows * Disable hover row background when expanded --------- Co-authored-by: André Pereira <adrapereira@gmail.com> Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2023-08-10 19:33:46 +08:00
{
name: `status_${idx}`,
type: FieldType.string,
values: [`ok_${idx}`, `humid_${idx}`],
},
Table: Support display of multiple sub tables (#71953) * Add nested option to DataFrame. Refactor Table to use nested dataframes for sub-tables * Use nested frames for TraceQL response * debugging * Fix cell text and table position * Update getItemSize * noHeader size * Update sub table renderer * Update table container height * Cleanup and fix RawPrometheusContainer height * Update resultTransformer and docker script * Updates to TableContainer, resultTransformer after merge * Fixes for table pagination in dashboards * Cell height and show footer enhancement/fix * Sub table links * Update RawPrometheusContainer * Remove console log * Update tests * Update storybook * Remove Tempo demo * Store nested data in single field via its values * Move nested prop into custom * Tempo demo * Add field type & update incorrect logic * Update docker compose image for Tempo * Update packages/grafana-data/src/field/fieldOverrides.ts Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> * Simplify logic for getting nestedFrames and rendering sub tables * Update docs for table * Update nested table bg color * Lighten nested table bg color * Renames * Migrate frames using parentRowIndex and add deprecation notice * Update title * Align expander icon size between Table and interactive table * Table: Refactor out the expanded rows bits * fix spacing * Add line along left side for expanded rows * Disable hover row background when expanded --------- Co-authored-by: André Pereira <adrapereira@gmail.com> Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2023-08-10 19:33:46 +08:00
],
})
Table: Support display of multiple sub tables (#71953) * Add nested option to DataFrame. Refactor Table to use nested dataframes for sub-tables * Use nested frames for TraceQL response * debugging * Fix cell text and table position * Update getItemSize * noHeader size * Update sub table renderer * Update table container height * Cleanup and fix RawPrometheusContainer height * Update resultTransformer and docker script * Updates to TableContainer, resultTransformer after merge * Fixes for table pagination in dashboards * Cell height and show footer enhancement/fix * Sub table links * Update RawPrometheusContainer * Remove console log * Update tests * Update storybook * Remove Tempo demo * Store nested data in single field via its values * Move nested prop into custom * Tempo demo * Add field type & update incorrect logic * Update docker compose image for Tempo * Update packages/grafana-data/src/field/fieldOverrides.ts Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> * Simplify logic for getting nestedFrames and rendering sub tables * Update docs for table * Update nested table bg color * Lighten nested table bg color * Renames * Migrate frames using parentRowIndex and add deprecation notice * Update title * Align expander icon size between Table and interactive table * Table: Refactor out the expanded rows bits * fix spacing * Add line along left side for expanded rows * Disable hover row background when expanded --------- Co-authored-by: André Pereira <adrapereira@gmail.com> Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2023-08-10 19:33:46 +08:00
);
const defaultFrame = getDataFrame(fullDataFrame);
Table: Support display of multiple sub tables (#71953) * Add nested option to DataFrame. Refactor Table to use nested dataframes for sub-tables * Use nested frames for TraceQL response * debugging * Fix cell text and table position * Update getItemSize * noHeader size * Update sub table renderer * Update table container height * Cleanup and fix RawPrometheusContainer height * Update resultTransformer and docker script * Updates to TableContainer, resultTransformer after merge * Fixes for table pagination in dashboards * Cell height and show footer enhancement/fix * Sub table links * Update RawPrometheusContainer * Remove console log * Update tests * Update storybook * Remove Tempo demo * Store nested data in single field via its values * Move nested prop into custom * Tempo demo * Add field type & update incorrect logic * Update docker compose image for Tempo * Update packages/grafana-data/src/field/fieldOverrides.ts Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> * Simplify logic for getting nestedFrames and rendering sub tables * Update docs for table * Update nested table bg color * Lighten nested table bg color * Renames * Migrate frames using parentRowIndex and add deprecation notice * Update title * Align expander icon size between Table and interactive table * Table: Refactor out the expanded rows bits * fix spacing * Add line along left side for expanded rows * Disable hover row background when expanded --------- Co-authored-by: André Pereira <adrapereira@gmail.com> Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2023-08-10 19:33:46 +08:00
getTestContext({
data: applyOverrides({
...defaultFrame,
fields: [
...defaultFrame.fields,
{
name: 'nested',
type: FieldType.nestedFrames,
values: [
[createNestedFrame(0), createNestedFrame(1)],
[createNestedFrame(2), createNestedFrame(3)],
],
Table: Support display of multiple sub tables (#71953) * Add nested option to DataFrame. Refactor Table to use nested dataframes for sub-tables * Use nested frames for TraceQL response * debugging * Fix cell text and table position * Update getItemSize * noHeader size * Update sub table renderer * Update table container height * Cleanup and fix RawPrometheusContainer height * Update resultTransformer and docker script * Updates to TableContainer, resultTransformer after merge * Fixes for table pagination in dashboards * Cell height and show footer enhancement/fix * Sub table links * Update RawPrometheusContainer * Remove console log * Update tests * Update storybook * Remove Tempo demo * Store nested data in single field via its values * Move nested prop into custom * Tempo demo * Add field type & update incorrect logic * Update docker compose image for Tempo * Update packages/grafana-data/src/field/fieldOverrides.ts Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> * Simplify logic for getting nestedFrames and rendering sub tables * Update docs for table * Update nested table bg color * Lighten nested table bg color * Renames * Migrate frames using parentRowIndex and add deprecation notice * Update title * Align expander icon size between Table and interactive table * Table: Refactor out the expanded rows bits * fix spacing * Add line along left side for expanded rows * Disable hover row background when expanded --------- Co-authored-by: André Pereira <adrapereira@gmail.com> Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2023-08-10 19:33:46 +08:00
config: {},
},
],
}),
});
});
it('then correct rows should be rendered and new table is rendered when expander is clicked', async () => {
expect(getTable()).toBeInTheDocument();
expect(screen.getAllByRole('columnheader')).toHaveLength(4);
expect(getColumnHeader(/time/)).toBeInTheDocument();
expect(getColumnHeader(/temperature/)).toBeInTheDocument();
expect(getColumnHeader(/img/)).toBeInTheDocument();
const rows = within(getTable()).getAllByRole('row');
expect(rows).toHaveLength(5);
expect(getRowsData(rows)).toEqual([
{ time: '2021-01-01 00:00:00', temperature: '10', link: '${__value.text} interpolation' },
{ time: '2021-01-01 03:00:00', temperature: 'NaN', link: '${__value.text} interpolation' },
{ time: '2021-01-01 01:00:00', temperature: '11', link: '${__value.text} interpolation' },
{ time: '2021-01-01 02:00:00', temperature: '12', link: '${__value.text} interpolation' },
]);
await userEvent.click(within(rows[1]).getByLabelText('Expand row'));
Table: Support display of multiple sub tables (#71953) * Add nested option to DataFrame. Refactor Table to use nested dataframes for sub-tables * Use nested frames for TraceQL response * debugging * Fix cell text and table position * Update getItemSize * noHeader size * Update sub table renderer * Update table container height * Cleanup and fix RawPrometheusContainer height * Update resultTransformer and docker script * Updates to TableContainer, resultTransformer after merge * Fixes for table pagination in dashboards * Cell height and show footer enhancement/fix * Sub table links * Update RawPrometheusContainer * Remove console log * Update tests * Update storybook * Remove Tempo demo * Store nested data in single field via its values * Move nested prop into custom * Tempo demo * Add field type & update incorrect logic * Update docker compose image for Tempo * Update packages/grafana-data/src/field/fieldOverrides.ts Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> * Simplify logic for getting nestedFrames and rendering sub tables * Update docs for table * Update nested table bg color * Lighten nested table bg color * Renames * Migrate frames using parentRowIndex and add deprecation notice * Update title * Align expander icon size between Table and interactive table * Table: Refactor out the expanded rows bits * fix spacing * Add line along left side for expanded rows * Disable hover row background when expanded --------- Co-authored-by: André Pereira <adrapereira@gmail.com> Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2023-08-10 19:33:46 +08:00
expect(screen.getAllByRole('columnheader')).toHaveLength(8);
expect(getColumnHeader(/humidity_0/)).toBeInTheDocument();
expect(getColumnHeader(/humidity_1/)).toBeInTheDocument();
expect(getColumnHeader(/status_0/)).toBeInTheDocument();
expect(getColumnHeader(/status_1/)).toBeInTheDocument();
const subTable0 = screen.getAllByRole('table')[1];
const subTableRows0 = within(subTable0).getAllByRole('row');
expect(subTableRows0).toHaveLength(3);
expect(within(subTableRows0[1]).getByText(/3%_0/)).toBeInTheDocument();
expect(within(subTableRows0[1]).getByText(/ok_0/)).toBeInTheDocument();
expect(within(subTableRows0[2]).getByText(/17%_0/)).toBeInTheDocument();
expect(within(subTableRows0[2]).getByText(/humid_0/)).toBeInTheDocument();
const subTable1 = screen.getAllByRole('table')[2];
const subTableRows1 = within(subTable1).getAllByRole('row');
expect(subTableRows1).toHaveLength(3);
expect(within(subTableRows1[1]).getByText(/3%_1/)).toBeInTheDocument();
expect(within(subTableRows1[1]).getByText(/ok_1/)).toBeInTheDocument();
expect(within(subTableRows1[2]).getByText(/17%_1/)).toBeInTheDocument();
expect(within(subTableRows1[2]).getByText(/humid_1/)).toBeInTheDocument();
});
it('then properly handle row expansion and sorting', async () => {
expect(getTable()).toBeInTheDocument();
expect(screen.getAllByRole('columnheader')).toHaveLength(4);
expect(getColumnHeader(/time/)).toBeInTheDocument();
expect(getColumnHeader(/temperature/)).toBeInTheDocument();
expect(getColumnHeader(/img/)).toBeInTheDocument();
let rows = within(getTable()).getAllByRole('row');
expect(rows).toHaveLength(5);
expect(getRowsData(rows)).toEqual([
{ time: '2021-01-01 00:00:00', temperature: '10', link: '${__value.text} interpolation' },
{ time: '2021-01-01 03:00:00', temperature: 'NaN', link: '${__value.text} interpolation' },
{ time: '2021-01-01 01:00:00', temperature: '11', link: '${__value.text} interpolation' },
{ time: '2021-01-01 02:00:00', temperature: '12', link: '${__value.text} interpolation' },
]);
// Sort rows, and check the new order
const table = getTable();
await userEvent.click(within(table).getAllByTitle('Toggle SortBy')[0]);
rows = within(table).getAllByRole('row');
expect(rows).toHaveLength(5);
expect(getRowsData(rows)).toEqual([
{ time: '2021-01-01 00:00:00', temperature: '10', link: '${__value.text} interpolation' },
{ time: '2021-01-01 01:00:00', temperature: '11', link: '${__value.text} interpolation' },
{ time: '2021-01-01 02:00:00', temperature: '12', link: '${__value.text} interpolation' },
{ time: '2021-01-01 03:00:00', temperature: 'NaN', link: '${__value.text} interpolation' },
]);
// No sub table exists before expending a row
let tables = screen.getAllByRole('table');
expect(tables).toHaveLength(1);
// Expand a row, and check its height
rows = within(getTable()).getAllByRole('row');
await userEvent.click(within(rows[1]).getByLabelText('Expand row'));
tables = screen.getAllByRole('table');
expect(tables).toHaveLength(3);
let subTable = screen.getAllByRole('table')[2];
expect(subTable).toHaveStyle({ height: '108px' });
// Sort again rows
tables = screen.getAllByRole('table');
await userEvent.click(within(tables[0]).getAllByTitle('Toggle SortBy')[0]);
rows = within(table).getAllByRole('row');
expect(rows).toHaveLength(5);
expect(getRowsData(rows)).toEqual([
{ time: '2021-01-01 03:00:00', temperature: 'NaN', link: '${__value.text} interpolation' },
{ time: '2021-01-01 02:00:00', temperature: '12', link: '${__value.text} interpolation' },
{ time: '2021-01-01 01:00:00', temperature: '11', link: '${__value.text} interpolation' },
{ time: '2021-01-01 00:00:00', temperature: '10', link: '${__value.text} interpolation' },
]);
// Expand another row
rows = within(getTable()).getAllByRole('row');
await userEvent.click(within(rows[1]).getByLabelText('Expand row'));
subTable = screen.getAllByRole('table')[2];
expect(subTable).toHaveStyle({ height: '108px' });
});
});
describe('when mounted with scrolled to specific row', () => {
it('the row should be visible', async () => {
getTestContext({
initialRowIndex: 2,
});
expect(getTable()).toBeInTheDocument();
const rows = within(getTable()).getAllByRole('row');
expect(rows).toHaveLength(5);
let selected = within(getTable()).getByRole('row', { selected: true });
expect(selected).toBeVisible();
});
});
});