Skip to content

Commit 44ba15c

Browse files
billyvgcursoragentmichellewzhang
authored
feat(replay): Show CTA when org is not opted into gen ai features (#95780)
There are a few flags at work here: - `replay-ai-summaries` is our rollout feature flag - `gen-ai-features` is config based feature flag to show AI features - `organization.hideAiFeatures` is a user/org opt-in to gen AI features (similar to above flag) - and finally there is the org opt-in/acknowledgement to Seer features The AI Summary tab is now visible if only the rollout flag `replay-ai-summaries` and the gen AI flags are enabled . If organization can see gen AI features but has *not* opted into Seer, the AI Summary panel will display a CTA guiding users to their organization settings to enable Seer (or get info on how to enable it). <img width="627" height="200" alt="image" src="https://github.com/user-attachments/assets/a34cab3a-a15e-4718-82a8-f6ddde2b9deb" /> Closes REPLAY-526 --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Michelle Zhang <[email protected]>
1 parent 1fa73e6 commit 44ba15c

File tree

2 files changed

+85
-45
lines changed

2 files changed

+85
-45
lines changed

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

Lines changed: 77 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import styled from '@emotion/styled';
33
import {Alert} from 'sentry/components/core/alert';
44
import {Badge} from 'sentry/components/core/badge';
55
import {Button} from 'sentry/components/core/button';
6+
import {LinkButton} from 'sentry/components/core/button/linkButton';
67
import {Flex} from 'sentry/components/core/layout';
8+
import {useOrganizationSeerSetup} from 'sentry/components/events/autofix/useOrganizationSeerSetup';
79
import LoadingIndicator from 'sentry/components/loadingIndicator';
810
import {IconSeer, IconSync, IconThumb} from 'sentry/icons';
911
import {t} from 'sentry/locale';
@@ -23,6 +25,7 @@ export default function Ai() {
2325
const replay = useReplayReader();
2426
const replayRecord = replay?.getReplay();
2527
const project = useProjectFromId({project_id: replayRecord?.project_id});
28+
const {areAiFeaturesAllowed, setupAcknowledgement} = useOrganizationSeerSetup();
2629
const {
2730
data: summaryData,
2831
isPending,
@@ -35,58 +38,56 @@ export default function Ai() {
3538
replayRecord?.id &&
3639
project?.slug &&
3740
organization.features.includes('replay-ai-summaries') &&
38-
organization.features.includes('gen-ai-features')
41+
areAiFeaturesAllowed &&
42+
setupAcknowledgement.orgHasAcknowledged
3943
),
4044
retry: false,
4145
});
4246

43-
const openForm = useFeedbackForm();
44-
45-
const feedbackButton = ({type}: {type: 'positive' | 'negative'}) => {
46-
return openForm ? (
47-
<Button
48-
aria-label={t('Give feedback on the AI summary section')}
49-
icon={<IconThumb direction={type === 'positive' ? 'up' : 'down'} />}
50-
title={type === 'positive' ? t('I like this') : t(`I don't like this`)}
51-
size={'xs'}
52-
onClick={() =>
53-
openForm({
54-
messagePlaceholder:
55-
type === 'positive'
56-
? t('What did you like about the AI summary and chapters?')
57-
: t('How can we make the AI summary and chapters work better for you?'),
58-
tags: {
59-
['feedback.source']: 'replay_ai_summary',
60-
['feedback.owner']: 'replay',
61-
['feedback.type']: type,
62-
},
63-
})
64-
}
65-
/>
66-
) : null;
67-
};
68-
69-
if (
70-
!organization.features.includes('replay-ai-summaries') ||
71-
!organization.features.includes('gen-ai-features')
72-
) {
47+
if (!organization.features.includes('replay-ai-summaries') || !areAiFeaturesAllowed) {
7348
return (
7449
<Wrapper data-test-id="replay-details-ai-summary-tab">
7550
<EmptySummaryContainer>
76-
<Alert type="info" showIcon={false}>
77-
{t('Replay AI summary is not available for this organization.')}
51+
<Alert type="warning">
52+
{t('AI features are not available for this organization.')}
7853
</Alert>
7954
</EmptySummaryContainer>
8055
</Wrapper>
8156
);
8257
}
8358

59+
// If our `replay-ai-summaries` ff is enabled and the org has gen AI ff enabled,
60+
// but the org hasn't acknowledged the gen AI features, then show CTA.
61+
if (!setupAcknowledgement.orgHasAcknowledged) {
62+
return (
63+
<Wrapper data-test-id="replay-details-ai-summary-tab">
64+
<EmptySummaryContainer>
65+
<CallToActionContainer>
66+
<div>
67+
<strong>{t('AI-Powered Replay Summaries')}</strong>
68+
</div>
69+
<div>
70+
{t(
71+
'Seer access is required to use replay summaries. Please view the Seer settings page for more information.'
72+
)}
73+
</div>
74+
<div>
75+
<LinkButton size="sm" priority="primary" to="/settings/seer/">
76+
{t('View Seer Settings')}
77+
</LinkButton>
78+
</div>
79+
</CallToActionContainer>
80+
</EmptySummaryContainer>
81+
</Wrapper>
82+
);
83+
}
84+
8485
if (replayRecord?.project_id && !project) {
8586
return (
8687
<Wrapper data-test-id="replay-details-ai-summary-tab">
8788
<EmptySummaryContainer>
88-
<Alert type="error" showIcon={false}>
89-
{t('Project not found. Unable to load AI summary.')}
89+
<Alert type="error">
90+
{t('Project not found. Unable to load replay summary.')}
9091
</Alert>
9192
</EmptySummaryContainer>
9293
</Wrapper>
@@ -107,9 +108,7 @@ export default function Ai() {
107108
return (
108109
<Wrapper data-test-id="replay-details-ai-summary-tab">
109110
<EmptySummaryContainer>
110-
<Alert type="error" showIcon={false}>
111-
{t('Failed to load AI summary')}
112-
</Alert>
111+
<Alert type="error">{t('Failed to load replay summary')}</Alert>
113112
</EmptySummaryContainer>
114113
</Wrapper>
115114
);
@@ -142,8 +141,8 @@ export default function Ai() {
142141
</SummaryLeft>
143142
<SummaryRight>
144143
<Flex gap={space(0.5)}>
145-
{feedbackButton({type: 'positive'})}
146-
{feedbackButton({type: 'negative'})}
144+
<FeedbackButton type="positive" />
145+
<FeedbackButton type="negative" />
147146
</Flex>
148147
<Button
149148
priority="default"
@@ -170,6 +169,35 @@ export default function Ai() {
170169
);
171170
}
172171

172+
function FeedbackButton({type}: {type: 'positive' | 'negative'}) {
173+
const openForm = useFeedbackForm();
174+
if (!openForm) {
175+
return null;
176+
}
177+
178+
return (
179+
<Button
180+
aria-label={t('Give feedback on the replay summary section')}
181+
icon={<IconThumb direction={type === 'positive' ? 'up' : 'down'} />}
182+
title={type === 'positive' ? t('I like this') : t(`I don't like this`)}
183+
size={'xs'}
184+
onClick={() =>
185+
openForm({
186+
messagePlaceholder:
187+
type === 'positive'
188+
? t('What did you like about the replay summary and chapters?')
189+
: t('How can we make the replay summary and chapters work better for you?'),
190+
tags: {
191+
['feedback.source']: 'replay_ai_summary',
192+
['feedback.owner']: 'replay',
193+
['feedback.type']: type,
194+
},
195+
})
196+
}
197+
/>
198+
);
199+
}
200+
173201
const Wrapper = styled('div')`
174202
display: flex;
175203
flex-direction: column;
@@ -248,3 +276,12 @@ const OverflowBody = styled('section')`
248276
flex: 1 1 auto;
249277
overflow: auto;
250278
`;
279+
280+
const CallToActionContainer = styled('div')`
281+
display: flex;
282+
flex-direction: column;
283+
gap: ${space(2)};
284+
padding: ${space(2)};
285+
align-items: center;
286+
text-align: center;
287+
`;

static/app/views/replays/detail/layout/focusTabs.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import styled from '@emotion/styled';
55
import {Flex} from 'sentry/components/core/layout';
66
import {TabList, Tabs} from 'sentry/components/core/tabs';
77
import {Tooltip} from 'sentry/components/core/tooltip';
8+
import {useOrganizationSeerSetup} from 'sentry/components/events/autofix/useOrganizationSeerSetup';
89
import {IconLab} from 'sentry/icons/iconLab';
910
import {t} from 'sentry/locale';
1011
import {space} from 'sentry/styles/space';
@@ -16,15 +17,16 @@ import useOrganization from 'sentry/utils/useOrganization';
1617
function getReplayTabs({
1718
isVideoReplay,
1819
organization,
20+
areAiFeaturesAllowed,
1921
}: {
22+
areAiFeaturesAllowed: boolean;
2023
isVideoReplay: boolean;
2124
organization: Organization;
2225
}): Record<TabKey, ReactNode> {
2326
// For video replays, we hide the memory tab (not applicable for mobile)
2427
return {
2528
[TabKey.AI]:
26-
organization.features.includes('replay-ai-summaries') &&
27-
organization.features.includes('gen-ai-features') ? (
29+
organization.features.includes('replay-ai-summaries') && areAiFeaturesAllowed ? (
2830
<Flex align="center" gap={space(0.75)}>
2931
{t('Summary')}
3032
<Tooltip
@@ -52,12 +54,13 @@ type Props = {
5254

5355
export default function FocusTabs({isVideoReplay}: Props) {
5456
const organization = useOrganization();
57+
const {areAiFeaturesAllowed} = useOrganizationSeerSetup();
5558
const {getActiveTab, setActiveTab} = useActiveReplayTab({isVideoReplay});
5659
const activeTab = getActiveTab();
5760

58-
const tabs = Object.entries(getReplayTabs({isVideoReplay, organization})).filter(
59-
([_, v]) => v !== null
60-
);
61+
const tabs = Object.entries(
62+
getReplayTabs({isVideoReplay, organization, areAiFeaturesAllowed})
63+
).filter(([_, v]) => v !== null);
6164

6265
return (
6366
<TabContainer>

0 commit comments

Comments
 (0)