Table: Fix inspect drawer disappears unexpectedly (#99025)

fix(table): inspect drawer disappears unexpectedly
pull/99101/head
Ihor Yeromin 5 months ago committed by GitHub
parent b532df36c4
commit 019ee9c2d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      .betterer.results
  2. 69
      packages/grafana-ui/src/components/Table/CellActions.tsx
  3. 22
      packages/grafana-ui/src/components/Table/DefaultCell.tsx
  4. 12
      packages/grafana-ui/src/components/Table/RowsList.tsx
  5. 136
      packages/grafana-ui/src/components/Table/Table.tsx
  6. 5
      packages/grafana-ui/src/components/Table/TableCell.tsx
  7. 5
      packages/grafana-ui/src/components/Table/types.ts

@ -830,7 +830,8 @@ exports[`better eslint`] = {
],
"packages/grafana-ui/src/components/Table/types.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"]
],
"packages/grafana-ui/src/components/Table/utils.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],

@ -1,4 +1,4 @@
import { useCallback, useState } from 'react';
import { useCallback } from 'react';
import * as React from 'react';
import { IconSize } from '../../types/icon';
@ -6,7 +6,7 @@ import { IconButton } from '../IconButton/IconButton';
import { Stack } from '../Layout/Stack/Stack';
import { TooltipPlacement } from '../Tooltip';
import { TableCellInspector, TableCellInspectorMode } from './TableCellInspector';
import { TableCellInspectorMode } from './TableCellInspector';
import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR, TableCellProps } from './types';
import { getTextAlign } from './utils';
@ -20,9 +20,14 @@ interface CommonButtonProps {
tooltipPlacement: TooltipPlacement;
}
export function CellActions({ field, cell, previewMode, showFilters, onCellFilterAdded }: CellActionProps) {
const [isInspecting, setIsInspecting] = useState(false);
export function CellActions({
field,
cell,
previewMode,
showFilters,
onCellFilterAdded,
setInspectCell,
}: CellActionProps) {
const isRightAligned = getTextAlign(field) === 'flex-end';
const inspectEnabled = Boolean(field.config.custom?.inspect);
const commonButtonProps: CommonButtonProps = {
@ -48,37 +53,27 @@ export function CellActions({ field, cell, previewMode, showFilters, onCellFilte
);
return (
<>
<div className={`cellActions${isRightAligned ? ' cellActionsLeft' : ''}`}>
<Stack gap={0.5}>
{inspectEnabled && (
<IconButton
name="eye"
tooltip="Inspect value"
onClick={() => {
setIsInspecting(true);
}}
{...commonButtonProps}
/>
)}
{showFilters && (
<IconButton name={'search-plus'} onClick={onFilterFor} tooltip="Filter for value" {...commonButtonProps} />
)}
{showFilters && (
<IconButton name={'search-minus'} onClick={onFilterOut} tooltip="Filter out value" {...commonButtonProps} />
)}
</Stack>
</div>
{isInspecting && (
<TableCellInspector
mode={previewMode}
value={cell.value}
onDismiss={() => {
setIsInspecting(false);
}}
/>
)}
</>
<div className={`cellActions${isRightAligned ? ' cellActionsLeft' : ''}`}>
<Stack gap={0.5}>
{inspectEnabled && (
<IconButton
name="eye"
tooltip="Inspect value"
onClick={() => {
if (setInspectCell) {
setInspectCell({ value: cell.value, mode: previewMode });
}
}}
{...commonButtonProps}
/>
)}
{showFilters && (
<IconButton name={'search-plus'} onClick={onFilterFor} tooltip="Filter for value" {...commonButtonProps} />
)}
{showFilters && (
<IconButton name={'search-minus'} onClick={onFilterOut} tooltip="Filter out value" {...commonButtonProps} />
)}
</Stack>
</div>
);
}

@ -1,5 +1,5 @@
import { cx } from '@emotion/css';
import { ReactElement, useState } from 'react';
import { ReactElement } from 'react';
import * as React from 'react';
import { DisplayValue, formattedValueToString } from '@grafana/data';
@ -28,18 +28,10 @@ export const DefaultCell = (props: TableCellProps) => {
const hasLinks = Boolean(getCellLinks(field, row)?.length);
const hasActions = Boolean(actions?.length);
const clearButtonStyle = useStyles2(clearLinkButtonStyles);
const [hover, setHover] = useState(false);
let value: string | ReactElement;
const OG_TWEET_LENGTH = 140; // 🙏
const onMouseLeave = () => {
setHover(false);
};
const onMouseEnter = () => {
setHover(true);
};
if (cellOptions.type === TableCellDisplayMode.Custom) {
const CustomCellComponent: React.ComponentType<CustomCellRendererProps> = cellOptions.cellComponent;
value = <CustomCellComponent field={field} value={cell.value} rowIndex={row.index} frame={frame} />;
@ -88,13 +80,7 @@ export const DefaultCell = (props: TableCellProps) => {
const { key, ...rest } = cellProps;
return (
<div
key={key}
{...rest}
onMouseEnter={showActions ? onMouseEnter : undefined}
onMouseLeave={showActions ? onMouseLeave : undefined}
className={cellStyle}
>
<div key={key} {...rest} className={cellStyle}>
{hasLinks || hasActions ? (
<DataLinksContextMenu links={() => getCellLinks(field, row) || []} actions={actions}>
{(api) => {
@ -118,9 +104,7 @@ export const DefaultCell = (props: TableCellProps) => {
<div className={tableStyles.cellText}>{value}</div>
)}
{hover && showActions && (
<CellActions {...props} previewMode={TableCellInspectorMode.text} showFilters={showFilters} />
)}
{showActions && <CellActions {...props} previewMode={TableCellInspectorMode.text} showFilters={showFilters} />}
</div>
);
};

@ -24,7 +24,13 @@ import { usePanelContext } from '../PanelChrome';
import { ExpandedRow, getExpandedRowHeight } from './ExpandedRow';
import { TableCell } from './TableCell';
import { TableStyles } from './styles';
import { CellColors, GetActionsFunction, TableFieldOptions, TableFilterActionCallback } from './types';
import {
CellColors,
GetActionsFunction,
TableFieldOptions,
TableFilterActionCallback,
TableInspectCellCallback,
} from './types';
import {
calculateAroundPointThreshold,
getCellColors,
@ -57,6 +63,7 @@ interface RowsListProps {
textWrapField?: Field;
getActions?: GetActionsFunction;
replaceVariables?: InterpolateFunction;
setInspectCell?: TableInspectCellCallback;
}
export const RowsList = (props: RowsListProps) => {
@ -85,6 +92,7 @@ export const RowsList = (props: RowsListProps) => {
textWrapField,
getActions,
replaceVariables,
setInspectCell,
} = props;
const [rowHighlightIndex, setRowHighlightIndex] = useState<number | undefined>(initialRowIndex);
@ -346,6 +354,7 @@ export const RowsList = (props: RowsListProps) => {
height={Number(style.height)}
getActions={getActions}
replaceVariables={replaceVariables}
setInspectCell={setInspectCell}
/>
))}
</div>
@ -374,6 +383,7 @@ export const RowsList = (props: RowsListProps) => {
timeRange,
getActions,
replaceVariables,
setInspectCell,
]
);

@ -22,10 +22,11 @@ import { Pagination } from '../Pagination/Pagination';
import { FooterRow } from './FooterRow';
import { HeaderRow } from './HeaderRow';
import { RowsList } from './RowsList';
import { TableCellInspector } from './TableCellInspector';
import { useFixScrollbarContainer, useResetVariableListSizeCache } from './hooks';
import { getInitialState, useTableStateReducer } from './reducer';
import { useTableStyles } from './styles';
import { FooterItem, GrafanaTableState, Props } from './types';
import { FooterItem, GrafanaTableState, InspectCell, Props } from './types';
import {
getColumns,
sortCaseInsensitive,
@ -72,6 +73,7 @@ export const Table = memo((props: Props) => {
const headerHeight = noHeader ? 0 : tableStyles.rowHeight;
const [footerItems, setFooterItems] = useState<FooterItem[] | undefined>(footerValues);
const noValuesDisplayText = fieldConfig?.defaults?.noValue ?? NO_DATA_TEXT;
const [inspectCell, setInspectCell] = useState<InspectCell | null>(null);
const footerHeight = useMemo(() => {
const EXTENDED_ROW_HEIGHT = FOOTER_ROW_HEIGHT;
@ -324,66 +326,82 @@ export const Table = memo((props: Props) => {
}
return (
<div
{...getTableProps()}
className={tableStyles.table}
aria-label={ariaLabel}
role="table"
ref={tableDivRef}
style={{ width, height }}
>
<CustomScrollbar hideVerticalTrack={true}>
<div className={tableStyles.tableContentWrapper(totalColumnsWidth)}>
{!noHeader && (
<HeaderRow headerGroups={headerGroups} showTypeIcons={showTypeIcons} tableStyles={tableStyles} />
)}
{itemCount > 0 ? (
<div data-testid={selectors.components.Panels.Visualization.Table.body} ref={variableSizeListScrollbarRef}>
<RowsList
headerGroups={headerGroups}
data={data}
rows={rows}
width={width}
cellHeight={cellHeight}
headerHeight={headerHeight}
rowHeight={tableStyles.rowHeight}
itemCount={itemCount}
pageIndex={state.pageIndex}
listHeight={listHeight}
listRef={listRef}
tableState={state}
prepareRow={prepareRow}
timeRange={timeRange}
onCellFilterAdded={onCellFilterAdded}
nestedDataField={nestedDataField}
<>
<div
{...getTableProps()}
className={tableStyles.table}
aria-label={ariaLabel}
role="table"
ref={tableDivRef}
style={{ width, height }}
>
<CustomScrollbar hideVerticalTrack={true}>
<div className={tableStyles.tableContentWrapper(totalColumnsWidth)}>
{!noHeader && (
<HeaderRow headerGroups={headerGroups} showTypeIcons={showTypeIcons} tableStyles={tableStyles} />
)}
{itemCount > 0 ? (
<div
data-testid={selectors.components.Panels.Visualization.Table.body}
ref={variableSizeListScrollbarRef}
>
<RowsList
headerGroups={headerGroups}
data={data}
rows={rows}
width={width}
cellHeight={cellHeight}
headerHeight={headerHeight}
rowHeight={tableStyles.rowHeight}
itemCount={itemCount}
pageIndex={state.pageIndex}
listHeight={listHeight}
listRef={listRef}
tableState={state}
prepareRow={prepareRow}
timeRange={timeRange}
onCellFilterAdded={onCellFilterAdded}
nestedDataField={nestedDataField}
tableStyles={tableStyles}
footerPaginationEnabled={Boolean(enablePagination)}
enableSharedCrosshair={enableSharedCrosshair}
initialRowIndex={initialRowIndex}
longestField={longestField}
textWrapField={textWrapField}
getActions={getActions}
replaceVariables={replaceVariables}
setInspectCell={setInspectCell}
/>
</div>
) : (
<div style={{ height: height - headerHeight, width }} className={tableStyles.noData}>
{noValuesDisplayText}
</div>
)}
{footerItems && (
<FooterRow
isPaginationVisible={Boolean(enablePagination)}
footerValues={footerItems}
footerGroups={footerGroups}
totalColumnsWidth={totalColumnsWidth}
tableStyles={tableStyles}
footerPaginationEnabled={Boolean(enablePagination)}
enableSharedCrosshair={enableSharedCrosshair}
initialRowIndex={initialRowIndex}
longestField={longestField}
textWrapField={textWrapField}
getActions={getActions}
replaceVariables={replaceVariables}
/>
</div>
) : (
<div style={{ height: height - headerHeight, width }} className={tableStyles.noData}>
{noValuesDisplayText}
</div>
)}
{footerItems && (
<FooterRow
isPaginationVisible={Boolean(enablePagination)}
footerValues={footerItems}
footerGroups={footerGroups}
totalColumnsWidth={totalColumnsWidth}
tableStyles={tableStyles}
/>
)}
</div>
</CustomScrollbar>
{paginationEl}
</div>
)}
</div>
</CustomScrollbar>
{paginationEl}
</div>
{inspectCell !== null && (
<TableCellInspector
mode={inspectCell.mode}
value={inspectCell.value}
onDismiss={() => {
setInspectCell(null);
}}
/>
)}
</>
);
});

@ -3,7 +3,7 @@ import { Cell } from 'react-table';
import { TimeRange, DataFrame, InterpolateFunction } from '@grafana/data';
import { TableStyles } from './styles';
import { GetActionsFunction, GrafanaTableColumn, TableFilterActionCallback } from './types';
import { GetActionsFunction, GrafanaTableColumn, TableFilterActionCallback, TableInspectCellCallback } from './types';
export interface Props {
cell: Cell;
@ -20,6 +20,7 @@ export interface Props {
height?: number;
getActions?: GetActionsFunction;
replaceVariables?: InterpolateFunction;
setInspectCell?: TableInspectCellCallback;
}
export const TableCell = ({
@ -35,6 +36,7 @@ export const TableCell = ({
height,
getActions,
replaceVariables,
setInspectCell,
}: Props) => {
const cellProps = cell.getCellProps();
const field = (cell.column as unknown as GrafanaTableColumn).field;
@ -78,6 +80,7 @@ export const TableCell = ({
textWrapped,
height,
actions,
setInspectCell,
})}
</>
);

@ -14,6 +14,7 @@ import {
} from '@grafana/data';
import * as schema from '@grafana/schema';
import { TableCellInspectorMode } from './TableCellInspector';
import { TableStyles } from './styles';
export {
@ -33,6 +34,8 @@ export interface TableRow {
[x: string]: any;
}
export type InspectCell = { value: any; mode: TableCellInspectorMode };
export const FILTER_FOR_OPERATOR = '=';
export const FILTER_OUT_OPERATOR = '!=';
export type AdHocFilterOperator = typeof FILTER_FOR_OPERATOR | typeof FILTER_OUT_OPERATOR;
@ -40,6 +43,7 @@ export type AdHocFilterItem = { key: string; value: string; operator: AdHocFilte
export type TableFilterActionCallback = (item: AdHocFilterItem) => void;
export type TableColumnResizeActionCallback = (fieldDisplayName: string, width: number) => void;
export type TableSortByActionCallback = (state: TableSortByFieldState[]) => void;
export type TableInspectCellCallback = (state: InspectCell) => void;
export interface TableSortByFieldState {
displayName: string;
@ -54,6 +58,7 @@ export interface TableCellProps extends CellProps<any> {
innerWidth: number;
frame: DataFrame;
actions?: ActionModel[];
setInspectCell?: TableInspectCellCallback;
}
export type CellComponent = FC<TableCellProps>;

Loading…
Cancel
Save