diff --git a/static/app/components/events/featureFlags/cta/featureFlagCTAContent.tsx b/static/app/components/events/featureFlags/cta/featureFlagCTAContent.tsx
new file mode 100644
index 00000000000000..fab85c2fc527a1
--- /dev/null
+++ b/static/app/components/events/featureFlags/cta/featureFlagCTAContent.tsx
@@ -0,0 +1,118 @@
+import {Fragment, useEffect} from 'react';
+import styled from '@emotion/styled';
+
+import onboardingInstall from 'sentry-images/spot/onboarding-install.svg';
+
+import {useAnalyticsArea} from 'sentry/components/analyticsArea';
+import {Button} from 'sentry/components/core/button';
+import {LinkButton} from 'sentry/components/core/button/linkButton';
+import {t} from 'sentry/locale';
+import {space} from 'sentry/styles/space';
+import {trackAnalytics} from 'sentry/utils/analytics';
+import useOrganization from 'sentry/utils/useOrganization';
+
+export default function FeatureFlagCTAContent({
+ handleSetupButtonClick,
+}: {
+ handleSetupButtonClick: (e: any) => void;
+}) {
+ const organization = useOrganization();
+ const analyticsArea = useAnalyticsArea();
+
+ useEffect(() => {
+ trackAnalytics('flags.cta_rendered', {
+ organization,
+ surface: analyticsArea,
+ });
+ }, [organization, analyticsArea]);
+
+ return (
+
+
+ {t('Set Up Feature Flags')}
+
+ {t(
+ 'Want to know which feature flags were associated with this issue? Set up your feature flag integration.'
+ )}
+
+
+
+ {
+ trackAnalytics('flags.cta_read_more_clicked', {
+ organization,
+ surface: analyticsArea,
+ });
+ }}
+ >
+ {t('Read More')}
+
+
+
+
+
+ );
+}
+
+const ActionButton = styled('div')`
+ display: flex;
+ gap: ${space(1)};
+`;
+
+const BannerTitle = styled('div')`
+ font-size: ${p => p.theme.fontSize.xl};
+ margin-bottom: ${space(1)};
+ font-weight: ${p => p.theme.fontWeight.bold};
+`;
+
+const BannerDescription = styled('div')`
+ margin-bottom: ${space(1.5)};
+ max-width: 340px;
+`;
+
+const BannerContent = styled('div')`
+ padding: ${space(2)};
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+`;
+
+const BannerIllustration = styled('img')`
+ object-fit: contain;
+ max-width: 30%;
+ min-width: 150px;
+ padding-inline: ${space(2)};
+ padding-top: ${space(2)};
+ align-self: flex-end;
+`;
+
+export const BannerWrapper = styled('div')`
+ position: relative;
+ border: 1px solid ${p => p.theme.border};
+ border-radius: ${p => p.theme.borderRadius};
+ background: linear-gradient(
+ 90deg,
+ ${p => p.theme.backgroundSecondary}00 0%,
+ ${p => p.theme.backgroundSecondary}FF 70%,
+ ${p => p.theme.backgroundSecondary}FF 100%
+ );
+ display: flex;
+ flex-direction: row;
+ align-items: flex-end;
+ justify-content: space-between;
+ gap: ${space(1)};
+
+ container-name: bannerWrapper;
+ container-type: inline-size;
+
+ @container bannerWrapper (max-width: 400px) {
+ img {
+ display: none;
+ }
+ }
+`;
diff --git a/static/app/components/events/featureFlags/cta/featureFlagInlineCTA.spec.tsx b/static/app/components/events/featureFlags/cta/featureFlagInlineCTA.spec.tsx
deleted file mode 100644
index 89aa9ac7f05d98..00000000000000
--- a/static/app/components/events/featureFlags/cta/featureFlagInlineCTA.spec.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
-
-import FeatureFlagInlineCTA from 'sentry/components/events/featureFlags/cta/featureFlagInlineCTA';
-
-describe('featureFlagInlineCTA', () => {
- beforeEach(() => {
- MockApiClient.clearMockResponses();
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/prompts-activity/',
- body: {data: {dismissed_ts: null}},
- });
- });
-
- it('shows an onboarding banner that may be dismissed', async () => {
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/prompts-activity/',
- body: {data: {}},
- });
- const dismissMock = MockApiClient.addMockResponse({
- url: '/organizations/org-slug/prompts-activity/',
- method: 'PUT',
- });
-
- render();
- expect(await screen.findByText('Set Up Feature Flags')).toBeInTheDocument();
-
- // Open the snooze or dismiss dropdown
- await userEvent.click(screen.getByTestId('icon-close'));
- expect(screen.getByText('Dismiss')).toBeInTheDocument();
- expect(screen.getByText('Snooze')).toBeInTheDocument();
-
- // Click dismiss
- await userEvent.click(screen.getByRole('menuitemradio', {name: 'Dismiss'}));
- expect(dismissMock).toHaveBeenCalledWith(
- '/organizations/org-slug/prompts-activity/',
- expect.objectContaining({
- data: expect.objectContaining({
- feature: 'issue_feature_flags_inline_onboarding',
- status: 'dismissed',
- }),
- })
- );
- expect(screen.queryByText('Set Up Feature Flags')).not.toBeInTheDocument();
- });
-
- it('shows an onboarding banner that may be snoozed', async () => {
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/prompts-activity/',
- body: {data: {}},
- });
- const snoozeMock = MockApiClient.addMockResponse({
- url: '/organizations/org-slug/prompts-activity/',
- method: 'PUT',
- });
-
- render();
- expect(await screen.findByText('Set Up Feature Flags')).toBeInTheDocument();
-
- // Open the snooze or dismiss dropdown
- await userEvent.click(screen.getByTestId('icon-close'));
- expect(screen.getByText('Dismiss')).toBeInTheDocument();
- expect(screen.getByText('Snooze')).toBeInTheDocument();
-
- // Click snooze
- await userEvent.click(screen.getByRole('menuitemradio', {name: 'Snooze'}));
- expect(snoozeMock).toHaveBeenCalledWith(
- '/organizations/org-slug/prompts-activity/',
- expect.objectContaining({
- data: expect.objectContaining({
- feature: 'issue_feature_flags_inline_onboarding',
- status: 'snoozed',
- }),
- })
- );
- expect(screen.queryByText('Set Up Feature Flags')).not.toBeInTheDocument();
- });
-
- it('does not render if already dismissed', () => {
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/prompts-activity/',
- body: {
- data: {
- feature: 'issue_feature_flags_inline_onboarding',
- status: 'dismissed',
- dismissed_ts: 3,
- },
- },
- });
-
- render();
- expect(screen.queryByText('Set Up Feature Flags')).not.toBeInTheDocument();
- });
-});
diff --git a/static/app/components/events/featureFlags/cta/featureFlagInlineCTA.tsx b/static/app/components/events/featureFlags/cta/featureFlagInlineCTA.tsx
deleted file mode 100644
index 5e41aa7b1074dd..00000000000000
--- a/static/app/components/events/featureFlags/cta/featureFlagInlineCTA.tsx
+++ /dev/null
@@ -1,240 +0,0 @@
-import {Fragment, useEffect} from 'react';
-import styled from '@emotion/styled';
-
-import onboardingInstall from 'sentry-images/spot/onboarding-install.svg';
-
-import {usePrompt} from 'sentry/actionCreators/prompts';
-import {useAnalyticsArea} from 'sentry/components/analyticsArea';
-import {Button} from 'sentry/components/core/button';
-import {ButtonBar} from 'sentry/components/core/button/buttonBar';
-import {LinkButton} from 'sentry/components/core/button/linkButton';
-import {DropdownMenu} from 'sentry/components/dropdownMenu';
-import FeatureFlagSettingsButton from 'sentry/components/events/featureFlags/featureFlagSettingsButton';
-import {useFeatureFlagOnboarding} from 'sentry/components/events/featureFlags/onboarding/useFeatureFlagOnboarding';
-import {IconClose, IconMegaphone} from 'sentry/icons';
-import {t} from 'sentry/locale';
-import {space} from 'sentry/styles/space';
-import type {PlatformKey} from 'sentry/types/project';
-import {trackAnalytics} from 'sentry/utils/analytics';
-import {useFeedbackForm} from 'sentry/utils/useFeedbackForm';
-import useOrganization from 'sentry/utils/useOrganization';
-import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
-import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection';
-
-export function FeatureFlagCTAContent({
- handleSetupButtonClick,
-}: {
- handleSetupButtonClick: (e: any) => void;
-}) {
- const organization = useOrganization();
- const analyticsArea = useAnalyticsArea();
-
- useEffect(() => {
- trackAnalytics('flags.cta_rendered', {
- organization,
- surface: analyticsArea,
- });
- }, [organization, analyticsArea]);
-
- return (
-
-
- {t('Set Up Feature Flags')}
-
- {t(
- 'Want to know which feature flags were associated with this issue? Set up your feature flag integration.'
- )}
-
-
-
- {
- trackAnalytics('flags.cta_read_more_clicked', {
- organization,
- surface: analyticsArea,
- });
- }}
- >
- {t('Read More')}
-
-
-
-
-
- );
-}
-
-export default function FeatureFlagInlineCTA({
- projectId,
- projectPlatform,
-}: {
- projectId: string;
- projectPlatform?: PlatformKey;
-}) {
- const organization = useOrganization();
- const analyticsArea = useAnalyticsArea();
-
- const {activateSidebar} = useFeatureFlagOnboarding({projectPlatform});
-
- const {isLoading, isError, isPromptDismissed, dismissPrompt, snoozePrompt} = usePrompt({
- feature: 'issue_feature_flags_inline_onboarding',
- organization,
- projectId,
- daysToSnooze: 7,
- });
-
- const openForm = useFeedbackForm();
- const feedbackButton = openForm ? (
- }
- size="xs"
- onClick={() =>
- openForm({
- messagePlaceholder: t('How can we make feature flags work better for you?'),
- tags: {
- ['feedback.source']: 'issue_details_feature_flags',
- ['feedback.owner']: 'replay',
- },
- })
- }
- >
- {t('Give Feedback')}
-
- ) : null;
-
- if (isLoading || isError || isPromptDismissed) {
- return null;
- }
-
- const actions = (
-
- {feedbackButton}
-
-
- );
-
- return (
-
-
-
- ,
- }}
- size="xs"
- items={[
- {
- key: 'dismiss',
- label: t('Dismiss'),
- onAction: () => {
- dismissPrompt();
- trackAnalytics('flags.cta_dismissed', {
- organization,
- type: 'dismiss',
- surface: analyticsArea,
- });
- },
- },
- {
- key: 'snooze',
- label: t('Snooze'),
- onAction: () => {
- snoozePrompt();
- trackAnalytics('flags.cta_dismissed', {
- organization,
- type: 'snooze',
- surface: analyticsArea,
- });
- },
- },
- ]}
- />
-
-
- );
-}
-
-const ActionButton = styled('div')`
- display: flex;
- gap: ${space(1)};
-`;
-
-const BannerTitle = styled('div')`
- font-size: ${p => p.theme.fontSize.xl};
- margin-bottom: ${space(1)};
- font-weight: ${p => p.theme.fontWeight.bold};
-`;
-
-const BannerDescription = styled('div')`
- margin-bottom: ${space(1.5)};
- max-width: 340px;
-`;
-
-const BannerContent = styled('div')`
- padding: ${space(2)};
- display: flex;
- flex-direction: column;
- justify-content: center;
-`;
-
-const BannerIllustration = styled('img')`
- object-fit: contain;
- max-width: 30%;
- min-width: 150px;
- padding-inline: ${space(2)};
- padding-top: ${space(2)};
- align-self: flex-end;
-`;
-
-export const BannerWrapper = styled('div')`
- position: relative;
- border: 1px solid ${p => p.theme.border};
- border-radius: ${p => p.theme.borderRadius};
- background: linear-gradient(
- 90deg,
- ${p => p.theme.backgroundSecondary}00 0%,
- ${p => p.theme.backgroundSecondary}FF 70%,
- ${p => p.theme.backgroundSecondary}FF 100%
- );
- display: flex;
- flex-direction: row;
- align-items: flex-end;
- justify-content: space-between;
- gap: ${space(1)};
-
- container-name: bannerWrapper;
- container-type: inline-size;
-
- @container bannerWrapper (max-width: 400px) {
- img {
- display: none;
- }
- }
-`;
-
-const CloseDropdownMenu = styled(DropdownMenu)`
- position: absolute;
- display: block;
- top: ${space(1)};
- right: ${space(1)};
- color: ${p => p.theme.white};
- cursor: pointer;
- z-index: 1;
-`;
diff --git a/static/app/components/events/featureFlags/eventFeatureFlagSection.spec.tsx b/static/app/components/events/featureFlags/eventFeatureFlagSection.spec.tsx
index e2bb16d0f80a56..af38e6e4f15364 100644
--- a/static/app/components/events/featureFlags/eventFeatureFlagSection.spec.tsx
+++ b/static/app/components/events/featureFlags/eventFeatureFlagSection.spec.tsx
@@ -5,7 +5,6 @@ import {
render,
screen,
userEvent,
- waitFor,
waitForDrawerToHide,
} from 'sentry-test/reactTestingLibrary';
@@ -16,9 +15,8 @@ import {
MOCK_DATA_SECTION_PROPS_MANY_FLAGS,
MOCK_DATA_SECTION_PROPS_ONE_EXTRA_FLAG,
MOCK_FLAGS,
- NO_FLAG_CONTEXT_SECTION_PROPS_CTA,
- NO_FLAG_CONTEXT_SECTION_PROPS_NO_CTA,
- NO_FLAG_CONTEXT_WITH_FLAGS_SECTION_PROPS_NO_CTA,
+ NO_FLAG_CONTEXT_SECTION_PROPS,
+ NO_FLAG_CONTEXT_WITH_FLAGS_SECTION_PROPS,
} from 'sentry/components/events/featureFlags/testUtils';
// Needed to mock useVirtualizer lists.
@@ -57,6 +55,7 @@ describe('EventFeatureFlagList', () => {
body: TagsFixture(),
});
});
+
it('renders a list of feature flags with a button to view more flags', async () => {
render();
@@ -220,36 +219,13 @@ describe('EventFeatureFlagList', () => {
).toBeInTheDocument();
});
- it('renders cta if event.contexts.flags is not set and should show cta', async () => {
- const org = OrganizationFixture({features: ['feature-flag-cta']});
+ it('renders empty state if event.contexts.flags is not set - flags already sent', () => {
+ const org = OrganizationFixture({features: []});
- render(, {
+ render(, {
organization: org,
});
- const control = screen.queryByRole('button', {name: 'Sort Flags'});
- expect(control).not.toBeInTheDocument();
- const search = screen.queryByRole('button', {name: 'Open Feature Flag Search'});
- expect(search).not.toBeInTheDocument();
- expect(
- screen.queryByRole('button', {name: 'Set Up Integration'})
- ).not.toBeInTheDocument();
-
- // wait for the CTA to be rendered
- expect(await screen.findByText('Set Up Feature Flags')).toBeInTheDocument();
- expect(screen.getByText('Feature Flags')).toBeInTheDocument();
- });
-
- it('renders empty state if event.contexts.flags is not set but should not show cta - flags already sent', () => {
- const org = OrganizationFixture({features: ['feature-flag-cta']});
-
- render(
- ,
- {
- organization: org,
- }
- );
-
const control = screen.queryByRole('button', {name: 'Sort Flags'});
expect(control).not.toBeInTheDocument();
const search = screen.queryByRole('button', {name: 'Open Feature Flag Search'});
@@ -262,32 +238,10 @@ describe('EventFeatureFlagList', () => {
).toBeInTheDocument();
});
- it('renders nothing if event.contexts.flags is not set and should not show cta - wrong platform', async () => {
- const org = OrganizationFixture({features: ['feature-flag-cta']});
-
- render(, {
- organization: org,
- });
-
- const control = screen.queryByRole('button', {name: 'Sort Flags'});
- expect(control).not.toBeInTheDocument();
- const search = screen.queryByRole('button', {name: 'Open Feature Flag Search'});
- expect(search).not.toBeInTheDocument();
- expect(
- screen.queryByRole('button', {name: 'Set Up Integration'})
- ).not.toBeInTheDocument();
-
- // CTA should not appear
- await waitFor(() => {
- expect(screen.queryByText('Set Up Feature Flags')).not.toBeInTheDocument();
- });
- expect(screen.queryByText('Feature Flags')).not.toBeInTheDocument();
- });
-
- it('renders nothing if event.contexts.flags is not set and should not show cta - no feature flag', async () => {
- const org = OrganizationFixture({features: ['fake-feature-flag']});
+ it('renders nothing if event.contexts.flags is not set - wrong platform', () => {
+ const org = OrganizationFixture({features: []});
- render(, {
+ render(, {
organization: org,
});
@@ -295,14 +249,6 @@ describe('EventFeatureFlagList', () => {
expect(control).not.toBeInTheDocument();
const search = screen.queryByRole('button', {name: 'Open Feature Flag Search'});
expect(search).not.toBeInTheDocument();
- expect(
- screen.queryByRole('button', {name: 'Set Up Integration'})
- ).not.toBeInTheDocument();
-
- // CTA should not appear
- await waitFor(() => {
- expect(screen.queryByText('Set Up Feature Flags')).not.toBeInTheDocument();
- });
expect(screen.queryByText('Feature Flags')).not.toBeInTheDocument();
});
});
diff --git a/static/app/components/events/featureFlags/eventFeatureFlagSection.tsx b/static/app/components/events/featureFlags/eventFeatureFlagSection.tsx
index 8bf35839500b0f..bce4239d2ba4b0 100644
--- a/static/app/components/events/featureFlags/eventFeatureFlagSection.tsx
+++ b/static/app/components/events/featureFlags/eventFeatureFlagSection.tsx
@@ -6,7 +6,6 @@ import AnalyticsArea from 'sentry/components/analyticsArea';
import {Button} from 'sentry/components/core/button';
import {ButtonBar} from 'sentry/components/core/button/buttonBar';
import EmptyStateWarning from 'sentry/components/emptyStateWarning';
-import FeatureFlagInlineCTA from 'sentry/components/events/featureFlags/cta/featureFlagInlineCTA';
import {
CardContainer,
EventFeatureFlagDrawer,
@@ -28,7 +27,6 @@ import {useGroupSuspectFlagScores} from 'sentry/components/issues/suspect/useGro
import useLegacyEventSuspectFlags from 'sentry/components/issues/suspect/useLegacyEventSuspectFlags';
import useSuspectFlagScoreThreshold from 'sentry/components/issues/suspect/useSuspectFlagScoreThreshold';
import {KeyValueData} from 'sentry/components/keyValueData';
-import {featureFlagOnboardingPlatforms} from 'sentry/data/platformCategories';
import {IconMegaphone, IconSearch} from 'sentry/icons';
import {t, tn} from 'sentry/locale';
import type {Event, FeatureFlag} from 'sentry/types/event';
@@ -252,17 +250,9 @@ function BaseEventFeatureFlagList({event, group, project}: EventFeatureFlagSecti
return null;
}
- // If the project has never ingested flags, either show a CTA or hide the section entirely.
+ // If the project has never ingested flags, hide the section entirely.
if (!hasFlags && !project.hasFlags) {
- const showCTA =
- featureFlagOnboardingPlatforms.includes(project.platform ?? 'other') &&
- organization.features.includes('feature-flag-cta');
- return showCTA ? (
-
- ) : null;
+ return null;
}
const actions = (
diff --git a/static/app/components/events/featureFlags/testUtils.tsx b/static/app/components/events/featureFlags/testUtils.tsx
index ef32b93ec64b96..d392b842e1d5db 100644
--- a/static/app/components/events/featureFlags/testUtils.tsx
+++ b/static/app/components/events/featureFlags/testUtils.tsx
@@ -154,7 +154,7 @@ export const EMPTY_STATE_SECTION_PROPS = {
group: GroupFixture(),
};
-export const NO_FLAG_CONTEXT_SECTION_PROPS_NO_CTA = {
+export const NO_FLAG_CONTEXT_SECTION_PROPS = {
event: EventFixture({
id: 'abc123def456ghi789jkl',
contexts: {other: {}},
@@ -164,17 +164,7 @@ export const NO_FLAG_CONTEXT_SECTION_PROPS_NO_CTA = {
group: GroupFixture({platform: 'unity'}),
};
-export const NO_FLAG_CONTEXT_SECTION_PROPS_CTA = {
- event: EventFixture({
- id: 'abc123def456ghi789jkl',
- contexts: {other: {}},
- platform: 'javascript',
- }),
- project: ProjectFixture({platform: 'javascript', hasFlags: false}),
- group: GroupFixture({platform: 'javascript'}),
-};
-
-export const NO_FLAG_CONTEXT_WITH_FLAGS_SECTION_PROPS_NO_CTA = {
+export const NO_FLAG_CONTEXT_WITH_FLAGS_SECTION_PROPS = {
event: EventFixture({
id: 'abc123def456ghi789jkl',
contexts: {other: {}},
diff --git a/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx b/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
index 0f395c7e93f0a7..beb8399bf9904e 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
@@ -1,7 +1,6 @@
-import {
+import FeatureFlagCTAContent, {
BannerWrapper,
- FeatureFlagCTAContent,
-} from 'sentry/components/events/featureFlags/cta/featureFlagInlineCTA';
+} from 'sentry/components/events/featureFlags/cta/featureFlagCTAContent';
import {useFeatureFlagOnboarding} from 'sentry/components/events/featureFlags/onboarding/useFeatureFlagOnboarding';
import {useDrawerContentContext} from 'sentry/components/globalDrawer/components';
import type {PlatformKey} from 'sentry/types/project';