Table: Prevent re-created functions and arrays (#109929)
* Table: Button up re-created functions and arrays * use the single styles object * restore automatically adjusted line * restore automatically adjusted line * update computeColWidths to create a string to help compare col widths * use compareArrayVals for widths comparisons * satisfy linter * whoops, had this backwards * clean up case with frozen columns * remove dep * avoid unnecessary change to computeColWidths
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
DataGridHandle,
|
||||
DataGridProps,
|
||||
RenderCellProps,
|
||||
Renderers,
|
||||
RenderRowProps,
|
||||
Row,
|
||||
SortColumn,
|
||||
@@ -42,6 +43,7 @@ import { TableCellTooltip } from './components/TableCellTooltip';
|
||||
import { COLUMN, TABLE } from './constants';
|
||||
import {
|
||||
useColumnResize,
|
||||
useColWidths,
|
||||
useFilteredRows,
|
||||
useFooterCalcs,
|
||||
useHeaderHeight,
|
||||
@@ -51,6 +53,7 @@ import {
|
||||
useSortedRows,
|
||||
} from './hooks';
|
||||
import {
|
||||
getCellActionStyles,
|
||||
getDefaultCellStyles,
|
||||
getFooterStyles,
|
||||
getGridStyles,
|
||||
@@ -58,11 +61,19 @@ import {
|
||||
getLinkStyles,
|
||||
getTooltipStyles,
|
||||
} from './styles';
|
||||
import { TableNGProps, TableRow, TableSummaryRow, TableColumn, InspectCellProps, TableCellStyleOptions } from './types';
|
||||
import {
|
||||
TableNGProps,
|
||||
TableRow,
|
||||
TableSummaryRow,
|
||||
TableColumn,
|
||||
InspectCellProps,
|
||||
TableCellStyleOptions,
|
||||
FromFieldsResult,
|
||||
CellRootRenderer,
|
||||
} from './types';
|
||||
import {
|
||||
applySort,
|
||||
canFieldBeColorized,
|
||||
computeColWidths,
|
||||
createTypographyContext,
|
||||
displayJsonValue,
|
||||
extractPixelValue,
|
||||
@@ -84,7 +95,7 @@ import {
|
||||
withDataLinksActionsTooltip,
|
||||
} from './utils';
|
||||
|
||||
type CellRootRenderer = (key: React.Key, props: CellRendererProps<TableRow, TableSummaryRow>) => React.ReactNode;
|
||||
const EXPANDED_COLUMN_KEY = 'expanded';
|
||||
|
||||
export function TableNG(props: TableNGProps) {
|
||||
const {
|
||||
@@ -148,6 +159,7 @@ export function TableNG(props: TableNGProps) {
|
||||
} = useSortedRows(filteredRows, data.fields, { hasNestedFrames, initialSortBy });
|
||||
|
||||
const [inspectCell, setInspectCell] = useState<InspectCellProps | null>(null);
|
||||
const [tooltipState, setTooltipState] = useState<DataLinksActionsTooltipState>();
|
||||
const [expandedRows, setExpandedRows] = useState(() => new Set<number>());
|
||||
|
||||
// vt scrollbar accounting for column auto-sizing
|
||||
@@ -171,20 +183,9 @@ export function TableNG(props: TableNGProps) {
|
||||
),
|
||||
[theme]
|
||||
);
|
||||
const widths = useMemo(() => computeColWidths(visibleFields, availableWidth), [visibleFields, availableWidth]);
|
||||
const numColsFullyInView = useMemo(
|
||||
() =>
|
||||
widths.reduce(
|
||||
([count, remainingWidth], nextWidth) => {
|
||||
if (remainingWidth - nextWidth >= 0) {
|
||||
return [count + 1, remainingWidth - nextWidth];
|
||||
}
|
||||
return [count, 0];
|
||||
},
|
||||
[0, availableWidth]
|
||||
)[0],
|
||||
[widths, availableWidth]
|
||||
);
|
||||
|
||||
const [widths, numFrozenColsFullyInView] = useColWidths(visibleFields, availableWidth, frozenColumns);
|
||||
|
||||
const headerHeight = useHeaderHeight({
|
||||
columnWidths: widths,
|
||||
fields: visibleFields,
|
||||
@@ -270,15 +271,84 @@ export function TableNG(props: TableNGProps) {
|
||||
[enableVirtualization, resizeHandler, sortColumns, rowHeight, hasFooter, setSortColumns, onSortByChange]
|
||||
);
|
||||
|
||||
interface Schema {
|
||||
columns: TableColumn[];
|
||||
cellRootRenderers: Record<string, CellRootRenderer>;
|
||||
colsWithTooltip: Record<string, boolean>;
|
||||
}
|
||||
const buildNestedTableExpanderColumn = useCallback(
|
||||
(
|
||||
nestedColumns: TableColumn[],
|
||||
hasNestedHeaders: boolean,
|
||||
renderers: Renderers<TableRow, TableSummaryRow>
|
||||
): TableColumn => ({
|
||||
key: EXPANDED_COLUMN_KEY,
|
||||
name: '',
|
||||
field: {
|
||||
name: '',
|
||||
type: FieldType.other,
|
||||
config: {},
|
||||
values: [],
|
||||
},
|
||||
cellClass(row) {
|
||||
if (row.__depth !== 0) {
|
||||
return styles.cellNested;
|
||||
}
|
||||
return;
|
||||
},
|
||||
colSpan(args) {
|
||||
return args.type === 'ROW' && args.row.__depth === 1 ? data.fields.length : 1;
|
||||
},
|
||||
renderCell: ({ row }) => {
|
||||
if (row.__depth === 0) {
|
||||
const rowIdx = row.__index;
|
||||
|
||||
const { columns, cellRootRenderers, colsWithTooltip } = useMemo(() => {
|
||||
const fromFields = (f: Field[], widths: number[]) => {
|
||||
const result: Schema = {
|
||||
return (
|
||||
<RowExpander
|
||||
isExpanded={expandedRows.has(rowIdx)}
|
||||
onCellExpand={() => {
|
||||
if (expandedRows.has(rowIdx)) {
|
||||
expandedRows.delete(rowIdx);
|
||||
} else {
|
||||
expandedRows.add(rowIdx);
|
||||
}
|
||||
setExpandedRows(new Set(expandedRows));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Type guard to check if data exists as it's optional
|
||||
const nestedData = row.data;
|
||||
if (!nestedData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const expandedRecords = applySort(frameToRecords(nestedData), nestedData.fields, sortColumns);
|
||||
if (!expandedRecords.length) {
|
||||
return (
|
||||
<div className={styles.noDataNested}>
|
||||
<Trans i18nKey="grafana-ui.table.nested-table.no-data">No data</Trans>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DataGrid<TableRow, TableSummaryRow>
|
||||
{...commonDataGridProps}
|
||||
className={clsx(styles.grid, styles.gridNested)}
|
||||
headerRowClass={clsx(styles.headerRow, { [styles.displayNone]: !hasNestedHeaders })}
|
||||
headerRowHeight={hasNestedHeaders ? TABLE.HEADER_HEIGHT : 0}
|
||||
columns={nestedColumns}
|
||||
rows={expandedRecords}
|
||||
renderers={renderers}
|
||||
/>
|
||||
);
|
||||
},
|
||||
width: COLUMN.EXPANDER_WIDTH,
|
||||
minWidth: COLUMN.EXPANDER_WIDTH,
|
||||
}),
|
||||
[commonDataGridProps, data.fields.length, expandedRows, sortColumns, styles]
|
||||
);
|
||||
|
||||
const fromFields = useCallback(
|
||||
(f: Field[], widths: number[]): FromFieldsResult => {
|
||||
const result: FromFieldsResult = {
|
||||
columns: [],
|
||||
cellRootRenderers: {},
|
||||
colsWithTooltip: {},
|
||||
@@ -334,11 +404,7 @@ export function TableNG(props: TableNGProps) {
|
||||
|
||||
// helps us avoid string cx and emotion per-cell
|
||||
const cellActionClassName = showActions
|
||||
? clsx(
|
||||
'table-cell-actions',
|
||||
styles.cellActions,
|
||||
justifyContent === 'flex-end' ? styles.cellActionsEnd : styles.cellActionsStart
|
||||
)
|
||||
? clsx('table-cell-actions', getCellActionStyles(theme, textAlign))
|
||||
: undefined;
|
||||
|
||||
const shouldOverflow = rowHeight !== 'auto' && shouldTextOverflow(field);
|
||||
@@ -509,13 +575,13 @@ export function TableNG(props: TableNGProps) {
|
||||
}
|
||||
}
|
||||
|
||||
const column: TableColumn = {
|
||||
result.columns.push({
|
||||
field,
|
||||
key: displayName,
|
||||
name: displayName,
|
||||
width,
|
||||
headerCellClass,
|
||||
frozen: Math.min(frozenColumns, numColsFullyInView) > i,
|
||||
frozen: Math.min(frozenColumns, numFrozenColsFullyInView) > i,
|
||||
renderCell: renderCellContent,
|
||||
renderHeaderCell: ({ column, sortDirection }) => (
|
||||
<HeaderCell
|
||||
@@ -543,136 +609,78 @@ export function TableNG(props: TableNGProps) {
|
||||
}
|
||||
return <div className={footerStyles.footerCell}>{footerCalcs[i]}</div>;
|
||||
},
|
||||
};
|
||||
|
||||
result.columns.push(column);
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
},
|
||||
[
|
||||
applyToRowBgFn,
|
||||
crossFilterOrder,
|
||||
crossFilterRows,
|
||||
data,
|
||||
disableSanitizeHtml,
|
||||
filter,
|
||||
footerCalcs,
|
||||
frozenColumns,
|
||||
getCellActions,
|
||||
isCountRowsSet,
|
||||
numFrozenColsFullyInView,
|
||||
onCellFilterAdded,
|
||||
rowHeight,
|
||||
rowHeightFn,
|
||||
rows,
|
||||
setFilter,
|
||||
showTypeIcons,
|
||||
theme,
|
||||
timeRange,
|
||||
]
|
||||
);
|
||||
|
||||
// set up the first row's nested data and the nest field widths using useColWidths to avoid
|
||||
// unnecessary re-renders on re-size.
|
||||
const firstRowNestedData = useMemo(
|
||||
() => (hasNestedFrames ? rows.find((r) => r.data)?.data : undefined),
|
||||
[hasNestedFrames, rows]
|
||||
);
|
||||
const [nestedFieldWidths] = useColWidths(firstRowNestedData?.fields ?? [], availableWidth);
|
||||
|
||||
const { columns, cellRootRenderers, colsWithTooltip } = useMemo(() => {
|
||||
const result = fromFields(visibleFields, widths);
|
||||
|
||||
// handle nested frames rendering from here.
|
||||
if (!hasNestedFrames) {
|
||||
// if nested frames are present, augment the columns to include the nested table expander column.
|
||||
if (!firstRowNestedData) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// pre-calculate renderRow and expandedColumns based on the first nested frame's fields.
|
||||
const firstNestedData = rows.find((r) => r.data)?.data;
|
||||
if (!firstNestedData) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const hasNestedHeaders = firstNestedData.meta?.custom?.noHeader !== true;
|
||||
const renderRow = renderRowFactory(firstNestedData.fields, panelContext, expandedRows, enableSharedCrosshair);
|
||||
const hasNestedHeaders = firstRowNestedData.meta?.custom?.noHeader !== true;
|
||||
const renderRow = renderRowFactory(firstRowNestedData.fields, panelContext, expandedRows, enableSharedCrosshair);
|
||||
const { columns: nestedColumns, cellRootRenderers: nestedCellRootRenderers } = fromFields(
|
||||
firstNestedData.fields,
|
||||
computeColWidths(firstNestedData.fields, availableWidth)
|
||||
firstRowNestedData.fields,
|
||||
nestedFieldWidths
|
||||
);
|
||||
|
||||
const renderCellRoot: CellRootRenderer = (key, props) => nestedCellRootRenderers[props.column.key](key, props);
|
||||
|
||||
result.cellRootRenderers.expanded = (key, props) => <Cell key={key} {...props} />;
|
||||
const expanderCellRenderer: CellRootRenderer = (key, props) => <Cell key={key} {...props} />;
|
||||
result.cellRootRenderers[EXPANDED_COLUMN_KEY] = expanderCellRenderer;
|
||||
|
||||
// If we have nested frames, we need to add a column for the row expansion
|
||||
result.columns.unshift({
|
||||
key: 'expanded',
|
||||
name: '',
|
||||
field: {
|
||||
name: '',
|
||||
type: FieldType.other,
|
||||
config: {},
|
||||
values: [],
|
||||
},
|
||||
cellClass(row) {
|
||||
if (row.__depth !== 0) {
|
||||
return styles.cellNested;
|
||||
}
|
||||
return;
|
||||
},
|
||||
colSpan(args) {
|
||||
return args.type === 'ROW' && args.row.__depth === 1 ? data.fields.length : 1;
|
||||
},
|
||||
renderCell: ({ row }) => {
|
||||
if (row.__depth === 0) {
|
||||
const rowIdx = row.__index;
|
||||
|
||||
return (
|
||||
<RowExpander
|
||||
isExpanded={expandedRows.has(rowIdx)}
|
||||
onCellExpand={() => {
|
||||
if (expandedRows.has(rowIdx)) {
|
||||
expandedRows.delete(rowIdx);
|
||||
} else {
|
||||
expandedRows.add(rowIdx);
|
||||
}
|
||||
setExpandedRows(new Set(expandedRows));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Type guard to check if data exists as it's optional
|
||||
const nestedData = row.data;
|
||||
if (!nestedData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const expandedRecords = applySort(frameToRecords(nestedData), nestedData.fields, sortColumns);
|
||||
if (!expandedRecords.length) {
|
||||
return (
|
||||
<div className={styles.noDataNested}>
|
||||
<Trans i18nKey="grafana-ui.table.nested-table.no-data">No data</Trans>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DataGrid<TableRow, TableSummaryRow>
|
||||
{...commonDataGridProps}
|
||||
className={clsx(styles.grid, styles.gridNested)}
|
||||
headerRowClass={clsx(styles.headerRow, { [styles.displayNone]: !hasNestedHeaders })}
|
||||
headerRowHeight={hasNestedHeaders ? TABLE.HEADER_HEIGHT : 0}
|
||||
columns={nestedColumns}
|
||||
rows={expandedRecords}
|
||||
renderers={{ renderRow, renderCell: renderCellRoot }}
|
||||
/>
|
||||
);
|
||||
},
|
||||
width: COLUMN.EXPANDER_WIDTH,
|
||||
minWidth: COLUMN.EXPANDER_WIDTH,
|
||||
});
|
||||
result.columns.unshift(
|
||||
buildNestedTableExpanderColumn(nestedColumns, hasNestedHeaders, {
|
||||
renderRow,
|
||||
renderCell: (key, props) => nestedCellRootRenderers[props.column.key](key, props),
|
||||
})
|
||||
);
|
||||
|
||||
return result;
|
||||
}, [
|
||||
applyToRowBgFn,
|
||||
availableWidth,
|
||||
commonDataGridProps,
|
||||
crossFilterOrder,
|
||||
crossFilterRows,
|
||||
data,
|
||||
disableSanitizeHtml,
|
||||
buildNestedTableExpanderColumn,
|
||||
enableSharedCrosshair,
|
||||
expandedRows,
|
||||
filter,
|
||||
footerCalcs,
|
||||
frozenColumns,
|
||||
getCellActions,
|
||||
hasNestedFrames,
|
||||
isCountRowsSet,
|
||||
numColsFullyInView,
|
||||
onCellFilterAdded,
|
||||
firstRowNestedData,
|
||||
fromFields,
|
||||
nestedFieldWidths,
|
||||
panelContext,
|
||||
rowHeight,
|
||||
rowHeightFn,
|
||||
rows,
|
||||
setFilter,
|
||||
showTypeIcons,
|
||||
sortColumns,
|
||||
styles,
|
||||
timeRange,
|
||||
theme,
|
||||
visibleFields,
|
||||
widths,
|
||||
]);
|
||||
@@ -680,18 +688,16 @@ export function TableNG(props: TableNGProps) {
|
||||
// invalidate columns on every structureRev change. this supports width editing in the fieldConfig.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const structureRevColumns = useMemo(() => columns, [columns, structureRev]);
|
||||
const renderCellRoot: CellRootRenderer = useCallback(
|
||||
(key, props) => cellRootRenderers[props.column.key](key, props),
|
||||
[cellRootRenderers]
|
||||
);
|
||||
|
||||
// we need to have variables with these exact names for the localization to work properly
|
||||
const itemsRangeStart = pageRangeStart;
|
||||
const displayedEnd = pageRangeEnd;
|
||||
const numRows = sortedRows.length;
|
||||
|
||||
const renderCellRoot: CellRootRenderer = (key, props) => {
|
||||
return cellRootRenderers[props.column.key](key, props);
|
||||
};
|
||||
|
||||
const [tooltipState, setTooltipState] = useState<DataLinksActionsTooltipState>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataGrid<TableRow, TableSummaryRow>
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { useState, useMemo, useCallback, useRef, useLayoutEffect, RefObject, CSSProperties } from 'react';
|
||||
import { useState, useMemo, useCallback, useRef, useLayoutEffect, RefObject, CSSProperties, useEffect } from 'react';
|
||||
import { Column, DataGridHandle, DataGridProps, SortColumn } from 'react-data-grid';
|
||||
|
||||
import { Field, fieldReducers, FieldType, formattedValueToString, reduceField } from '@grafana/data';
|
||||
import {
|
||||
compareArrayValues,
|
||||
Field,
|
||||
fieldReducers,
|
||||
FieldType,
|
||||
formattedValueToString,
|
||||
reduceField,
|
||||
} from '@grafana/data';
|
||||
|
||||
import { TableColumnResizeActionCallback } from '../types';
|
||||
|
||||
@@ -13,6 +20,7 @@ import {
|
||||
applySort,
|
||||
getColumnTypes,
|
||||
getRowHeight,
|
||||
computeColWidths,
|
||||
buildHeaderHeightMeasurers,
|
||||
buildCellHeightMeasurers,
|
||||
} from './utils';
|
||||
@@ -183,16 +191,17 @@ export function usePaginatedRows(
|
||||
return rows.slice(0, 100).reduce((avg, row, _, { length }) => avg + rowHeight(row) / length, 0);
|
||||
}, [rows, rowHeight, enabled]);
|
||||
|
||||
const smallPagination = useMemo(() => enabled && width < TABLE.PAGINATION_LIMIT, [enabled, width]);
|
||||
|
||||
// using dimensions of the panel, calculate pagination parameters
|
||||
const { numPages, rowsPerPage, pageRangeStart, pageRangeEnd, smallPagination } = useMemo((): {
|
||||
const { numPages, rowsPerPage, pageRangeStart, pageRangeEnd } = useMemo((): {
|
||||
numPages: number;
|
||||
rowsPerPage: number;
|
||||
pageRangeStart: number;
|
||||
pageRangeEnd: number;
|
||||
smallPagination: boolean;
|
||||
} => {
|
||||
if (!enabled) {
|
||||
return { numPages: 0, rowsPerPage: 0, pageRangeStart: 1, pageRangeEnd: numRows, smallPagination: false };
|
||||
return { numPages: 0, rowsPerPage: 0, pageRangeStart: 1, pageRangeEnd: numRows };
|
||||
}
|
||||
|
||||
// calculate number of rowsPerPage based on height stack
|
||||
@@ -207,16 +216,15 @@ export function usePaginatedRows(
|
||||
if (pageRangeEnd > numRows) {
|
||||
pageRangeEnd = numRows;
|
||||
}
|
||||
const smallPagination = width < TABLE.PAGINATION_LIMIT;
|
||||
|
||||
const numPages = Math.ceil(numRows / rowsPerPage);
|
||||
return {
|
||||
numPages,
|
||||
rowsPerPage,
|
||||
pageRangeStart,
|
||||
pageRangeEnd,
|
||||
smallPagination,
|
||||
};
|
||||
}, [width, height, headerHeight, footerHeight, avgRowHeight, enabled, numRows, page]);
|
||||
}, [height, headerHeight, footerHeight, avgRowHeight, enabled, numRows, page]);
|
||||
|
||||
// safeguard against page overflow on panel resize or other factors
|
||||
useLayoutEffect(() => {
|
||||
@@ -542,3 +550,45 @@ export function useScrollbarWidth(ref: RefObject<DataGridHandle>, height: number
|
||||
|
||||
return scrollbarWidth;
|
||||
}
|
||||
|
||||
const numIsEqual = (a: number, b: number) => a === b;
|
||||
|
||||
export function useColWidths(
|
||||
visibleFields: Field[],
|
||||
availableWidth: number,
|
||||
frozenColumns?: number
|
||||
): [number[], number] {
|
||||
const [widths, setWidths] = useState<number[]>(computeColWidths(visibleFields, availableWidth));
|
||||
|
||||
// only replace the widths array if something actually changed
|
||||
useEffect(() => {
|
||||
const newWidths = computeColWidths(visibleFields, availableWidth);
|
||||
if (!compareArrayValues(widths, newWidths, numIsEqual)) {
|
||||
setWidths(newWidths);
|
||||
}
|
||||
}, [availableWidth, widths, visibleFields]);
|
||||
|
||||
// this is to avoid buggy situations where all visible columns are frozen
|
||||
const numFrozenColsFullyInView = useMemo(() => {
|
||||
if (!frozenColumns || frozenColumns <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const fullyVisibleCols = widths.reduce(
|
||||
([count, remainingWidth], nextWidth) => {
|
||||
if (remainingWidth - nextWidth >= 0) {
|
||||
return [count + 1, remainingWidth - nextWidth];
|
||||
}
|
||||
return [count, 0];
|
||||
},
|
||||
[0, availableWidth]
|
||||
)[0];
|
||||
|
||||
// de-noise memoized changes to the columns array, and only change this
|
||||
// number when the number of frozen columns changes or once there are fewer
|
||||
// visible columns than the number of frozen columns.
|
||||
return Math.min(fullyVisibleCols, frozenColumns);
|
||||
}, [widths, availableWidth, frozenColumns]);
|
||||
|
||||
return [widths, numFrozenColsFullyInView];
|
||||
}
|
||||
|
||||
@@ -88,19 +88,6 @@ export const getGridStyles = (theme: GrafanaTheme2, enablePagination?: boolean,
|
||||
color: theme.colors.text.secondary,
|
||||
fontSize: theme.typography.h4.fontSize,
|
||||
}),
|
||||
cellActions: css({
|
||||
display: 'none',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
margin: 'auto',
|
||||
height: '100%',
|
||||
color: theme.colors.text.primary,
|
||||
background: theme.isDark ? 'rgba(0, 0, 0, 0.7)' : 'rgba(255, 255, 255, 0.7)',
|
||||
padding: theme.spacing.x0_5,
|
||||
paddingInlineStart: theme.spacing.x1,
|
||||
}),
|
||||
cellActionsEnd: css({ left: 0 }),
|
||||
cellActionsStart: css({ right: 0 }),
|
||||
headerRow: css({
|
||||
paddingBlockStart: 0,
|
||||
fontWeight: 'normal',
|
||||
@@ -158,6 +145,20 @@ export const getDefaultCellStyles: TableCellStyles = (theme, { textAlign, should
|
||||
},
|
||||
});
|
||||
|
||||
export const getCellActionStyles = (theme: GrafanaTheme2, textAlign: TextAlign) =>
|
||||
css({
|
||||
display: 'none',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
margin: 'auto',
|
||||
height: '100%',
|
||||
color: theme.colors.text.primary,
|
||||
background: theme.isDark ? 'rgba(0, 0, 0, 0.7)' : 'rgba(255, 255, 255, 0.7)',
|
||||
padding: theme.spacing.x0_5,
|
||||
paddingInlineStart: theme.spacing.x1,
|
||||
[textAlign === 'right' ? 'left' : 'right']: 0,
|
||||
});
|
||||
|
||||
export const getLinkStyles = (theme: GrafanaTheme2, canBeColorized: boolean) =>
|
||||
css({
|
||||
a: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FC, SyntheticEvent } from 'react';
|
||||
import { Column } from 'react-data-grid';
|
||||
import { CellRendererProps, Column } from 'react-data-grid';
|
||||
|
||||
import {
|
||||
DataFrame,
|
||||
@@ -306,3 +306,11 @@ export interface MeasureCellHeightEntry {
|
||||
*/
|
||||
fieldIdxs: number[];
|
||||
}
|
||||
|
||||
export type CellRootRenderer = (key: React.Key, props: CellRendererProps<TableRow, TableSummaryRow>) => React.ReactNode;
|
||||
|
||||
export interface FromFieldsResult {
|
||||
columns: TableColumn[];
|
||||
cellRootRenderers: Record<string, CellRootRenderer>;
|
||||
colsWithTooltip: Record<string, boolean>;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,19 @@ import { Options } from './panelcfg.gen';
|
||||
interface Props extends PanelProps<Options> {}
|
||||
|
||||
export function TablePanel(props: Props) {
|
||||
const { data, height, width, options, fieldConfig, id, timeRange, replaceVariables, transparent } = props;
|
||||
const {
|
||||
data,
|
||||
height,
|
||||
width,
|
||||
options,
|
||||
onFieldConfigChange,
|
||||
onOptionsChange,
|
||||
fieldConfig,
|
||||
id,
|
||||
timeRange,
|
||||
replaceVariables,
|
||||
transparent,
|
||||
} = props;
|
||||
|
||||
useMemo(() => {
|
||||
cacheFieldDisplayNames(data.series);
|
||||
@@ -47,6 +59,59 @@ export function TablePanel(props: Props) {
|
||||
const currentIndex = getCurrentFrameIndex(frames, options);
|
||||
const main = frames[currentIndex];
|
||||
|
||||
const onColumnResize = useCallback(
|
||||
(fieldDisplayName: string, width: number) => {
|
||||
const { overrides } = fieldConfig;
|
||||
|
||||
const matcherId = FieldMatcherID.byName;
|
||||
const propId = 'custom.width';
|
||||
|
||||
// look for existing override
|
||||
const override = overrides.find((o) => o.matcher.id === matcherId && o.matcher.options === fieldDisplayName);
|
||||
|
||||
if (override) {
|
||||
// look for existing property
|
||||
const property = override.properties.find((prop) => prop.id === propId);
|
||||
if (property) {
|
||||
property.value = width;
|
||||
} else {
|
||||
override.properties.push({ id: propId, value: width });
|
||||
}
|
||||
} else {
|
||||
overrides.push({
|
||||
matcher: { id: matcherId, options: fieldDisplayName },
|
||||
properties: [{ id: propId, value: width }],
|
||||
});
|
||||
}
|
||||
|
||||
onFieldConfigChange({
|
||||
...fieldConfig,
|
||||
overrides,
|
||||
});
|
||||
},
|
||||
[fieldConfig, onFieldConfigChange]
|
||||
);
|
||||
|
||||
const onSortByChange = useCallback(
|
||||
(sortBy: TableSortByFieldState[]) => {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
sortBy,
|
||||
});
|
||||
},
|
||||
[options, onOptionsChange]
|
||||
);
|
||||
|
||||
const onChangeTableSelection = useCallback(
|
||||
(val: SelectableValue<number>) => {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
frameIndex: val.value || 0,
|
||||
});
|
||||
},
|
||||
[options, onOptionsChange]
|
||||
);
|
||||
|
||||
let tableHeight = height;
|
||||
|
||||
if (!count || !hasFields) {
|
||||
@@ -73,8 +138,8 @@ export function TablePanel(props: Props) {
|
||||
showTypeIcons={options.showTypeIcons}
|
||||
resizable={true}
|
||||
initialSortBy={options.sortBy}
|
||||
onSortByChange={(sortBy) => onSortByChange(sortBy, props)}
|
||||
onColumnResize={(displayName, resizedWidth) => onColumnResize(displayName, resizedWidth, props)}
|
||||
onSortByChange={onSortByChange}
|
||||
onColumnResize={onColumnResize}
|
||||
onCellFilterAdded={panelContext.onAddAdHocFilter}
|
||||
footerOptions={options.footer}
|
||||
frozenColumns={options.frozenColumns?.left}
|
||||
@@ -105,7 +170,7 @@ export function TablePanel(props: Props) {
|
||||
<div className={tableStyles.wrapper}>
|
||||
{tableElement}
|
||||
<div className={tableStyles.selectWrapper}>
|
||||
<Select options={names} value={names[currentIndex]} onChange={(val) => onChangeTableSelection(val, props)} />
|
||||
<Select options={names} value={names[currentIndex]} onChange={onChangeTableSelection} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -115,51 +180,6 @@ function getCurrentFrameIndex(frames: DataFrame[], options: Options) {
|
||||
return options.frameIndex > 0 && options.frameIndex < frames.length ? options.frameIndex : 0;
|
||||
}
|
||||
|
||||
function onColumnResize(fieldDisplayName: string, width: number, props: Props) {
|
||||
const { fieldConfig } = props;
|
||||
const { overrides } = fieldConfig;
|
||||
|
||||
const matcherId = FieldMatcherID.byName;
|
||||
const propId = 'custom.width';
|
||||
|
||||
// look for existing override
|
||||
const override = overrides.find((o) => o.matcher.id === matcherId && o.matcher.options === fieldDisplayName);
|
||||
|
||||
if (override) {
|
||||
// look for existing property
|
||||
const property = override.properties.find((prop) => prop.id === propId);
|
||||
if (property) {
|
||||
property.value = width;
|
||||
} else {
|
||||
override.properties.push({ id: propId, value: width });
|
||||
}
|
||||
} else {
|
||||
overrides.push({
|
||||
matcher: { id: matcherId, options: fieldDisplayName },
|
||||
properties: [{ id: propId, value: width }],
|
||||
});
|
||||
}
|
||||
|
||||
props.onFieldConfigChange({
|
||||
...fieldConfig,
|
||||
overrides,
|
||||
});
|
||||
}
|
||||
|
||||
function onSortByChange(sortBy: TableSortByFieldState[], props: Props) {
|
||||
props.onOptionsChange({
|
||||
...props.options,
|
||||
sortBy,
|
||||
});
|
||||
}
|
||||
|
||||
function onChangeTableSelection(val: SelectableValue<number>, props: Props) {
|
||||
props.onOptionsChange({
|
||||
...props.options,
|
||||
frameIndex: val.value || 0,
|
||||
});
|
||||
}
|
||||
|
||||
// placeholder function; assuming the values are already interpolated
|
||||
const replaceVars: InterpolateFunction = (value: string) => value;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user