From ccedfa5bd1b88815617e44a6b187d3a228aa7213 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Tue, 30 Sep 2025 16:56:41 +0100 Subject: [PATCH 01/39] feat: add copy button functionality to RawLogTable for easier data retrieval --- packages/app/src/components/DBRowTable.tsx | 62 +++++++++++++++++++-- packages/app/styles/LogTable.module.scss | 65 ++++++++++++++++++---- 2 files changed, 109 insertions(+), 18 deletions(-) diff --git a/packages/app/src/components/DBRowTable.tsx b/packages/app/src/components/DBRowTable.tsx index d29dd8b66..c63c128d0 100644 --- a/packages/app/src/components/DBRowTable.tsx +++ b/packages/app/src/components/DBRowTable.tsx @@ -44,6 +44,7 @@ import { Tooltip as MantineTooltip, UnstyledButton, } from '@mantine/core'; +import { IconCheck, IconCopy } from '@tabler/icons-react'; import { FetchNextPageOptions, useQuery } from '@tanstack/react-query'; import { ColumnDef, @@ -491,6 +492,45 @@ export const RawLogTable = memo( (isDate ? 170 : isMaybeSeverityText ? 115 : 160)), }; }) as ColumnDef[]), + // Copy column - always last + { + id: 'copy-row', + header: '', + size: 40, + cell: info => { + const CopyButton = () => { + const [isCopied, setIsCopied] = useState(false); + const copyRowData = async () => { + const rowData = displayedColumns + .map(col => { + const value = retrieveColumnValue(col, info.row.original); + return `${columnNameMap?.[col] ?? col}: ${value}`; + }) + .join('\n'); + await navigator.clipboard.writeText(rowData); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + }; + + return ( + { + e.stopPropagation(); + e.preventDefault(); + copyRowData(); + }} + className={cx('text-muted-hover', styles.copyButton, { + [styles.copied]: isCopied, + })} + title={isCopied ? 'Copied!' : 'Copy row data'} + > + {isCopied ? : } + + ); + }; + return ; + }, + }, ], [ isUTC, @@ -851,7 +891,7 @@ export const RawLogTable = memo( {/* Content columns grouped as one button */} - - {/* Copy button positioned absolutely */} - {(() => { - const copyCell = - row.getVisibleCells()[row.getVisibleCells().length - 1]; - return flexRender( - copyCell.column.columnDef.cell, - copyCell.getContext(), - ); - })()} {showExpandButton && isExpanded && ( Date: Wed, 1 Oct 2025 17:50:42 +0100 Subject: [PATCH 15/39] feat: implement DBRowTableFieldWithPopover component for enhanced field interaction and copy functionality --- packages/app/src/components/DBRowTable.tsx | 115 ++---------------- .../components/DBRowTableFieldWithPopover.tsx | 98 +++++++++++++++ 2 files changed, 105 insertions(+), 108 deletions(-) create mode 100644 packages/app/src/components/DBRowTableFieldWithPopover.tsx diff --git a/packages/app/src/components/DBRowTable.tsx b/packages/app/src/components/DBRowTable.tsx index 46fcb3da1..5b467845e 100644 --- a/packages/app/src/components/DBRowTable.tsx +++ b/packages/app/src/components/DBRowTable.tsx @@ -7,7 +7,7 @@ import React, { useState, } from 'react'; import cx from 'classnames'; -import { format, formatDistance } from 'date-fns'; +import { formatDistance } from 'date-fns'; import { isString } from 'lodash'; import curry from 'lodash/curry'; import ms from 'ms'; @@ -40,13 +40,10 @@ import { Code, Flex, Modal, - Popover, Text, Tooltip as MantineTooltip, UnstyledButton, } from '@mantine/core'; -import { useDisclosure } from '@mantine/hooks'; -import { IconCopy, IconFilterPlus, IconLink } from '@tabler/icons-react'; import { FetchNextPageOptions, useQuery } from '@tanstack/react-query'; import { ColumnDef, @@ -76,12 +73,11 @@ import { logLevelColor, useLocalStorage, usePrevious, - useWindowSize, } from '@/utils'; import { SQLPreview } from './ChartSQLPreview'; import { CsvExportButton } from './CsvExportButton'; -import DBRowTableIconButton from './DBRowTableIconButton'; +import DBRowTableFieldWithPopover from './DBRowTableFieldWithPopover'; import { createExpandButtonColumn, ExpandedLogRow, @@ -91,95 +87,6 @@ import LogLevel from './LogLevel'; import styles from '../../styles/LogTable.module.scss'; -// Field with popover component -const FieldWithPopover = ({ - children, - cellValue, - wrapLinesEnabled, -}: { - children: React.ReactNode; - cellValue: any; - wrapLinesEnabled: boolean; -}) => { - const [opened, { close, open }] = useDisclosure(false); - const [isCopied, setIsCopied] = useState(false); - const timeoutRef = useRef(); - - const handleMouseEnter = () => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - open(); - }; - - const handleMouseLeave = () => { - timeoutRef.current = setTimeout(() => { - close(); - }, 100); // Small delay to allow moving to popover - }; - - const copyFieldValue = async () => { - const value = typeof cellValue === 'string' ? cellValue : `${cellValue}`; - await navigator.clipboard.writeText(value); - setIsCopied(true); - setTimeout(() => setIsCopied(false), 2000); - }; - - return ( -
- - - - {children} - - - -
- - - - { - // TODO: Implement add filter functionality - }} - variant="copy" - title="Add filter (coming soon)" - > - - -
-
-
-
- ); -}; - type Row = Record & { duration: number }; type AccessorFn = (row: Row, column: string) => any; @@ -237,7 +144,7 @@ function inferLogLevelColumn(rows: Record[]) { return undefined; } -const PatternTrendChartTooltip = (props: any) => { +const PatternTrendChartTooltip = () => { return null; }; @@ -289,7 +196,7 @@ export const PatternTrendChart = ({ // tickFormatter={tick => // format(new Date(tick * 1000), 'MMM d HH:mm') // } - tickFormatter={tick => ''} + tickFormatter={() => ''} minTickGap={50} tick={{ fontSize: 12, fontFamily: 'IBM Plex Mono, monospace' }} /> @@ -454,14 +361,12 @@ export const RawLogTable = memo( }, [rows, dedupRows, generateRowMatcher]); const _onRowExpandClick = useCallback( - ({ __hyperdx_id, ...row }: Record) => { + (row: Record) => { onRowDetailsClick?.(row); }, [onRowDetailsClick], ); - const { width } = useWindowSize(); - const isSmallScreen = (width ?? 1000) < 900; const { userPreferences: { isUTC }, } = useUserPreferences(); @@ -596,9 +501,6 @@ export const RawLogTable = memo( expandedRows, toggleRowExpansion, showExpandButton, - config, - source, - generateRowId, ], ); @@ -973,9 +875,6 @@ export const RawLogTable = memo( cell.column.columnDef.meta as any )?.className; const columnSize = cell.column.getSize(); - const totalContentCells = - row.getVisibleCells().length - - (showExpandButton ? 1 : 0); const cellValue = cell.getValue(); return ( @@ -997,7 +896,7 @@ export const RawLogTable = memo( }} >
- @@ -1005,7 +904,7 @@ export const RawLogTable = memo( cell.column.columnDef.cell, cell.getContext(), )} - +
); diff --git a/packages/app/src/components/DBRowTableFieldWithPopover.tsx b/packages/app/src/components/DBRowTableFieldWithPopover.tsx new file mode 100644 index 000000000..6c30d0c71 --- /dev/null +++ b/packages/app/src/components/DBRowTableFieldWithPopover.tsx @@ -0,0 +1,98 @@ +import React, { useRef, useState } from 'react'; +import cx from 'classnames'; +import { Popover } from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; +import { IconCopy, IconFilterPlus } from '@tabler/icons-react'; + +import DBRowTableIconButton from './DBRowTableIconButton'; + +import styles from '../../styles/LogTable.module.scss'; + +export interface DBRowTableFieldWithPopoverProps { + children: React.ReactNode; + cellValue: any; + wrapLinesEnabled: boolean; +} + +export const DBRowTableFieldWithPopover: React.FC< + DBRowTableFieldWithPopoverProps +> = ({ children, cellValue, wrapLinesEnabled }) => { + const [opened, { close, open }] = useDisclosure(false); + const [isCopied, setIsCopied] = useState(false); + const timeoutRef = useRef(); + + const handleMouseEnter = () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + open(); + }; + + const handleMouseLeave = () => { + timeoutRef.current = setTimeout(() => { + close(); + }, 100); // Small delay to allow moving to popover + }; + + const copyFieldValue = async () => { + const value = typeof cellValue === 'string' ? cellValue : `${cellValue}`; + await navigator.clipboard.writeText(value); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + }; + + return ( +
+ + + + {children} + + + +
+ + + + { + // TODO: Implement add filter functionality + }} + variant="copy" + title="Add filter (coming soon)" + > + + +
+
+
+
+ ); +}; + +export default DBRowTableFieldWithPopover; From 4f28a1aa0c559fd3b183451944edf69e940b2b9d Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Wed, 1 Oct 2025 18:03:11 +0100 Subject: [PATCH 16/39] feat: add RowButtons component for row-level copy functionality in RawLogTable --- packages/app/src/components/DBRowTable.tsx | 62 ++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/packages/app/src/components/DBRowTable.tsx b/packages/app/src/components/DBRowTable.tsx index 5b467845e..c8b16615a 100644 --- a/packages/app/src/components/DBRowTable.tsx +++ b/packages/app/src/components/DBRowTable.tsx @@ -44,6 +44,7 @@ import { Tooltip as MantineTooltip, UnstyledButton, } from '@mantine/core'; +import { IconCopy, IconLink } from '@tabler/icons-react'; import { FetchNextPageOptions, useQuery } from '@tanstack/react-query'; import { ColumnDef, @@ -78,6 +79,7 @@ import { import { SQLPreview } from './ChartSQLPreview'; import { CsvExportButton } from './CsvExportButton'; import DBRowTableFieldWithPopover from './DBRowTableFieldWithPopover'; +import DBRowTableIconButton from './DBRowTableIconButton'; import { createExpandButtonColumn, ExpandedLogRow, @@ -87,6 +89,56 @@ import LogLevel from './LogLevel'; import styles from '../../styles/LogTable.module.scss'; +// Row buttons component for copy functionality +const RowButtons = ({ + row, + getRowWhere, +}: { + row: Record; + getRowWhere: (row: Record) => string; +}) => { + const [isCopied, setIsCopied] = useState(false); + const [isUrlCopied, setIsUrlCopied] = useState(false); + + const copyRowData = async () => { + const rowWhere = getRowWhere(row); + await navigator.clipboard.writeText(rowWhere); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + }; + + const copyRowUrl = async () => { + const rowWhere = getRowWhere(row); + const currentUrl = new URL(window.location.href); + // Add the row identifier as query parameters + currentUrl.searchParams.set('rowWhere', rowWhere); + await navigator.clipboard.writeText(currentUrl.toString()); + setIsUrlCopied(true); + setTimeout(() => setIsUrlCopied(false), 2000); + }; + + return ( +
+ + + + + + +
+ ); +}; + type Row = Record & { duration: number }; type AccessorFn = (row: Row, column: string) => any; @@ -302,6 +354,7 @@ export const RawLogTable = memo( onExpandedRowsChange, collapseAllRows, showExpandButton = true, + getRowWhere, }: { wrapLines: boolean; displayedColumns: string[]; @@ -338,6 +391,7 @@ export const RawLogTable = memo( collapseAllRows?: boolean; showExpandButton?: boolean; renderRowDetails?: (row: Record) => React.ReactNode; + getRowWhere?: (row: Record) => string; }) => { const generateRowMatcher = generateRowId; @@ -909,6 +963,13 @@ export const RawLogTable = memo( ); })} + {/* Row-level copy buttons */} + {getRowWhere && ( + + )} @@ -1376,6 +1437,7 @@ function DBSqlRowTableComponent({ onExpandedRowsChange={onExpandedRowsChange} collapseAllRows={collapseAllRows} showExpandButton={showExpandButton} + getRowWhere={getRowWhere} /> ); From 53e89609385a0f896d7864af6f94a64406a79dc5 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Wed, 1 Oct 2025 18:05:09 +0100 Subject: [PATCH 17/39] feat: implement DBRowTableRowButtons component for row-level copy functionality --- packages/app/src/components/DBRowTable.tsx | 55 +---------------- .../src/components/DBRowTableRowButtons.tsx | 59 +++++++++++++++++++ 2 files changed, 61 insertions(+), 53 deletions(-) create mode 100644 packages/app/src/components/DBRowTableRowButtons.tsx diff --git a/packages/app/src/components/DBRowTable.tsx b/packages/app/src/components/DBRowTable.tsx index c8b16615a..007b97047 100644 --- a/packages/app/src/components/DBRowTable.tsx +++ b/packages/app/src/components/DBRowTable.tsx @@ -44,7 +44,6 @@ import { Tooltip as MantineTooltip, UnstyledButton, } from '@mantine/core'; -import { IconCopy, IconLink } from '@tabler/icons-react'; import { FetchNextPageOptions, useQuery } from '@tanstack/react-query'; import { ColumnDef, @@ -79,7 +78,7 @@ import { import { SQLPreview } from './ChartSQLPreview'; import { CsvExportButton } from './CsvExportButton'; import DBRowTableFieldWithPopover from './DBRowTableFieldWithPopover'; -import DBRowTableIconButton from './DBRowTableIconButton'; +import DBRowTableRowButtons from './DBRowTableRowButtons'; import { createExpandButtonColumn, ExpandedLogRow, @@ -89,56 +88,6 @@ import LogLevel from './LogLevel'; import styles from '../../styles/LogTable.module.scss'; -// Row buttons component for copy functionality -const RowButtons = ({ - row, - getRowWhere, -}: { - row: Record; - getRowWhere: (row: Record) => string; -}) => { - const [isCopied, setIsCopied] = useState(false); - const [isUrlCopied, setIsUrlCopied] = useState(false); - - const copyRowData = async () => { - const rowWhere = getRowWhere(row); - await navigator.clipboard.writeText(rowWhere); - setIsCopied(true); - setTimeout(() => setIsCopied(false), 2000); - }; - - const copyRowUrl = async () => { - const rowWhere = getRowWhere(row); - const currentUrl = new URL(window.location.href); - // Add the row identifier as query parameters - currentUrl.searchParams.set('rowWhere', rowWhere); - await navigator.clipboard.writeText(currentUrl.toString()); - setIsUrlCopied(true); - setTimeout(() => setIsUrlCopied(false), 2000); - }; - - return ( -
- - - - - - -
- ); -}; - type Row = Record & { duration: number }; type AccessorFn = (row: Row, column: string) => any; @@ -965,7 +914,7 @@ export const RawLogTable = memo( })} {/* Row-level copy buttons */} {getRowWhere && ( - diff --git a/packages/app/src/components/DBRowTableRowButtons.tsx b/packages/app/src/components/DBRowTableRowButtons.tsx new file mode 100644 index 000000000..49b9eb611 --- /dev/null +++ b/packages/app/src/components/DBRowTableRowButtons.tsx @@ -0,0 +1,59 @@ +import React, { useState } from 'react'; +import { IconCopy, IconLink } from '@tabler/icons-react'; + +import DBRowTableIconButton from './DBRowTableIconButton'; + +import styles from '../../styles/LogTable.module.scss'; + +export interface DBRowTableRowButtonsProps { + row: Record; + getRowWhere: (row: Record) => string; +} + +export const DBRowTableRowButtons: React.FC = ({ + row, + getRowWhere, +}) => { + const [isCopied, setIsCopied] = useState(false); + const [isUrlCopied, setIsUrlCopied] = useState(false); + + const copyRowData = async () => { + const rowWhere = getRowWhere(row); + await navigator.clipboard.writeText(rowWhere); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + }; + + const copyRowUrl = async () => { + const rowWhere = getRowWhere(row); + const currentUrl = new URL(window.location.href); + // Add the row identifier as query parameters + currentUrl.searchParams.set('rowWhere', rowWhere); + await navigator.clipboard.writeText(currentUrl.toString()); + setIsUrlCopied(true); + setTimeout(() => setIsUrlCopied(false), 2000); + }; + + return ( +
+ + + + + + +
+ ); +}; + +export default DBRowTableRowButtons; From 3e3dc86f92675da7e59d37b72e263ad40abd55e8 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Thu, 2 Oct 2025 15:56:09 +0100 Subject: [PATCH 18/39] Make row lines thinner --- packages/app/styles/LogTable.module.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/styles/LogTable.module.scss b/packages/app/styles/LogTable.module.scss index dd97d31fa..178de9ac6 100644 --- a/packages/app/styles/LogTable.module.scss +++ b/packages/app/styles/LogTable.module.scss @@ -1,10 +1,10 @@ @import './variables'; -$button-height: 24px; +$button-height: 18px; .table { table-layout: fixed; - border-spacing: 0 2px; + border-spacing: 0; border-collapse: separate; } From 17c9929ce0c8e3ac6a54d8b99f71c17748c042ef Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Thu, 2 Oct 2025 16:00:22 +0100 Subject: [PATCH 19/39] refactor: remove padding and line-height from field text styles for improved layout consistency --- packages/app/styles/LogTable.module.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/app/styles/LogTable.module.scss b/packages/app/styles/LogTable.module.scss index 178de9ac6..901da44e7 100644 --- a/packages/app/styles/LogTable.module.scss +++ b/packages/app/styles/LogTable.module.scss @@ -129,7 +129,6 @@ $button-height: 18px; &.isWrapped { align-items: flex-start; - padding-block: 4px; } &.isTruncated { @@ -202,7 +201,6 @@ $button-height: 18px; .fieldText.truncated { text-overflow: ellipsis; white-space: nowrap; - line-height: $button-height; min-height: $button-height; } From d8dac8a9f0885408c3de8f40046bd4164d1889af Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Thu, 2 Oct 2025 16:23:34 +0100 Subject: [PATCH 20/39] Update packages/app/src/components/DBRowTableFieldWithPopover.tsx Co-authored-by: Mike Shi --- packages/app/src/components/DBRowTableFieldWithPopover.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/components/DBRowTableFieldWithPopover.tsx b/packages/app/src/components/DBRowTableFieldWithPopover.tsx index 6c30d0c71..bd37d6cee 100644 --- a/packages/app/src/components/DBRowTableFieldWithPopover.tsx +++ b/packages/app/src/components/DBRowTableFieldWithPopover.tsx @@ -16,7 +16,7 @@ export interface DBRowTableFieldWithPopoverProps { export const DBRowTableFieldWithPopover: React.FC< DBRowTableFieldWithPopoverProps -> = ({ children, cellValue, wrapLinesEnabled }) => { += ({ children, cellValue, wrapLinesEnabled }: DBRowTableFieldWithPopoverProps) => { const [opened, { close, open }] = useDisclosure(false); const [isCopied, setIsCopied] = useState(false); const timeoutRef = useRef(); From af72303808e5fe0bf50d35cf26bd9d0edc15e9a9 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Thu, 2 Oct 2025 16:32:47 +0100 Subject: [PATCH 21/39] refactor: simplify component declaration for DBRowTableFieldWithPopover --- .../app/src/components/DBRowTableFieldWithPopover.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/app/src/components/DBRowTableFieldWithPopover.tsx b/packages/app/src/components/DBRowTableFieldWithPopover.tsx index bd37d6cee..407a06546 100644 --- a/packages/app/src/components/DBRowTableFieldWithPopover.tsx +++ b/packages/app/src/components/DBRowTableFieldWithPopover.tsx @@ -14,9 +14,11 @@ export interface DBRowTableFieldWithPopoverProps { wrapLinesEnabled: boolean; } -export const DBRowTableFieldWithPopover: React.FC< - DBRowTableFieldWithPopoverProps -= ({ children, cellValue, wrapLinesEnabled }: DBRowTableFieldWithPopoverProps) => { +export const DBRowTableFieldWithPopover = ({ + children, + cellValue, + wrapLinesEnabled, +}: DBRowTableFieldWithPopoverProps) => { const [opened, { close, open }] = useDisclosure(false); const [isCopied, setIsCopied] = useState(false); const timeoutRef = useRef(); From 0aaccb11684a8161df4716938ba8d77c01ec1c04 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Fri, 3 Oct 2025 15:58:27 +0100 Subject: [PATCH 22/39] refactor: update button styles and improve layout in DBRowTableFieldWithPopover and DBRowTableIconButton components --- .../src/components/DBRowTableFieldWithPopover.tsx | 14 ++++++++++++-- .../app/src/components/DBRowTableIconButton.tsx | 2 +- packages/app/styles/LogTable.module.scss | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/app/src/components/DBRowTableFieldWithPopover.tsx b/packages/app/src/components/DBRowTableFieldWithPopover.tsx index 407a06546..b3410a476 100644 --- a/packages/app/src/components/DBRowTableFieldWithPopover.tsx +++ b/packages/app/src/components/DBRowTableFieldWithPopover.tsx @@ -43,6 +43,11 @@ export const DBRowTableFieldWithPopover = ({ setTimeout(() => setIsCopied(false), 2000); }; + const buttonSize = 20; + const gapSize = 4; + const numberOfButtons = 2; + const numberOfGaps = numberOfButtons - 1; + return (
- + diff --git a/packages/app/src/components/DBRowTableIconButton.tsx b/packages/app/src/components/DBRowTableIconButton.tsx index b0bb4a412..b15f4226a 100644 --- a/packages/app/src/components/DBRowTableIconButton.tsx +++ b/packages/app/src/components/DBRowTableIconButton.tsx @@ -34,7 +34,7 @@ export const DBRowTableIconButton: React.FC = ({ const baseClasses = variant === 'copy' - ? cx('text-muted-hover', styles.copyButton, { + ? cx('text-muted-hover', styles.iconActionButton, { [styles.copied]: isActive, }) : className; diff --git a/packages/app/styles/LogTable.module.scss b/packages/app/styles/LogTable.module.scss index 901da44e7..71eca909f 100644 --- a/packages/app/styles/LogTable.module.scss +++ b/packages/app/styles/LogTable.module.scss @@ -154,7 +154,7 @@ $button-height: 18px; } } -.copyButton { +.iconActionButton { padding: 4px; border-radius: 4px; background-color: var(--mantine-color-dark-4); From bb73674187944d688cba7d7fe55dcb8d7db9694a Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Fri, 3 Oct 2025 16:16:01 +0100 Subject: [PATCH 23/39] refactor: enhance popover interaction by adding hover disable functionality --- .../components/DBRowTableFieldWithPopover.tsx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/app/src/components/DBRowTableFieldWithPopover.tsx b/packages/app/src/components/DBRowTableFieldWithPopover.tsx index b3410a476..48acadc90 100644 --- a/packages/app/src/components/DBRowTableFieldWithPopover.tsx +++ b/packages/app/src/components/DBRowTableFieldWithPopover.tsx @@ -21,9 +21,13 @@ export const DBRowTableFieldWithPopover = ({ }: DBRowTableFieldWithPopoverProps) => { const [opened, { close, open }] = useDisclosure(false); const [isCopied, setIsCopied] = useState(false); + const [hoverDisabled, setHoverDisabled] = useState(false); const timeoutRef = useRef(); + const hoverDisableTimeoutRef = useRef(); const handleMouseEnter = () => { + if (hoverDisabled) return; + if (timeoutRef.current) { clearTimeout(timeoutRef.current); } @@ -36,6 +40,22 @@ export const DBRowTableFieldWithPopover = ({ }, 100); // Small delay to allow moving to popover }; + const handleClick = () => { + close(); + setHoverDisabled(true); + + if (hoverDisableTimeoutRef.current) { + clearTimeout(hoverDisableTimeoutRef.current); + } + + // Prevent the popover from immediately reopening on hover for 1 second + // This gives users time to move their cursor or interact with modals + // without the popover interfering with their intended action + hoverDisableTimeoutRef.current = setTimeout(() => { + setHoverDisabled(false); + }, 1000); + }; + const copyFieldValue = async () => { const value = typeof cellValue === 'string' ? cellValue : `${cellValue}`; await navigator.clipboard.writeText(value); @@ -65,6 +85,7 @@ export const DBRowTableFieldWithPopover = ({ {children} From 88e2cc5592248da7ca0601b26ffdf7a519bfc347 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Fri, 3 Oct 2025 17:25:39 +0100 Subject: [PATCH 24/39] refactor: replace vertical dots icon with Tabler icon in HDXMultiSeriesTableChart and DBRowTable components --- packages/app/src/HDXMultiSeriesTableChart.tsx | 3 ++- packages/app/src/components/DBRowTable.tsx | 3 ++- packages/app/styles/LogTable.module.scss | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/app/src/HDXMultiSeriesTableChart.tsx b/packages/app/src/HDXMultiSeriesTableChart.tsx index 1dc86c616..3a9b02e54 100644 --- a/packages/app/src/HDXMultiSeriesTableChart.tsx +++ b/packages/app/src/HDXMultiSeriesTableChart.tsx @@ -2,6 +2,7 @@ import { memo, useCallback, useMemo, useRef, useState } from 'react'; import Link from 'next/link'; import cx from 'classnames'; import { Flex, Text, UnstyledButton } from '@mantine/core'; +import { IconGripVertical } from '@tabler/icons-react'; import { flexRender, getCoreRowModel, @@ -242,7 +243,7 @@ export const Table = ({ : '' }`} > - +
)} {headerIndex === headerGroup.headers.length - 1 && ( diff --git a/packages/app/src/components/DBRowTable.tsx b/packages/app/src/components/DBRowTable.tsx index 007b97047..8944f6746 100644 --- a/packages/app/src/components/DBRowTable.tsx +++ b/packages/app/src/components/DBRowTable.tsx @@ -44,6 +44,7 @@ import { Tooltip as MantineTooltip, UnstyledButton, } from '@mantine/core'; +import { IconGripVertical } from '@tabler/icons-react'; import { FetchNextPageOptions, useQuery } from '@tanstack/react-query'; import { ColumnDef, @@ -744,7 +745,7 @@ export const RawLogTable = memo( width: 12, }} > - + )} {headerIndex === headerGroup.headers.length - 1 && ( diff --git a/packages/app/styles/LogTable.module.scss b/packages/app/styles/LogTable.module.scss index 71eca909f..ea6718e1b 100644 --- a/packages/app/styles/LogTable.module.scss +++ b/packages/app/styles/LogTable.module.scss @@ -9,9 +9,10 @@ $button-height: 18px; } .tableHead { - background: inherit; + background: var(--mantine-color-dark-8); position: sticky; top: 0; + z-index: 200; } .tableRow { From df91d237a5ae2ce8f930e92ac495951acb6b652f Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Fri, 3 Oct 2025 18:34:08 +0100 Subject: [PATCH 25/39] refactor: enhance DBSearchPage layout and styling with new CSS classes and z-index adjustments --- packages/app/src/DBSearchPage.tsx | 85 +++++++------------ .../components/DBRowTableFieldWithPopover.tsx | 1 + packages/app/styles/SearchPage.module.scss | 21 +++++ 3 files changed, 53 insertions(+), 54 deletions(-) diff --git a/packages/app/src/DBSearchPage.tsx b/packages/app/src/DBSearchPage.tsx index c70671eac..c846d564b 100644 --- a/packages/app/src/DBSearchPage.tsx +++ b/packages/app/src/DBSearchPage.tsx @@ -1203,7 +1203,11 @@ function DBSearchPage() { /> )} -
+ {/* */} @@ -1474,7 +1478,6 @@ function DBSearchPage() { )} @@ -1512,12 +1515,8 @@ function DBSearchPage() { {analysisMode === 'pattern' && histogramTimeChartConfig != null && ( - - + + {!hasQueryError && ( - + - + - + + {shouldShowLiveModeHint && + analysisMode === 'results' && + denoiseResults != true && ( + + )} + + {!hasQueryError && ( - + ) : ( <> - {shouldShowLiveModeHint && - analysisMode === 'results' && - denoiseResults != true && ( -
-
- -
-
- )} {chartConfig && searchedConfig.source && dbSqlRowTableConfig && diff --git a/packages/app/src/components/DBRowTableFieldWithPopover.tsx b/packages/app/src/components/DBRowTableFieldWithPopover.tsx index 48acadc90..1f80653ce 100644 --- a/packages/app/src/components/DBRowTableFieldWithPopover.tsx +++ b/packages/app/src/components/DBRowTableFieldWithPopover.tsx @@ -80,6 +80,7 @@ export const DBRowTableFieldWithPopover = ({ position="top-start" offset={5} opened={opened} + zIndex={10} > Date: Fri, 3 Oct 2025 18:51:08 +0100 Subject: [PATCH 26/39] refactor: adjust z-index values for improved layout consistency in DBRowTableFieldWithPopover and SearchPage styles --- packages/app/src/components/DBRowTableFieldWithPopover.tsx | 2 +- packages/app/styles/SearchPage.module.scss | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/app/src/components/DBRowTableFieldWithPopover.tsx b/packages/app/src/components/DBRowTableFieldWithPopover.tsx index 1f80653ce..ff243468f 100644 --- a/packages/app/src/components/DBRowTableFieldWithPopover.tsx +++ b/packages/app/src/components/DBRowTableFieldWithPopover.tsx @@ -80,7 +80,7 @@ export const DBRowTableFieldWithPopover = ({ position="top-start" offset={5} opened={opened} - zIndex={10} + zIndex={0} > Date: Mon, 6 Oct 2025 16:33:13 +0100 Subject: [PATCH 27/39] refactor: reorganize imports and create new components for row buttons and field popover --- packages/app/src/components/DBRowTable.tsx | 4 ++-- .../components/{ => DBTable}/DBRowTableFieldWithPopover.tsx | 2 +- .../app/src/components/{ => DBTable}/DBRowTableIconButton.tsx | 2 +- .../app/src/components/{ => DBTable}/DBRowTableRowButtons.tsx | 2 +- packages/app/styles/LogTable.module.scss | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) rename packages/app/src/components/{ => DBTable}/DBRowTableFieldWithPopover.tsx (98%) rename packages/app/src/components/{ => DBTable}/DBRowTableIconButton.tsx (95%) rename packages/app/src/components/{ => DBTable}/DBRowTableRowButtons.tsx (96%) diff --git a/packages/app/src/components/DBRowTable.tsx b/packages/app/src/components/DBRowTable.tsx index ab51595bd..f381b4ca4 100644 --- a/packages/app/src/components/DBRowTable.tsx +++ b/packages/app/src/components/DBRowTable.tsx @@ -76,10 +76,10 @@ import { usePrevious, } from '@/utils'; +import DBRowTableFieldWithPopover from './DBTable/DBRowTableFieldWithPopover'; +import DBRowTableRowButtons from './DBTable/DBRowTableRowButtons'; import { SQLPreview } from './ChartSQLPreview'; import { CsvExportButton } from './CsvExportButton'; -import DBRowTableFieldWithPopover from './DBRowTableFieldWithPopover'; -import DBRowTableRowButtons from './DBRowTableRowButtons'; import { createExpandButtonColumn, ExpandedLogRow, diff --git a/packages/app/src/components/DBRowTableFieldWithPopover.tsx b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx similarity index 98% rename from packages/app/src/components/DBRowTableFieldWithPopover.tsx rename to packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx index ff243468f..6a8493746 100644 --- a/packages/app/src/components/DBRowTableFieldWithPopover.tsx +++ b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx @@ -6,7 +6,7 @@ import { IconCopy, IconFilterPlus } from '@tabler/icons-react'; import DBRowTableIconButton from './DBRowTableIconButton'; -import styles from '../../styles/LogTable.module.scss'; +import styles from '../../../styles/LogTable.module.scss'; export interface DBRowTableFieldWithPopoverProps { children: React.ReactNode; diff --git a/packages/app/src/components/DBRowTableIconButton.tsx b/packages/app/src/components/DBTable/DBRowTableIconButton.tsx similarity index 95% rename from packages/app/src/components/DBRowTableIconButton.tsx rename to packages/app/src/components/DBTable/DBRowTableIconButton.tsx index b15f4226a..d1df45183 100644 --- a/packages/app/src/components/DBRowTableIconButton.tsx +++ b/packages/app/src/components/DBTable/DBRowTableIconButton.tsx @@ -3,7 +3,7 @@ import cx from 'classnames'; import { UnstyledButton } from '@mantine/core'; import { IconCheck } from '@tabler/icons-react'; -import styles from '../../styles/LogTable.module.scss'; +import styles from '../../../styles/LogTable.module.scss'; export interface DBRowTableIconButtonProps { onClick: (e: React.MouseEvent) => void; diff --git a/packages/app/src/components/DBRowTableRowButtons.tsx b/packages/app/src/components/DBTable/DBRowTableRowButtons.tsx similarity index 96% rename from packages/app/src/components/DBRowTableRowButtons.tsx rename to packages/app/src/components/DBTable/DBRowTableRowButtons.tsx index 49b9eb611..25463d7cd 100644 --- a/packages/app/src/components/DBRowTableRowButtons.tsx +++ b/packages/app/src/components/DBTable/DBRowTableRowButtons.tsx @@ -3,7 +3,7 @@ import { IconCopy, IconLink } from '@tabler/icons-react'; import DBRowTableIconButton from './DBRowTableIconButton'; -import styles from '../../styles/LogTable.module.scss'; +import styles from '../../../styles/LogTable.module.scss'; export interface DBRowTableRowButtonsProps { row: Record; diff --git a/packages/app/styles/LogTable.module.scss b/packages/app/styles/LogTable.module.scss index ea6718e1b..0f2c7db25 100644 --- a/packages/app/styles/LogTable.module.scss +++ b/packages/app/styles/LogTable.module.scss @@ -12,7 +12,7 @@ $button-height: 18px; background: var(--mantine-color-dark-8); position: sticky; top: 0; - z-index: 200; + z-index: 2; } .tableRow { @@ -147,7 +147,7 @@ $button-height: 18px; gap: 2px; opacity: 0; transition: opacity 0.2s ease-in-out; - z-index: 10; + z-index: 1; align-items: flex-start; &:hover { From 814dee3d717917062ec10e4aab0739865f3a37ca Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Mon, 6 Oct 2025 18:20:44 +0100 Subject: [PATCH 28/39] refactor: enhance DBRowTableFieldWithPopover with filtering options and improve button layout --- packages/app/src/components/DBRowTable.tsx | 6 +- .../DBTable/DBRowTableFieldWithPopover.tsx | 62 +++++++++++++++---- .../DBTable/DBRowTableIconButton.tsx | 25 +++++--- packages/app/styles/LogTable.module.scss | 3 +- packages/app/styles/SearchPage.module.scss | 8 +-- 5 files changed, 77 insertions(+), 27 deletions(-) diff --git a/packages/app/src/components/DBRowTable.tsx b/packages/app/src/components/DBRowTable.tsx index f381b4ca4..e0273e42d 100644 --- a/packages/app/src/components/DBRowTable.tsx +++ b/packages/app/src/components/DBRowTable.tsx @@ -740,7 +740,7 @@ export const RawLogTable = memo( style={{ position: 'absolute', right: 4, - top: 0, + top: 2, bottom: 0, width: 12, }} @@ -903,6 +903,10 @@ export const RawLogTable = memo( {flexRender( cell.column.columnDef.cell, diff --git a/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx index 6a8493746..852c682ac 100644 --- a/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx +++ b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx @@ -1,8 +1,10 @@ -import React, { useRef, useState } from 'react'; +import React, { useContext, useRef, useState } from 'react'; import cx from 'classnames'; import { Popover } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; -import { IconCopy, IconFilterPlus } from '@tabler/icons-react'; +import { IconCopy, IconFilterPlus, IconFilterX } from '@tabler/icons-react'; + +import { RowSidePanelContext } from '../DBRowSidePanel'; import DBRowTableIconButton from './DBRowTableIconButton'; @@ -12,12 +14,14 @@ export interface DBRowTableFieldWithPopoverProps { children: React.ReactNode; cellValue: any; wrapLinesEnabled: boolean; + columnName?: string; } export const DBRowTableFieldWithPopover = ({ children, cellValue, wrapLinesEnabled, + columnName, }: DBRowTableFieldWithPopoverProps) => { const [opened, { close, open }] = useDisclosure(false); const [isCopied, setIsCopied] = useState(false); @@ -25,6 +29,12 @@ export const DBRowTableFieldWithPopover = ({ const timeoutRef = useRef(); const hoverDisableTimeoutRef = useRef(); + // Get filter functionality from context + const { onPropertyAddClick } = useContext(RowSidePanelContext); + + // Check if we have both the column name and filter function available + const canFilter = columnName && onPropertyAddClick && cellValue != null; + const handleMouseEnter = () => { if (hoverDisabled) return; @@ -63,9 +73,26 @@ export const DBRowTableFieldWithPopover = ({ setTimeout(() => setIsCopied(false), 2000); }; + const addFilter = () => { + if (canFilter) { + const value = typeof cellValue === 'string' ? cellValue : `${cellValue}`; + onPropertyAddClick(columnName, value); + handleClick(); // Close the popover + } + }; + + const addExcludeFilter = () => { + if (canFilter) { + const value = typeof cellValue === 'string' ? cellValue : `${cellValue}`; + // Use the 'exclude' action for the filter + (onPropertyAddClick as any)(columnName, value, 'exclude'); + handleClick(); // Close the popover + } + }; + const buttonSize = 20; const gapSize = 4; - const numberOfButtons = 2; + const numberOfButtons = canFilter ? 4 : 1; // Copy + Filter Add + Filter Out (if filtering available) const numberOfGaps = numberOfButtons - 1; return ( @@ -80,7 +107,7 @@ export const DBRowTableFieldWithPopover = ({ position="top-start" offset={5} opened={opened} - zIndex={0} + zIndex={1} > - { - // TODO: Implement add filter functionality - }} - variant="copy" - title="Add filter (coming soon)" - > - - + {canFilter && ( + <> + + + + + + + + )} diff --git a/packages/app/src/components/DBTable/DBRowTableIconButton.tsx b/packages/app/src/components/DBTable/DBRowTableIconButton.tsx index d1df45183..c409c477a 100644 --- a/packages/app/src/components/DBTable/DBRowTableIconButton.tsx +++ b/packages/app/src/components/DBTable/DBRowTableIconButton.tsx @@ -1,6 +1,6 @@ import React from 'react'; import cx from 'classnames'; -import { UnstyledButton } from '@mantine/core'; +import { Tooltip, UnstyledButton } from '@mantine/core'; import { IconCheck } from '@tabler/icons-react'; import styles from '../../../styles/LogTable.module.scss'; @@ -40,14 +40,23 @@ export const DBRowTableIconButton: React.FC = ({ : className; return ( - - {isActive ? : children} - + + {isActive ? : children} + + ); }; diff --git a/packages/app/styles/LogTable.module.scss b/packages/app/styles/LogTable.module.scss index 0f2c7db25..286da9f0f 100644 --- a/packages/app/styles/LogTable.module.scss +++ b/packages/app/styles/LogTable.module.scss @@ -12,7 +12,8 @@ $button-height: 18px; background: var(--mantine-color-dark-8); position: sticky; top: 0; - z-index: 2; + z-index: 1; + height: 24px; } .tableRow { diff --git a/packages/app/styles/SearchPage.module.scss b/packages/app/styles/SearchPage.module.scss index 549f6fd7b..991d9d7b7 100644 --- a/packages/app/styles/SearchPage.module.scss +++ b/packages/app/styles/SearchPage.module.scss @@ -14,7 +14,7 @@ flex-grow: 0; border-right: 1px solid var(--mantine-color-dark-6); overflow: hidden; - z-index: 1; + z-index: 3; // higher z-index to be above other elements :global { .mantine-TextInput-wrapper { @@ -156,7 +156,7 @@ background-color: $bg-hdx-dark; padding-inline: var(--mantine-spacing-xs); padding-block: var(--mantine-spacing-xxs); - z-index: 1; + z-index: 3; } .timeChartContainer { @@ -164,11 +164,11 @@ padding-inline: var(--mantine-spacing-xs); padding-block: var(--mantine-spacing-xxs); height: 120px; - z-index: 1; + z-index: 3; } .searchForm { background-color: $body-bg; - z-index: 1; + z-index: 4; padding-bottom: var(--mantine-spacing-xs); } From 2c5cfecb6fab56ccd03f753db45f832a4fdc5b2c Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Mon, 6 Oct 2025 18:24:55 +0100 Subject: [PATCH 29/39] fix: adjust z-index of rowButtons for proper layering in LogTable styles --- packages/app/styles/LogTable.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/styles/LogTable.module.scss b/packages/app/styles/LogTable.module.scss index 286da9f0f..b34d6d4da 100644 --- a/packages/app/styles/LogTable.module.scss +++ b/packages/app/styles/LogTable.module.scss @@ -148,7 +148,7 @@ $button-height: 18px; gap: 2px; opacity: 0; transition: opacity 0.2s ease-in-out; - z-index: 1; + z-index: 0; align-items: flex-start; &:hover { From 0abd43e4849ba032e2ee3043b8af900dd504eb7a Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Mon, 6 Oct 2025 18:35:22 +0100 Subject: [PATCH 30/39] refactor: update copyRowData function to copy entire row as JSON and enhance button titles for clarity --- .../components/DBTable/DBRowTableRowButtons.tsx | 14 ++++++++++---- packages/app/styles/LogTable.module.scss | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/app/src/components/DBTable/DBRowTableRowButtons.tsx b/packages/app/src/components/DBTable/DBRowTableRowButtons.tsx index 25463d7cd..7b45db6d9 100644 --- a/packages/app/src/components/DBTable/DBRowTableRowButtons.tsx +++ b/packages/app/src/components/DBTable/DBRowTableRowButtons.tsx @@ -18,8 +18,8 @@ export const DBRowTableRowButtons: React.FC = ({ const [isUrlCopied, setIsUrlCopied] = useState(false); const copyRowData = async () => { - const rowWhere = getRowWhere(row); - await navigator.clipboard.writeText(rowWhere); + const rowData = JSON.stringify(row, null, 2); + await navigator.clipboard.writeText(rowData); setIsCopied(true); setTimeout(() => setIsCopied(false), 2000); }; @@ -40,7 +40,9 @@ export const DBRowTableRowButtons: React.FC = ({ onClick={copyRowData} variant="copy" isActive={isCopied} - title={isCopied ? 'Copied row data!' : 'Copy row data'} + title={ + isCopied ? 'Copied entire row as JSON!' : 'Copy entire row as JSON' + } > @@ -48,7 +50,11 @@ export const DBRowTableRowButtons: React.FC = ({ onClick={copyRowUrl} variant="copy" isActive={isUrlCopied} - title={isUrlCopied ? 'Copied URL!' : 'Copy row URL'} + title={ + isUrlCopied + ? 'Copied shareable link!' + : 'Copy shareable link to this specific row' + } > diff --git a/packages/app/styles/LogTable.module.scss b/packages/app/styles/LogTable.module.scss index b34d6d4da..fbbfef447 100644 --- a/packages/app/styles/LogTable.module.scss +++ b/packages/app/styles/LogTable.module.scss @@ -145,7 +145,7 @@ $button-height: 18px; padding-left: 10px; padding-right: 4px; display: flex; - gap: 2px; + gap: 4px; opacity: 0; transition: opacity 0.2s ease-in-out; z-index: 0; From ce22e35758fe639794c08176411502df2d35cc68 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Mon, 6 Oct 2025 18:46:23 +0100 Subject: [PATCH 31/39] refactor: update filter button titles for clarity and replace IconFilterPlus with IconFilter --- .../src/components/DBTable/DBRowTableFieldWithPopover.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx index 852c682ac..e29edc3c7 100644 --- a/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx +++ b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx @@ -2,7 +2,7 @@ import React, { useContext, useRef, useState } from 'react'; import cx from 'classnames'; import { Popover } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; -import { IconCopy, IconFilterPlus, IconFilterX } from '@tabler/icons-react'; +import { IconCopy, IconFilter, IconFilterX } from '@tabler/icons-react'; import { RowSidePanelContext } from '../DBRowSidePanel'; @@ -145,14 +145,14 @@ export const DBRowTableFieldWithPopover = ({ - + From 5829495bf8f59598c7560447ba466783c72a9c42 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Wed, 8 Oct 2025 11:03:45 +0100 Subject: [PATCH 32/39] refactor: remove unused IconFilterX import from DBRowTableFieldWithPopover --- .../DBTable/DBRowTableFieldWithPopover.tsx | 36 +++++-------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx index e29edc3c7..426ea07df 100644 --- a/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx +++ b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx @@ -2,7 +2,7 @@ import React, { useContext, useRef, useState } from 'react'; import cx from 'classnames'; import { Popover } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; -import { IconCopy, IconFilter, IconFilterX } from '@tabler/icons-react'; +import { IconCopy, IconFilter } from '@tabler/icons-react'; import { RowSidePanelContext } from '../DBRowSidePanel'; @@ -81,18 +81,9 @@ export const DBRowTableFieldWithPopover = ({ } }; - const addExcludeFilter = () => { - if (canFilter) { - const value = typeof cellValue === 'string' ? cellValue : `${cellValue}`; - // Use the 'exclude' action for the filter - (onPropertyAddClick as any)(columnName, value, 'exclude'); - handleClick(); // Close the popover - } - }; - const buttonSize = 20; const gapSize = 4; - const numberOfButtons = canFilter ? 4 : 1; // Copy + Filter Add + Filter Out (if filtering available) + const numberOfButtons = canFilter ? 2 : 1; // Copy + Filter (if filtering available) const numberOfGaps = numberOfButtons - 1; return ( @@ -141,22 +132,13 @@ export const DBRowTableFieldWithPopover = ({ {canFilter && ( - <> - - - - - - - + + + )} From f4ad4e85b15becbbd35667484f2e530fd87f70fd Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Wed, 8 Oct 2025 12:10:36 +0100 Subject: [PATCH 33/39] Enhance changeset --- .changeset/tricky-apples-complain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/tricky-apples-complain.md b/.changeset/tricky-apples-complain.md index 368471047..c41af8d7e 100644 --- a/.changeset/tricky-apples-complain.md +++ b/.changeset/tricky-apples-complain.md @@ -2,4 +2,4 @@ "@hyperdx/app": minor --- -Add copy-to-clipboard buttons in RawLogTable for log line data and URLs, with improved button styling. +Add toggle filters button, copy field, and per-row copy-to-clipboard for JSON data and modal URLs in RawLogTable From 5dfa32ae50c6d4b434ec50c975bdeb7b14fdef75 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Wed, 8 Oct 2025 13:04:18 +0100 Subject: [PATCH 34/39] feat(DBRowTable): add isChart prop to handle chart rendering in DBRowTableFieldWithPopover style(LogTable): adjust alignment for truncated fields and add chart styling --- packages/app/src/components/DBRowTable.tsx | 4 ++++ .../DBTable/DBRowTableFieldWithPopover.tsx | 22 ++++++++++++++++++- packages/app/styles/LogTable.module.scss | 7 +++++- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/app/src/components/DBRowTable.tsx b/packages/app/src/components/DBRowTable.tsx index 66dad2fc1..c786dedf2 100644 --- a/packages/app/src/components/DBRowTable.tsx +++ b/packages/app/src/components/DBRowTable.tsx @@ -901,6 +901,10 @@ export const RawLogTable = memo( (cell.column.columnDef.meta as any) ?.column } + isChart={ + (cell.column.columnDef.meta as any) + ?.column === '__hdx_pattern_trend' + } > {flexRender( cell.column.columnDef.cell, diff --git a/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx index 426ea07df..a2ac61b6a 100644 --- a/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx +++ b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx @@ -15,6 +15,7 @@ export interface DBRowTableFieldWithPopoverProps { cellValue: any; wrapLinesEnabled: boolean; columnName?: string; + isChart?: boolean; } export const DBRowTableFieldWithPopover = ({ @@ -22,6 +23,7 @@ export const DBRowTableFieldWithPopover = ({ cellValue, wrapLinesEnabled, columnName, + isChart = false, }: DBRowTableFieldWithPopoverProps) => { const [opened, { close, open }] = useDisclosure(false); const [isCopied, setIsCopied] = useState(false); @@ -86,11 +88,29 @@ export const DBRowTableFieldWithPopover = ({ const numberOfButtons = canFilter ? 2 : 1; // Copy + Filter (if filtering available) const numberOfGaps = numberOfButtons - 1; + // If it's a chart, just render the children without popover functionality + if (isChart) { + return ( +
+ {children} +
+ ); + } + return (
Date: Wed, 8 Oct 2025 17:11:32 +0100 Subject: [PATCH 35/39] refactor(DBRowTableFieldWithPopover): improve canFilter logic for clarity --- .../src/components/DBTable/DBRowTableFieldWithPopover.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx index a2ac61b6a..a8387ad7c 100644 --- a/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx +++ b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx @@ -35,7 +35,12 @@ export const DBRowTableFieldWithPopover = ({ const { onPropertyAddClick } = useContext(RowSidePanelContext); // Check if we have both the column name and filter function available - const canFilter = columnName && onPropertyAddClick && cellValue != null; + // Only show filter for ServiceName and SeverityText + const canFilter = + columnName && + (columnName === 'ServiceName' || columnName === 'SeverityText') && + onPropertyAddClick && + cellValue != null; const handleMouseEnter = () => { if (hoverDisabled) return; From 23d5f180df89e21bcd909be37cac2b8da45bee1d Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Wed, 8 Oct 2025 17:40:26 +0100 Subject: [PATCH 36/39] feat(DBRowTableFieldWithPopover): enhance filtering options with include and exclude actions --- .../app/src/components/DBRowSidePanel.tsx | 6 ++- .../DBTable/DBRowTableFieldWithPopover.tsx | 37 ++++++++++++++----- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/packages/app/src/components/DBRowSidePanel.tsx b/packages/app/src/components/DBRowSidePanel.tsx index c12a895cf..733eb1a19 100644 --- a/packages/app/src/components/DBRowSidePanel.tsx +++ b/packages/app/src/components/DBRowSidePanel.tsx @@ -40,7 +40,11 @@ import 'react-modern-drawer/dist/index.css'; import styles from '@/../styles/LogSidePanel.module.scss'; export type RowSidePanelContextProps = { - onPropertyAddClick?: (keyPath: string, value: string) => void; + onPropertyAddClick?: ( + keyPath: string, + value: string, + action?: 'only' | 'exclude' | 'include', + ) => void; generateSearchUrl?: ({ where, whereLanguage, diff --git a/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx index a8387ad7c..3c363386c 100644 --- a/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx +++ b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx @@ -2,7 +2,7 @@ import React, { useContext, useRef, useState } from 'react'; import cx from 'classnames'; import { Popover } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; -import { IconCopy, IconFilter } from '@tabler/icons-react'; +import { IconCopy, IconFilter, IconFilterX } from '@tabler/icons-react'; import { RowSidePanelContext } from '../DBRowSidePanel'; @@ -83,14 +83,22 @@ export const DBRowTableFieldWithPopover = ({ const addFilter = () => { if (canFilter) { const value = typeof cellValue === 'string' ? cellValue : `${cellValue}`; - onPropertyAddClick(columnName, value); + onPropertyAddClick(columnName, value, 'include'); + handleClick(); // Close the popover + } + }; + + const excludeFilter = () => { + if (canFilter) { + const value = typeof cellValue === 'string' ? cellValue : `${cellValue}`; + onPropertyAddClick(columnName, value, 'exclude'); handleClick(); // Close the popover } }; const buttonSize = 20; const gapSize = 4; - const numberOfButtons = canFilter ? 2 : 1; // Copy + Filter (if filtering available) + const numberOfButtons = canFilter ? 3 : 1; // Copy + Include Filter + Exclude Filter (if filtering available) const numberOfGaps = numberOfButtons - 1; // If it's a chart, just render the children without popover functionality @@ -157,13 +165,22 @@ export const DBRowTableFieldWithPopover = ({ {canFilter && ( - - - + <> + + + + + + + )}
From c9eb745c135a7de1cbb51d992bd90ebe16ccc6c3 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Wed, 8 Oct 2025 18:51:07 +0100 Subject: [PATCH 37/39] feat(DBRowTableRowButtons): add sourceId prop to enhance URL copying functionality --- packages/app/src/components/DBRowTable.tsx | 1 + packages/app/src/components/DBTable/DBRowTableRowButtons.tsx | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/packages/app/src/components/DBRowTable.tsx b/packages/app/src/components/DBRowTable.tsx index c786dedf2..496ba1600 100644 --- a/packages/app/src/components/DBRowTable.tsx +++ b/packages/app/src/components/DBRowTable.tsx @@ -920,6 +920,7 @@ export const RawLogTable = memo( )} diff --git a/packages/app/src/components/DBTable/DBRowTableRowButtons.tsx b/packages/app/src/components/DBTable/DBRowTableRowButtons.tsx index 7b45db6d9..240f99be0 100644 --- a/packages/app/src/components/DBTable/DBRowTableRowButtons.tsx +++ b/packages/app/src/components/DBTable/DBRowTableRowButtons.tsx @@ -8,11 +8,13 @@ import styles from '../../../styles/LogTable.module.scss'; export interface DBRowTableRowButtonsProps { row: Record; getRowWhere: (row: Record) => string; + sourceId?: string; } export const DBRowTableRowButtons: React.FC = ({ row, getRowWhere, + sourceId, }) => { const [isCopied, setIsCopied] = useState(false); const [isUrlCopied, setIsUrlCopied] = useState(false); @@ -29,6 +31,9 @@ export const DBRowTableRowButtons: React.FC = ({ const currentUrl = new URL(window.location.href); // Add the row identifier as query parameters currentUrl.searchParams.set('rowWhere', rowWhere); + if (sourceId) { + currentUrl.searchParams.set('rowSource', sourceId); + } await navigator.clipboard.writeText(currentUrl.toString()); setIsUrlCopied(true); setTimeout(() => setIsUrlCopied(false), 2000); From 98b412269fa838ada9059bb82eb2c082c1668c0e Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Fri, 10 Oct 2025 11:47:47 +0100 Subject: [PATCH 38/39] feat: add error handling for clipboard copy operations in DBRowTable components --- .../DBTable/DBRowTableFieldWithPopover.tsx | 29 ++++++++++++--- .../DBTable/DBRowTableRowButtons.tsx | 36 ++++++++++++------- 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx index 3c363386c..91208c527 100644 --- a/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx +++ b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useRef, useState } from 'react'; +import React, { useContext, useEffect, useRef, useState } from 'react'; import cx from 'classnames'; import { Popover } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; @@ -31,6 +31,18 @@ export const DBRowTableFieldWithPopover = ({ const timeoutRef = useRef(); const hoverDisableTimeoutRef = useRef(); + // Cleanup timeouts on unmount to prevent memory leaks + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + if (hoverDisableTimeoutRef.current) { + clearTimeout(hoverDisableTimeoutRef.current); + } + }; + }, []); + // Get filter functionality from context const { onPropertyAddClick } = useContext(RowSidePanelContext); @@ -74,10 +86,15 @@ export const DBRowTableFieldWithPopover = ({ }; const copyFieldValue = async () => { - const value = typeof cellValue === 'string' ? cellValue : `${cellValue}`; - await navigator.clipboard.writeText(value); - setIsCopied(true); - setTimeout(() => setIsCopied(false), 2000); + try { + const value = typeof cellValue === 'string' ? cellValue : `${cellValue}`; + await navigator.clipboard.writeText(value); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + } catch (error) { + console.error('Failed to copy to clipboard:', error); + // Optionally show an error toast notification to the user + } }; const addFilter = () => { @@ -138,6 +155,8 @@ export const DBRowTableFieldWithPopover = ({ onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} onClick={handleClick} + tabIndex={-1} + aria-hidden="true" style={{ cursor: 'pointer' }} > {children} diff --git a/packages/app/src/components/DBTable/DBRowTableRowButtons.tsx b/packages/app/src/components/DBTable/DBRowTableRowButtons.tsx index 240f99be0..df6fe4aaf 100644 --- a/packages/app/src/components/DBTable/DBRowTableRowButtons.tsx +++ b/packages/app/src/components/DBTable/DBRowTableRowButtons.tsx @@ -20,23 +20,33 @@ export const DBRowTableRowButtons: React.FC = ({ const [isUrlCopied, setIsUrlCopied] = useState(false); const copyRowData = async () => { - const rowData = JSON.stringify(row, null, 2); - await navigator.clipboard.writeText(rowData); - setIsCopied(true); - setTimeout(() => setIsCopied(false), 2000); + try { + const rowData = JSON.stringify(row, null, 2); + await navigator.clipboard.writeText(rowData); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + } catch (error) { + console.error('Failed to copy row data to clipboard:', error); + // Optionally show an error toast notification to the user + } }; const copyRowUrl = async () => { - const rowWhere = getRowWhere(row); - const currentUrl = new URL(window.location.href); - // Add the row identifier as query parameters - currentUrl.searchParams.set('rowWhere', rowWhere); - if (sourceId) { - currentUrl.searchParams.set('rowSource', sourceId); + try { + const rowWhere = getRowWhere(row); + const currentUrl = new URL(window.location.href); + // Add the row identifier as query parameters + currentUrl.searchParams.set('rowWhere', rowWhere); + if (sourceId) { + currentUrl.searchParams.set('rowSource', sourceId); + } + await navigator.clipboard.writeText(currentUrl.toString()); + setIsUrlCopied(true); + setTimeout(() => setIsUrlCopied(false), 2000); + } catch (error) { + console.error('Failed to copy URL to clipboard:', error); + // Optionally show an error toast notification to the user } - await navigator.clipboard.writeText(currentUrl.toString()); - setIsUrlCopied(true); - setTimeout(() => setIsUrlCopied(false), 2000); }; return ( From 3d26a38507d159fa0ee5cfd0803d93b168e8b316 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Fri, 10 Oct 2025 13:01:31 +0100 Subject: [PATCH 39/39] Address claude review: improve clipboard copy functionality in DBRowTable components --- .../DBTable/DBRowTableFieldWithPopover.tsx | 11 +++++--- .../DBTable/DBRowTableRowButtons.tsx | 27 ++++++++++++++++++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx index 91208c527..18206b460 100644 --- a/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx +++ b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx @@ -12,7 +12,7 @@ import styles from '../../../styles/LogTable.module.scss'; export interface DBRowTableFieldWithPopoverProps { children: React.ReactNode; - cellValue: any; + cellValue: unknown; wrapLinesEnabled: boolean; columnName?: string; isChart?: boolean; @@ -87,7 +87,8 @@ export const DBRowTableFieldWithPopover = ({ const copyFieldValue = async () => { try { - const value = typeof cellValue === 'string' ? cellValue : `${cellValue}`; + const value = + typeof cellValue === 'string' ? cellValue : String(cellValue ?? ''); await navigator.clipboard.writeText(value); setIsCopied(true); setTimeout(() => setIsCopied(false), 2000); @@ -99,7 +100,8 @@ export const DBRowTableFieldWithPopover = ({ const addFilter = () => { if (canFilter) { - const value = typeof cellValue === 'string' ? cellValue : `${cellValue}`; + const value = + typeof cellValue === 'string' ? cellValue : String(cellValue ?? ''); onPropertyAddClick(columnName, value, 'include'); handleClick(); // Close the popover } @@ -107,7 +109,8 @@ export const DBRowTableFieldWithPopover = ({ const excludeFilter = () => { if (canFilter) { - const value = typeof cellValue === 'string' ? cellValue : `${cellValue}`; + const value = + typeof cellValue === 'string' ? cellValue : String(cellValue ?? ''); onPropertyAddClick(columnName, value, 'exclude'); handleClick(); // Close the popover } diff --git a/packages/app/src/components/DBTable/DBRowTableRowButtons.tsx b/packages/app/src/components/DBTable/DBRowTableRowButtons.tsx index df6fe4aaf..4619701e0 100644 --- a/packages/app/src/components/DBTable/DBRowTableRowButtons.tsx +++ b/packages/app/src/components/DBTable/DBRowTableRowButtons.tsx @@ -21,7 +21,32 @@ export const DBRowTableRowButtons: React.FC = ({ const copyRowData = async () => { try { - const rowData = JSON.stringify(row, null, 2); + // Filter out internal metadata fields that start with __ or are generated IDs + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { __hyperdx_id, ...cleanRow } = row; + + // Parse JSON string fields to make them proper JSON objects + const parsedRow = Object.entries(cleanRow).reduce( + (acc, [key, value]) => { + if ( + (typeof value === 'string' && value.startsWith('{')) || + value.startsWith('[') + ) { + try { + acc[key] = JSON.parse(value); + } catch { + // If parsing fails, keep the original string + acc[key] = value; + } + } else { + acc[key] = value; + } + return acc; + }, + {} as Record, + ); + + const rowData = JSON.stringify(parsedRow, null, 2); await navigator.clipboard.writeText(rowData); setIsCopied(true); setTimeout(() => setIsCopied(false), 2000);