mirror of
https://github.com/grafana/grafana.git
synced 2025-01-08 23:23:45 -06:00
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:
parent
ec9366b300
commit
0083b2e346
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user