Explore: Support display of multiple tables (#70935)

* Explore: Display multiple tables

* Fix tests

* Add multi table test

* Use refID and not weird custom key

* Remove tempo changes

* Consolidate/clean up some table frame logic

* clean up some code, have meta text in panel
This commit is contained in:
Kristina 2023-07-13 06:44:42 -05:00 committed by GitHub
parent ec9366b300
commit 0083b2e346
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 105 additions and 79 deletions

View File

@ -5,8 +5,8 @@ import { DataFrame, FieldType, getDefaultTimeRange, InternalTimeZones, toDataFra
import { TableContainer } from './TableContainer';
function getTable(): HTMLElement {
return screen.getAllByRole('table')[0];
function getTables(): HTMLElement[] {
return screen.getAllByRole('table');
}
function getRowsData(rows: HTMLElement[]): Object[] {
@ -58,48 +58,63 @@ const defaultProps = {
};
describe('TableContainer', () => {
it('should render component', () => {
render(<TableContainer {...defaultProps} />);
expect(getTable()).toBeInTheDocument();
const rows = within(getTable()).getAllByRole('row');
expect(rows).toHaveLength(5);
expect(getRowsData(rows)).toEqual([
{ time: '2021-01-01 00:00:00', text: 'test_string_1' },
{ time: '2021-01-01 03:00:00', text: 'test_string_2' },
{ time: '2021-01-01 01:00:00', text: 'test_string_3' },
{ time: '2021-01-01 02:00:00', text: 'test_string_4' },
]);
describe('With one main frame', () => {
it('should render component', () => {
render(<TableContainer {...defaultProps} />);
const tables = getTables();
expect(tables.length).toBe(1);
expect(tables[0]).toBeInTheDocument();
const rows = within(tables[0]).getAllByRole('row');
expect(rows).toHaveLength(5);
expect(getRowsData(rows)).toEqual([
{ time: '2021-01-01 00:00:00', text: 'test_string_1' },
{ time: '2021-01-01 03:00:00', text: 'test_string_2' },
{ time: '2021-01-01 01:00:00', text: 'test_string_3' },
{ time: '2021-01-01 02:00:00', text: 'test_string_4' },
]);
});
it('should render 0 series returned on no items', () => {
const emptyFrames: DataFrame[] = [
{
name: 'TableResultName',
fields: [],
length: 0,
},
];
render(<TableContainer {...defaultProps} tableResult={emptyFrames} />);
expect(screen.getByText('0 series returned')).toBeInTheDocument();
});
it('should update time when timezone changes', () => {
const { rerender } = render(<TableContainer {...defaultProps} />);
const rowsBeforeChange = within(getTables()[0]).getAllByRole('row');
expect(getRowsData(rowsBeforeChange)).toEqual([
{ time: '2021-01-01 00:00:00', text: 'test_string_1' },
{ time: '2021-01-01 03:00:00', text: 'test_string_2' },
{ time: '2021-01-01 01:00:00', text: 'test_string_3' },
{ time: '2021-01-01 02:00:00', text: 'test_string_4' },
]);
rerender(<TableContainer {...defaultProps} timeZone="cest" />);
const rowsAfterChange = within(getTables()[0]).getAllByRole('row');
expect(getRowsData(rowsAfterChange)).toEqual([
{ time: '2020-12-31 19:00:00', text: 'test_string_1' },
{ time: '2020-12-31 22:00:00', text: 'test_string_2' },
{ time: '2020-12-31 20:00:00', text: 'test_string_3' },
{ time: '2020-12-31 21:00:00', text: 'test_string_4' },
]);
});
});
it('should render 0 series returned on no items', () => {
const emptyFrames: DataFrame[] = [
{
name: 'TableResultName',
fields: [],
length: 0,
},
];
render(<TableContainer {...defaultProps} tableResult={emptyFrames} />);
expect(screen.getByText('0 series returned')).toBeInTheDocument();
});
it('should update time when timezone changes', () => {
const { rerender } = render(<TableContainer {...defaultProps} />);
const rowsBeforeChange = within(getTable()).getAllByRole('row');
expect(getRowsData(rowsBeforeChange)).toEqual([
{ time: '2021-01-01 00:00:00', text: 'test_string_1' },
{ time: '2021-01-01 03:00:00', text: 'test_string_2' },
{ time: '2021-01-01 01:00:00', text: 'test_string_3' },
{ time: '2021-01-01 02:00:00', text: 'test_string_4' },
]);
rerender(<TableContainer {...defaultProps} timeZone="cest" />);
const rowsAfterChange = within(getTable()).getAllByRole('row');
expect(getRowsData(rowsAfterChange)).toEqual([
{ time: '2020-12-31 19:00:00', text: 'test_string_1' },
{ time: '2020-12-31 22:00:00', text: 'test_string_2' },
{ time: '2020-12-31 20:00:00', text: 'test_string_3' },
{ time: '2020-12-31 21:00:00', text: 'test_string_4' },
]);
describe('With multiple main frames', () => {
it('should render multiple tables for multiple frames', () => {
const dataFrames = [dataFrame, dataFrame];
const multiDefaultProps = { ...defaultProps, tableResult: dataFrames };
render(<TableContainer {...multiDefaultProps} />);
const tables = getTables();
expect(tables.length).toBe(2);
expect(tables[0]).toBeInTheDocument();
expect(tables[1]).toBeInTheDocument();
});
});
});

View File

@ -34,25 +34,21 @@ const connector = connect(mapStateToProps, {});
type Props = TableContainerProps & ConnectedProps<typeof connector>;
export class TableContainer extends PureComponent<Props> {
getMainFrame(frames: DataFrame[] | null) {
return frames?.find((df) => df.meta?.custom?.parentRowIndex === undefined) || frames?.[0];
getMainFrames(frames: DataFrame[] | null) {
return frames?.filter((df) => df.meta?.custom?.parentRowIndex === undefined) || [frames?.[0]];
}
getTableHeight() {
const { tableResult } = this.props;
const mainFrame = this.getMainFrame(tableResult);
if (!mainFrame || mainFrame.length === 0) {
getTableHeight(rowCount: number, isSingleTable = true) {
if (rowCount === 0) {
return 200;
}
// tries to estimate table height
return Math.min(600, Math.max(mainFrame.length * 36, 300) + 40 + 46);
// tries to estimate table height, with a min of 300 and a max of 600
// if there are multiple tables, there is no min
return Math.min(600, Math.max(rowCount * 36, isSingleTable ? 300 : 0) + 40 + 46);
}
render() {
const { loading, onCellFilterAdded, tableResult, width, splitOpenFn, range, ariaLabel, timeZone } = this.props;
const height = this.getTableHeight();
let dataFrames = tableResult;
@ -85,33 +81,48 @@ export class TableContainer extends PureComponent<Props> {
}
}
const mainFrame = this.getMainFrame(dataFrames);
const subFrames = dataFrames?.filter((df) => df.meta?.custom?.parentRowIndex !== undefined);
// move dataframes to be grouped by table, with optional sub-tables for a row
const tableData: Array<{ main: DataFrame; sub?: DataFrame[] }> = [];
const mainFrames = this.getMainFrames(dataFrames).filter(
(frame: DataFrame | undefined): frame is DataFrame => !!frame && frame.length !== 0
);
mainFrames?.forEach((frame) => {
const subFrames =
dataFrames?.filter((df) => frame.refId === df.refId && df.meta?.custom?.parentRowIndex !== undefined) ||
undefined;
tableData.push({ main: frame, sub: subFrames });
});
return (
<PanelChrome
title="Table"
width={width}
height={height}
loadingState={loading ? LoadingState.Loading : undefined}
>
{(innerWidth, innerHeight) => (
<>
{mainFrame?.length ? (
<Table
ariaLabel={ariaLabel}
data={mainFrame}
subData={subFrames}
width={innerWidth}
height={innerHeight}
onCellFilterAdded={onCellFilterAdded}
/>
) : (
<MetaInfoText metaItems={[{ value: '0 series returned' }]} />
)}
</>
<>
{tableData.length === 0 && (
<PanelChrome title={'Table'} width={width} height={200}>
{() => <MetaInfoText metaItems={[{ value: '0 series returned' }]} />}
</PanelChrome>
)}
</PanelChrome>
{tableData.length > 0 &&
tableData.map((data, i) => (
<PanelChrome
key={data.main.refId || `table-${i}`}
title={tableData.length > 1 ? `Table - ${data.main.name || data.main.refId || i}` : 'Table'}
width={width}
height={this.getTableHeight(data.main.length, tableData.length === 1)}
loadingState={loading ? LoadingState.Loading : undefined}
>
{(innerWidth, innerHeight) => (
<Table
ariaLabel={ariaLabel}
data={data.main}
subData={data.sub}
width={innerWidth}
height={innerHeight}
onCellFilterAdded={onCellFilterAdded}
/>
)}
</PanelChrome>
))}
</>
);
}
}