Table: Add enable pagination option (#45732)

* Table: Add page size option / pagination

* Update docs/sources/visualizations/table/_index.md

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>

* FIx packages build

* Move pagination below footer

* Move styles to tableStyles

* Fix typecheck in jaeger-ui

* Set footer to hide onChange

* Styling tweaks

* Center paging

* Tweaks

* Change pageSize to enablePagination

* Move header and footer options to a separate category

* Fix performance and styling issue for the pagination

* Some more styling and tweaking

* Fix tests

* Update docs/sources/visualizations/table/_index.md

Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>

* Update docs/sources/visualizations/table/_index.md

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
Zoltán Bedi 2022-03-31 09:36:04 +02:00 committed by GitHub
parent 502cf8b37f
commit 15b48fc188
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 361 additions and 86 deletions

View File

@ -105,3 +105,7 @@ Enables value inspection from table cell. The raw value is presented in a modal
## Column filter ## Column filter
You can temporarily change how column data is displayed. For example, you can order values from highest to lowest or hide specific values. For more information, refer to [Filter table columns]({{< relref "./filter-table-columns.md" >}}). You can temporarily change how column data is displayed. For example, you can order values from highest to lowest or hide specific values. For more information, refer to [Filter table columns]({{< relref "./filter-table-columns.md" >}}).
## Pagination
Use this option to enable or disable pagination. It is a front-end option that does not affect queries. When enabled, the page size automatically adjusts to the height of the table.

View File

@ -102,7 +102,7 @@ e2e.scenario({
e2e.components.PanelEditor.DataPane.content().should('be.visible'); e2e.components.PanelEditor.DataPane.content().should('be.visible');
// Field & Overrides tabs (need to switch to React based vis, i.e. Table) // Field & Overrides tabs (need to switch to React based vis, i.e. Table)
e2e.components.PanelEditor.OptionsPane.fieldLabel('Table Show header').should('be.visible'); e2e.components.PanelEditor.OptionsPane.fieldLabel('Header and footer Show header').should('be.visible');
e2e.components.PanelEditor.OptionsPane.fieldLabel('Table Column width').should('be.visible'); e2e.components.PanelEditor.OptionsPane.fieldLabel('Table Column width').should('be.visible');
}, },
}); });

View File

@ -9,5 +9,10 @@
}, },
"exclude": ["dist", "node_modules"], "exclude": ["dist", "node_modules"],
"extends": "@grafana/tsconfig", "extends": "@grafana/tsconfig",
"include": ["src/**/*.ts*", "../../public/app/types/jquery/*.ts", "../../public/app/types/*.d.ts"] "include": [
"src/**/*.ts*",
"../../public/app/types/jquery/*.ts",
"../../public/app/types/*.d.ts",
"../grafana-ui/src/types/*.d.ts"
]
} }

View File

@ -0,0 +1,14 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { Pagination } from './Pagination';
describe('Pagination component', () => {
it('should render only 10 buttons when number of pages is higher than 8', () => {
render(<Pagination currentPage={1} numberOfPages={90} onNavigate={() => {}} />);
expect(screen.getAllByRole('button')).toHaveLength(10);
});
it('should only show 3 buttons when showSmallVersion is true', () => {
render(<Pagination currentPage={1} numberOfPages={90} onNavigate={() => {}} showSmallVersion />);
expect(screen.getAllByRole('button')).toHaveLength(4);
});
});

View File

