diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.test.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.test.tsx index 9dfe1a2719b..336f37f01f7 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.test.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.test.tsx @@ -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( - - ); - - // 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( - - ); - - 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 = { diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx index 84132fc64cc..c461dd23a33 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx @@ -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(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(null); const [expandedRows, setExpandedRows] = useState(() => new Set()); // 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 && ( - ( - setIsInspecting(true)} - className={styles.menuItem} - /> - )} - focusOnOpen={false} - /> - )} - - {isInspecting && ( + {inspectCell && ( { - setIsInspecting(false); - setContextMenuProps(null); - }} + mode={inspectCell.mode ?? TableCellInspectorMode.text} + value={inspectCell.value} + onDismiss={() => setInspectCell(null)} /> )} diff --git a/packages/grafana-ui/src/components/Table/TableNG/components/TableCellActions.tsx b/packages/grafana-ui/src/components/Table/TableNG/components/TableCellActions.tsx index 746ca7027d1..cdbcde443c1 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/components/TableCellActions.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/components/TableCellActions.tsx @@ -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); }} /> )} diff --git a/packages/grafana-ui/src/components/Table/TableNG/styles.ts b/packages/grafana-ui/src/components/Table/TableNG/styles.ts index 76de0e3e1bb..ca837f66dbc 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/styles.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/styles.ts @@ -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, }), diff --git a/packages/grafana-ui/src/components/Table/TableNG/types.ts b/packages/grafana-ui/src/components/Table/TableNG/types.ts index 7d61ac6200a..03705ef669d 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/types.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/types.ts @@ -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>; - setContextMenuProps: React.Dispatch>; + setInspectCell: React.Dispatch>; className?: string; onCellFilterAdded?: TableFilterActionCallback; } diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index b0299f1ee32..c49a81b5ae7 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -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" },