Commit b709f6ad by Torkel Ödegaard Committed by GitHub

TablePanel: Adding sort order persistance (#24705)

* TablePanel: Adding sort order persistance

* adds panel test dashboard for table panel
parent 6b29c117
import React, { FC, memo, useCallback, useMemo } from 'react'; import React, { FC, memo, useCallback, useMemo } from 'react';
import { DataFrame, Field } from '@grafana/data'; import { DataFrame, Field, getFieldDisplayName } from '@grafana/data';
import { import {
Cell, Cell,
Column, Column,
...@@ -14,7 +14,12 @@ import { ...@@ -14,7 +14,12 @@ import {
import { FixedSizeList } from 'react-window'; import { FixedSizeList } from 'react-window';
import { getColumns, getTextAlign } from './utils'; import { getColumns, getTextAlign } from './utils';
import { useTheme } from '../../themes'; import { useTheme } from '../../themes';
import { TableColumnResizeActionCallback, TableFilterActionCallback, TableSortByActionCallback } from './types'; import {
TableColumnResizeActionCallback,
TableFilterActionCallback,
TableSortByActionCallback,
TableSortByFieldState,
} from './types';
import { getTableStyles, TableStyles } from './styles'; import { getTableStyles, TableStyles } from './styles';
import { TableCell } from './TableCell'; import { TableCell } from './TableCell';
import { Icon } from '../Icon/Icon'; import { Icon } from '../Icon/Icon';
...@@ -30,9 +35,10 @@ export interface Props { ...@@ -30,9 +35,10 @@ export interface Props {
columnMinWidth?: number; columnMinWidth?: number;
noHeader?: boolean; noHeader?: boolean;
resizable?: boolean; resizable?: boolean;
initialSortBy?: TableSortByFieldState[];
onCellClick?: TableFilterActionCallback; onCellClick?: TableFilterActionCallback;
onColumnResize?: TableColumnResizeActionCallback; onColumnResize?: TableColumnResizeActionCallback;
onSortBy?: TableSortByActionCallback; onSortByChange?: TableSortByActionCallback;
} }
interface ReactTableInternalState extends UseResizeColumnsState<{}>, UseSortByState<{}> {} interface ReactTableInternalState extends UseResizeColumnsState<{}>, UseSortByState<{}> {}
...@@ -43,25 +49,66 @@ function useTableStateReducer(props: Props) { ...@@ -43,25 +49,66 @@ function useTableStateReducer(props: Props) {
switch (action.type) { switch (action.type) {
case 'columnDoneResizing': case 'columnDoneResizing':
if (props.onColumnResize) { if (props.onColumnResize) {
const { data } = props;
const info = (newState.columnResizing.headerIdWidths as any)[0]; const info = (newState.columnResizing.headerIdWidths as any)[0];
const columnIdString = info[0]; const columnIdString = info[0];
const fieldIndex = parseInt(columnIdString, 10); const fieldIndex = parseInt(columnIdString, 10);
const width = Math.round(newState.columnResizing.columnWidths[columnIdString] as number); const width = Math.round(newState.columnResizing.columnWidths[columnIdString] as number);
props.onColumnResize(fieldIndex, width);
const field = data.fields[fieldIndex];
if (!field) {
return newState;
}
const fieldDisplayName = getFieldDisplayName(field, data);
props.onColumnResize(fieldDisplayName, width);
} }
case 'toggleSortBy': case 'toggleSortBy':
if (props.onSortBy) { if (props.onSortByChange) {
// todo call callback and persist const { data } = props;
const sortByFields: TableSortByFieldState[] = [];
for (const sortItem of newState.sortBy) {
const field = data.fields[parseInt(sortItem.id, 10)];
if (!field) {
continue;
}
sortByFields.push({
displayName: getFieldDisplayName(field, data),
desc: sortItem.desc,
});
}
props.onSortByChange(sortByFields);
} }
break; break;
} }
return newState; return newState;
}, },
[props.onColumnResize] [props.onColumnResize, props.onSortByChange, props.data]
); );
} }
function getInitialState(props: Props, columns: Column[]): Partial<ReactTableInternalState> {
const state: Partial<ReactTableInternalState> = {};
if (props.initialSortBy) {
state.sortBy = [];
for (const sortBy of props.initialSortBy) {
for (const col of columns) {
if (col.Header === sortBy.displayName) {
state.sortBy.push({ id: col.id as string, desc: sortBy.desc });
}
}
}
}
return state;
}
export const Table: FC<Props> = memo((props: Props) => { export const Table: FC<Props> = memo((props: Props) => {
const { data, height, onCellClick, width, columnMinWidth = COLUMN_MIN_WIDTH, noHeader, resizable = true } = props; const { data, height, onCellClick, width, columnMinWidth = COLUMN_MIN_WIDTH, noHeader, resizable = true } = props;
const theme = useTheme(); const theme = useTheme();
...@@ -91,10 +138,7 @@ export const Table: FC<Props> = memo((props: Props) => { ...@@ -91,10 +138,7 @@ export const Table: FC<Props> = memo((props: Props) => {
data: memoizedData, data: memoizedData,
disableResizing: !resizable, disableResizing: !resizable,
stateReducer: stateReducer, stateReducer: stateReducer,
// this is how you set initial sort by state initialState: getInitialState(props, memoizedColumns),
// initialState: {
// sortBy: [{ id: '2', desc: true }],
// },
}), }),
[memoizedColumns, memoizedData, stateReducer, resizable] [memoizedColumns, memoizedData, stateReducer, resizable]
); );
......
...@@ -26,11 +26,11 @@ export interface TableRow { ...@@ -26,11 +26,11 @@ export interface TableRow {
} }
export type TableFilterActionCallback = (key: string, value: string) => void; export type TableFilterActionCallback = (key: string, value: string) => void;
export type TableColumnResizeActionCallback = (fieldIndex: number, width: number) => void; export type TableColumnResizeActionCallback = (fieldDisplayName: string, width: number) => void;
export type TableSortByActionCallback = (state: TableSortByFieldState[]) => void; export type TableSortByActionCallback = (state: TableSortByFieldState[]) => void;
export interface TableSortByFieldState { export interface TableSortByFieldState {
fieldIndex: number; displayName: string;
desc?: boolean; desc?: boolean;
} }
......
...@@ -45,7 +45,7 @@ export { ModalsProvider, ModalRoot, ModalsController } from './Modal/ModalsConte ...@@ -45,7 +45,7 @@ export { ModalsProvider, ModalRoot, ModalsController } from './Modal/ModalsConte
export { SetInterval } from './SetInterval/SetInterval'; export { SetInterval } from './SetInterval/SetInterval';
export { Table } from './Table/Table'; export { Table } from './Table/Table';
export { TableCellDisplayMode } from './Table/types'; export { TableCellDisplayMode, TableSortByFieldState } from './Table/types';
export { TableInputCSV } from './TableInputCSV/TableInputCSV'; export { TableInputCSV } from './TableInputCSV/TableInputCSV';
export { TabsBar } from './Tabs/TabsBar'; export { TabsBar } from './Tabs/TabsBar';
export { Tab } from './Tabs/Tab'; export { Tab } from './Tabs/Tab';
......
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Table, Select } from '@grafana/ui'; import { Table, Select } from '@grafana/ui';
import { import { FieldMatcherID, PanelProps, DataFrame, SelectableValue, getFrameDisplayName } from '@grafana/data';
FieldMatcherID,
PanelProps,
DataFrame,
SelectableValue,
getFrameDisplayName,
getFieldDisplayName,
} from '@grafana/data';
import { Options } from './types'; import { Options } from './types';
import { css } from 'emotion'; import { css } from 'emotion';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
import { TableSortByFieldState } from '@grafana/ui/src/components/Table/types';
interface Props extends PanelProps<Options> {} interface Props extends PanelProps<Options> {}
...@@ -20,21 +14,10 @@ export class TablePanel extends Component<Props> { ...@@ -20,21 +14,10 @@ export class TablePanel extends Component<Props> {
super(props); super(props);
} }
onColumnResize = (fieldIndex: number, width: number) => { onColumnResize = (fieldDisplayName: string, width: number) => {
const { fieldConfig, data } = this.props; const { fieldConfig } = this.props;
const { overrides } = fieldConfig; const { overrides } = fieldConfig;
const frame = data.series[this.getCurrentFrameIndex()];
if (!frame) {
return;
}
const field = frame.fields[fieldIndex];
if (!field) {
return;
}
const fieldDisplayName = getFieldDisplayName(field, frame, data.series);
const matcherId = FieldMatcherID.byName; const matcherId = FieldMatcherID.byName;
const propId = 'custom.width'; const propId = 'custom.width';
...@@ -62,6 +45,13 @@ export class TablePanel extends Component<Props> { ...@@ -62,6 +45,13 @@ export class TablePanel extends Component<Props> {
}); });
}; };
onSortByChange = (sortBy: TableSortByFieldState[]) => {
this.props.onOptionsChange({
...this.props.options,
sortBy,
});
};
onChangeTableSelection = (val: SelectableValue<number>) => { onChangeTableSelection = (val: SelectableValue<number>) => {
this.props.onOptionsChange({ this.props.onOptionsChange({
...this.props.options, ...this.props.options,
...@@ -82,6 +72,8 @@ export class TablePanel extends Component<Props> { ...@@ -82,6 +72,8 @@ export class TablePanel extends Component<Props> {
data={frame} data={frame}
noHeader={!options.showHeader} noHeader={!options.showHeader}
resizable={true} resizable={true}
initialSortBy={options.sortBy}
onSortByChange={this.onSortByChange}
onColumnResize={this.onColumnResize} onColumnResize={this.onColumnResize}
/> />
); );
......
import { TableSortByFieldState } from '@grafana/ui';
export interface Options { export interface Options {
frameIndex: number; frameIndex: number;
showHeader: boolean; showHeader: boolean;
sortBy?: TableSortByFieldState[];
}
export interface TableSortBy {
displayName: string;
desc: boolean;
} }
export interface CustomFieldConfig { export interface CustomFieldConfig {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment