Skip to content

feat(replay): Show CTA when org is not opted into gen ai features #95780

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 21, 2025
Merged
115 changes: 78 additions & 37 deletions static/app/views/replays/detail/ai/ai.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import styled from '@emotion/styled';
import {Alert} from 'sentry/components/core/alert';
import {Badge} from 'sentry/components/core/badge';
import {Button} from 'sentry/components/core/button';
import {LinkButton} from 'sentry/components/core/button/linkButton';
import {Flex} from 'sentry/components/core/layout';
import {useOrganizationSeerSetup} from 'sentry/components/events/autofix/useOrganizationSeerSetup';
import LoadingIndicator from 'sentry/components/loadingIndicator';
import {useReplayContext} from 'sentry/components/replays/replayContext';
import {IconSeer, IconSync, IconThumb} from 'sentry/icons';
Expand All @@ -23,6 +25,7 @@ export default function Ai() {
const {replay} = useReplayContext();
const replayRecord = replay?.getReplay();
const project = useProjectFromId({project_id: replayRecord?.project_id});
const {areAiFeaturesAllowed, setupAcknowledgement} = useOrganizationSeerSetup();
const {
data: summaryData,
isPending,
Expand All @@ -35,57 +38,57 @@ export default function Ai() {
replayRecord?.id &&
project?.slug &&
organization.features.includes('replay-ai-summaries') &&
organization.features.includes('gen-ai-features')
areAiFeaturesAllowed &&
setupAcknowledgement.orgHasAcknowledged
),
retry: false,
});

const openForm = useFeedbackForm();

const feedbackButton = ({type}: {type: 'positive' | 'negative'}) => {
return openForm ? (
<Button
aria-label={t('Give feedback on the AI summary section')}
icon={<IconThumb direction={type === 'positive' ? 'up' : 'down'} />}
title={type === 'positive' ? t('I like this') : t(`I don't like this`)}
size={'xs'}
onClick={() =>
openForm({
messagePlaceholder:
type === 'positive'
? t('What did you like about the AI summary and chapters?')
: t('How can we make the AI summary and chapters work better for you?'),
tags: {
['feedback.source']: 'replay_ai_summary',
['feedback.owner']: 'replay',
['feedback.type']: type,
},
})
}
/>
) : null;
};
Comment on lines -43 to -67
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored this into a new component <FeedbackButton>


