Skip to content

Commit cefe0e4

Browse files
authored
ref(replays): setup AnalyticsArea context and track timestamp button clicks (#95760)
[REPLAY-522: Add amplitude tracking](https://linear.app/getsentry/issue/REPLAY-522/add-amplitude-tracking)
1 parent 5151349 commit cefe0e4

File tree

9 files changed

+174
-100
lines changed

9 files changed

+174
-100
lines changed

static/app/components/replays/replayContext.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type HighlightCallbacks = ReturnType<typeof useReplayHighlighting>;
3030
// Instead only expose methods that wrap `Replayer` and manage state.
3131
interface ReplayPlayerContextProps extends HighlightCallbacks {
3232
/**
33+
* DEPRECATED - use `useAnalyticsArea` instead.
3334
* The context in which the replay is being viewed.
3435
*/
3536
analyticsContext: string;
@@ -143,6 +144,7 @@ const ReplayPlayerContext = createContext<ReplayPlayerContextProps>({
143144

144145
type Props = {
145146
/**
147+
* DEPRECATED - use `useAnalyticsArea` instead.
146148
* The context in which the replay is being viewed.
147149
* Attached to certain analytics events.
148150
*/

static/app/utils/analytics/replayAnalyticsEvents.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,24 @@ export type ReplayEventParameters = {
5555
seconds: number;
5656
user_email: string;
5757
};
58+
'replay.details-timestamp-button-clicked': {
59+
area: string;
60+
};
61+
5862
'replay.frame-after-background': {
5963
frame: string;
6064
};
61-
6265
'replay.gaps_detected': {
6366
gaps: number;
6467
max_gap: number;
6568
replay_duration: number;
6669
};
6770
'replay.hydration-error.issue-details-opened': Record<string, unknown>;
6871
'replay.hydration-modal.slider-interaction': Record<string, unknown>;
72+
6973
'replay.hydration-modal.tab-change': {
7074
tabKey: string;
7175
};
72-
7376
// similar purpose as "replay.details-viewed", however we're capturing the navigation action
7477
// in order to also include a project platform
7578
'replay.list-navigate-to-details': {
@@ -138,6 +141,7 @@ export const replayEventMap: Record<ReplayEventKey, string | null> = {
138141
'replay.details-resource-docs-clicked': 'Replay Details Resource Docs Clicked',
139142
'replay.details-tab-changed': 'Changed Replay Details Tab',
140143
'replay.details-time-spent': 'Time Spent Viewing Replay Details',
144+
'replay.details-timestamp-button-clicked': 'Clicked Timestamp in Replay Details',
141145
'replay.frame-after-background': 'Replay Frame Following Background Frame',
142146
'replay.hydration-error.issue-details-opened': 'Hydration Issue Details Opened',
143147
'replay.hydration-modal.slider-interaction': 'Hydration Modal Slider Clicked',

static/app/views/feedback/feedbackListPage.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {Fragment} from 'react';
22
import styled from '@emotion/styled';
33

4+
import AnalyticsArea from 'sentry/components/analyticsArea';
45
import ErrorBoundary from 'sentry/components/errorBoundary';
56
import FeedbackFilters from 'sentry/components/feedback/feedbackFilters';
67
import FeedbackItemLoader from 'sentry/components/feedback/feedbackItem/feedbackItemLoader';
@@ -95,7 +96,9 @@ export default function FeedbackListPage() {
9596
<FeedbackSearch />
9697
</SearchContainer>
9798
<Container style={{gridArea: 'details'}}>
98-
<FeedbackItemLoader />
99+
<AnalyticsArea name="details">
100+
<FeedbackItemLoader />
101+
</AnalyticsArea>
99102
</Container>
100103
</Fragment>
101104
) : (

static/app/views/replays/deadRageClick/deadRageClickList.tsx

Lines changed: 55 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import styled from '@emotion/styled';
22

3+
import AnalyticsArea from 'sentry/components/analyticsArea';
34
import * as Layout from 'sentry/components/layouts/thirds';
45
import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
56
import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
@@ -32,57 +33,61 @@ export default function DeadRageClickList() {
3233
});
3334

3435
return (
35-
<SentryDocumentTitle
36-
title={t('Top Selectors with Dead Clicks')}
37-
orgSlug={organization.slug}
38-
>
39-
<Layout.Header>
40-
<Layout.HeaderContent>
41-
<Layout.Title>
42-
{t('Top Selectors with Dead and Rage Clicks')}
43-
<PageHeadingQuestionTooltip
44-
title={t('See the top selectors your users have dead and rage clicked on.')}
45-
docsUrl="https://docs.sentry.io/product/session-replay/replay-page-and-filters/"
46-
/>
47-
</Layout.Title>
48-
</Layout.HeaderContent>
49-
<div /> {/* wraps the tabs below the page title */}
50-
<ReplayTabs selected="selectors" />
51-
</Layout.Header>
52-
<PageFiltersContainer>
53-
<Layout.Body>
54-
<Layout.Main fullWidth>
55-
<PageFilterBar condensed>
56-
<ProjectPageFilter resetParamsOnChange={['cursor']} />
57-
<EnvironmentPageFilter resetParamsOnChange={['cursor']} />
58-
<DatePageFilter resetParamsOnChange={['cursor']} />
59-
</PageFilterBar>
60-
<LayoutGap>
61-
<SelectorTable
62-
data={data}
63-
isError={isError}
64-
isLoading={isLoading}
65-
location={location}
66-
clickCountColumns={[
67-
{key: 'count_dead_clicks', name: 'dead clicks'},
68-
{key: 'count_rage_clicks', name: 'rage clicks'},
69-
]}
70-
clickCountSortable
36+
<AnalyticsArea name="selectors">
37+
<SentryDocumentTitle
38+
title={t('Top Selectors with Dead Clicks')}
39+
orgSlug={organization.slug}
40+
>
41+
<Layout.Header>
42+
<Layout.HeaderContent>
43+
<Layout.Title>
44+
{t('Top Selectors with Dead and Rage Clicks')}
45+
<PageHeadingQuestionTooltip
46+
title={t(
47+
'See the top selectors your users have dead and rage clicked on.'
48+
)}
49+
docsUrl="https://docs.sentry.io/product/session-replay/replay-page-and-filters/"
7150
/>
72-
</LayoutGap>
73-
<PaginationNoMargin
74-
pageLinks={pageLinks}
75-
onCursor={(cursor, path, searchQuery) => {
76-
navigate({
77-
pathname: path,
78-
query: {...searchQuery, cursor},
79-
});
80-
}}
81-
/>
82-
</Layout.Main>
83-
</Layout.Body>
84-
</PageFiltersContainer>
85-
</SentryDocumentTitle>
51+
</Layout.Title>
52+
</Layout.HeaderContent>
53+
<div /> {/* wraps the tabs below the page title */}
54+
<ReplayTabs selected="selectors" />
55+
</Layout.Header>
56+
<PageFiltersContainer>
57+
<Layout.Body>
58+
<Layout.Main fullWidth>
59+
<PageFilterBar condensed>
60+
<ProjectPageFilter resetParamsOnChange={['cursor']} />
61+
<EnvironmentPageFilter resetParamsOnChange={['cursor']} />
62+
<DatePageFilter resetParamsOnChange={['cursor']} />
63+
</PageFilterBar>
64+
<LayoutGap>
65+
<SelectorTable
66+
data={data}
67+
isError={isError}
68+
isLoading={isLoading}
69+
location={location}
70+
clickCountColumns={[
71+
{key: 'count_dead_clicks', name: 'dead clicks'},
72+
{key: 'count_rage_clicks', name: 'rage clicks'},
73+
]}
74+
clickCountSortable
75+
/>
76+
</LayoutGap>
77+
<PaginationNoMargin
78+
pageLinks={pageLinks}
79+
onCursor={(cursor, path, searchQuery) => {
80+
navigate({
81+
pathname: path,
82+
query: {...searchQuery, cursor},
83+
});
84+
}}
85+
/>
86+
</Layout.Main>
87+
</Layout.Body>
88+
</PageFiltersContainer>
89+
</SentryDocumentTitle>
90+
</AnalyticsArea>
8691
);
8792
}
8893

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import AnalyticsArea from 'sentry/components/analyticsArea';
12
import useActiveReplayTab, {TabKey} from 'sentry/utils/replays/hooks/useActiveReplayTab';
23
import Ai from 'sentry/views/replays/detail/ai/ai';
34
import Breadcrumbs from 'sentry/views/replays/detail/breadcrumbs';
@@ -13,22 +14,54 @@ export default function FocusArea({isVideoReplay}: {isVideoReplay?: boolean}) {
1314

1415
switch (getActiveTab()) {
1516
case TabKey.AI:
16-
return <Ai />;
17+
return (
18+
<AnalyticsArea name="ai_summary_tab">
19+
<Ai />
20+
</AnalyticsArea>
21+
);
1722
case TabKey.NETWORK:
18-
return <NetworkList />;
23+
return (
24+
<AnalyticsArea name="network_tab">
25+
<NetworkList />
26+
</AnalyticsArea>
27+
);
1928
case TabKey.TRACE:
20-
return <TraceFeature />;
29+
return (
30+
<AnalyticsArea name="trace_tab">
31+
<TraceFeature />
32+
</AnalyticsArea>
33+
);
2134
case TabKey.ERRORS:
22-
return <ErrorList />;
35+
return (
36+
<AnalyticsArea name="errors_tab">
37+
<ErrorList />
38+
</AnalyticsArea>
39+
);
2340
case TabKey.MEMORY:
24-
return <MemoryPanel />;
41+
return (
42+
<AnalyticsArea name="memory_tab">
43+
<MemoryPanel />
44+
</AnalyticsArea>
45+
);
2546
case TabKey.CONSOLE:
26-
return <Console />;
47+
return (
48+
<AnalyticsArea name="console_tab">
49+
<Console />
50+
</AnalyticsArea>
51+
);
2752
case TabKey.TAGS:
28-
return <TagPanel />;
53+
return (
54+
<AnalyticsArea name="tags_tab">
55+
<TagPanel />
56+
</AnalyticsArea>
57+
);
2958
case TabKey.BREADCRUMBS:
3059
default: {
31-
return <Breadcrumbs />;
60+
return (
61+
<AnalyticsArea name="breadcrumbs_tab">
62+
<Breadcrumbs />
63+
</AnalyticsArea>
64+
);
3265
}
3366
}
3467
}

static/app/views/replays/detail/timestampButton.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import type {MouseEvent} from 'react';
22
import styled from '@emotion/styled';
33

4+
import {useAnalyticsArea} from 'sentry/components/analyticsArea';
45
import {Tooltip} from 'sentry/components/core/tooltip';
56
import {DateTime} from 'sentry/components/dateTime';
67
import Duration from 'sentry/components/duration/duration';
78
import ReplayTooltipTime from 'sentry/components/replays/replayTooltipTime';
89
import {IconPlay} from 'sentry/icons';
910
import {space} from 'sentry/styles/space';
11+
import {trackAnalytics} from 'sentry/utils/analytics';
1012
import {useReplayPrefs} from 'sentry/utils/replays/playback/providers/replayPreferencesContext';
13+
import useOrganization from 'sentry/utils/useOrganization';
1114

1215
type Props = {
1316
startTimestampMs: number;
@@ -26,6 +29,10 @@ export default function TimestampButton({
2629
}: Props) {
2730
const [prefs] = useReplayPrefs();
2831
const timestampType = prefs.timestampType;
32+
33+
const organization = useOrganization();
34+
const analyticsArea = useAnalyticsArea();
35+
2936
return (
3037
<Tooltip
3138
title={
@@ -40,7 +47,13 @@ export default function TimestampButton({
4047
>
4148
<StyledButton
4249
as={onClick ? 'button' : 'span'}
43-
onClick={onClick}
50+
onClick={event => {
51+
onClick?.(event);
52+
trackAnalytics('replay.details-timestamp-button-clicked', {
53+
organization,
54+
area: analyticsArea,
55+
});
56+
}}
4457
className={className}
4558
>
4659
<IconPlay size="xs" />

static/app/views/replays/details.tsx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {Fragment} from 'react';
22
import styled from '@emotion/styled';
33

4+
import AnalyticsArea from 'sentry/components/analyticsArea';
45
import FullViewport from 'sentry/components/layouts/fullViewport';
56
import * as Layout from 'sentry/components/layouts/thirds';
67
import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
@@ -70,17 +71,22 @@ export default function ReplayDetails({params: {replaySlug}}: Props) {
7071
</Fragment>
7172
);
7273
return (
73-
<SentryDocumentTitle title={title}>
74-
<FullViewport>
75-
{replay ? (
76-
<ReplayDetailsProviders replay={replay} projectSlug={readerResult.projectSlug}>
77-
{content}
78-
</ReplayDetailsProviders>
79-
) : (
80-
content
81-
)}
82-
</FullViewport>
83-
</SentryDocumentTitle>
74+
<AnalyticsArea name="details">
75+
<SentryDocumentTitle title={title}>
76+
<FullViewport>
77+
{replay ? (
78+
<ReplayDetailsProviders
79+
replay={replay}
80+
projectSlug={readerResult.projectSlug}
81+
>
82+
{content}
83+
</ReplayDetailsProviders>
84+
) : (
85+
content
86+
)}
87+
</FullViewport>
88+
</SentryDocumentTitle>
89+
</AnalyticsArea>
8490
);
8591
}
8692

static/app/views/replays/index.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import AnalyticsArea from 'sentry/components/analyticsArea';
12
import NoProjectMessage from 'sentry/components/noProjectMessage';
23
import Redirect from 'sentry/components/redirect';
34
import type {RouteComponentProps} from 'sentry/types/legacyReactRouter';
@@ -20,5 +21,9 @@ export default function ReplaysContainer({children}: Props) {
2021
return <Redirect to={redirectPath} />;
2122
}
2223

23-
return <NoProjectMessage organization={organization}>{children}</NoProjectMessage>;
24+
return (
25+
<AnalyticsArea name="replays">
26+
<NoProjectMessage organization={organization}>{children}</NoProjectMessage>
27+
</AnalyticsArea>
28+
);
2429
}

0 commit comments

Comments
 (0)