Table: Fix inspect drawer disappears unexpectedly (#99025)
fix(table): inspect drawer disappears unexpectedly
This commit is contained in:
+2
-1
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user