Table: Get rid of ContextMenu on right click (#109825)

* Table: Get rid of ContextMenu on right click

* fix a scroll thing for expanded table, git rid of some conditional accessors

* remove context menu test

* remove context menu test

* i18n

* fix lint issue
This commit is contained in:
Paul Marbach
2025-08-18 17:03:11 -04:00
committed by GitHub
parent 9a6b012ea4
commit 523758c9b3
6 changed files with 16 additions and 161 deletions
@@ -1,4 +1,4 @@
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {
@@ -1242,57 +1242,6 @@ describe('TableNG', () => {
});
});
// TODO we need to test this with an e2e rather than a unit test, because the element dimensions calcs
// don't work in unit tests (no clientWidth/Height)
describe.skip('Resizing', () => {
beforeEach(() => {
window.HTMLElement.prototype.scrollIntoView = jest.fn();
window.HTMLElement.prototype.setPointerCapture = jest.fn();
window.HTMLElement.prototype.hasPointerCapture = jest.fn();
window.HTMLElement.prototype.releasePointerCapture = jest.fn();
window.HTMLElement.prototype.getBoundingClientRect = jest.fn(() => ({
width: 100,
height: 20,
top: 0,
left: 0,
bottom: 0,
right: 0,
x: 0,
y: 0,
toJSON: jest.fn(() => ''),
}));
});
it('calls onColumnResize when column is resized', async () => {
const onColumnResize = jest.fn();
const { container } = render(
<TableNG
enableVirtualization={false}
data={createBasicDataFrame()}
width={800}
height={600}
onColumnResize={onColumnResize}
/>
);
// Find resize handle
const resizeHandles = container.querySelectorAll('.rdg-header-row > [role="columnheader"] > div:last-child');
const handle = resizeHandles[0];
if (!handle) {
throw new Error('Resize handle not found');
}
// simulate a click, then drag, then release.
await userEvent.pointer({ keys: '[MouseLeft>]', coords: { x: 0, y: 0 }, target: handle });
await userEvent.pointer({ coords: { x: 250, y: 0 }, target: handle });
await userEvent.pointer({ keys: '[/MouseLeft]', coords: { x: 250, y: 0 }, target: handle });
await waitFor(() => expect(onColumnResize).toHaveBeenCalled());
});
});
describe('Text wrapping', () => {
it('defaults to not wrapping text', () => {
const { container } = render(
@@ -1344,34 +1293,6 @@ describe('TableNG', () => {
});
});
describe('Context menu', () => {
it('should show context menu on right-click', async () => {
const { container } = render(
<TableNG enableVirtualization={false} data={createBasicDataFrame()} width={400} height={400} />
);
const cell = container.querySelector('[role="gridcell"]');
expect(cell).toBeInTheDocument();
// Trigger context menu directly on the cell element
if (cell) {
fireEvent.contextMenu(cell);
}
// Check that context menu is shown
const menu = await screen.findByRole('menu');
expect(menu).toBeInTheDocument();
// Check for the Inspect value menu item
const menuItem = await screen.findByText('Inspect value');
expect(menuItem).toBeInTheDocument();
// close the menu
await userEvent.click(container);
expect(menuItem).not.toBeInTheDocument();
});
});
describe('Cell inspection', () => {
it('shows inspect icon when hovering over a cell with inspection enabled', async () => {
const inspectDataFrame = {
@@ -1,7 +1,7 @@
import 'react-data-grid/lib/styles.css';
import { clsx } from 'clsx';
import { CSSProperties, Key, ReactNode, useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { CSSProperties, Key, ReactNode, useCallback, useMemo, useRef, useState } from 'react';
import {
Cell,
CellRendererProps,
@@ -23,12 +23,10 @@ import {
getDisplayProcessor,
ReducerID,
} from '@grafana/data';
import { t, Trans } from '@grafana/i18n';
import { Trans } from '@grafana/i18n';
import { FieldColorModeId, TableCellTooltipPlacement } from '@grafana/schema';
import { useStyles2, useTheme2 } from '../../../themes/ThemeContext';
import { ContextMenu } from '../../ContextMenu/ContextMenu';
import { MenuItem } from '../../Menu/MenuItem';
import { Pagination } from '../../Pagination/Pagination';
import { PanelContext, usePanelContext } from '../../PanelChrome';
import { DataLinksActionsTooltip } from '../DataLinksActionsTooltip';
@@ -60,7 +58,7 @@ import {
getLinkStyles,
getTooltipStyles,
} from './styles';
import { TableNGProps, TableRow, TableSummaryRow, TableColumn, ContextMenuProps, TableCellStyleOptions } from './types';
import { TableNGProps, TableRow, TableSummaryRow, TableColumn, InspectCellProps, TableCellStyleOptions } from './types';
import {
applySort,
canFieldBeColorized,
@@ -133,27 +131,8 @@ export function TableNG(props: TableNGProps) {
footerOptions.reducer[0] === ReducerID.count
);
const [contextMenuProps, setContextMenuProps] = useState<ContextMenuProps | null>(null);
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);
const resizeHandler = useColumnResize(onColumnResize);
useLayoutEffect(() => {
if (!isContextMenuOpen) {
return;
}
function onClick(_event: MouseEvent) {
setIsContextMenuOpen(false);
}
window.addEventListener('click', onClick);
return () => {
window.removeEventListener('click', onClick);
};
}, [isContextMenuOpen]);
const rows = useMemo(() => frameToRecords(data), [data]);
const hasNestedFrames = useMemo(() => getIsNestedTable(data.fields), [data]);
@@ -171,7 +150,7 @@ export function TableNG(props: TableNGProps) {
setSortColumns,
} = useSortedRows(filteredRows, data.fields, { hasNestedFrames, initialSortBy });
const [isInspecting, setIsInspecting] = useState(false);
const [inspectCell, setInspectCell] = useState<InspectCellProps | null>(null);
const [expandedRows, setExpandedRows] = useState(() => new Set<number>());
// vt scrollbar accounting for column auto-sizing
@@ -277,27 +256,6 @@ export function TableNG(props: TableNGProps) {
sortable: true,
// draggable: true,
},
onCellContextMenu: ({ row, column }, event) => {
// in nested tables, it's possible for this event to trigger in a column header
// when holding Ctrl for multi-row sort.
if (column.key === 'expanded') {
return;
}
event.preventGridDefault();
// Do not show the default context menu
event.preventDefault();
const cellValue = row[column.key];
setContextMenuProps({
// rowIdx: rows.indexOf(row),
value: String(cellValue ?? ''),
top: event.clientY,
left: event.clientX,
});
setIsContextMenuOpen(true);
},
onColumnResize: resizeHandler,
onSortColumnsChange: (newSortColumns: SortColumn[]) => {
setSortColumns(newSortColumns);
@@ -475,8 +433,7 @@ export function TableNG(props: TableNGProps) {
cellInspect={cellInspect}
showFilters={showFilters}
className={cellActionClassName}
setIsInspecting={setIsInspecting}
setContextMenuProps={setContextMenuProps}
setInspectCell={setInspectCell}
onCellFilterAdded={onCellFilterAdded}
/>
)}
@@ -817,29 +774,11 @@ export function TableNG(props: TableNGProps) {
/>
)}
{isContextMenuOpen && (
<ContextMenu
x={contextMenuProps?.left || 0}
y={contextMenuProps?.top || 0}
renderMenuItems={() => (
<MenuItem
label={t('grafana-ui.table.inspect-menu-label', 'Inspect value')}
onClick={() => setIsInspecting(true)}
className={styles.menuItem}
/>
)}
focusOnOpen={false}
/>
)}
{isInspecting && (
{inspectCell && (
<TableCellInspector
mode={contextMenuProps?.mode ?? TableCellInspectorMode.text}
value={contextMenuProps?.value}
onDismiss={() => {
setIsInspecting(false);
setContextMenuProps(null);
}}
mode={inspectCell.mode ?? TableCellInspectorMode.text}
value={inspectCell.value}
onDismiss={() => setInspectCell(null)}
/>
)}
</>
@@ -15,8 +15,7 @@ export function TableCellActions(props: TableCellActionsProps) {
value,
cellOptions,
displayName,
setIsInspecting,
setContextMenuProps,
setInspectCell,
onCellFilterAdded,
className,
cellInspect,
@@ -47,11 +46,10 @@ export function TableCellActions(props: TableCellActionsProps) {
mode = TableCellInspectorMode.code;
}
setContextMenuProps({
setInspectCell({
value: String(inspectValue ?? ''),
mode,
});
setIsInspecting(true);
}}
/>
)}
@@ -77,7 +77,8 @@ export const getGridStyles = (
gridNested: css({
height: '100%',
width: `calc(100% - ${COLUMN.EXPANDER_WIDTH - TABLE.CELL_PADDING * 2 - 1}px)`,
overflow: 'visible',
overflowX: 'scroll',
overflowY: 'hidden',
marginLeft: COLUMN.EXPANDER_WIDTH - TABLE.CELL_PADDING - 1,
marginBlock: TABLE.CELL_PADDING,
}),
@@ -169,12 +169,10 @@ export interface TableCellRendererProps {
disableSanitizeHtml?: boolean;
}
export type ContextMenuProps = {
export type InspectCellProps = {
rowIdx?: number;
value: string;
mode?: TableCellInspectorMode.code | TableCellInspectorMode.text;
top?: number;
left?: number;
};
export interface TableCellActionsProps {
@@ -184,8 +182,7 @@ export interface TableCellActionsProps {
displayName: string;
cellInspect: boolean;
showFilters: boolean;
setIsInspecting: React.Dispatch<React.SetStateAction<boolean>>;
setContextMenuProps: React.Dispatch<React.SetStateAction<ContextMenuProps | null>>;
setInspectCell: React.Dispatch<React.SetStateAction<InspectCellProps | null>>;
className?: string;
onCellFilterAdded?: TableFilterActionCallback;
}
-1
View File
@@ -8744,7 +8744,6 @@
"filter-popup-input-placeholder": "Filter values",
"filter-popup-match-case": "Match case",
"inspect-drawer-title": "Inspect value",
"inspect-menu-label": "Inspect value",
"nested-table": {
"no-data": "No data"
},