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
5 changes: 5 additions & 0 deletions .changeset/browser-event-row-summary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hyperdx/app': patch
---

feat: Row side panel summarizes clicks, navigations and console messages from OpenTelemetry browser SDKs, and navigation rows no longer show an HTTP Request section
78 changes: 78 additions & 0 deletions packages/app/src/components/BrowserEventSubpanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
BrowserEventKind,
CONSOLE_METHOD_ATTRIBUTE,
firstLine,
getElementAttributes,
} from '@/utils/browserEvents';
import {
Attributes,
firstSetString,
isSet,
} from '@/utils/spanAttributeReaders';

import { RequestDetailRow, RequestDetailsTable } from './RequestDetailsTable';

const PAGE_URL_KEY = 'browser.document.url.full';

function read(attributes: Attributes, key: string): string | undefined {
return firstSetString(attributes, [key]);
}

// Map columns hold the flag as 'true'/'false', JSON columns as a Bool.
function yesNo(value: unknown): string | undefined {
if (!isSet(value)) {
return undefined;
}
return String(value) === 'true' ? 'Yes' : 'No';
}

function getClickRows(attributes: Attributes): RequestDetailRow[] {
return [
['Element', read(attributes, 'browser.tag_name')?.toLowerCase()],
['CSS selector', read(attributes, 'browser.css_selector')],
...getElementAttributes(attributes).map(
([name, value]): RequestDetailRow => [`data-otel-${name}`, value],
),
['Page URL', read(attributes, PAGE_URL_KEY)],
];
}

function getNavigationRows(attributes: Attributes): RequestDetailRow[] {
return [
['URL', read(attributes, 'url.full')],
['Navigation type', read(attributes, 'browser.navigation.type')],
['Same document', yesNo(attributes['browser.navigation.same_document'])],
['Hash change', yesNo(attributes['browser.navigation.hash_change'])],
];
}

function getConsoleRows(
attributes: Attributes,
body: string | undefined,
): RequestDetailRow[] {
return [
['Method', read(attributes, CONSOLE_METHOD_ATTRIBUTE)],
// The SDK sends the console message as the body, never as an attribute.
['Message', body ? firstLine(body) : undefined],
['Page URL', read(attributes, PAGE_URL_KEY)],
];
}

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 — Navigation URL row skips the browser.document.url.full fallback the event label uses

getNavigationLabel in utils/browserEvents.ts reads ['url.full', 'browser.document.url.full'], but this row only reads url.full. On a navigation row that carries only the document URL, the list shows "Navigated /checkout" while the panel shows no URL. If the flags are also missing, RequestDetailsTable returns null and the section is an empty "Browser event" accordion, and HTTP Request is hidden too. Use the same firstSetString(attributes, ['url.full', PAGE_URL_KEY]) lookup, and don't render the accordion item when it has no rows.

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

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 — Navigation URL row ignores the browser.document.url.full fallback the event label uses

getNavigationLabel in utils/browserEvents.ts reads ['url.full', 'browser.document.url.full']. This subpanel reads only url.full, so when url.full is missing the event list shows "Navigated " but the Browser event section shows no URL. Use the same key list here, e.g. firstSetString(attributes, ['url.full', PAGE_URL_KEY]), ideally through one shared constant.

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

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 — Navigation URL row reads only url.full, but the event list label for the same row falls back to browser.document.url.full

Read the same keys the label reads: firstSetString(attributes, ['url.full', 'browser.document.url.full']). getNavigationLabel in utils/browserEvents.ts:109 already does this. As written, a navigation with no url.full shows a URL in the session list ("Navigated /checkout") and none in the side panel.

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


