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
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');
// 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');
},
});

View File

@ -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"
]
}

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,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);
`,
};
});
};

View File

@ -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',
}}

View File

@ -186,3 +186,8 @@ export const Footer: Story = (args) => {
</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 {
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>

View File

@ -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};

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"],
"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}
onCellFilterAdded={this.onCellFilterAdded}
footerValues={footerValues}
enablePagination={options.footer?.enablePagination}
/>
);
}

View File

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

View File

@ -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());

View File

@ -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"
]
}