Skip to content

Commit b82e064

Browse files
aliu39michellewzhanggetsantry[bot]
authored andcommitted
feat(flags): add CTA to flag drawer (#87354)
Closes #87149, branched from #87251 ![Screenshot 2025-03-18 at 4 35 31 PM](https://github.com/user-attachments/assets/20d50902-0dcd-4b54-8b6a-00f5aa6941e2) [Slack screen recording of behavior](https://sentry.slack.com/archives/C07GR2V2CH4/p1742337413726239) Also updates style for issue details inline CTA (component is shared): ![Screenshot 2025-03-20 at 2 36 30 PM](https://github.com/user-attachments/assets/03f55f1b-bd73-4d75-9c46-02e205d79295) Adds a missing analytics definition, for an event we were emitting from inline CTA `'flags.setup_sidebar_opened'` --------- Co-authored-by: Michelle Zhang <[email protected]> Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com>
1 parent e547529 commit b82e064

File tree

10 files changed

+345
-40
lines changed

10 files changed

+345
-40
lines changed

static/app/components/events/featureFlags/eventFeatureFlagList.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ describe('EventFeatureFlagList', function () {
217217
).toBe(document.DOCUMENT_POSITION_FOLLOWING);
218218
});
219219

220-
it('renders empty state', function () {
220+
it('renders empty state if project has flags', function () {
221221
render(<EventFeatureFlagList {...EMPTY_STATE_SECTION_PROPS} />);
222222

223223
const control = screen.queryByRole('button', {name: 'Sort Flags'});

static/app/components/events/featureFlags/eventFeatureFlagList.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,6 @@ export function EventFeatureFlagList({
127127
: new Set(suspectFlags.map(f => f.flag));
128128
}, [isSuspectError, isSuspectPending, suspectFlags]);
129129

130-
const hasFlagContext = Boolean(event.contexts?.flags?.values);
131-
132130
const eventFlags: Array<Required<FeatureFlag>> = useMemo(() => {
133131
// At runtime there's no type guarantees on the event flags. So we have to
134132
// explicitly validate against SDK developer error or user-provided contexts.
@@ -218,8 +216,8 @@ export function EventFeatureFlagList({
218216
return null;
219217
}
220218

221-
// contexts.flags is not set and project has not ingested flags
222-
if (!hasFlagContext && !project.hasFlags) {
219+
// If the project has never ingested flags, either show a CTA or hide the section entirely.
220+
if (!hasFlags && !project.hasFlags) {
223221
const showCTA =
224222
featureFlagOnboardingPlatforms.includes(project.platform ?? 'other') &&
225223
organization.features.includes('feature-flag-cta');

static/app/components/events/featureFlags/featureFlagInlineCTA.tsx

Lines changed: 70 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import {Fragment} from 'react';
12
import styled from '@emotion/styled';
23

4+
import onboardingInstall from 'sentry-images/spot/onboarding-install.svg';
5+
36
import {usePrompt} from 'sentry/actionCreators/prompts';
47
import {Button, LinkButton} from 'sentry/components/core/button';
58
import {ButtonBar} from 'sentry/components/core/button/buttonBar';
@@ -15,8 +18,41 @@ import useOrganization from 'sentry/utils/useOrganization';
1518
import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
1619
import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection';
1720

21+
export function FeatureFlagCTAContent({
22+
handleSetupButtonClick,
23+
}: {
24+
handleSetupButtonClick: (e: any) => void;
25+
}) {
26+
return (
27+
<Fragment>
28+
<BannerContent>
29+
<BannerTitle>{t('Set Up Feature Flags')}</BannerTitle>
30+
<BannerDescription>
31+
{t(
32+
'Want to know which feature flags were associated with this issue? Set up your feature flag integration.'
33+
)}
34+
</BannerDescription>
35+
<ActionButton>
36+
<Button onClick={handleSetupButtonClick} priority="primary">
37+
{t('Set Up Now')}
38+
</Button>
39+
<LinkButton
40+
priority="default"
41+
href="https://docs.sentry.io/product/explore/feature-flags/"
42+
external
43+
>
44+
{t('Read More')}
45+
</LinkButton>
46+
</ActionButton>
47+
</BannerContent>
48+
<BannerIllustration src={onboardingInstall} alt={t('Install')} />
49+
</Fragment>
50+
);
51+
}
52+
1853
export default function FeatureFlagInlineCTA({projectId}: {projectId: string}) {
1954
const organization = useOrganization();
55+
2056
const {activateSidebar} = useFeatureFlagOnboarding({
2157
analyticsSurface: 'issue_details.flags_section',
2258
});
@@ -70,26 +106,7 @@ export default function FeatureFlagInlineCTA({projectId}: {projectId: string}) {
70106
actions={actions}
71107
>
72108
<BannerWrapper>
73-
<div>
74-
<BannerTitle>{t('Set Up Feature Flags')}</BannerTitle>
75-
<BannerDescription>
76-
{t(
77-
'Want to know which feature flags were associated with this error? Set up your feature flag integration.'
78-
)}
79-
</BannerDescription>
80-
<ActionButton>
81-
<Button onClick={activateSidebar} priority="primary">
82-
{t('Set Up Now')}
83-
</Button>
84-
<LinkButton
85-
priority="default"
86-
href="https://docs.sentry.io/product/explore/feature-flags/"
87-
external
88-
>
89-
{t('Read More')}
90-
</LinkButton>
91-
</ActionButton>
92-
</div>
109+
<FeatureFlagCTAContent handleSetupButtonClick={activateSidebar} />
93110
<CloseDropdownMenu
94111
position="bottom-end"
95112
triggerProps={{
@@ -128,6 +145,11 @@ export default function FeatureFlagInlineCTA({projectId}: {projectId: string}) {
128145
);
129146
}
130147

148+
const ActionButton = styled('div')`
149+
display: flex;
150+
gap: ${space(1)};
151+
`;
152+
131153
const BannerTitle = styled('div')`
132154
font-size: ${p => p.theme.fontSizeExtraLarge};
133155
margin-bottom: ${space(1)};
@@ -139,31 +161,46 @@ const BannerDescription = styled('div')`
139161
max-width: 340px;
140162
`;
141163

142-
const CloseDropdownMenu = styled(DropdownMenu)`
143-
position: absolute;
144-
display: block;
145-
top: ${space(1)};
146-
right: ${space(1)};
147-
color: ${p => p.theme.white};
148-
cursor: pointer;
149-
z-index: 1;
164+
const BannerContent = styled('div')`
165+
padding: ${space(2)};
166+
display: flex;
167+
flex-direction: column;
168+
justify-content: center;
150169
`;
151170

152-
const ActionButton = styled('div')`
153-
display: flex;
154-
gap: ${space(1)};
171+
const BannerIllustration = styled('img')`
172+
height: 100%;
173+
object-fit: contain;
174+
max-width: 30%;
175+
margin-right: 10px;
176+
margin-bottom: -${space(2)};
177+
padding: ${space(2)};
155178
`;
156179

157-
const BannerWrapper = styled('div')`
180+
export const BannerWrapper = styled('div')`
158181
position: relative;
159182
border: 1px solid ${p => p.theme.border};
160183
border-radius: ${p => p.theme.borderRadius};
161-
padding: ${space(2)};
162184
margin: ${space(1)} 0;
163185
background: linear-gradient(
164186
90deg,
165187
${p => p.theme.backgroundSecondary}00 0%,
166188
${p => p.theme.backgroundSecondary}FF 70%,
167189
${p => p.theme.backgroundSecondary}FF 100%
168190
);
191+
display: flex;
192+
flex-direction: row;
193+
align-items: flex-end;
194+
justify-content: space-between;
195+
gap: ${space(1)};
196+
`;
197+
198+
const CloseDropdownMenu = styled(DropdownMenu)`
199+
position: absolute;
200+
display: block;
201+
top: ${space(1)};
202+
right: ${space(1)};
203+
color: ${p => p.theme.white};
204+
cursor: pointer;
205+
z-index: 1;
169206
`;

static/app/components/events/featureFlags/testUtils.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ export const EMPTY_STATE_SECTION_PROPS = {
150150
id: 'abc123def456ghi789jkl',
151151
contexts: {flags: {values: []}},
152152
}),
153-
project: ProjectFixture(),
153+
project: ProjectFixture({hasFlags: true}),
154154
group: GroupFixture(),
155155
};
156156

static/app/components/globalDrawer/components.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const DrawerContentContext = createContext<DrawerContentContextType>({
1919
ariaLabel: 'slide out drawer',
2020
});
2121

22-
function useDrawerContentContext() {
22+
export function useDrawerContentContext() {
2323
return useContext(DrawerContentContext);
2424
}
2525

static/app/utils/analytics/featureFlagAnalyticsEvents.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ export type FeatureFlagEventParameters = {
1111
direction: 'next' | 'prev';
1212
surface: 'settings' | 'flag_drawer';
1313
};
14+
'flags.setup_sidebar_opened': {
15+
surface: 'issue_details.flags_section' | 'issue_details.flags_drawer';
16+
};
1417
'flags.sort_flags': {sortMethod: string};
1518
'flags.table_rendered': {
1619
numFlags: number;
@@ -33,4 +36,5 @@ export const featureFlagEventMap: Record<FeatureFlagEventKey, string | null> = {
3336
'flags.cta_dismissed': 'Flag CTA Dismissed',
3437
'flags.logs-paginated': 'Feature Flag Logs Paginated',
3538
'flags.view-setup-sidebar': 'Viewed Feature Flag Onboarding Sidebar',
39+
'flags.setup_sidebar_opened': 'Feature Flag Setup Sidebar Opened',
3640
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {
2+
BannerWrapper,
3+
FeatureFlagCTAContent,
4+
} from 'sentry/components/events/featureFlags/featureFlagInlineCTA';
5+
import {useFeatureFlagOnboarding} from 'sentry/components/events/featureFlags/useFeatureFlagOnboarding';
6+
import {useDrawerContentContext} from 'sentry/components/globalDrawer/components';
7+
8+
export default function FlagDrawerCTA() {
9+
const {activateSidebar} = useFeatureFlagOnboarding({
10+
analyticsSurface: 'issue_details.flags_drawer',
11+
});
12+
const {onClose: closeDrawer} = useDrawerContentContext();
13+
14+
function handleSetupButtonClick(e: any) {
15+
closeDrawer?.();
16+
setTimeout(() => {
17+
// Wait for global drawer state to update
18+
activateSidebar(e);
19+
}, 100);
20+
}
21+
22+
return (
23+
<BannerWrapper>
24+
<FeatureFlagCTAContent handleSetupButtonClick={handleSetupButtonClick} />
25+
</BannerWrapper>
26+
);
27+
}

0 commit comments

Comments
 (0)