mirror of
https://github.com/grafana/grafana.git
synced 2024-12-25 08:21:46 -06:00
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:
parent
502cf8b37f
commit
15b48fc188
@ -105,3 +105,7 @@ Enables value inspection from table cell. The raw value is presented in a modal
|
||||
## 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" >}}).
|
||||
|
||||
## 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.
|
||||
|
@ -102,7 +102,7 @@ e2e.scenario({
|
||||
e2e.components.PanelEditor.DataPane.content().should('be.visible');
|
||||
|
||||
// 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');
|
||||
},
|
||||
});
|
||||
|
@ -9,5 +9,10 @@
|
||||
},
|
||||
"exclude": ["dist", "node_modules"],
|
||||
"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"
|
||||
]
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
@ -1,77 +1,94 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { stylesFactory } from '../../themes';
|
||||
import { useStyles2 } from '../../themes';
|
||||
import { Button, ButtonVariant } from '../Button';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
|
||||
const PAGE_LENGTH_TO_CONDENSE = 8;
|
||||
|
||||
export interface Props {
|
||||
/** The current page index being shown. */
|
||||
/** The current page index being shown. */
|
||||
currentPage: number;
|
||||
/** Number of total pages. */
|
||||
/** Number of total pages. */
|
||||
numberOfPages: number;
|
||||
/** Callback function for fetching the selected page */
|
||||
/** Callback function for fetching the selected page. */
|
||||
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;
|
||||
/** Small version only shows the current page and the navigation buttons. */
|
||||
showSmallVersion?: boolean;
|
||||
}
|
||||
|
||||
export const Pagination: React.FC<Props> = ({ currentPage, numberOfPages, onNavigate, hideWhenSinglePage }) => {
|
||||
const styles = getStyles();
|
||||
const pages = [...new Array(numberOfPages).keys()];
|
||||
export const Pagination: React.FC<Props> = ({
|
||||
currentPage,
|
||||
numberOfPages,
|
||||
onNavigate,
|
||||
hideWhenSinglePage,
|
||||
showSmallVersion,
|
||||
}) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const pageLengthToCondense = showSmallVersion ? 1 : 8;
|
||||
|
||||
const condensePages = numberOfPages > PAGE_LENGTH_TO_CONDENSE;
|
||||
const getListItem = (page: number, variant: 'primary' | 'secondary') => (
|
||||
<li key={page} className={styles.item}>
|
||||
<Button size="sm" variant={variant} onClick={() => onNavigate(page)}>
|
||||
{page}
|
||||
</Button>
|
||||
</li>
|
||||
);
|
||||
const pageButtons = useMemo(() => {
|
||||
const pages = [...new Array(numberOfPages).keys()];
|
||||
|
||||
const pageButtons = pages.reduce<JSX.Element[]>((pagesToRender, pageIndex) => {
|
||||
const page = pageIndex + 1;
|
||||
const variant: ButtonVariant = page === currentPage ? 'primary' : 'secondary';
|
||||
const condensePages = numberOfPages > pageLengthToCondense;
|
||||
const getListItem = (page: number, variant: 'primary' | 'secondary') => (
|
||||
<li key={page} className={styles.item}>
|
||||
<Button size="sm" variant={variant} onClick={() => onNavigate(page)}>
|
||||
{page}
|
||||
</Button>
|
||||
</li>
|
||||
);
|
||||
|
||||
// The indexes at which to start and stop condensing pages
|
||||
const lowerBoundIndex = PAGE_LENGTH_TO_CONDENSE;
|
||||
const upperBoundIndex = numberOfPages - PAGE_LENGTH_TO_CONDENSE + 1;
|
||||
// When the indexes overlap one another this number is negative
|
||||
const differenceOfBounds = upperBoundIndex - lowerBoundIndex;
|
||||
return pages.reduce<JSX.Element[]>((pagesToRender, pageIndex) => {
|
||||
const page = pageIndex + 1;
|
||||
const variant: ButtonVariant = page === currentPage ? 'primary' : 'secondary';
|
||||
|
||||
const isFirstOrLastPage = page === 1 || page === numberOfPages;
|
||||
// This handles when the lowerBoundIndex < currentPage < upperBoundIndex
|
||||
const currentPageIsBetweenBounds =
|
||||
differenceOfBounds > -1 && currentPage >= lowerBoundIndex && currentPage <= upperBoundIndex;
|
||||
// The indexes at which to start and stop condensing pages
|
||||
const lowerBoundIndex = pageLengthToCondense;
|
||||
const upperBoundIndex = numberOfPages - pageLengthToCondense + 1;
|
||||
// When the indexes overlap one another this number is negative
|
||||
const differenceOfBounds = upperBoundIndex - lowerBoundIndex;
|
||||
|
||||
if (condensePages) {
|
||||
if (
|
||||
isFirstOrLastPage ||
|
||||
(currentPage < lowerBoundIndex && page < lowerBoundIndex) ||
|
||||
(differenceOfBounds >= 0 && currentPage > upperBoundIndex && page > upperBoundIndex) ||
|
||||
(differenceOfBounds < 0 && currentPage >= lowerBoundIndex && page > upperBoundIndex) ||
|
||||
(currentPageIsBetweenBounds && page >= currentPage - 2 && page <= currentPage + 2)
|
||||
) {
|
||||
// Renders a button for the page
|
||||
const isFirstOrLastPage = page === 1 || page === numberOfPages;
|
||||
// This handles when the lowerBoundIndex < currentPage < upperBoundIndex
|
||||
const currentPageIsBetweenBounds =
|
||||
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 (
|
||||
isFirstOrLastPage ||
|
||||
(currentPage < lowerBoundIndex && page < lowerBoundIndex) ||
|
||||
(differenceOfBounds >= 0 && currentPage > upperBoundIndex && page > upperBoundIndex) ||
|
||||
(differenceOfBounds < 0 && currentPage >= lowerBoundIndex && page > upperBoundIndex) ||
|
||||
(currentPageIsBetweenBounds && page >= currentPage - pageOffset && page <= currentPage + pageOffset)
|
||||
) {
|
||||
// Renders a button for the page
|
||||
pagesToRender.push(getListItem(page, variant));
|
||||
} else if (
|
||||
(page === lowerBoundIndex && currentPage < lowerBoundIndex) ||
|
||||
(page === upperBoundIndex && currentPage > upperBoundIndex) ||
|
||||
(currentPageIsBetweenBounds &&
|
||||
(page === currentPage - ellipsisOffset || page === currentPage + ellipsisOffset))
|
||||
) {
|
||||
// Renders and ellipsis to represent condensed pages
|
||||
pagesToRender.push(
|
||||
<li key={page} className={styles.item}>
|
||||
<Icon className={styles.ellipsis} name="ellipsis-v" />
|
||||
</li>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
pagesToRender.push(getListItem(page, variant));
|
||||
} else if (
|
||||
(page === lowerBoundIndex && currentPage < lowerBoundIndex) ||
|
||||
(page === upperBoundIndex && currentPage > upperBoundIndex) ||
|
||||
(currentPageIsBetweenBounds && (page === currentPage - 3 || page === currentPage + 3))
|
||||
) {
|
||||
// Renders and ellipsis to represent condensed pages
|
||||
pagesToRender.push(
|
||||
<li key={page} className={styles.item}>
|
||||
<Icon className={styles.ellipsis} name="ellipsis-v" />
|
||||
</li>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
pagesToRender.push(getListItem(page, variant));
|
||||
}
|
||||
return pagesToRender;
|
||||
}, []);
|
||||
return pagesToRender;
|
||||
}, []);
|
||||
}, [currentPage, numberOfPages, onNavigate, pageLengthToCondense, showSmallVersion, styles.ellipsis, styles.item]);
|
||||
|
||||
if (hideWhenSinglePage && numberOfPages <= 1) {
|
||||
return null;
|
||||
@ -108,7 +125,7 @@ export const Pagination: React.FC<Props> = ({ currentPage, numberOfPages, onNavi
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = stylesFactory(() => {
|
||||
const getStyles = () => {
|
||||
return {
|
||||
container: css`
|
||||
float: right;
|
||||
@ -122,4 +139,4 @@ const getStyles = stylesFactory(() => {
|
||||
transform: rotate(90deg);
|
||||
`,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
@ -10,18 +10,19 @@ export interface FooterRowProps {
|
||||
totalColumnsWidth: number;
|
||||
footerGroups: HeaderGroup[];
|
||||
footerValues: FooterItem[];
|
||||
isPaginationVisible: boolean;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export const FooterRow = (props: FooterRowProps) => {
|
||||
const { totalColumnsWidth, footerGroups, height } = props;
|
||||
const { totalColumnsWidth, footerGroups, height, isPaginationVisible } = props;
|
||||
const e2eSelectorsTable = selectors.components.Panels.Visualization.Table;
|
||||
const tableStyles = useStyles2(getTableStyles);
|
||||
|
||||
return (
|
||||
<table
|
||||
style={{
|
||||
position: 'absolute',
|
||||
position: isPaginationVisible ? 'relative' : 'absolute',
|
||||
width: totalColumnsWidth ? `${totalColumnsWidth}px` : '100%',
|
||||
bottom: '0px',
|
||||
}}
|
||||
|
@ -186,3 +186,8 @@ export const Footer: Story = (args) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Pagination: Story = (args) => <Basic {...args} />;
|
||||
Pagination.args = {
|
||||
pageSize: 10,
|
||||
};
|
||||
|
@ -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 {
|
||||
Cell,
|
||||
Column,
|
||||
TableState,
|
||||
useAbsoluteLayout,
|
||||
useFilters,
|
||||
UseFiltersState,
|
||||
usePagination,
|
||||
useResizeColumns,
|
||||
UseResizeColumnsState,
|
||||
useSortBy,
|
||||
UseSortByState,
|
||||
useTable,
|
||||
} from 'react-table';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
@ -27,6 +26,7 @@ import { TableCell } from './TableCell';
|
||||
import { useStyles2 } from '../../themes';
|
||||
import { FooterRow } from './FooterRow';
|
||||
import { HeaderRow } from './HeaderRow';
|
||||
import { Pagination } from '../Pagination/Pagination';
|
||||
|
||||
const COLUMN_MIN_WIDTH = 150;
|
||||
|
||||
@ -45,13 +45,12 @@ export interface Props {
|
||||
onSortByChange?: TableSortByActionCallback;
|
||||
onCellFilterAdded?: TableFilterActionCallback;
|
||||
footerValues?: FooterItem[];
|
||||
enablePagination?: boolean;
|
||||
}
|
||||
|
||||
interface ReactTableInternalState extends UseResizeColumnsState<{}>, UseSortByState<{}>, UseFiltersState<{}> {}
|
||||
|
||||
function useTableStateReducer({ onColumnResize, onSortByChange, data }: Props) {
|
||||
return useCallback(
|
||||
(newState: ReactTableInternalState, action: any) => {
|
||||
(newState: TableState, action: any) => {
|
||||
switch (action.type) {
|
||||
case 'columnDoneResizing':
|
||||
if (onColumnResize) {
|
||||
@ -95,8 +94,8 @@ function useTableStateReducer({ onColumnResize, onSortByChange, data }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
function getInitialState(initialSortBy: Props['initialSortBy'], columns: Column[]): Partial<ReactTableInternalState> {
|
||||
const state: Partial<ReactTableInternalState> = {};
|
||||
function getInitialState(initialSortBy: Props['initialSortBy'], columns: Column[]): Partial<TableState> {
|
||||
const state: Partial<TableState> = {};
|
||||
|
||||
if (initialSortBy) {
|
||||
state.sortBy = [];
|
||||
@ -126,6 +125,7 @@ export const Table: FC<Props> = memo((props: Props) => {
|
||||
initialSortBy,
|
||||
footerValues,
|
||||
showTypeIcons,
|
||||
enablePagination,
|
||||
} = props;
|
||||
|
||||
const tableStyles = useStyles2(getTableStyles);
|
||||
@ -188,17 +188,40 @@ export const Table: FC<Props> = memo((props: Props) => {
|
||||
[initialSortBy, memoizedColumns, memoizedData, resizable, stateReducer]
|
||||
);
|
||||
|
||||
const { getTableProps, headerGroups, rows, prepareRow, totalColumnsWidth, footerGroups } = useTable(
|
||||
options,
|
||||
useFilters,
|
||||
useSortBy,
|
||||
useAbsoluteLayout,
|
||||
useResizeColumns
|
||||
);
|
||||
const {
|
||||
getTableProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
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(
|
||||
({ index: rowIndex, style }) => {
|
||||
const row = rows[rowIndex];
|
||||
let row = rows[rowIndex];
|
||||
if (enablePagination) {
|
||||
row = page[rowIndex];
|
||||
}
|
||||
prepareRow(row);
|
||||
return (
|
||||
<div {...row.getRowProps({ style })} className={tableStyles.row}>
|
||||
@ -215,20 +238,52 @@ export const Table: FC<Props> = memo((props: Props) => {
|
||||
</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 (
|
||||
<div {...getTableProps()} className={tableStyles.table} aria-label={ariaLabel} role="table">
|
||||
<CustomScrollbar hideVerticalTrack={true}>
|
||||
<div style={{ width: totalColumnsWidth ? `${totalColumnsWidth}px` : '100%' }}>
|
||||
<div className={tableStyles.tableContentWrapper(totalColumnsWidth)}>
|
||||
{!noHeader && <HeaderRow data={data} headerGroups={headerGroups} showTypeIcons={showTypeIcons} />}
|
||||
{rows.length > 0 ? (
|
||||
{itemCount > 0 ? (
|
||||
<FixedSizeList
|
||||
height={listHeight}
|
||||
itemCount={rows.length}
|
||||
itemCount={itemCount}
|
||||
itemSize={tableStyles.rowHeight}
|
||||
width={'100%'}
|
||||
style={{ overflow: 'hidden auto' }}
|
||||
@ -243,11 +298,13 @@ export const Table: FC<Props> = memo((props: Props) => {
|
||||
{footerValues && (
|
||||
<FooterRow
|
||||
height={footerHeight}
|
||||
isPaginationVisible={Boolean(enablePagination)}
|
||||
footerValues={footerValues}
|
||||
footerGroups={footerGroups}
|
||||
totalColumnsWidth={totalColumnsWidth}
|
||||
/>
|
||||
)}
|
||||
{paginationEl}
|
||||
</div>
|
||||
</CustomScrollbar>
|
||||
</div>
|
||||
|
@ -170,6 +170,32 @@ export const getTableStyles = (theme: GrafanaTheme2) => {
|
||||
label: headerFilter;
|
||||
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`
|
||||
label: row;
|
||||
border-bottom: 1px solid ${borderColor};
|
||||
|
111
packages/grafana-ui/src/types/react-table-config.d.ts
vendored
Normal file
111
packages/grafana-ui/src/types/react-table-config.d.ts
vendored
Normal 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> {}
|
||||
}
|
@ -6,5 +6,11 @@
|
||||
},
|
||||
"exclude": ["dist", "node_modules"],
|
||||
"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"
|
||||
]
|
||||
}
|
||||
|
14
public/app/plugins/panel/table/PaginationEditor.tsx
Normal file
14
public/app/plugins/panel/table/PaginationEditor.tsx
Normal 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} />;
|
||||
}
|
@ -108,6 +108,7 @@ export class TablePanel extends Component<Props> {
|
||||
onColumnResize={this.onColumnResize}
|
||||
onCellFilterAdded={this.onCellFilterAdded}
|
||||
footerValues={footerValues}
|
||||
enablePagination={options.footer?.enablePagination}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ export interface TableFooterCalc {
|
||||
show: boolean;
|
||||
reducer: string[]; // actually 1 value
|
||||
fields?: string[];
|
||||
enablePagination?: boolean;
|
||||
}
|
||||
|
||||
export const defaultPanelOptions: PanelOptions = {
|
||||
|
@ -12,6 +12,7 @@ import { TableFieldOptions } from '@grafana/schema';
|
||||
import { tableMigrationHandler, tablePanelChangedHandler } from './migrations';
|
||||
import { TableCellDisplayMode } from '@grafana/ui';
|
||||
import { TableSuggestionsSupplier } from './suggestions';
|
||||
import { PaginationEditor } from './PaginationEditor';
|
||||
|
||||
export const plugin = new PanelPlugin<PanelOptions, TableFieldOptions>(TablePanel)
|
||||
.setPanelChangeHandler(tablePanelChangedHandler)
|
||||
@ -108,18 +109,21 @@ export const plugin = new PanelPlugin<PanelOptions, TableFieldOptions>(TablePane
|
||||
builder
|
||||
.addBooleanSwitch({
|
||||
path: 'showHeader',
|
||||
category: ['Header and footer'],
|
||||
name: 'Show header',
|
||||
description: "To display table's header or not to display",
|
||||
defaultValue: defaultPanelOptions.showHeader,
|
||||
})
|
||||
.addBooleanSwitch({
|
||||
path: 'footer.show',
|
||||
category: ['Header and footer'],
|
||||
name: 'Show Footer',
|
||||
description: "To display table's footer or not to display",
|
||||
defaultValue: defaultPanelOptions.footer?.show,
|
||||
})
|
||||
.addCustomEditor({
|
||||
id: 'footer.reducer',
|
||||
category: ['Header and footer'],
|
||||
path: 'footer.reducer',
|
||||
name: 'Calculation',
|
||||
description: 'Choose a reducer function / calculation',
|
||||
@ -129,6 +133,7 @@ export const plugin = new PanelPlugin<PanelOptions, TableFieldOptions>(TablePane
|
||||
})
|
||||
.addMultiSelect({
|
||||
path: 'footer.fields',
|
||||
category: ['Header and footer'],
|
||||
name: 'Fields',
|
||||
description: 'Select the fields that should be calculated',
|
||||
settings: {
|
||||
@ -152,6 +157,13 @@ export const plugin = new PanelPlugin<PanelOptions, TableFieldOptions>(TablePane
|
||||
},
|
||||
defaultValue: '',
|
||||
showIf: (cfg) => cfg.footer?.show,
|
||||
})
|
||||
.addCustomEditor({
|
||||
id: 'footer.enablePagination',
|
||||
category: ['Header and footer'],
|
||||
path: 'footer.enablePagination',
|
||||
name: 'Enable pagination',
|
||||
editor: PaginationEditor,
|
||||
});
|
||||
})
|
||||
.setSuggestionsSupplier(new TableSuggestionsSupplier());
|
||||
|
@ -18,6 +18,7 @@
|
||||
"public/test/**/*.ts",
|
||||
"public/vendor/**/*.ts",
|
||||
"packages/jaeger-ui-components/typings",
|
||||
"packages/grafana-data/typings"
|
||||
"packages/grafana-data/typings",
|
||||
"packages/grafana-ui/src/types"
|
||||
]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user