@ -1,27 +1,36 @@
import React from 'react'; import React, { useMemo } from 'react';
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { stylesFactory } from '../../themes'; import { useStyles2 } from '../../themes';
import { Button, ButtonVariant } from '../Button'; import { Button, ButtonVariant } from '../Button';
import { Icon } from '../Icon/Icon'; import { Icon } from '../Icon/Icon';
const PAGE_LENGTH_TO_CONDENSE = 8;
export interface Props { export interface Props {
/** The current page index being shown. */ /** The current page index being shown. */
currentPage: number; currentPage: number;
/** Number of total pages. */ /** Number of total pages. */
numberOfPages: number; numberOfPages: number;
/** Callback function for fetching the selected page */ /** Callback function for fetching the selected page. */
onNavigate: (toPage: number) => void; onNavigate: (toPage: number) => void;
/** When set to true and the pagination result is only one page it will not render the pagination at all */ /** When set to true and the pagination result is only one page it will not render the pagination at all. */
hideWhenSinglePage?: boolean; hideWhenSinglePage?: boolean;
/** Small version only shows the current page and the navigation buttons. */
showSmallVersion?: boolean;
} }
export const Pagination: React.FC<Props> = ({ currentPage, numberOfPages, onNavigate, hideWhenSinglePage }) => { export const Pagination: React.FC<Props> = ({
const styles = getStyles(); currentPage,
numberOfPages,
onNavigate,
hideWhenSinglePage,
showSmallVersion,
}) => {
const styles = useStyles2(getStyles);
const pageLengthToCondense = showSmallVersion ? 1 : 8;
const pageButtons = useMemo(() => {
const pages = [...new Array(numberOfPages).keys()]; const pages = [...new Array(numberOfPages).keys()];
const condensePages = numberOfPages > PAGE_LENGTH_TO_CONDENSE; const condensePages = numberOfPages > pageLengthToCondense;
const getListItem = (page: number, variant: 'primary' | 'secondary') => ( const getListItem = (page: number, variant: 'primary' | 'secondary') => (
<li key={page} className={styles.item}> <li key={page} className={styles.item}>
<Button size="sm" variant={variant} onClick={() => onNavigate(page)}> <Button size="sm" variant={variant} onClick={() => onNavigate(page)}>
@ -30,13 +39,13 @@ export const Pagination: React.FC<Props> = ({ currentPage, numberOfPages, onNavi
</li> </li>
); );
const pageButtons = pages.reduce<JSX.Element[]>((pagesToRender, pageIndex) => { return pages.reduce<JSX.Element[]>((pagesToRender, pageIndex) => {
const page = pageIndex + 1; const page = pageIndex + 1;
const variant: ButtonVariant = page === currentPage ? 'primary' : 'secondary'; const variant: ButtonVariant = page === currentPage ? 'primary' : 'secondary';
// The indexes at which to start and stop condensing pages // The indexes at which to start and stop condensing pages
const lowerBoundIndex = PAGE_LENGTH_TO_CONDENSE; const lowerBoundIndex = pageLengthToCondense;
const upperBoundIndex = numberOfPages - PAGE_LENGTH_TO_CONDENSE + 1; const upperBoundIndex = numberOfPages - pageLengthToCondense + 1;
// When the indexes overlap one another this number is negative // When the indexes overlap one another this number is negative
const differenceOfBounds = upperBoundIndex - lowerBoundIndex; const differenceOfBounds = upperBoundIndex - lowerBoundIndex;
@ -45,20 +54,27 @@ export const Pagination: React.FC<Props> = ({ currentPage, numberOfPages, onNavi
const currentPageIsBetweenBounds = const currentPageIsBetweenBounds =
differenceOfBounds > -1 && currentPage >= lowerBoundIndex && currentPage <= upperBoundIndex; differenceOfBounds > -1 && currentPage >= lowerBoundIndex && currentPage <= upperBoundIndex;
// Show ellipsis after that many pages
const ellipsisOffset = showSmallVersion ? 1 : 3;
// The offset to show more pages when currentPageIsBetweenBounds
const pageOffset = showSmallVersion ? 0 : 2;
if (condensePages) { if (condensePages) {
if ( if (
isFirstOrLastPage || isFirstOrLastPage ||
(currentPage < lowerBoundIndex && page < lowerBoundIndex) || (currentPage < lowerBoundIndex && page < lowerBoundIndex) ||
(differenceOfBounds >= 0 && currentPage > upperBoundIndex && page > upperBoundIndex) || (differenceOfBounds >= 0 && currentPage > upperBoundIndex && page > upperBoundIndex) ||
(differenceOfBounds < 0 && currentPage >= lowerBoundIndex && page > upperBoundIndex) || (differenceOfBounds < 0 && currentPage >= lowerBoundIndex && page > upperBoundIndex) ||
(currentPageIsBetweenBounds && page >= currentPage - 2 && page <= currentPage + 2) (currentPageIsBetweenBounds && page >= currentPage - pageOffset && page <= currentPage + pageOffset)
) { ) {
// Renders a button for the page // Renders a button for the page
pagesToRender.push(getListItem(page, variant)); pagesToRender.push(getListItem(page, variant));
} else if ( } else if (
(page === lowerBoundIndex && currentPage < lowerBoundIndex) || (page === lowerBoundIndex && currentPage < lowerBoundIndex) ||
(page === upperBoundIndex && currentPage > upperBoundIndex) || (page === upperBoundIndex && currentPage > upperBoundIndex) ||
(currentPageIsBetweenBounds && (page === currentPage - 3 || page === currentPage + 3)) (currentPageIsBetweenBounds &&
(page === currentPage - ellipsisOffset || page === currentPage + ellipsisOffset))
) { ) {
// Renders and ellipsis to represent condensed pages // Renders and ellipsis to represent condensed pages
pagesToRender.push( pagesToRender.push(
@ -72,6 +88,7 @@ export const Pagination: React.FC<Props> = ({ currentPage, numberOfPages, onNavi
} }
return pagesToRender; return pagesToRender;
}, []); }, []);
}, [currentPage, numberOfPages, onNavigate, pageLengthToCondense, showSmallVersion, styles.ellipsis, styles.item]);
if (hideWhenSinglePage && numberOfPages <= 1) { if (hideWhenSinglePage && numberOfPages <= 1) {
return null; return null;
@ -108,7 +125,7 @@ export const Pagination: React.FC<Props> = ({ currentPage, numberOfPages, onNavi
); );
}; };
const getStyles = stylesFactory(() => { const getStyles = () => {
return { return {
container: css` container: css`
float: right; float: right;
@ -122,4 +139,4 @@ const getStyles = stylesFactory(() => {
transform: rotate(90deg); transform: rotate(90deg);
`, `,
}; };
}); };

View File

@ -10,18 +10,19 @@ export interface FooterRowProps {
totalColumnsWidth: number; totalColumnsWidth: number;
footerGroups: HeaderGroup[]; footerGroups: HeaderGroup[];
footerValues: FooterItem[]; footerValues: FooterItem[];
isPaginationVisible: boolean;
height: number; height: number;
} }
export const FooterRow = (props: FooterRowProps) => { export const FooterRow = (props: FooterRowProps) => {
const { totalColumnsWidth, footerGroups, height } = props; const { totalColumnsWidth, footerGroups, height, isPaginationVisible } = props;
const e2eSelectorsTable = selectors.components.Panels.Visualization.Table; const e2eSelectorsTable = selectors.components.Panels.Visualization.Table;
const tableStyles = useStyles2(getTableStyles); const tableStyles = useStyles2(getTableStyles);
return ( return (
<table <table
style={{ style={{
position: 'absolute', position: isPaginationVisible ? 'relative' : 'absolute',
width: totalColumnsWidth ? `${totalColumnsWidth}px` : '100%', width: totalColumnsWidth ? `${totalColumnsWidth}px` : '100%',
bottom: '0px', bottom: '0px',
}} }}

View File

@ -186,3 +186,8 @@ export const Footer: Story = (args) => {
</div> </div>
); );
}; };
export const Pagination: Story = (args) => <Basic {...args} />;
Pagination.args = {
pageSize: 10,
};

View File

@ -1,15 +1,14 @@
import React, { FC, memo, useCallback, useMemo } from 'react'; import React, { FC, memo, useCallback, useEffect, useMemo } from 'react';
import { DataFrame, getFieldDisplayName } from '@grafana/data'; import { DataFrame, getFieldDisplayName } from '@grafana/data';
import { import {
Cell, Cell,
Column, Column,
TableState,
useAbsoluteLayout, useAbsoluteLayout,
useFilters, useFilters,
UseFiltersState, usePagination,
useResizeColumns, useResizeColumns,
UseResizeColumnsState,
useSortBy, useSortBy,
UseSortByState,
useTable, useTable,
} from 'react-table'; } from 'react-table';
import { FixedSizeList } from 'react-window'; import { FixedSizeList } from 'react-window';
@ -27,6 +26,7 @@ import { TableCell } from './TableCell';
import { useStyles2 } from '../../themes'; import { useStyles2 } from '../../themes';
import { FooterRow } from './FooterRow'; import { FooterRow } from './FooterRow';
import { HeaderRow } from './HeaderRow'; import { HeaderRow } from './HeaderRow';
import { Pagination } from '../Pagination/Pagination';
const COLUMN_MIN_WIDTH = 150; const COLUMN_MIN_WIDTH = 150;
@ -45,13 +45,12 @@ export interface Props {
onSortByChange?: TableSortByActionCallback; onSortByChange?: TableSortByActionCallback;
onCellFilterAdded?: TableFilterActionCallback; onCellFilterAdded?: TableFilterActionCallback;
footerValues?: FooterItem[]; footerValues?: FooterItem[];
enablePagination?: boolean;
} }
interface ReactTableInternalState extends UseResizeColumnsState<{}>, UseSortByState<{}>, UseFiltersState<{}> {}
function useTableStateReducer({ onColumnResize, onSortByChange, data }: Props) { function useTableStateReducer({ onColumnResize, onSortByChange, data }: Props) {
return useCallback( return useCallback(
(newState: ReactTableInternalState, action: any) => { (newState: TableState, action: any) => {
switch (action.type) { switch (action.type) {
case 'columnDoneResizing': case 'columnDoneResizing':
if (onColumnResize) { if (onColumnResize) {
@ -95,8 +94,8 @@ function useTableStateReducer({ onColumnResize, onSortByChange, data }: Props) {
); );
} }
function getInitialState(initialSortBy: Props['initialSortBy'], columns: Column[]): Partial<ReactTableInternalState> { function getInitialState(initialSortBy: Props['initialSortBy'], columns: Column[]): Partial<TableState> {
const state: Partial<ReactTableInternalState> = {}; const state: Partial<TableState> = {};
if (initialSortBy) { if (initialSortBy) {
state.sortBy = []; state.sortBy = [];
@ -126,6 +125,7 @@ export const Table: FC<Props> = memo((props: Props) => {
initialSortBy, initialSortBy,
footerValues, footerValues,
showTypeIcons, showTypeIcons,
enablePagination,
} = props; } = props;
const tableStyles = useStyles2(getTableStyles); const tableStyles = useStyles2(getTableStyles);
@ -188,17 +188,40 @@ export const Table: FC<Props> = memo((props: Props) => {
[initialSortBy, memoizedColumns, memoizedData, resizable, stateReducer] [initialSortBy, memoizedColumns, memoizedData, resizable, stateReducer]
); );
const { getTableProps, headerGroups, rows, prepareRow, totalColumnsWidth, footerGroups } = useTable( const {
options, getTableProps,
useFilters, headerGroups,
useSortBy, rows,
useAbsoluteLayout, prepareRow,
useResizeColumns totalColumnsWidth,
); footerGroups,
page,
state,
gotoPage,
setPageSize,
pageOptions,
} = useTable(options, useFilters, useSortBy, usePagination, useAbsoluteLayout, useResizeColumns);
let listHeight = height - (headerHeight + footerHeight);
if (enablePagination) {
listHeight -= tableStyles.cellHeight;
}
const pageSize = Math.round(listHeight / tableStyles.cellHeight) - 1;
useEffect(() => {
// Don't update the page size if it is less than 1
if (pageSize <= 0) {
return;
}
setPageSize(pageSize);
}, [pageSize, setPageSize]);
const RenderRow = React.useCallback( const RenderRow = React.useCallback(
({ index: rowIndex, style }) => { ({ index: rowIndex, style }) => {
const row = rows[rowIndex]; let row = rows[rowIndex];
if (enablePagination) {
row = page[rowIndex];
}
prepareRow(row); prepareRow(row);
return ( return (
<div {...row.getRowProps({ style })} className={tableStyles.row}> <div {...row.getRowProps({ style })} className={tableStyles.row}>
@ -215,20 +238,52 @@ export const Table: FC<Props> = memo((props: Props) => {
</div> </div>
); );
}, },
[onCellFilterAdded, prepareRow, rows, tableStyles] [onCellFilterAdded, page, enablePagination, prepareRow, rows, tableStyles]
); );
const listHeight = height - (headerHeight + footerHeight); const onNavigate = useCallback(
(toPage: number) => {
gotoPage(toPage - 1);
},
[gotoPage]
);
const itemCount = enablePagination ? page.length : data.length;
let paginationEl = null;
if (enablePagination) {
const itemsRangeStart = state.pageIndex * state.pageSize + 1;
let itemsRangeEnd = itemsRangeStart + state.pageSize - 1;
const isSmall = width < 500;
if (itemsRangeEnd > data.length) {
itemsRangeEnd = data.length;
}
paginationEl = (
<div className={tableStyles.paginationWrapper}>
<div>
<Pagination
currentPage={state.pageIndex + 1}
numberOfPages={pageOptions.length}
showSmallVersion={isSmall}
onNavigate={onNavigate}
/>
</div>
{isSmall ? null : (
<div className={tableStyles.paginationSummary}>
{itemsRangeStart} - {itemsRangeEnd} of {data.length} rows
</div>
)}
</div>
);
}
return ( return (
<div {...getTableProps()} className={tableStyles.table} aria-label={ariaLabel} role="table"> <div {...getTableProps()} className={tableStyles.table} aria-label={ariaLabel} role="table">
<CustomScrollbar hideVerticalTrack={true}> <CustomScrollbar hideVerticalTrack={true}>
<div style={{ width: totalColumnsWidth ? `${totalColumnsWidth}px` : '100%' }}> <div className={tableStyles.tableContentWrapper(totalColumnsWidth)}>
{!noHeader && <HeaderRow data={data} headerGroups={headerGroups} showTypeIcons={showTypeIcons} />} {!noHeader && <HeaderRow data={data} headerGroups={headerGroups} showTypeIcons={showTypeIcons} />}
{rows.length > 0 ? ( {itemCount > 0 ? (
<FixedSizeList <FixedSizeList
height={listHeight} height={listHeight}
itemCount={rows.length} itemCount={itemCount}
itemSize={tableStyles.rowHeight} itemSize={tableStyles.rowHeight}
width={'100%'} width={'100%'}
style={{ overflow: 'hidden auto' }} style={{ overflow: 'hidden auto' }}
@ -243,11 +298,13 @@ export const Table: FC<Props> = memo((props: Props) => {
{footerValues && ( {footerValues && (
<FooterRow <FooterRow
height={footerHeight} height={footerHeight}
isPaginationVisible={Boolean(enablePagination)}
footerValues={footerValues} footerValues={footerValues}
footerGroups={footerGroups} footerGroups={footerGroups}
totalColumnsWidth={totalColumnsWidth} totalColumnsWidth={totalColumnsWidth}
/> />
)} )}
{paginationEl}
</div> </div>
</CustomScrollbar> </CustomScrollbar>
</div> </div>

View File

@ -170,6 +170,32 @@ export const getTableStyles = (theme: GrafanaTheme2) => {
label: headerFilter; label: headerFilter;
cursor: pointer; cursor: pointer;
`, `,
paginationWrapper: css`
display: flex;
background: ${headerBg};
height: ${cellHeight}px;
justify-content: center;
align-items: center;
width: 100%;
border-top: 1px solid ${theme.colors.border.weak};
li {
margin-bottom: 0;
}
div:not(:only-child):first-child {
flex-grow: 0.6;
}
`,
paginationSummary: css`
color: ${theme.colors.text.secondary};
font-size: ${theme.typography.bodySmall.fontSize};
margin-left: auto;
`,
tableContentWrapper: (totalColumnsWidth: number) => css`
width: ${totalColumnsWidth ?? '100%'};
display: flex;
flex-direction: column;
`,
row: css` row: css`
label: row; label: row;
border-bottom: 1px solid ${borderColor}; border-bottom: 1px solid ${borderColor};

View File

@ -0,0 +1,111 @@
import type {
UseColumnOrderInstanceProps,
UseColumnOrderState,
UseExpandedHooks,
UseExpandedInstanceProps,
UseExpandedOptions,
UseExpandedRowProps,
UseExpandedState,
UseFiltersColumnOptions,
UseFiltersColumnProps,
UseFiltersInstanceProps,
UseFiltersOptions,
UseFiltersState,
UseGlobalFiltersColumnOptions,
UseGlobalFiltersInstanceProps,
UseGlobalFiltersOptions,
UseGlobalFiltersState,
UseGroupByCellProps,
UseGroupByColumnOptions,
UseGroupByColumnProps,
UseGroupByHooks,
UseGroupByInstanceProps,
UseGroupByOptions,
UseGroupByRowProps,
UseGroupByState,
UsePaginationInstanceProps,
UsePaginationOptions,
UsePaginationState,
UseResizeColumnsColumnOptions,
UseResizeColumnsColumnProps,
UseResizeColumnsOptions,
UseResizeColumnsState,
UseRowSelectHooks,
UseRowSelectInstanceProps,
UseRowSelectOptions,
UseRowSelectRowProps,
UseRowSelectState,
UseRowStateCellProps,
UseRowStateInstanceProps,
UseRowStateOptions,
UseRowStateRowProps,
UseRowStateState,
UseSortByColumnOptions,
UseSortByColumnProps,
UseSortByHooks,
UseSortByInstanceProps,
UseSortByOptions,
UseSortByState,
} from 'react-table';
declare module 'react-table' {
export interface TableOptions<D extends Record<string, unknown>>
extends UseExpandedOptions<D>,
UseFiltersOptions<D>,
UseGlobalFiltersOptions<D>,
UseGroupByOptions<D>,
UsePaginationOptions<D>,
UseResizeColumnsOptions<D>,
UseRowSelectOptions<D>,
UseRowStateOptions<D>,
UseSortByOptions<D>,
// note that having Record here allows you to add anything to the options, this matches the spirit of the
// underlying js library, but might be cleaner if it's replaced by a more specific type that matches your
// feature set, this is a safe default.
Record<string, any> {}
export interface Hooks<D extends Record<string, unknown> = Record<string, unknown>>
extends UseExpandedHooks<D>,
UseGroupByHooks<D>,
UseRowSelectHooks<D>,
UseSortByHooks<D> {}
export interface TableInstance<D extends Record<string, unknown> = Record<string, unknown>>
extends UseColumnOrderInstanceProps<D>,
UseExpandedInstanceProps<D>,
UseFiltersInstanceProps<D>,
UseGlobalFiltersInstanceProps<D>,
UseGroupByInstanceProps<D>,
UsePaginationInstanceProps<D>,
UseRowSelectInstanceProps<D>,
UseRowStateInstanceProps<D>,
UseSortByInstanceProps<D> {}
export interface TableState<D extends Record<string, unknown> = Record<string, unknown>>
extends UseColumnOrderState<D>,
UseExpandedState<D>,
UseFiltersState<D>,
UseGlobalFiltersState<D>,
UseGroupByState<D>,
UsePaginationState<D>,
UseResizeColumnsState<D>,
UseRowSelectState<D>,
UseRowStateState<D>,
UseSortByState<D> {}
export interface ColumnInterface<D extends Record<string, unknown> = Record<string, unknown>>
extends UseGlobalFiltersColumnOptions<D>,
UseGroupByColumnOptions<D>,
UseResizeColumnsColumnOptions<D>,
UseSortByColumnOptions<D> {}
export interface ColumnInstance<D extends Record<string, unknown> = Record<string, unknown>>
extends UseFiltersColumnProps<D>,
UseGroupByColumnProps<D>,
UseResizeColumnsColumnProps<D>,
UseSortByColumnProps<D> {}
export interface Cell<D extends Record<string, unknown> = Record<string, unknown>, V = any>
extends UseGroupByCellProps<D>,
UseRowStateCellProps<D> {}
}

View File

@ -6,5 +6,11 @@
}, },
"exclude": ["dist", "node_modules"], "exclude": ["dist", "node_modules"],
"extends": "@grafana/tsconfig", "extends": "@grafana/tsconfig",
"include": ["src/**/*.ts*", "typings", "../../public/app/types/jquery/*.ts", "../../public/app/types/*.d.ts"] "include": [
"src/**/*.ts*",
"typings",
"../../public/app/types/jquery/*.ts",
"../../public/app/types/*.d.ts",
"../grafana-ui/src/types/*.d.ts"
]
} }

View File

@ -0,0 +1,14 @@
import { StandardEditorProps } from '@grafana/data';
import { Switch } from '@grafana/ui';
import React from 'react';
export function PaginationEditor({ onChange, value, context }: StandardEditorProps<boolean>) {
const changeValue = (event: React.FormEvent<HTMLInputElement> | undefined) => {
if (event?.currentTarget.checked) {
context.options.footer.show = false;
}
onChange(event?.currentTarget.checked);
};
return <Switch value={Boolean(value)} onChange={changeValue} />;
}

View File

@ -108,6 +108,7 @@ export class TablePanel extends Component<Props> {
onColumnResize={this.onColumnResize} onColumnResize={this.onColumnResize}
onCellFilterAdded={this.onCellFilterAdded} onCellFilterAdded={this.onCellFilterAdded}
footerValues={footerValues} footerValues={footerValues}
enablePagination={options.footer?.enablePagination}
/> />
); );
} }

View File

@ -24,6 +24,7 @@ export interface TableFooterCalc {
show: boolean; show: boolean;
reducer: string[]; // actually 1 value reducer: string[]; // actually 1 value
fields?: string[]; fields?: string[];
enablePagination?: boolean;
} }
export const defaultPanelOptions: PanelOptions = { export const defaultPanelOptions: PanelOptions = {

View File

@ -12,6 +12,7 @@ import { TableFieldOptions } from '@grafana/schema';
import { tableMigrationHandler, tablePanelChangedHandler } from './migrations'; import { tableMigrationHandler, tablePanelChangedHandler } from './migrations';
import { TableCellDisplayMode } from '@grafana/ui'; import { TableCellDisplayMode } from '@grafana/ui';
import { TableSuggestionsSupplier } from './suggestions'; import { TableSuggestionsSupplier } from './suggestions';
import { PaginationEditor } from './PaginationEditor';
export const plugin = new PanelPlugin<PanelOptions, TableFieldOptions>(TablePanel) export const plugin = new PanelPlugin<PanelOptions, TableFieldOptions>(TablePanel)
.setPanelChangeHandler(tablePanelChangedHandler) .setPanelChangeHandler(tablePanelChangedHandler)
@ -108,18 +109,21 @@ export const plugin = new PanelPlugin<PanelOptions, TableFieldOptions>(TablePane
builder builder
.addBooleanSwitch({ .addBooleanSwitch({
path: 'showHeader', path: 'showHeader',
category: ['Header and footer'],
name: 'Show header', name: 'Show header',
description: "To display table's header or not to display", description: "To display table's header or not to display",
defaultValue: defaultPanelOptions.showHeader, defaultValue: defaultPanelOptions.showHeader,
}) })
.addBooleanSwitch({ .addBooleanSwitch({
path: 'footer.show', path: 'footer.show',
category: ['Header and footer'],
name: 'Show Footer', name: 'Show Footer',
description: "To display table's footer or not to display", description: "To display table's footer or not to display",
defaultValue: defaultPanelOptions.footer?.show, defaultValue: defaultPanelOptions.footer?.show,
}) })
.addCustomEditor({ .addCustomEditor({
id: 'footer.reducer', id: 'footer.reducer',
category: ['Header and footer'],
path: 'footer.reducer', path: 'footer.reducer',
name: 'Calculation', name: 'Calculation',
description: 'Choose a reducer function / calculation', description: 'Choose a reducer function / calculation',
@ -129,6 +133,7 @@ export const plugin = new PanelPlugin<PanelOptions, TableFieldOptions>(TablePane
}) })
.addMultiSelect({ .addMultiSelect({
path: 'footer.fields', path: 'footer.fields',
category: ['Header and footer'],
name: 'Fields', name: 'Fields',
description: 'Select the fields that should be calculated', description: 'Select the fields that should be calculated',
settings: { settings: {
@ -152,6 +157,13 @@ export const plugin = new PanelPlugin<PanelOptions, TableFieldOptions>(TablePane
}, },
defaultValue: '', defaultValue: '',
showIf: (cfg) => cfg.footer?.show, showIf: (cfg) => cfg.footer?.show,
})
.addCustomEditor({
id: 'footer.enablePagination',
category: ['Header and footer'],
path: 'footer.enablePagination',
name: 'Enable pagination',
editor: PaginationEditor,
}); });
}) })
.setSuggestionsSupplier(new TableSuggestionsSupplier()); .setSuggestionsSupplier(new TableSuggestionsSupplier());

View File

@ -18,6 +18,7 @@
"public/test/**/*.ts", "public/test/**/*.ts",
"public/vendor/**/*.ts", "public/vendor/**/*.ts",
"packages/jaeger-ui-components/typings", "packages/jaeger-ui-components/typings",
"packages/grafana-data/typings" "packages/grafana-data/typings",
"packages/grafana-ui/src/types"
] ]
} }