diff --git a/static/app/components/replays/breadcrumbs/breadcrumbItem.tsx b/static/app/components/replays/breadcrumbs/breadcrumbItem.tsx
index ccbff2e2dcf86..a1ad9f1214b6e 100644
--- a/static/app/components/replays/breadcrumbs/breadcrumbItem.tsx
+++ b/static/app/components/replays/breadcrumbs/breadcrumbItem.tsx
@@ -9,7 +9,6 @@ import {CodeSnippet} from 'sentry/components/codeSnippet';
import {Flex} from 'sentry/components/container/flex';
import ErrorBoundary from 'sentry/components/errorBoundary';
import Link from 'sentry/components/links/link';
-import ObjectInspector from 'sentry/components/objectInspector';
import PanelItem from 'sentry/components/panels/panelItem';
import {OpenReplayComparisonButton} from 'sentry/components/replays/breadcrumbs/openReplayComparisonButton';
import {useReplayContext} from 'sentry/components/replays/replayContext';
@@ -88,17 +87,19 @@ function BreadcrumbItem({
{description}
) : (
-
-
+ {
+ onInspectorExpanded(
+ path,
+ Object.fromEntries(expandedPaths.map(item => [item, true]))
+ );
}}
+ data={description}
+ withAnnotatedText
/>
-
+
);
}, [description, expandPaths, onInspectorExpanded]);
@@ -295,17 +296,19 @@ function WebVitalData({
}
return (
- {
- onInspectorExpanded(
- path,
- Object.fromEntries(expandedPaths.map(item => [item, true]))
- );
- }}
- data={webVitalData}
- withAnnotatedText
- />
+
+ {
+ onInspectorExpanded(
+ path,
+ Object.fromEntries(expandedPaths.map(item => [item, true]))
+ );
+ }}
+ data={webVitalData}
+ withAnnotatedText
+ />
+
);
}
@@ -376,10 +379,6 @@ const CrumbIssueWrapper = styled('div')`
color: ${p => p.theme.subText};
`;
-const InspectorWrapper = styled('div')`
- font-family: ${p => p.theme.text.familyMono};
-`;
-
const CrumbDetails = styled('div')`
display: flex;
flex-direction: column;
@@ -521,4 +520,10 @@ const SelectorButton = styled(Button)`
min-height: auto;
`;
+const Wrapper = styled('div')`
+ pre {
+ margin: 0;
+ }
+`;
+
export default memo(BreadcrumbItem);
diff --git a/static/app/utils/replays/getFrameDetails.tsx b/static/app/utils/replays/getFrameDetails.tsx
index 4cd54bc56fd93..6e9df5e34f260 100644
--- a/static/app/utils/replays/getFrameDetails.tsx
+++ b/static/app/utils/replays/getFrameDetails.tsx
@@ -430,9 +430,9 @@ const MAPPER_FOR_FRAME: Record Details> = {
const MAPPER_DEFAULT = (frame): Details => ({
color: 'gray300',
- description: frame.message ?? '',
- tabKey: TabKey.CONSOLE,
- title: defaultTitle(frame),
+ description: frame.message ?? frame.data ?? '',
+ tabKey: TabKey.BREADCRUMBS,
+ title: toTitleCase(defaultTitle(frame)),
icon: ,
});
diff --git a/static/app/utils/replays/replayReader.spec.tsx b/static/app/utils/replays/replayReader.spec.tsx
index e1f0cc75640c8..9a61ea252ad5a 100644
--- a/static/app/utils/replays/replayReader.spec.tsx
+++ b/static/app/utils/replays/replayReader.spec.tsx
@@ -167,10 +167,11 @@ describe('ReplayReader', () => {
},
{
method: 'getConsoleFrames',
- expected: [
- expect.objectContaining({category: 'console'}),
- expect.objectContaining({category: 'redux.action'}),
- ],
+ expected: [expect.objectContaining({category: 'console'})],
+ },
+ {
+ method: 'getCustomFrames',
+ expected: [expect.objectContaining({category: 'redux.action'})],
},
{
method: 'getNetworkFrames',
@@ -196,6 +197,7 @@ describe('ReplayReader', () => {
expected: [
expect.objectContaining({category: 'replay.init'}),
expect.objectContaining({category: 'ui.slowClickDetected'}),
+ expect.objectContaining({category: 'redux.action'}),
expect.objectContaining({op: 'navigation.navigate'}), // prefer the nav span over the breadcrumb
expect.objectContaining({category: 'ui.click'}),
expect.objectContaining({category: 'ui.click'}),
diff --git a/static/app/utils/replays/replayReader.tsx b/static/app/utils/replays/replayReader.tsx
index 16c2bf8bef8f0..2a6d6f6d3ad6f 100644
--- a/static/app/utils/replays/replayReader.tsx
+++ b/static/app/utils/replays/replayReader.tsx
@@ -538,10 +538,7 @@ export default class ReplayReader {
getErrorFrames = () => this._errors;
getConsoleFrames = memoize(() =>
- this._sortedBreadcrumbFrames.filter(
- frame =>
- frame.category === 'console' || !BreadcrumbCategories.includes(frame.category)
- )
+ this._sortedBreadcrumbFrames.filter(frame => frame.category === 'console')
);
getNavigationFrames = memoize(() =>
@@ -589,11 +586,18 @@ export default class ReplayReader {
this._sortedSpanFrames.filter((frame): frame is MemoryFrame => frame.op === 'memory')
);
+ getCustomFrames = memoize(() =>
+ this._sortedBreadcrumbFrames.filter(
+ frame => !BreadcrumbCategories.includes(frame.category)
+ )
+ );
+
getChapterFrames = memoize(() =>
this._trimFramesToClipWindow(
[
...this.getPerfFrames(),
...this.getWebVitalFrames(),
+ ...this.getCustomFrames(),
...this._sortedBreadcrumbFrames.filter(frame =>
[
'replay.hydrate-error',
diff --git a/static/app/utils/replays/types.tsx b/static/app/utils/replays/types.tsx
index cd7c710fdad96..2c004272f2bd8 100644
--- a/static/app/utils/replays/types.tsx
+++ b/static/app/utils/replays/types.tsx
@@ -350,6 +350,7 @@ export const BreadcrumbCategories = [
'device.battery',
'device.connectivity',
'device.orientation',
+ 'feedback',
'navigation',
'replay.init',
'replay.mutations',
diff --git a/static/app/views/replays/detail/breadcrumbs/useBreadcrumbFilters.tsx b/static/app/views/replays/detail/breadcrumbs/useBreadcrumbFilters.tsx
index 7e90013f0343b..9ead461ded05e 100644
--- a/static/app/views/replays/detail/breadcrumbs/useBreadcrumbFilters.tsx
+++ b/static/app/views/replays/detail/breadcrumbs/useBreadcrumbFilters.tsx
@@ -52,6 +52,7 @@ const TYPE_TO_LABEL: Record = {
tap: 'User Tap',
device: 'Device',
app: 'App',
+ custom: 'Custom',
};
const OPORCATEGORY_TO_TYPE: Record = {
@@ -114,6 +115,13 @@ function useBreadcrumbFilters({frames}: Options): Return {
const type = useMemo(() => decodeList(query.f_b_type), [query.f_b_type]);
const searchTerm = decodeScalar(query.f_b_search, '').toLowerCase();
+ // add custom breadcrumbs to filter
+ frames.forEach(frame => {
+ if (!(getFrameOpOrCategory(frame) in OPORCATEGORY_TO_TYPE)) {
+ OPORCATEGORY_TO_TYPE[getFrameOpOrCategory(frame)] = 'custom';
+ }
+ });
+
const items = useMemo(() => {
// flips OPORCATERGORY_TO_TYPE and prevents overwriting nav entry, nav entry becomes nav: ['navigation','navigation.push']
const TYPE_TO_OPORCATEGORY = Object.entries(OPORCATEGORY_TO_TYPE).reduce(