Skip to content

Commit 28655d6

Browse files
authored
ref(replay): Refactor Replay Details>Console tab to use the new *Frames types (#53660)
Refactored to use the `*Frame` types. Also, we are now also rendering custom breadcrumbs in the console tab! see screen shot and `redux.action` rows. | Note | Before | After | | --- | --- | --- | | Console Breadcrumbs | ![SCR-20230726-mcrc](https://github.com/getsentry/sentry/assets/187460/d30d0419-a0f3-4621-b91b-e0bc490f7327) | ![SCR-20230726-mcsc](https://github.com/getsentry/sentry/assets/187460/1805793a-5f8d-4696-b83f-d7740931c4ea) | | Custom breadcrumbs | n/a | ![SCR-20230726-miiq](https://github.com/getsentry/sentry/assets/187460/fdc4b5b5-01a8-462c-a49d-61494aa53ddc) | relates to #47991
1 parent 7d247ff commit 28655d6

17 files changed

+410
-480
lines changed

static/app/utils/replays/replayReader.spec.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,10 @@ describe('ReplayReader', () => {
133133
},
134134
{
135135
method: 'getConsoleFrames',
136-
expected: [expect.objectContaining({category: 'console'})],
136+
expected: [
137+
expect.objectContaining({category: 'console'}),
138+
expect.objectContaining({category: 'redux.action'}),
139+
],
137140
},
138141
{
139142
method: 'getNetworkFrames',

static/app/utils/replays/replayReader.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ import type {
3131
SlowClickFrame,
3232
SpanFrame,
3333
} from 'sentry/utils/replays/types';
34-
import {isDeadClick, isDeadRageClick} from 'sentry/utils/replays/types';
34+
import {
35+
BreadcrumbCategories,
36+
isDeadClick,
37+
isDeadRageClick,
38+
} from 'sentry/utils/replays/types';
3539
import type {
3640
RecordingEvent,
3741
ReplayCrumb,
@@ -212,7 +216,10 @@ export default class ReplayReader {
212216
getErrorFrames = () => this._errors;
213217

214218
getConsoleFrames = memoize(() =>
215-
this._sortedBreadcrumbFrames.filter(frame => frame.category === 'console')
219+
this._sortedBreadcrumbFrames.filter(
220+
frame =>
221+
frame.category === 'console' || !BreadcrumbCategories.includes(frame.category)
222+
)
216223
);
217224

218225
getNavigationFrames = memoize(() =>
@@ -302,10 +309,6 @@ export default class ReplayReader {
302309
/*********************/
303310
/** OLD STUFF BELOW **/
304311
/*********************/
305-
getConsoleCrumbs = memoize(() =>
306-
this.breadcrumbs.filter(crumb => crumb.category === 'console')
307-
);
308-
309312
getNonConsoleCrumbs = memoize(() =>
310313
this.breadcrumbs.filter(crumb => crumb.category !== 'console')
311314
);

static/app/utils/replays/types.tsx

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ export function getFrameOpOrCategory(frame: ReplayFrame) {
8181
return val;
8282
}
8383

84+
export function isConsoleFrame(frame: BreadcrumbFrame): frame is ConsoleFrame {
85+
return frame.category === 'console';
86+
}
87+
8488
export function isDeadClick(frame: SlowClickFrame) {
8589
return frame.data.endReason === 'timeout';
8690
}
@@ -163,17 +167,20 @@ export type MutationFrame = HydratedBreadcrumb<'replay.mutations'>;
163167
export type NavFrame = HydratedBreadcrumb<'navigation'>;
164168
export type SlowClickFrame = HydratedBreadcrumb<'ui.slowClickDetected'>;
165169

166-
// This list should match each of the categories used in `HydratedBreadcrumb` above.
170+
// This list must match each of the categories used in `HydratedBreadcrumb` above
171+
// and any app-specific types that we hydrate (ie: replay.init).
167172
export const BreadcrumbCategories = [
168173
'console',
169-
'ui.click',
170-
'ui.input',
174+
'navigation',
175+
'replay.init',
171176
'replay.mutations',
172-
'ui.keyDown',
173177
'ui.blur',
178+
'ui.click',
174179
'ui.focus',
175-
'ui.slowClickDetected',
180+
'ui.input',
181+
'ui.keyDown',
176182
'ui.multiClick',
183+
'ui.slowClickDetected',
177184
];
178185

179186
// Spans
@@ -195,23 +202,26 @@ export type ResourceFrame = HydratedSpan<
195202
| 'resource.script'
196203
>;
197204

198-
// This list should match each of the operations used in `HydratedSpan` above.
205+
// This list should match each of the operations used in `HydratedSpan` above
206+
// And any app-specific types that we hydrate (ie: replay.start & replay.end).
199207
export const SpanOps = [
200-
'navigation.push',
201208
'largest-contentful-paint',
202209
'memory',
210+
'navigation.back_forward',
203211
'navigation.navigate',
212+
'navigation.push',
204213
'navigation.reload',
205-
'navigation.back_forward',
206214
'paint',
207-
'resource.fetch',
208-
'resource.xhr',
215+
'replay.end',
216+
'replay.start',
209217
'resource.css',
218+
'resource.fetch',
210219
'resource.iframe',
211220
'resource.img',
212221
'resource.link',
213222
'resource.other',
214223
'resource.script',
224+
'resource.xhr',
215225
];
216226

217227
/**

static/app/views/replays/detail/console/consoleFilters.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import {CompactSelect} from 'sentry/components/compactSelect';
22
import SearchBar from 'sentry/components/searchBar';
33
import {t} from 'sentry/locale';
4-
import type {Crumb} from 'sentry/types/breadcrumbs';
54
import useConsoleFilters from 'sentry/views/replays/detail/console/useConsoleFilters';
65
import FiltersGrid from 'sentry/views/replays/detail/filtersGrid';
76

87
type Props = {
9-
breadcrumbs: undefined | Crumb[];
8+
frames: undefined | unknown[];
109
} & ReturnType<typeof useConsoleFilters>;
1110

1211
function Filters({
13-
breadcrumbs,
12+
frames,
1413
getLogLevels,
1514
logLevel,
1615
searchTerm,
@@ -35,7 +34,7 @@ function Filters({
3534
placeholder={t('Search Console Logs')}
3635
size="sm"
3736
query={searchTerm}
38-
disabled={!breadcrumbs || !breadcrumbs.length}
37+
disabled={!frames || !frames.length}
3938
/>
4039
</FiltersGrid>
4140
);

static/app/views/replays/detail/console/consoleLogRow.tsx

Lines changed: 49 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,22 @@
1-
import {CSSProperties, memo, useCallback, useMemo} from 'react';
1+
import {CSSProperties, memo, useCallback} from 'react';
22
import styled from '@emotion/styled';
33
import classNames from 'classnames';
44

55
import ErrorBoundary from 'sentry/components/errorBoundary';
6-
import {relativeTimeInMs} from 'sentry/components/replays/utils';
7-
import {IconFire, IconWarning} from 'sentry/icons';
6+
import {Tooltip} from 'sentry/components/tooltip';
7+
import {IconClose, IconInfo, IconWarning} from 'sentry/icons';
88
import {space} from 'sentry/styles/space';
9-
import type {BreadcrumbTypeDefault, Crumb} from 'sentry/types/breadcrumbs';
109
import {BreadcrumbLevelType} from 'sentry/types/breadcrumbs';
1110
import useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers';
11+
import type {BreadcrumbFrame, ConsoleFrame} from 'sentry/utils/replays/types';
1212
import MessageFormatter from 'sentry/views/replays/detail/console/messageFormatter';
13-
import {breadcrumbHasIssue} from 'sentry/views/replays/detail/console/utils';
14-
import ViewIssueLink from 'sentry/views/replays/detail/console/viewIssueLink';
1513
import TimestampButton from 'sentry/views/replays/detail/timestampButton';
16-
17-
import {OnDimensionChange} from '../useVirtualizedInspector';
14+
import {OnDimensionChange} from 'sentry/views/replays/detail/useVirtualizedInspector';
1815

1916
type Props = {
20-
breadcrumb: Extract<Crumb, BreadcrumbTypeDefault>;
2117
currentHoverTime: number | undefined;
2218
currentTime: number;
19+
frame: BreadcrumbFrame;
2320
index: number;
2421
startTimestampMs: number;
2522
style: CSSProperties;
@@ -29,7 +26,7 @@ type Props = {
2926

3027
function UnmemoizedConsoleLogRow({
3128
index,
32-
breadcrumb,
29+
frame,
3330
currentTime,
3431
currentHoverTime,
3532
startTimestampMs,
@@ -40,30 +37,24 @@ function UnmemoizedConsoleLogRow({
4037
const {handleMouseEnter, handleMouseLeave, handleClick} =
4138
useCrumbHandlers(startTimestampMs);
4239

43-
const onClickTimestamp = useCallback(
44-
() => handleClick(breadcrumb),
45-
[handleClick, breadcrumb]
46-
);
40+
const onClickTimestamp = useCallback(() => handleClick(frame), [handleClick, frame]);
4741
const onMouseEnter = useCallback(
48-
() => handleMouseEnter(breadcrumb),
49-
[handleMouseEnter, breadcrumb]
42+
() => handleMouseEnter(frame),
43+
[handleMouseEnter, frame]
5044
);
5145
const onMouseLeave = useCallback(
52-
() => handleMouseLeave(breadcrumb),
53-
[handleMouseLeave, breadcrumb]
46+
() => handleMouseLeave(frame),
47+
[handleMouseLeave, frame]
5448
);
5549
const handleDimensionChange = useCallback(
5650
(path, expandedState, e) =>
5751
onDimensionChange && onDimensionChange(index, path, expandedState, e),
5852
[onDimensionChange, index]
5953
);
6054

61-
const crumbTime = useMemo(
62-
() => relativeTimeInMs(breadcrumb.timestamp || 0, startTimestampMs),
63-
[breadcrumb.timestamp, startTimestampMs]
64-
);
65-
const hasOccurred = currentTime >= crumbTime;
66-
const isBeforeHover = currentHoverTime === undefined || currentHoverTime >= crumbTime;
55+
const hasOccurred = currentTime >= frame.offsetMs;
56+
const isBeforeHover =
57+
currentHoverTime === undefined || currentHoverTime >= frame.offsetMs;
6758

6859
return (
6960
<ConsoleLog
@@ -74,61 +65,53 @@ function UnmemoizedConsoleLogRow({
7465
afterHoverTime: currentHoverTime !== undefined && !isBeforeHover,
7566
})}
7667
hasOccurred={hasOccurred}
77-
level={breadcrumb.level}
68+
level={(frame as ConsoleFrame).level}
7869
onMouseEnter={onMouseEnter}
7970
onMouseLeave={onMouseLeave}
8071
style={style}
8172
>
82-
<Icon level={breadcrumb.level} />
73+
<ConsoleLevelIcon level={(frame as ConsoleFrame).level} />
8374
<Message>
84-
{breadcrumbHasIssue(breadcrumb) ? (
85-
<IssueLinkWrapper>
86-
<ViewIssueLink breadcrumb={breadcrumb} />
87-
</IssueLinkWrapper>
88-
) : null}
8975
<ErrorBoundary mini>
9076
<MessageFormatter
9177
expandPaths={expandPaths}
92-
breadcrumb={breadcrumb}
78+
frame={frame}
9379
onExpand={handleDimensionChange}
9480
/>
9581
</ErrorBoundary>
9682
</Message>
9783
<TimestampButton
9884
onClick={onClickTimestamp}
9985
startTimestampMs={startTimestampMs}
100-
timestampMs={breadcrumb.timestamp || ''}
86+
timestampMs={frame.timestampMs}
10187
/>
10288
</ConsoleLog>
10389
);
10490
}
10591

106-
const IssueLinkWrapper = styled('div')`
107-
float: right;
108-
`;
109-
11092
const ConsoleLog = styled('div')<{
11193
hasOccurred: boolean;
112-
level: string;
94+
level: undefined | string;
11395
}>`
11496
display: grid;
11597
grid-template-columns: 12px 1fr max-content;
11698
gap: ${space(0.75)};
99+
align-items: baseline;
117100
padding: ${space(0.5)} ${space(1)};
118101
font-size: ${p => p.theme.fontSizeSmall};
119102
120103
background-color: ${p =>
121-
['warning', 'error'].includes(p.level)
122-
? p.theme.alert[p.level].backgroundLight
104+
['warning', 'error'].includes(String(p.level))
105+
? p.theme.alert[String(p.level)].backgroundLight
123106
: 'inherit'};
124107
125108
/* Overridden in TabItemContainer, depending on *CurrentTime and *HoverTime classes */
126109
border-top: 1px solid transparent;
127110
border-bottom: 1px solid transparent;
128111
129112
color: ${p =>
130-
['warning', 'error'].includes(p.level)
131-
? p.theme.alert[p.level].iconColor
113+
['warning', 'error'].includes(String(p.level))
114+
? p.theme.alert[String(p.level)].iconColor
132115
: p.hasOccurred
133116
? 'inherit'
134117
: p.theme.gray300};
@@ -144,18 +127,35 @@ const ConsoleLog = styled('div')<{
144127
`;
145128

146129
const ICONS = {
147-
[BreadcrumbLevelType.ERROR]: <IconFire size="xs" />,
148-
[BreadcrumbLevelType.WARNING]: <IconWarning size="xs" />,
130+
[BreadcrumbLevelType.ERROR]: (
131+
<Tooltip title={BreadcrumbLevelType.ERROR}>
132+
<IconClose size="xs" isCircled />
133+
</Tooltip>
134+
),
135+
[BreadcrumbLevelType.WARNING]: (
136+
<Tooltip title={BreadcrumbLevelType.WARNING}>
137+
<IconWarning size="xs" />
138+
</Tooltip>
139+
),
140+
[BreadcrumbLevelType.INFO]: (
141+
<Tooltip title={BreadcrumbLevelType.INFO}>
142+
<IconInfo size="xs" />
143+
</Tooltip>
144+
),
149145
};
150146

151-
const Icon = styled(
152-
({level, className}: {level: BreadcrumbLevelType; className?: string}) => (
153-
<span className={className}>{ICONS[level]}</span>
154-
)
155-
)`
147+
const MediumFontSize = styled('span')`
156148
font-size: ${p => p.theme.fontSizeMedium};
157149
`;
158150

151+
function ConsoleLevelIcon({level}: {level: string | undefined}) {
152+
return level && level in ICONS ? (
153+
<MediumFontSize>{ICONS[level]}</MediumFontSize>
154+
) : (
155+
<i />
156+
);
157+
}
158+
159159
const Message = styled('div')`
160160
font-family: ${p => p.theme.text.familyMono};
161161

static/app/views/replays/detail/console/index.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
import Placeholder from 'sentry/components/placeholder';
1010
import {useReplayContext} from 'sentry/components/replays/replayContext';
1111
import {t} from 'sentry/locale';
12-
import type {Crumb} from 'sentry/types/breadcrumbs';
12+
import type {BreadcrumbFrame} from 'sentry/utils/replays/types';
1313
import ConsoleFilters from 'sentry/views/replays/detail/console/consoleFilters';
1414
import ConsoleLogRow from 'sentry/views/replays/detail/console/consoleLogRow';
1515
import useConsoleFilters from 'sentry/views/replays/detail/console/useConsoleFilters';
@@ -21,7 +21,7 @@ import useVirtualizedList from 'sentry/views/replays/detail/useVirtualizedList';
2121
import useVirtualizedInspector from '../useVirtualizedInspector';
2222

2323
interface Props {
24-
breadcrumbs: undefined | Crumb[];
24+
frames: undefined | BreadcrumbFrame[];
2525
startTimestampMs: number;
2626
}
2727

@@ -32,8 +32,8 @@ const cellMeasurer = {
3232
minHeight: 24,
3333
};
3434

35-
function Console({breadcrumbs, startTimestampMs}: Props) {
36-
const filterProps = useConsoleFilters({breadcrumbs: breadcrumbs || []});
35+
function Console({frames, startTimestampMs}: Props) {
36+
const filterProps = useConsoleFilters({frames: frames || []});
3737
const {expandPathsRef, searchTerm, logLevel, items, setSearchTerm} = filterProps;
3838
const clearSearchTerm = () => setSearchTerm('');
3939
const {currentTime, currentHoverTime} = useReplayContext();
@@ -67,7 +67,7 @@ function Console({breadcrumbs, startTimestampMs}: Props) {
6767
rowIndex={index}
6868
>
6969
<ConsoleLogRow
70-
breadcrumb={item}
70+
frame={item}
7171
currentTime={currentTime}
7272
currentHoverTime={currentHoverTime}
7373
expandPaths={Array.from(expandPathsRef.current?.get(index) || [])}
@@ -82,17 +82,17 @@ function Console({breadcrumbs, startTimestampMs}: Props) {
8282

8383
return (
8484
<FluidHeight>
85-
<ConsoleFilters breadcrumbs={breadcrumbs} {...filterProps} />
85+
<ConsoleFilters frames={frames} {...filterProps} />
8686
<TabItemContainer>
87-
{breadcrumbs ? (
87+
{frames ? (
8888
<AutoSizer onResize={updateList}>
8989
{({width, height}) => (
9090
<ReactVirtualizedList
9191
deferredMeasurementCache={cache}
9292
height={height}
9393
noRowsRenderer={() => (
9494
<NoRowRenderer
95-
unfilteredItems={breadcrumbs}
95+
unfilteredItems={frames}
9696
clearSearchTerm={clearSearchTerm}
9797
>
9898
{t('No console logs recorded')}

0 commit comments

Comments
 (0)