Improve Discoverability Inline Expansion of Items in Search Table (#1168)

This commit is contained in:
Elizabet Oliveira 2025-09-17 18:09:43 +01:00 committed by GitHub
parent 3d82583fce
commit 970c0027b8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 207 additions and 56 deletions

View file

@ -0,0 +1,5 @@
---
"@hyperdx/app": minor
---
Fix: improve the discoverability of inline item expansion within the search table

View file

@ -43,11 +43,7 @@ import {
Tooltip as MantineTooltip,
UnstyledButton,
} from '@mantine/core';
import {
FetchNextPageOptions,
useQuery,
useQueryClient,
} from '@tanstack/react-query';
import { FetchNextPageOptions, useQuery } from '@tanstack/react-query';
import {
ColumnDef,
ColumnResizeMode,
@ -696,11 +692,7 @@ export const RawLogTable = memo(
config={config}
/>
)}
<table
className="w-100 bg-inherit"
id={tableId}
style={{ tableLayout: 'fixed' }}
>
<table className={cx('w-100 bg-inherit', styles.table)} id={tableId}>
<thead className={styles.tableHead}>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
@ -832,34 +824,82 @@ export const RawLogTable = memo(
<React.Fragment key={virtualRow.key}>
<tr
data-testid={`table-row-${rowId}`}
onClick={() => {
// onRowExpandClick(row.original.id, row.original.sort_key);
_onRowExpandClick(row.original);
}}
role="button"
// TODO: Restore highlight
className={cx(styles.tableRow, {
[styles.tableRow__selected]: highlightedLineId === rowId,
})}
data-index={virtualRow.index}
ref={rowVirtualizer.measureElement}
>
{row.getVisibleCells().map(cell => {
return (
<td
key={cell.id}
className={cx('align-top overflow-hidden', {
'text-break': wrapLinesEnabled,
'text-truncate': !wrapLinesEnabled,
{/* Expand button cell */}
{showExpandButton && (
<td
className="align-top overflow-hidden"
style={{ width: '40px' }}
>
{flexRender(
row.getVisibleCells()[0].column.columnDef.cell,
row.getVisibleCells()[0].getContext(),
)}
</td>
)}
{/* Content columns grouped as one button */}
<td
className="align-top overflow-hidden p-0"
colSpan={columns.length - (showExpandButton ? 1 : 0)}
>
<button
type="button"
className={styles.rowContentButton}
onClick={e => {
e.stopPropagation();
_onRowExpandClick(row.original);
}}
aria-label="View details for log entry"
>
{row
.getVisibleCells()
.slice(showExpandButton ? 1 : 0) // Skip expand column
.map((cell, cellIndex) => {
const columnCustomClassName = (
cell.column.columnDef.meta as any
)?.className;
const columnSize = cell.column.getSize();
const totalContentCells =
row.getVisibleCells().length -
(showExpandButton ? 1 : 0);
return (
<div
key={cell.id}
className={cx(
'flex-shrink-0 overflow-hidden',
{
'text-break': wrapLinesEnabled,
'text-truncate': !wrapLinesEnabled,
},
columnCustomClassName,
)}
style={{
width:
columnSize === UNDEFINED_WIDTH
? 'auto'
: `${columnSize}px`,
flex:
columnSize === UNDEFINED_WIDTH
? '1'
: 'none',
}}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</div>
);
})}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</td>
);
})}
</button>
</td>
</tr>
{showExpandButton && isExpanded && (
<ExpandedLogRow
@ -1213,8 +1253,6 @@ function DBSqlRowTableComponent({
return noisyPatterns.data?.map(p => p.id) ?? [];
}, [noisyPatterns.data]);
const queryClient = useQueryClient();
const denoisedRows = useQuery({
queryKey: [
'denoised-rows',

View file

@ -2,6 +2,7 @@ import React, { memo, useCallback, useState } from 'react';
import cx from 'classnames';
import { useQueryState } from 'nuqs';
import { TSource } from '@hyperdx/common-utils/dist/types';
import { IconChevronRight } from '@tabler/icons-react';
import { useLocalStorage } from '@/utils';
@ -179,6 +180,44 @@ export const useExpandableRows = (
};
};
const ExpandButton = memo(
({
rowId,
isExpanded,
highlightedLineId,
toggleRowExpansion,
}: {
rowId: string;
isExpanded: boolean;
highlightedLineId?: string;
toggleRowExpansion: (rowId: string) => void;
}) => {
return (
<span className="d-flex align-items-center justify-content-center">
<button
type="button"
className={cx(styles.expandButton, {
[styles.expanded]: isExpanded,
'text-success': highlightedLineId === rowId,
'text-muted': highlightedLineId !== rowId,
})}
onClick={e => {
e.stopPropagation();
toggleRowExpansion(rowId);
}}
aria-expanded={isExpanded}
aria-label={`${isExpanded ? 'Collapse' : 'Expand'} log details`}
>
<IconChevronRight size={16} />
</button>
<span className={styles.expandButtonSeparator} />
</span>
);
},
);
ExpandButton.displayName = 'ExpandButton';
// Utility function for creating expand button column
export const createExpandButtonColumn = (
expandedRows: Record<string, boolean>,
@ -191,25 +230,19 @@ export const createExpandButtonColumn = (
cell: (info: any) => {
const rowId = info.getValue() as string;
const isExpanded = expandedRows[rowId] ?? false;
return (
<button
type="button"
className={cx('btn btn-link p-0 border-0', {
'text-success': highlightedLineId === rowId,
'text-muted': highlightedLineId !== rowId,
})}
onClick={e => {
e.stopPropagation();
toggleRowExpansion(rowId);
}}
aria-expanded={isExpanded}
aria-label={`${isExpanded ? 'Collapse' : 'Expand'} log details`}
style={{ lineHeight: 1 }}
>
<i className={`bi bi-chevron-${isExpanded ? 'down' : 'right'}`} />
</button>
<ExpandButton
rowId={rowId}
isExpanded={isExpanded}
highlightedLineId={highlightedLineId}
toggleRowExpansion={toggleRowExpansion}
/>
);
},
size: 8,
size: 32,
enableResizing: false,
meta: {
className: 'text-center',
},
});

View file

@ -1,5 +1,11 @@
@import './variables';
.table {
table-layout: fixed;
border-spacing: 0 2px;
border-collapse: separate;
}
.tableHead {
background: inherit;
position: sticky;
@ -7,10 +13,6 @@
}
.tableRow {
&:hover {
background-color: $slate-800;
}
&__selected {
background-color: $slate-800;
font-weight: bold;
@ -25,8 +27,81 @@
}
.expandButton {
background: transparent;
border: none;
padding: 0 2px;
color: inherit;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
transition: background-color 0.15s ease-in-out;
border-radius: 4px;
&:hover {
background-color: rgb(255 255 255 / 10%) !important;
color: #fff !important;
background-color: $slate-800;
}
&:focus,
&:focus-visible {
background-color: $slate-800;
outline: none;
}
&:active {
outline: none;
box-shadow: none;
background-color: $slate-800;
}
svg {
transition: transform 0.2s ease-in-out;
transform-origin: center;
}
&.expanded svg {
transform: rotate(90deg);
}
}
.expandButtonSeparator {
width: 1px;
height: 12px;
border-right: 1px solid $slate-800;
margin-left: 2px;
margin-right: 2px;
display: inline-block;
vertical-align: middle;
}
.rowContentButton {
background: transparent;
border: none;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
cursor: pointer;
display: flex;
align-items: stretch;
text-align: left;
color: inherit;
transition: background-color 0.15s ease-in-out;
border-radius: 4px;
&:hover {
background-color: $slate-800;
}
&:focus,
&:focus-visible {
background-color: $slate-800;
outline: none;
}
&:active {
outline: none;
box-shadow: none;
background-color: $slate-800;
}
}