Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/olive-pugs-arrive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@hyperdx/app': patch
---

Fix search results going blank after expanding and collapsing rows. A row and
its inline expansion are two `tr`s sharing one virtual index, and both were
measured by the virtualizer, so the expanded row took over that index's
ResizeObserver registration and left its height cached there after it
collapsed. Each expand/collapse shrank the render window a little further until
scrolling showed only a handful of rows above empty space. The row and its
expansion are now wrapped in a `tbody` that is measured as one unit.
304 changes: 156 additions & 148 deletions packages/app/src/components/DBRowTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1128,161 +1128,169 @@ export const RawLogTable = memo(
</tr>
))}
</thead>
<tbody>
{paddingTop > 0 && (
{paddingTop > 0 && (
<tbody>
<tr>
<td colSpan={99999} style={{ height: `${paddingTop}px` }} />
</tr>
)}
{items.map(virtualRow => {
const row = _rows[virtualRow.index] as TableRow<any>;
const rowId = getRowId(row.original);
const isExpanded = expandedRows[rowId] ?? false;
const isRowSelected =
enableRowSelection && rowSelection.isSelected(rowId);

return (
<React.Fragment key={virtualRow.key}>
<tr
data-testid={`table-row-${rowId}`}
className={cx(styles.tableRow, {
[styles.tableRow__selected]:
highlightedLineId && highlightedLineId === rowId,
[styles.tableRow__multiSelected]: isRowSelected,
})}
data-index={virtualRow.index}
ref={rowVirtualizer.measureElement}
>
{enableRowSelection && (
<RowSelectionCell
rowId={rowId}
isSelected={isRowSelected}
onToggle={rowSelection.toggleRow}
/>
)}

{/* 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 back to preserve row hover/click */}
</tbody>
)}
{items.map(virtualRow => {
const row = _rows[virtualRow.index] as TableRow<any>;
const rowId = getRowId(row.original);
const isExpanded = expandedRows[rowId] ?? false;
const isRowSelected =
enableRowSelection && rowSelection.isSelected(rowId);

return (
// A row and its inline expansion are two `tr`s but one
// virtual item, so the group — not either `tr` — is what the
// virtualizer measures. Measuring the `tr`s directly gave
// both the same `data-index`, and the expanded one took over
// that index's ResizeObserver registration, then left its
// height cached there after it unmounted.
<tbody
key={virtualRow.key}
data-index={virtualRow.index}
ref={rowVirtualizer.measureElement}
>
<tr
data-testid={`table-row-${rowId}`}
className={cx(styles.tableRow, {
[styles.tableRow__selected]:
highlightedLineId && highlightedLineId === rowId,
[styles.tableRow__multiSelected]: isRowSelected,
})}
>
{enableRowSelection && (
<RowSelectionCell
rowId={rowId}
isSelected={isRowSelected}
onToggle={rowSelection.toggleRow}
/>
)}

{/* Expand button cell */}
{showExpandButton && (
<td
className="align-top overflow-hidden p-0"
colSpan={columns.length - (showExpandButton ? 1 : 0)}
className="align-top overflow-hidden"
style={{ width: '40px' }}
>
<button
type="button"
className={cx(styles.rowContentButton, {
[styles.isWrapped]: wrapLinesEnabled,
[styles.isTruncated]: !wrapLinesEnabled,
})}
onClick={() => {
_onRowExpandClick(row.original);
}}
aria-label="View details for log entry"
>
{row
.getVisibleCells()
.slice(showExpandButton ? 1 : 0) // Skip expand
.map(cell => {
const columnCustomClassName = (
cell.column.columnDef.meta as any
)?.className;
const columnSize = cell.column.getSize();
const cellValue = cell.getValue<any>();

return (
<div
key={cell.id}
className={cx(
'flex-shrink-0 overflow-hidden position-relative',
columnCustomClassName,
)}
style={{
width:
columnSize === UNDEFINED_WIDTH
? 0
: `${columnSize}px`,
flex:
columnSize === UNDEFINED_WIDTH
? '1 1 0'
: 'none',
minWidth:
columnSize === UNDEFINED_WIDTH
? MIN_LAST_COLUMN_WIDTH
: undefined,
}}
>
<div className={styles.fieldTextContainer}>
<DBRowTableFieldWithPopover
key={cell.id}
cellValue={cellValue}
wrapLinesEnabled={wrapLinesEnabled}
tableContainerRef={tableContainerRef}
columnName={
(cell.column.columnDef.meta as any)
?.column
}
isChart={
(cell.column.columnDef.meta as any)
?.column === '__hdx_pattern_trend'
}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</DBRowTableFieldWithPopover>
</div>
</div>
);
})}
{/* Row-level copy buttons */}
{getRowWhere && (
<DBRowTableRowButtons
row={row.original}
getRowWhere={getRowWhere}
sourceId={source?.id}
isWrapped={wrapLinesEnabled}
onToggleWrap={() =>
setWrapLinesEnabled(!wrapLinesEnabled)
}
/>
)}
</button>
{flexRender(
row.getVisibleCells()[0].column.columnDef.cell,
row.getVisibleCells()[0].getContext(),
)}
</td>
</tr>
{showExpandButton && isExpanded && (
<ExpandedLogRow
columnsLength={
columns.length + (enableRowSelection ? 1 : 0)
}
virtualKey={virtualRow.key.toString()}
source={source}
rowId={rowId}
measureElement={rowVirtualizer.measureElement}
virtualIndex={virtualRow.index}
>
{renderRowDetails?.({
id: rowId,
aliasWith:
row.original[INTERNAL_ROW_FIELDS.ALIAS_WITH],
...row.original,
})}
</ExpandedLogRow>
)}
</React.Fragment>
);
})}

{/* Content columns grouped back to preserve row hover/click */}
<td
className="align-top overflow-hidden p-0"
colSpan={columns.length - (showExpandButton ? 1 : 0)}
>
<button
type="button"
className={cx(styles.rowContentButton, {
[styles.isWrapped]: wrapLinesEnabled,
[styles.isTruncated]: !wrapLinesEnabled,
})}
onClick={() => {
_onRowExpandClick(row.original);
}}
aria-label="View details for log entry"
>
{row
.getVisibleCells()
.slice(showExpandButton ? 1 : 0) // Skip expand
.map(cell => {
const columnCustomClassName = (
cell.column.columnDef.meta as any
)?.className;
const columnSize = cell.column.getSize();
const cellValue = cell.getValue<any>();

return (
<div
key={cell.id}
className={cx(
'flex-shrink-0 overflow-hidden position-relative',
columnCustomClassName,
)}
style={{
width:
columnSize === UNDEFINED_WIDTH
? 0
: `${columnSize}px`,
flex:
columnSize === UNDEFINED_WIDTH
? '1 1 0'
: 'none',
minWidth:
columnSize === UNDEFINED_WIDTH
? MIN_LAST_COLUMN_WIDTH
: undefined,
}}
>
<div className={styles.fieldTextContainer}>
<DBRowTableFieldWithPopover
key={cell.id}
cellValue={cellValue}
wrapLinesEnabled={wrapLinesEnabled}
tableContainerRef={tableContainerRef}
columnName={
(cell.column.columnDef.meta as any)
?.column
}
isChart={
(cell.column.columnDef.meta as any)
?.column === '__hdx_pattern_trend'
}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</DBRowTableFieldWithPopover>
</div>
</div>
);
})}
{/* Row-level copy buttons */}
{getRowWhere && (
<DBRowTableRowButtons
row={row.original}
getRowWhere={getRowWhere}
sourceId={source?.id}
isWrapped={wrapLinesEnabled}
onToggleWrap={() =>
setWrapLinesEnabled(!wrapLinesEnabled)
}
/>
)}
</button>
</td>
</tr>
{showExpandButton && isExpanded && (
<ExpandedLogRow
columnsLength={
columns.length + (enableRowSelection ? 1 : 0)
}
virtualKey={virtualRow.key.toString()}
source={source}
rowId={rowId}
>
{renderRowDetails?.({
id: rowId,
aliasWith:
row.original[INTERNAL_ROW_FIELDS.ALIAS_WITH],
...row.original,
})}
</ExpandedLogRow>
)}
</tbody>
);
})}
<tbody>
<tr>
<td colSpan={800}>
<div
Expand Down
8 changes: 2 additions & 6 deletions packages/app/src/components/ExpandableRowTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,22 @@ export const ExpandedLogRow = memo(
virtualKey,
source,
rowId,
measureElement,
virtualIndex,
}: {
children: React.ReactNode;
columnsLength: number;
virtualKey: string;
source?: TSource;
rowId: string;
measureElement?: (element: HTMLElement | null) => void;
virtualIndex?: number;
}) => {
const openSidebar = useSidebarOpener();

return (
// Deliberately not measured by the virtualizer: the enclosing `tbody` is
// the element registered for this row's virtual index.
<tr
data-testid={`expanded-row-${rowId}`}
key={`${virtualKey}-expanded`}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — virtualKey prop is now dead

With measureElement/virtualIndex gone, virtualKey's only remaining use is key={${virtualKey}-expanded} on the component's single root element, which has no reconciliation effect — the key that matters is on the <tbody> at DBRowTable.tsx:1153. Drop the prop and the virtualKey={virtualRow.key.toString()} argument at DBRowTable.tsx:1278.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

className={styles.expandedRow}
data-index={virtualIndex}
ref={measureElement}
>
<td colSpan={columnsLength} className="p-0 border-0">
<div className={cx('mx-2 mb-2 rounded', styles.expandedRowContent)}>
Expand Down
Loading
Loading