if (
!organization.features.includes('replay-ai-summaries') ||
!organization.features.includes('gen-ai-features')
) {
if (!organization.features.includes('replay-ai-summaries') || !areAiFeaturesAllowed) {
return (
<Wrapper data-test-id="replay-details-ai-summary-tab">
<EmptySummaryContainer>
<Alert type="info">
{t('Replay AI summary is not available for this organization.')}
<Alert type="warning">
{t('AI features are not available for this organization.')}
</Alert>
</EmptySummaryContainer>
</Wrapper>
);
}

// If our `replay-ai-summaries` ff is enabled and the org has gen AI ff enabled,
// but the org hasn't acknowledged the gen AI features, then show CTA.
if (!setupAcknowledgement.orgHasAcknowledged) {
return (
<Wrapper data-test-id="replay-details-ai-summary-tab">
<EmptySummaryContainer>
<CallToActionContainer>
<div>
<strong>{t('AI-Powered Replay Summaries')}</strong>
</div>
<div>
{t(
'Seer access is required to use replay summaries. Please view the Seer settings page for more information.'
)}
</div>
<div>
<LinkButton size="sm" priority="primary" to="/settings/seer/">
{t('View Seer Settings')}
</LinkButton>
</div>
</CallToActionContainer>
</EmptySummaryContainer>
</Wrapper>
);
}

if (replayRecord?.project_id && !project) {
return (
<Wrapper data-test-id="replay-details-ai-summary-tab">
<EmptySummaryContainer>
<Alert type="error">{t('Project not found. Unable to load AI summary.')}</Alert>
<Alert type="error">
{t('Project not found. Unable to load replay summary.')}
</Alert>
</EmptySummaryContainer>
</Wrapper>
);
Expand All @@ -105,7 +108,7 @@ export default function Ai() {
return (
<Wrapper data-test-id="replay-details-ai-summary-tab">
<EmptySummaryContainer>
<Alert type="error">{t('Failed to load AI summary')}</Alert>
<Alert type="error">{t('Failed to load replay summary')}</Alert>
</EmptySummaryContainer>
</Wrapper>
);
Expand Down Expand Up @@ -136,8 +139,8 @@ export default function Ai() {
</SummaryLeft>
<SummaryRight>
<Flex gap={space(0.5)}>
{feedbackButton({type: 'positive'})}
{feedbackButton({type: 'negative'})}
<FeedbackButton type="positive" />
<FeedbackButton type="negative" />
</Flex>
<Button
priority="default"
Expand All @@ -164,6 +167,35 @@ export default function Ai() {
);
}

function FeedbackButton({type}: {type: 'positive' | 'negative'}) {
const openForm = useFeedbackForm();
if (!openForm) {
return null;
}

return (
<Button
aria-label={t('Give feedback on the replay summary section')}
icon={<IconThumb direction={type === 'positive' ? 'up' : 'down'} />}
title={type === 'positive' ? t('I like this') : t(`I don't like this`)}
size={'xs'}
onClick={() =>
openForm({
messagePlaceholder:
type === 'positive'
? t('What did you like about the replay summary and chapters?')
: t('How can we make the replay summary and chapters work better for you?'),
tags: {
['feedback.source']: 'replay_ai_summary',
['feedback.owner']: 'replay',
['feedback.type']: type,
},
})
}
/>
);
}

const Wrapper = styled('div')`
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -242,3 +274,12 @@ const OverflowBody = styled('section')`
flex: 1 1 auto;
overflow: auto;
`;

const CallToActionContainer = styled('div')`
display: flex;
flex-direction: column;
gap: ${space(2)};
padding: ${space(2)};
align-items: center;
text-align: center;
`;
13 changes: 8 additions & 5 deletions static/app/views/replays/detail/layout/focusTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import styled from '@emotion/styled';
import {Flex} from 'sentry/components/core/layout';
import {TabList, Tabs} from 'sentry/components/core/tabs';
import {Tooltip} from 'sentry/components/core/tooltip';
import {useOrganizationSeerSetup} from 'sentry/components/events/autofix/useOrganizationSeerSetup';
import {IconLab} from 'sentry/icons/iconLab';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
Expand All @@ -16,15 +17,16 @@ import useOrganization from 'sentry/utils/useOrganization';
function getReplayTabs({
isVideoReplay,
organization,
areAiFeaturesAllowed,
}: {
areAiFeaturesAllowed: boolean;
isVideoReplay: boolean;
organization: Organization;
}): Record<TabKey, ReactNode> {
// For video replays, we hide the memory tab (not applicable for mobile)
return {
[TabKey.AI]:
organization.features.includes('replay-ai-summaries') &&
organization.features.includes('gen-ai-features') ? (
organization.features.includes('replay-ai-summaries') && areAiFeaturesAllowed ? (
<Flex align="center" gap={space(0.75)}>
{t('Summary')}
<Tooltip
Expand Down Expand Up @@ -52,12 +54,13 @@ type Props = {

export default function FocusTabs({isVideoReplay}: Props) {
const organization = useOrganization();
const {areAiFeaturesAllowed} = useOrganizationSeerSetup();
const {getActiveTab, setActiveTab} = useActiveReplayTab({isVideoReplay});
const activeTab = getActiveTab();

const tabs = Object.entries(getReplayTabs({isVideoReplay, organization})).filter(
([_, v]) => v !== null
);
const tabs = Object.entries(
getReplayTabs({isVideoReplay, organization, areAiFeaturesAllowed})
).filter(([_, v]) => v !== null);

return (
<TabContainer>
Expand Down
Loading