Skip to content

Commit 1cf8ceb

Browse files
authored
feat: Support JSON Sessions (#1628)
1 parent db84560 commit 1cf8ceb

15 files changed

Lines changed: 682 additions & 384 deletions

File tree

‎.changeset/breezy-cougars-shave.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@hyperdx/common-utils": patch
3+
"@hyperdx/app": patch
4+
---
5+
6+
feat: Support JSON Sessions

‎docker-compose.dev.yml‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,6 @@ services:
7272
volumes:
7373
- ./docker/otel-collector/config.yaml:/etc/otelcol-contrib/config.yaml
7474
- ./docker/otel-collector/supervisor_docker.yaml.tmpl:/etc/otel/supervisor.yaml.tmpl
75-
# Add a custom config file
76-
- ./docker/otel-collector/dev.json.config.yaml:/etc/otelcol-contrib/custom.config.yaml
7775
ports:
7876
- '14318:4318' # OTLP http receiver
7977
restart: always

‎docker/otel-collector/dev.json.config.yaml‎

Lines changed: 0 additions & 11 deletions
This file was deleted.

‎packages/app/src/DOMPlayer.tsx‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
import { useRRWebEventStream } from '@/sessions';
1818
import { useDebugMode } from '@/utils';
1919

20+
import { FieldExpressionGenerator } from './hooks/useFieldExpressionGenerator';
21+
2022
import styles from '../styles/SessionSubpanelV2.module.scss';
2123

2224
function getPlayerCurrentTime(player: Replayer) {
@@ -27,7 +29,7 @@ const URLHoverCard = memo(({ url }: { url: string }) => {
2729
let parsedUrl: URL | undefined;
2830
try {
2931
parsedUrl = new URL(url);
30-
} catch (e) {
32+
} catch {
3133
// ignore
3234
}
3335

@@ -38,7 +40,7 @@ const URLHoverCard = memo(({ url }: { url: string }) => {
3840
for (const [key, value] of _searchParams.entries()) {
3941
searchParams.push({ key, value });
4042
}
41-
} catch (e) {
43+
} catch {
4244
// ignore
4345
}
4446

@@ -92,6 +94,7 @@ export default function DOMPlayer({
9294
setPlayerFullWidth,
9395
playerFullWidth,
9496
resizeKey,
97+
getSessionSourceFieldExpression,
9598
}: {
9699
config: {
97100
dateRange: [Date, Date];
@@ -106,14 +109,11 @@ export default function DOMPlayer({
106109
playerSpeed: number;
107110
setPlayerStartTimestamp?: (ts: number) => void;
108111
setPlayerEndTimestamp?: (ts: number) => void;
109-
// setPlayerSpeed: (playerSpeed: number) => void;
110112
skipInactive: boolean;
111-
// setSkipInactive: (skipInactive: boolean) => void;
112-
// highlightedResultId: string | undefined;
113-
// onClick: (logId: string, sortKey: number) => void;
114113
resizeKey?: string;
115114
setPlayerFullWidth: (fullWidth: boolean) => void;
116115
playerFullWidth: boolean;
116+
getSessionSourceFieldExpression: FieldExpressionGenerator;
117117
}) {
118118
const debug = useDebugMode();
119119
const wrapper = useRef<HTMLDivElement>(null);
@@ -141,7 +141,7 @@ export default function DOMPlayer({
141141
limit: 1000000, // large enough to get all events
142142
onEvent: (event: { b: string; ck: number; tcks: number; t: number }) => {
143143
try {
144-
const { b: body, ck: chunk, tcks: totalChunks, t: type } = event;
144+
const { b: body, ck: chunk, tcks: totalChunks } = event;
145145
currentRrwebEvent += body;
146146
if (!chunk || chunk === totalChunks) {
147147
const parsedEvent = JSON.parse(currentRrwebEvent);
@@ -202,11 +202,10 @@ export default function DOMPlayer({
202202
}
203203
}
204204
},
205+
getSessionSourceFieldExpression,
205206
},
206207
{
207-
enabled: dateRange != null,
208-
// @ts-ignore
209-
keepPreviousData: true, // TODO: support streaming
208+
keepPreviousData: true,
210209
shouldAbortPendingRequest: true,
211210
},
212211
);
@@ -358,7 +357,7 @@ export default function DOMPlayer({
358357
setIsReplayerInitialized(true);
359358

360359
if (debug) {
361-
// @ts-ignore
360+
// @ts-expect-error this is for debugging purposes only
362361
window.__hdx_replayer = replayer.current;
363362
}
364363

‎packages/app/src/Playbar.tsx‎

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { useMemo } from 'react';
22
import uniqBy from 'lodash/uniqBy';
33
import { ChartConfigWithOptDateRange } from '@hyperdx/common-utils/dist/types';
4+
import { keepPreviousData } from '@tanstack/react-query';
45

56
import { useQueriedChartConfig } from '@/hooks/useChartConfig';
67

8+
import { SessionRow, sessionRowSchema } from './utils/sessions';
79
import type { PlaybarMarker } from './PlaybarSlider';
810
import { PlaybarSlider } from './PlaybarSlider';
911
import { getShortUrl } from './utils';
@@ -29,14 +31,19 @@ export default function Playbar({
2931
const maxSliderVal = Math.ceil(playbackRange[1].getTime() / 1000) * 1000;
3032
const minSliderVal = Math.floor(playbackRange[0].getTime() / 1000) * 1000;
3133

32-
const { data, isLoading, isError, error } = useQueriedChartConfig(
33-
queriedConfig,
34-
{
35-
placeholderData: (prev: any) => prev,
36-
queryKey: ['PlayBar', queriedConfig],
37-
},
34+
const { data } = useQueriedChartConfig(queriedConfig, {
35+
placeholderData: keepPreviousData,
36+
queryKey: ['PlayBar', queriedConfig],
37+
});
38+
39+
const events: SessionRow[] = useMemo(
40+
() =>
41+
data?.data
42+
?.map(row => sessionRowSchema.safeParse(row))
43+
.filter(parsed => parsed.success)
44+
.map(parsed => parsed.data) ?? [],
45+
[data?.data],
3846
);
39-
const events: any[] = useMemo(() => data?.data ?? [], [data?.data]);
4047

4148
const markers = useMemo<PlaybarMarker[]>(() => {
4249
return uniqBy(
@@ -59,14 +66,14 @@ export default function Playbar({
5966
)
6067
.map(event => {
6168
const spanName = event['span_name'];
62-
const locationHref = event['location.href'];
69+
const locationHref = event['location.href'] ?? '';
6370
const shortLocationHref = getShortUrl(locationHref);
6471

65-
const errorMessage = event['error.message'];
72+
const errorMessage = event['error.message'] ?? '';
6673

67-
const url = event['http.url'];
68-
const statusCode = event['http.status_code'];
69-
const method = event['http.method'];
74+
const url = event['http.url'] ?? '';
75+
const statusCode = Number(event['http.status_code'] ?? '');
76+
const method = event['http.method'] ?? '';
7077
const shortUrl = getShortUrl(url);
7178

7279
const isNavigation =
@@ -94,7 +101,7 @@ export default function Playbar({
94101
? errorMessage
95102
: spanName === 'intercom.onShow'
96103
? 'Intercom Chat Opened'
97-
: event.body,
104+
: (event.body ?? ''),
98105
isError,
99106
isSuccess,
100107
};

‎packages/app/src/SessionEventList.tsx‎

Lines changed: 38 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as React from 'react';
22
import cx from 'classnames';
33
import { ChartConfigWithOptDateRange } from '@hyperdx/common-utils/dist/types';
44
import { ScrollArea, Skeleton, Stack } from '@mantine/core';
5-
import { useThrottledCallback, useThrottledValue } from '@mantine/hooks';
5+
import { useThrottledValue } from '@mantine/hooks';
66
import {
77
IconArrowsLeftRight,
88
IconMapPin,
@@ -16,18 +16,18 @@ import { useVirtualizer } from '@tanstack/react-virtual';
1616
import useRowWhere, { RowWhereResult } from '@/hooks/useRowWhere';
1717

1818
import { useQueriedChartConfig } from './hooks/useChartConfig';
19+
import { SessionRow, sessionRowSchema } from './utils/sessions';
1920
import { useFormatTime } from './useFormatTime';
2021
import { formatmmss, getShortUrl } from './utils';
2122

2223
import styles from '../styles/SessionSubpanelV2.module.scss';
2324

2425
type SessionEvent = {
2526
id: string;
26-
row: Record<string, string>; // original row object
27-
sortKey: string;
27+
row: SessionRow; // original row object
2828
isError: boolean;
2929
isSuccess: boolean;
30-
eventSource: 'navigation' | 'chat' | 'network' | 'custom';
30+
eventSource: 'navigation' | 'chat' | 'network' | 'custom' | 'log';
3131
title: string;
3232
description: string;
3333
timestamp: Date;
@@ -112,47 +112,48 @@ export const SessionEventList = ({
112112
onTimeClick: (ts: number) => void;
113113
eventsFollowPlayerPosition: boolean;
114114
}) => {
115-
const { data, isLoading, isError, error, isPlaceholderData, isSuccess } =
116-
useQueriedChartConfig(queriedConfig, {
117-
placeholderData: (prev: any) => prev,
118-
queryKey: ['SessionEventList', queriedConfig],
119-
});
115+
const { data, isLoading } = useQueriedChartConfig(queriedConfig, {
116+
placeholderData: (prev: any) => prev,
117+
queryKey: ['SessionEventList', queriedConfig],
118+
});
120119
const formatTime = useFormatTime();
121120

122-
const events = React.useMemo(() => data?.data ?? [], [data?.data]);
121+
const events: SessionRow[] = React.useMemo(
122+
() =>
123+
data?.data
124+
?.map(row => sessionRowSchema.safeParse(row))
125+
.filter(parsed => parsed.success)
126+
.map(parsed => parsed.data) ?? [],
127+
[data?.data],
128+
);
123129

124130
const getRowWhere = useRowWhere({ meta: data?.meta, aliasMap });
125131

126132
const rows = React.useMemo(() => {
127133
return (
128-
events.map((event, i) => {
134+
events.map(event => {
129135
const { timestamp, durationInMs } = event;
130136

131137
// TODO: we should just use timestamp and durationInMs instead of startOffset and endOffset
132138
const startOffset = new Date(timestamp).getTime();
133139
const endOffset = new Date(startOffset).getTime() + durationInMs;
134140

135-
const isHighlighted = false;
136-
137-
const url = event['http.url'];
138-
const statusCode = event['http.status_code'];
139-
const method = event['http.method'];
141+
const url = event['http.url'] ?? '';
142+
const statusCode = Number(event['http.status_code'] ?? '');
143+
const method = event['http.method'] ?? '';
140144
const shortUrl = getShortUrl(url);
141145

142146
const isNetworkRequest =
143147
method != '' && method != null && url != null && url != '';
144148

145149
const errorMessage = event['error.message'];
146150

147-
const body = event['body'];
151+
const body = event['body'] ?? '';
148152
const component = event['component'];
149153
const spanName = event['span_name'];
150-
const locationHref = event['location.href'];
154+
const locationHref = event['location.href'] ?? '';
151155
const otelLibraryName = event['otel.library.name'];
152156
const shortLocationHref = getShortUrl(locationHref);
153-
const isException =
154-
event['exception.group_id'] != '' &&
155-
event['exception.group_id'] != null;
156157

157158
const isCustomEvent = otelLibraryName === 'custom-action';
158159
const isNavigation =
@@ -167,7 +168,6 @@ export const SessionEventList = ({
167168

168169
return {
169170
id: event.id,
170-
sortKey: event.sort_key,
171171
row: event,
172172
isError,
173173
isSuccess,
@@ -182,29 +182,27 @@ export const SessionEventList = ({
182182
: 'log',
183183
title: isNavigation
184184
? `Navigated`
185-
: isException
186-
? 'Exception'
187-
: spanName === 'console.error'
188-
? 'console.error'
189-
: spanName === 'console.log'
190-
? 'console.log'
191-
: spanName === 'console.warn'
192-
? 'console.warn'
193-
: url.length > 0
194-
? `${statusCode} ${method}`
195-
: spanName === 'intercom.onShow'
196-
? 'Intercom Chat Opened'
197-
: isCustomEvent
185+
: spanName === 'console.error'
186+
? 'console.error'
187+
: spanName === 'console.log'
188+
? 'console.log'
189+
: spanName === 'console.warn'
190+
? 'console.warn'
191+
: url.length > 0
192+
? `${statusCode} ${method}`
193+
: spanName === 'intercom.onShow'
194+
? 'Intercom Chat Opened'
195+
: isCustomEvent
196+
? spanName
197+
: component === 'console'
198198
? spanName
199-
: component === 'console'
200-
? spanName
201-
: 'console.error',
199+
: 'console.error',
202200
description: isNavigation
203201
? shortLocationHref
204202
: errorMessage != null && errorMessage.length > 0
205203
? errorMessage
206204
: component === 'console'
207-
? body
205+
? (body ?? '')
208206
: url.length > 0
209207
? shortUrl
210208
: '',
@@ -215,7 +213,7 @@ export const SessionEventList = ({
215213
format: 'time',
216214
}),
217215
duration: endOffset - startOffset,
218-
} as SessionEvent;
216+
} satisfies SessionEvent;
219217
}) ?? []
220218
);
221219
}, [events, showRelativeTime, minTs, formatTime]);

0 commit comments

Comments
 (0)