export function BrowserEventSubpanel({
kind,
attributes,
body,
}: {
kind: BrowserEventKind;
/** The row's flattened event attributes. */
attributes: Attributes;
body: string | undefined;
}) {
const rows =
kind === 'click'
? getClickRows(attributes)
: kind === 'navigation'
? getNavigationRows(attributes)
: getConsoleRows(attributes, body);
return <RequestDetailsTable rows={rows} />;
}
75 changes: 70 additions & 5 deletions packages/app/src/components/DBRowDataPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useMemo } from 'react';
import { flatten } from 'flat';
import type { ResponseJSON } from '@hyperdx/common-utils/dist/clickhouse';
import { tcFromSource } from '@hyperdx/common-utils/dist/core/metadata';
import {
isLogSource,
isTraceSource,
Expand All @@ -15,6 +16,7 @@ import {
getSelectAllColumnsQuerySettings,
useSelectAllColumnsQuerySettings,
} from '@/hooks/useMaterializedAliasColumnsOption';
import { useColumns } from '@/hooks/useMetadata';
import { WithClause } from '@/hooks/useRowWhere';
import {
getDisplayedTimestampValueExpression,
Expand Down Expand Up @@ -43,6 +45,7 @@ export enum ROW_DATA_ALIASES {
DURATION_MS = '__hdx_duration_ms',
SPAN_KIND = '__hdx_span_kind',
SPAN_LINKS = '__hdx_span_links',
EVENT_NAME = '__hdx_event_name',
}

export function useRowData({
Expand Down Expand Up @@ -97,6 +100,46 @@ export function useRowData({
// select that instead of `*` when fetching full row data.
const knownColumns = getKnownColumnsList(source);

// A bare column comes back from `*` or the Known Columns List whenever the
// table has it, so it is read from the row instead of selected: older OTel
// log tables have no EventName column, and selecting it would fail the whole
// lookup.
const eventNameExpr = isLogSource(source)
? source.eventNameExpression?.trim()
: undefined;
const eventNameColumn =

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.

🟠 major — A bare event-name column that is ALIAS or MATERIALIZED never reaches the row, so detection quietly falls back to attribute keys

ClickHouse leaves ALIAS and MATERIALIZED columns out of SELECT * unless asterisk_include_alias_columns/asterisk_include_materialized_columns is set. That setting is only added when the user turns on the off-by-default option (useSelectAllColumnsQuerySettings in hooks/useMaterializedAliasColumnsOption.ts). So a source with eventNameExpression: 'EventName' on a table where EventName is e.g. ALIAS LogAttributes['event.name'] gets row.EventName === undefined. Every row then goes through the attribute fallback: browser.resource_timing/web_vital rows show 'No body for this event.' and navigations missing the fallback keys keep the HTTP Request section. Fix: decide between reading and selecting from the table's column metadata (getMetadata().getColumns, select it under the alias only when the column exists, whatever its kind). Or, at minimum, select the bare column under __hdx_event_name when metadata shows it as an ALIAS/MATERIALIZED column. Add a test for the case.

Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond

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.

@SpencerTorres I think we should fix this

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 — Bare event name column is lost when * or the Known Columns List leaves it out

SELECT * skips MATERIALIZED and ALIAS columns unless the viewer option is on (useSelectAllColumnsQuerySettings in hooks/useMaterializedAliasColumnsOption.ts), and a Known Columns List may simply not include EventName. In either case row[eventNameColumn] is undefined, so web-vital or resource-timing rows (which have no fallback keys) still show "No body for this event.". To fix it, check that the column exists using the table metadata and select it under __hdx_event_name when it does, rather than reading it off *. If you keep the current approach, add a test for an expression that is set but missing from knownColumns.

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

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 — A bare event-name column that is ALIAS or MATERIALIZED never reaches the row

SELECT * leaves out ALIAS and MATERIALIZED columns unless the opt-in asterisk_include_* settings are on (hooks/useMaterializedAliasColumnsOption.ts). A Known Columns List that doesn't name the column also drops it. In those cases row[eventNameColumn] is undefined, so browser.resource_timing and browser.web_vital rows lose detection and show "No body for this event." again. Fix: when the source's columns are known to contain the identifier (via Metadata), select it under __hdx_event_name. At minimum, use the row value only when it is present and fall back to selecting it otherwise.

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

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 — A bare EventName that is an ALIAS or MATERIALIZED column is never read

A bare identifier is only read from the SELECT * result. ClickHouse leaves ALIAS and MATERIALIZED columns out of * unless the asterisk_include_* settings are on, and useSelectAllColumnsQuerySettings (hooks/useMaterializedAliasColumnsOption.ts:46) only turns them on behind a user toggle. So an EventName ... ALIAS LogAttributes['event.name'] column gives an undefined event name. Detection then falls back to attribute keys, and browser.resource_timing and browser.web_vital rows show 'No body for this event.' again. Either read the column from the row only when data.meta lists it and select it under the alias otherwise, or note the limitation in the comment.

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

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 — A bare event-name column that is MATERIALIZED or ALIAS is never read

SELECT * leaves out MATERIALIZED/ALIAS columns unless the opt-in setting in hooks/useMaterializedAliasColumnsOption.ts is on. So with EventName ... MATERIALIZED LogAttributes['event.name'], row[eventNameColumn] is undefined and every non-click/nav/console browser.* row (web vitals, resource timing) loses its event name, so hideEmptyBody stays off for it. Fix: only take the bare-column shortcut when the column is known to come back from the row query (check the column kind via metadata, or check knownColumns). Otherwise select it under __hdx_event_name. Add a test for the case where the row has no such key but the column exists.

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

eventNameExpr && /^[A-Za-z_][A-Za-z0-9_]*$/.test(eventNameExpr)
? eventNameExpr
: undefined;

// `*` leaves out ALIAS and MATERIALIZED columns, so the table's columns
// decide whether a bare column is selected after all. renderChartConfig
// already reads these cached columns before the lookup runs, so waiting on
// them adds no query. A Known Columns List names its columns, so it returns
// such a column whenever it lists it.
const checksEventNameColumn =
rowId != null && eventNameColumn != null && knownColumns == null;
const { data: columns, isLoading: isColumnsLoading } = useColumns(
tcFromSource(source),
{
enabled: checksEventNameColumn,
// A failed DESCRIBE only costs the event name, so the lookup does not
// wait out retries.
retry: false,
},
);
const isEventNameColumnPending = checksEventNameColumn && isColumnsLoading;
// A disabled query still returns columns another page has cached.
const eventNameColumnKind = checksEventNameColumn
? columns?.find(column => column.name === eventNameColumn)?.default_type
: undefined;
const selectsEventNameColumn =
eventNameColumnKind === 'ALIAS' || eventNameColumnKind === 'MATERIALIZED';
const selectedEventNameExpr =
eventNameColumn == null || selectsEventNameColumn
? eventNameExpr
: undefined;

const baseConfig = {
source: source.id,
connection: source.connection,
Expand Down Expand Up @@ -168,6 +211,14 @@ export function useRowData({
},
]
: []),
...(selectedEventNameExpr
? [
{
valueExpression: selectedEventNameExpr,
alias: ROW_DATA_ALIASES.EVENT_NAME,
},
]
: []),
...(source.kind === SourceKind.Trace && source.spanEventsValueExpression
? [
{
Expand Down Expand Up @@ -214,7 +265,15 @@ export function useRowData({

const additionalQuerySettings = useSelectAllColumnsQuerySettings(source);

const baseQueryKey = ['row_side_panel', rowId, aliasWith, source];
// This key replaces the config-derived one, so it has to carry the column
// check's effect on the select.
const baseQueryKey = [
'row_side_panel',
rowId,
aliasWith,
source,
selectsEventNameColumn,
];
// Both halves of the filter are needed for `renderChartConfig` to emit one, so
// a source with no usable timestamp expression can't be bounded at all.
const hasWindow = dateRange != null && timestampValueExpr != null;
Expand All @@ -228,7 +287,7 @@ export function useRowData({
},
{
queryKey: [...baseQueryKey, dateRange],
enabled: rowId != null && hasWindow,
enabled: rowId != null && hasWindow && !isEventNameColumnPending,
additionalQuerySettings,
},
);
Expand All @@ -249,7 +308,7 @@ export function useRowData({
// often resolve from cache instead of scanning.
const fallbackResult = useQueriedChartConfig(baseConfig, {
queryKey: [...baseQueryKey, undefined],
enabled: rowId != null && isFallbackActive,
enabled: rowId != null && isFallbackActive && !isEventNameColumnPending,
additionalQuerySettings,
});

Expand All @@ -259,7 +318,9 @@ export function useRowData({
// report loading until it settles rather than briefly claiming the row is
// absent.
const isLoading =
queryResult.isLoading || (isBoundedEmpty && queryResult.isPending);
isEventNameColumnPending ||
queryResult.isLoading ||
(isBoundedEmpty && queryResult.isPending);

// Normalize resource, event and span-event exception attributes to always use flat keys for both JSON and Map columns
const normalizedData = useMemo(() => {
Expand Down Expand Up @@ -288,11 +349,15 @@ export function useRowData({
);
}

if (eventNameColumn != null && !selectsEventNameColumn) {
normalizedRow[ROW_DATA_ALIASES.EVENT_NAME] = row[eventNameColumn];
}

return {
...queryResult.data,
data: [normalizedRow],
};
}, [queryResult.data]);
}, [queryResult.data, eventNameColumn, selectsEventNameColumn]);

return {
...queryResult,
Expand Down
64 changes: 61 additions & 3 deletions packages/app/src/components/DBRowOverviewPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { use, useCallback, useMemo } from 'react';
import isString from 'lodash/isString';
import pickBy from 'lodash/pickBy';
import { SourceKind, TSource } from '@hyperdx/common-utils/dist/types';
import {
isLogSource,
SourceKind,
TSource,
} from '@hyperdx/common-utils/dist/types';
import { Accordion, Box, Flex, Text } from '@mantine/core';

import { WithClause } from '@/hooks/useRowWhere';
import { getEventBody } from '@/source';
import { getBrowserEventKind, isBrowserEventRow } from '@/utils/browserEvents';
import { getHighlightedAttributesFromData } from '@/utils/highlightedAttributes';
import {
isJavaScriptRuntime,
Expand All @@ -15,6 +20,7 @@ import { resolveRowTimestampAnchor } from '@/utils/rowTimestamps';
import { getRequestPanelTitle } from '@/utils/rpcSpanAttributes';
import { isSet } from '@/utils/spanAttributeReaders';

import { BrowserEventSubpanel } from './BrowserEventSubpanel';
import {
getJSONColumnNames,
getMapColumnNames,
Expand Down Expand Up @@ -98,6 +104,7 @@ export function RowOverviewPanel({
// assume OTel column names for now
const topLevelAttributeKeys = [
'ServiceName',
'EventName',
'SpanName',
'Duration',
'SeverityText',
Expand All @@ -110,7 +117,16 @@ export function RowOverviewPanel({
'ScopeName',
'ScopeVersion',
];
const topLevelAttributes = pickBy(firstRow, (value, key) => {
// `*` leaves out an ALIAS or MATERIALIZED EventName, so `useRowData` only
// returns it under its alias.
const rowWithEventName =
isLogSource(source) &&
source.eventNameExpression?.trim() === 'EventName' &&
firstRow?.EventName == null &&
firstRow?.__hdx_event_name != null
? { ...firstRow, EventName: firstRow.__hdx_event_name }
: firstRow;
const topLevelAttributes = pickBy(rowWithEventName, (value, key) => {
if (value === '') {
return false;
}
Expand Down Expand Up @@ -151,6 +167,24 @@ export function RowOverviewPanel({
[flattenedEventAttributes],
);

// Only log sources select the event name, and gating here keeps every trace
// row out whatever attributes it carries.
const { browserEventKind, isBrowserRow } = useMemo(() => {
if (!isLogSource(source)) {
return { browserEventKind: undefined, isBrowserRow: false };
}

const row = {
eventName: firstRow?.__hdx_event_name,
attributes: flattenedEventAttributes,
};

return {
browserEventKind: getBrowserEventKind(row),
isBrowserRow: isBrowserEventRow(row),
};
}, [source, firstRow?.__hdx_event_name, flattenedEventAttributes]);

const exceptionAttributes = useMemo(() => {
const spanEventAttributes = firstRow?.__hdx_events_exception_attributes;
if (
Expand Down Expand Up @@ -271,6 +305,7 @@ export function RowOverviewPanel({
// In that case suppress the body paper entirely instead of
// rendering an "[Empty]" placeholder.
bodyConfigured={mainContentColumn !== undefined}
hideEmptyBody={isBrowserRow}
severityText={firstRow?.__hdx_severity_text}
rowData={firstRow}
/>
Expand All @@ -280,6 +315,7 @@ export function RowOverviewPanel({
mt="sm"
defaultValue={[
'exception',
'browserEvent',
'spanEvents',
'spanLinks',
'linkedFrom',
Expand Down Expand Up @@ -311,7 +347,29 @@ export function RowOverviewPanel({
</Accordion.Item>
)}

{requestPanelTitle != null && (
{browserEventKind != null && (

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 — The 'Browser event' section can open with nothing in it

The section shows whenever browserEventKind != null, but RequestDetailsTable (RequestDetailsTable.tsx:8) returns null when every row value is unset. A log source with an Event Name Expression but no eventAttributesExpression hits this: a browser.user_action.click row gets an expanded, empty 'Browser event' section. Build the rows in RowOverviewPanel, or have BrowserEventSubpanel report whether it has content, and render the Accordion.Item only when at least one row is set.

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

<Accordion.Item
value="browserEvent"
data-testid="browser-event-section"
>
<Accordion.Control>
<Text size="sm" ps={contentPx}>
Browser event
</Text>
</Accordion.Control>
<Accordion.Panel>
<Box px={contentPx}>
<BrowserEventSubpanel
kind={browserEventKind}
attributes={flattenedEventAttributes}
body={mainContent}
/>
</Box>
</Accordion.Panel>
</Accordion.Item>
)}

{requestPanelTitle != null && browserEventKind == null && (
<Accordion.Item value="network">
<Accordion.Control>
<Text size="sm" ps={contentPx}>
Expand Down
8 changes: 8 additions & 0 deletions packages/app/src/components/DBRowSidePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import TabBar from '@/TabBar';
import { SearchConfig } from '@/types';
import { FormatTime } from '@/useFormatTime';
import { formatDistanceToNowStrictShort } from '@/utils';
import { isBrowserEventRow } from '@/utils/browserEvents';
import { getHighlightedAttributesFromData } from '@/utils/highlightedAttributes';
import {
getRowLookupWindow,
Expand Down Expand Up @@ -1109,6 +1110,13 @@ export const DBRowSidePanelInner = ({
mainContent={mainContent}
mainContentHeader={mainContentColumn}
bodyShownInTitle={isBodyShownInTitle}
hideEmptyBody={

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 — The browser-row check for hideEmptyBody is written twice, in DBRowSidePanel and in RowOverviewPanel

DBRowSidePanelInner builds isLogSource(source) && isBrowserEventRow({eventName: normalizedRow[EVENT_NAME], attributes: normalizedRow[EVENT_ATTRIBUTES]}) inline. RowOverviewPanel (DBRowOverviewPanel.tsx:158-172) separately builds the same {eventName, attributes} row behind the same isLogSource gate. The side panel renders the header itself and passes hideHeader to the overview, so the two copies feed different headers and can drift apart. Fix: move it into one helper in utils/browserEvents.ts, e.g. isBrowserEventRowForSource(source, row) that reads __hdx_event_name/__hdx_event_attributes, and call it from both places.

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

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 — Two copies of the empty-body decision, one per header caller

DBRowSidePanel and RowOverviewPanel each rebuild isLogSource(source) && isBrowserEventRow({eventName, attributes}) from the same row aliases before rendering DBRowSidePanelHeader. The two copies must be kept in sync by hand, and a third header caller would miss the rule. Move the check into one shared helper that takes (source, row) (or into the header, which already gets rowData), so every caller uses the same rule.

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

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 — The browser-row predicate is built in two places that must stay in sync

isLogSource(source) && isBrowserEventRow({ eventName, attributes }) is written inline here and again in the useMemo in DBRowOverviewPanel.tsx:158. Each copy picks the event-name key its own way: one uses ROW_DATA_ALIASES.EVENT_NAME, the other the literal __hdx_event_name. Move both into one helper such as isBrowserEventLogRow(source, row) in utils/browserEvents.ts and call it from both components.

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

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 — The 'is this a browser log row' check is written out in two places

DBRowSidePanel repeats isLogSource(source) && isBrowserEventRow({eventName, attributes}) from RowOverviewPanel (DBRowOverviewPanel.tsx:158-172) to decide hideEmptyBody for the same header. The two copies can drift, for example if one gains a source-kind check and the other does not. Add one helper to utils/browserEvents.ts, such as isBrowserEventLogRow(source, row), and call it from both places.

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

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 — Browser-row detection with the log-source gate is duplicated in two components

isLogSource(source) && isBrowserEventRow({eventName: row.__hdx_event_name, attributes: row.__hdx_event_attributes}) is now built separately here and in DBRowOverviewPanel.tsx:159-173. Both feed the same DBRowSidePanelHeader, which already receives rowData. Move this into one place so the two call sites can't drift. Either add a helper in utils/browserEvents.ts that takes (source, normalizedRow), or have the header work it out from rowData plus a source/isLogSource prop.

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

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 — The rule for hiding the empty body is written out twice and must be kept in sync by hand

Both DBRowSidePanel.tsx:1079-1085 and DBRowOverviewPanel.tsx:168-182 repeat isLogSource(source) && isBrowserEventRow({ eventName: row.__hdx_event_name, attributes: row.__hdx_event_attributes }). Move it into one helper in utils/browserEvents.ts, e.g. shouldHideEmptyBody(source, row), and call it from both places. Otherwise a later change to one copy (such as allowing trace sources, or adding a new alias) leaves the side panel header and the inline/trace-view header disagreeing.

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

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 — Browser-row detection is duplicated in DBRowSidePanel and RowOverviewPanel and has to be kept in sync by hand

isLogSource(source) && isBrowserEventRow({eventName: __hdx_event_name, attributes: __hdx_event_attributes}) is computed inline here and again in DBRowOverviewPanel.tsx:172. Move it into one helper that takes the source and the row (or have useRowData return it), so the two headers can't disagree about hiding the empty body.

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

isLogSource(source) &&
isBrowserEventRow({
eventName: normalizedRow?.[ROW_DATA_ALIASES.EVENT_NAME],
attributes: normalizedRow?.[ROW_DATA_ALIASES.EVENT_ATTRIBUTES],
})
}
severityText={severityText}
rowData={normalizedRow}
/>
Expand Down
Loading
Loading