From b43e3e7cf42b1558fd14f2988a491bd92b90ab22 Mon Sep 17 00:00:00 2001
From: Michelle Zhang <56095982+michellewzhang@users.noreply.github.com>
Date: Mon, 17 Mar 2025 16:01:24 -0700
Subject: [PATCH 01/23] feat(flags): add feature flag audit log table to tags
 dist drawer

---
 .../flagDetailsDrawerContent.tsx              | 232 ++++++++++++++++++
 .../groupFeatureFlagsDrawerContent.tsx        |   5 +-
 .../groupTags/groupTagsDrawer.tsx             | 142 ++++++++---
 .../streamline/featureFlagUtils.tsx           |  15 ++
 .../organizationFeatureFlagsAuditLogTable.tsx |  22 +-
 5 files changed, 365 insertions(+), 51 deletions(-)
 create mode 100644 static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx

diff --git a/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx
new file mode 100644
index 00000000000000..34f7ae59f92b7c
--- /dev/null
+++ b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx
@@ -0,0 +1,232 @@
+import {Fragment, useMemo, useState} from 'react';
+import styled from '@emotion/styled';
+
+import {DateTime} from 'sentry/components/dateTime';
+import {DropdownMenu} from 'sentry/components/dropdownMenu';
+import EmptyStateWarning from 'sentry/components/emptyStateWarning';
+import LoadingError from 'sentry/components/loadingError';
+import LoadingIndicator from 'sentry/components/loadingIndicator';
+import Pagination from 'sentry/components/pagination';
+import {IconArrow, IconEllipsis} from 'sentry/icons';
+import {t} from 'sentry/locale';
+import {space} from 'sentry/styles/space';
+import {decodeScalar} from 'sentry/utils/queryString';
+import useLocationQuery from 'sentry/utils/url/useLocationQuery';
+import useCopyToClipboard from 'sentry/utils/useCopyToClipboard';
+import {useNavigate} from 'sentry/utils/useNavigate';
+import useOrganization from 'sentry/utils/useOrganization';
+import {useParams} from 'sentry/utils/useParams';
+import {
+  getFlagActionLabel,
+  type RawFlag,
+} from 'sentry/views/issueDetails/streamline/featureFlagUtils';
+import {useOrganizationFlagLog} from 'sentry/views/issueDetails/streamline/hooks/useOrganizationFlagLog';
+
+export function FlagDetailsDrawerContent() {
+  const navigate = useNavigate();
+  const organization = useOrganization();
+  const {tagKey} = useParams<{tagKey: string}>();
+  const sortArrow = <IconArrow color="gray300" size="xs" direction="down" />;
+
+  const locationQuery = useLocationQuery({
+    fields: {
+      cursor: decodeScalar,
+      end: decodeScalar,
+      flag: decodeScalar,
+      sort: (value: any) => decodeScalar(value, '-created_at'),
+      start: decodeScalar,
+      statsPeriod: decodeScalar,
+      utc: decodeScalar,
+    },
+  });
+
+  const flagQuery = useMemo(() => {
+    const filteredFields = Object.fromEntries(
+      Object.entries(locationQuery).filter(([_key, val]) => val !== '')
+    );
+    return {
+      ...filteredFields,
+      flag: tagKey,
+      per_page: 15,
+      queryReferrer: 'featureFlagDetailsDrawer',
+    };
+  }, [locationQuery, tagKey]);
+
+  const {
+    data: flagLog,
+    isPending,
+    isError,
+    getResponseHeader,
+  } = useOrganizationFlagLog({
+    organization,
+    query: flagQuery,
+  });
+  const pageLinks = getResponseHeader?.('Link') ?? null;
+
+  if (isPending) {
+    return <LoadingIndicator />;
+  }
+
+  if (isError) {
+    return (
+      <LoadingError message={t('There was an error loading feature flag details.')} />
+    );
+  }
+
+  if (!flagLog.data.length) {
+    return (
+      <EmptyStateWarning withIcon={false} small>
+        {t('No audit log events were found for this flag.')}
+      </EmptyStateWarning>
+    );
+  }
+
+  return (
+    <Fragment>
+      <Table>
+        <Header>
+          <ColumnTitle>{t('Provider')}</ColumnTitle>
+          <ColumnTitle>{t('Flag Name')}</ColumnTitle>
+          <ColumnTitle>{t('Action')}</ColumnTitle>
+          <ColumnTitle>
+            {sortArrow}
+            {t('Date')}
+          </ColumnTitle>
+        </Header>
+        <Body>
+          {flagLog.data.map((fv, i) => (
+            <FlagDetailsRow key={`${fv.id}-${i}`} flagValue={fv} />
+          ))}
+        </Body>
+      </Table>
+      <Pagination
+        pageLinks={pageLinks}
+        onCursor={(cursor, path, query) =>
+          navigate({
+            pathname: path,
+            query: {
+              ...query,
+              flagDrawerCursor: cursor,
+            },
+          })
+        }
+        size="xs"
+      />
+    </Fragment>
+  );
+}
+
+function FlagDetailsRow({flagValue}: {flagValue: RawFlag}) {
+  return (
+    <Row>
+      <LeftAlignedValue>{flagValue.provider}</LeftAlignedValue>
+      <LeftAlignedValue>
+        <code>{flagValue.flag}</code>
+      </LeftAlignedValue>
+      {getFlagActionLabel(flagValue.action)}
+      <DateTime date={flagValue.createdAt} year timeZone />
+      <FlagValueActionsMenu flagValue={flagValue} />
+    </Row>
+  );
+}
+
+function FlagValueActionsMenu({flagValue}: {flagValue: RawFlag}) {
+  const organization = useOrganization();
+  const {onClick: handleCopy} = useCopyToClipboard({
+    text: flagValue.flag,
+  });
+  const key = flagValue.flag;
+  const [isVisible, setIsVisible] = useState(false);
+
+  return (
+    <DropdownMenu
+      size="xs"
+      className={isVisible ? '' : 'invisible'}
+      onOpenChange={isOpen => setIsVisible(isOpen)}
+      triggerProps={{
+        'aria-label': t('Tag Value Actions Menu'),
+        icon: <IconEllipsis />,
+        showChevron: false,
+        size: 'xs',
+      }}
+      items={[
+        {
+          key: 'view-issues-true',
+          label: t('Search issues where this flag value is TRUE'),
+          to: {
+            pathname: `/organizations/${organization.slug}/issues/`,
+            query: {query: `flags["${key}"]:"true"`},
+          },
+        },
+        {
+          key: 'view-issues-false',
+          label: t('Search issues where this flag value is FALSE'),
+          to: {
+            pathname: `/organizations/${organization.slug}/issues/`,
+            query: {query: `flags["${key}"]:"false"`},
+          },
+        },
+        {
+          key: 'copy-value',
+          label: t('Copy tag value to clipboard'),
+          onAction: handleCopy,
+        },
+      ]}
+    />
+  );
+}
+
+const Table = styled('div')`
+  display: grid;
+  grid-template-columns: 0.4fr 0.7fr 0.3fr 0.5fr min-content;
+  column-gap: ${space(1)};
+  row-gap: ${space(0.5)};
+  margin: 0 -${space(1)};
+
+  @media (min-width: ${p => p.theme.breakpoints.xlarge}) {
+    column-gap: ${space(2)};
+  }
+`;
+
+const ColumnTitle = styled('div')`
+  white-space: nowrap;
+  color: ${p => p.theme.subText};
+  font-weight: ${p => p.theme.fontWeightBold};
+`;
+
+const Body = styled('div')`
+  display: grid;
+  grid-column: 1 / -1;
+  grid-template-columns: subgrid;
+`;
+
+const Header = styled(Body)`
+  display: grid;
+  grid-column: 1 / -1;
+  grid-template-columns: subgrid;
+  border-bottom: 1px solid ${p => p.theme.border};
+  margin: 0 ${space(1)};
+`;
+
+const Row = styled(Body)`
+  &:nth-child(even) {
+    background: ${p => p.theme.backgroundSecondary};
+  }
+  align-items: center;
+  border-radius: 4px;
+  padding: ${space(0.25)} ${space(1)};
+
+  .invisible {
+    visibility: hidden;
+  }
+  &:hover,
+  &:active {
+    .invisible {
+      visibility: visible;
+    }
+  }
+`;
+
+const LeftAlignedValue = styled('div')`
+  text-align: left;
+`;
diff --git a/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
index 12b800d698a0b6..20d457d6b5d141 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
@@ -7,6 +7,7 @@ import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import type {Group} from 'sentry/types/group';
 import useGroupFeatureFlags from 'sentry/views/issueDetails/groupFeatureFlags/useGroupFeatureFlags';
+import TagDetailsLink from 'sentry/views/issueDetails/groupTags/tagDetailsLink';
 import {TagDistribution} from 'sentry/views/issueDetails/groupTags/tagDistribution';
 import type {GroupTag} from 'sentry/views/issueDetails/groupTags/useGroupTags';
 
@@ -69,7 +70,9 @@ export default function GroupFeatureFlagsDrawerContent({
     <Wrapper>
       <Container>
         {displayTags.map((tag, tagIdx) => (
-          <TagDistribution tag={tag} key={tagIdx} />
+          <TagDetailsLink tag={tag} groupId={group.id} key={tagIdx}>
+            <TagDistribution tag={tag} key={tagIdx} />
+          </TagDetailsLink>
         ))}
       </Container>
     </Wrapper>
diff --git a/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx b/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
index 3afa974a26d597..40dd920a07b562 100644
--- a/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
+++ b/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
@@ -31,11 +31,15 @@ import useOrganization from 'sentry/utils/useOrganization';
 import {useParams} from 'sentry/utils/useParams';
 import useProjects from 'sentry/utils/useProjects';
 import useUrlParams from 'sentry/utils/useUrlParams';
+import {FlagDetailsDrawerContent} from 'sentry/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent';
 import GroupFeatureFlagsDrawerContent from 'sentry/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent';
 import {TagDetailsDrawerContent} from 'sentry/views/issueDetails/groupTags/tagDetailsDrawerContent';
 import TagDetailsLink from 'sentry/views/issueDetails/groupTags/tagDetailsLink';
 import {TagDistribution} from 'sentry/views/issueDetails/groupTags/tagDistribution';
-import {useGroupTags} from 'sentry/views/issueDetails/groupTags/useGroupTags';
+import {
+  type GroupTag,
+  useGroupTags,
+} from 'sentry/views/issueDetails/groupTags/useGroupTags';
 import {Tab, TabPaths} from 'sentry/views/issueDetails/types';
 import {useGroupDetailsRoute} from 'sentry/views/issueDetails/useGroupDetailsRoute';
 import {useEnvironmentsFromUrl} from 'sentry/views/issueDetails/utils';
@@ -63,6 +67,89 @@ function useDrawerTab({enabled}: {enabled: boolean}) {
   return {tab, setTab};
 }
 
+function getHeaderTitle(
+  tagKey: string | undefined,
+  tab: DrawerTab,
+  includeFeatureFlagsTab: boolean
+) {
+  if (tagKey) {
+    return tab === TAGS_TAB
+      ? tct('Tag Details - [tagKey]', {tagKey})
+      : tct('Feature Flag Details - [tagKey]', {tagKey});
+  }
+
+  return includeFeatureFlagsTab ? t('Tags & Feature Flags') : t('All Tags');
+}
+
+function DrawerContent({
+  tagKey,
+  tab,
+  group,
+  environments,
+  search,
+  isPending,
+  isHighlightsPending,
+  isError,
+  refetch,
+  displayTags,
+}: {
+  displayTags: GroupTag[];
+  environments: string[];
+  group: Group;
+  isError: boolean;
+  isHighlightsPending: boolean;
+  isPending: boolean;
+  refetch: () => void;
+  search: string;
+  tab: DrawerTab;
+  tagKey: string | undefined;
+}) {
+  if (tagKey) {
+    return tab === TAGS_TAB ? (
+      <TagDetailsDrawerContent group={group} />
+    ) : (
+      <FlagDetailsDrawerContent />
+    );
+  }
+
+  if (tab === FEATURE_FLAGS_TAB) {
+    return (
+      <GroupFeatureFlagsDrawerContent
+        group={group}
+        environments={environments}
+        search={search}
+      />
+    );
+  }
+
+  if (isPending || isHighlightsPending) {
+    return <LoadingIndicator />;
+  }
+
+  if (isError) {
+    return (
+      <LoadingError
+        message={t('There was an error loading issue tags.')}
+        onRetry={refetch}
+      />
+    );
+  }
+
+  return (
+    <Wrapper>
+      <Container>
+        {displayTags.map(tag => (
+          <div key={tag.name}>
+            <TagDetailsLink tag={tag} groupId={group.id}>
+              <TagDistribution tag={tag} />
+            </TagDetailsLink>
+          </div>
+        ))}
+      </Container>
+    </Wrapper>
+  );
+}
+
 export function GroupTagsDrawer({
   group,
   includeFeatureFlagsTab,
@@ -243,7 +330,14 @@ export function GroupTagsDrawer({
                 ? [
                     {
                       label: t('All Feature Flags'),
+                      to: tagKey
+                        ? {
+                            pathname: `${baseUrl}${TabPaths[Tab.TAGS]}`,
+                            query: {tab: FEATURE_FLAGS_TAB, ...location.query},
+                          }
+                        : undefined,
                     },
+                    ...(tagKey ? [{label: tagKey}] : []),
                   ]
                 : []),
           ]}
@@ -251,43 +345,23 @@ export function GroupTagsDrawer({
       </EventDrawerHeader>
       <EventNavigator>
         <Header>
-          {tagKey
-            ? tct('Tag Details - [tagKey]', {tagKey})
-            : includeFeatureFlagsTab
-              ? t('Tags & Feature Flags')
-              : t('All Tags')}
+          {getHeaderTitle(tagKey, tab as DrawerTab, includeFeatureFlagsTab)}
         </Header>
         {headerActions}
       </EventNavigator>
       <EventDrawerBody>
-        {tagKey ? (
-          <TagDetailsDrawerContent group={group} />
-        ) : tab === FEATURE_FLAGS_TAB ? (
-          <GroupFeatureFlagsDrawerContent
-            group={group}
-            environments={environments}
-            search={search}
-          />
-        ) : isPending || isHighlightsPending ? (
-          <LoadingIndicator />
-        ) : isError ? (
-          <LoadingError
-            message={t('There was an error loading issue tags.')}
-            onRetry={refetch}
-          />
-        ) : (
-          <Wrapper>
-            <Container>
-              {displayTags.map(tag => (
-                <div key={tag.name}>
-                  <TagDetailsLink tag={tag} groupId={group.id}>
-                    <TagDistribution tag={tag} />
-                  </TagDetailsLink>
-                </div>
-              ))}
-            </Container>
-          </Wrapper>
-        )}
+        <DrawerContent
+          tagKey={tagKey}
+          tab={tab as DrawerTab}
+          group={group}
+          environments={environments}
+          search={search}
+          isPending={isPending}
+          isHighlightsPending={isHighlightsPending}
+          isError={isError}
+          refetch={refetch}
+          displayTags={displayTags}
+        />
       </EventDrawerBody>
     </EventDrawerContainer>
   );
diff --git a/static/app/views/issueDetails/streamline/featureFlagUtils.tsx b/static/app/views/issueDetails/streamline/featureFlagUtils.tsx
index 255855e8dfebe0..b64c06eba0f9fa 100644
--- a/static/app/views/issueDetails/streamline/featureFlagUtils.tsx
+++ b/static/app/views/issueDetails/streamline/featureFlagUtils.tsx
@@ -1,3 +1,5 @@
+import {Tag} from 'sentry/components/core/badge/tag';
+
 export type RawFlag = {
   action: string;
   createdAt: string;
@@ -38,3 +40,16 @@ export function hydrateToFlagSeries(
   });
   return flagData;
 }
+
+export function getFlagActionLabel(action: string) {
+  const labelType =
+    action === 'created' ? 'info' : action === 'deleted' ? 'error' : undefined;
+
+  const capitalized = action.toUpperCase();
+
+  return (
+    <div style={{alignSelf: 'flex-start'}}>
+      <Tag type={labelType}>{capitalized}</Tag>
+    </div>
+  );
+}
diff --git a/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsAuditLogTable.tsx b/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsAuditLogTable.tsx
index 997d1a72c2a91d..14c5085e2ee735 100644
--- a/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsAuditLogTable.tsx
+++ b/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsAuditLogTable.tsx
@@ -1,6 +1,5 @@
 import {Fragment, useMemo, useState} from 'react';
 
-import {Tag} from 'sentry/components/core/badge/tag';
 import GridEditable, {type GridColumnOrder} from 'sentry/components/gridEditable';
 import Pagination from 'sentry/components/pagination';
 import useQueryBasedColumnResize from 'sentry/components/replays/useQueryBasedColumnResize';
@@ -12,7 +11,10 @@ import useLocationQuery from 'sentry/utils/url/useLocationQuery';
 import {useLocation} from 'sentry/utils/useLocation';
 import {useNavigate} from 'sentry/utils/useNavigate';
 import useOrganization from 'sentry/utils/useOrganization';
-import type {RawFlag} from 'sentry/views/issueDetails/streamline/featureFlagUtils';
+import {
+  getFlagActionLabel,
+  type RawFlag,
+} from 'sentry/views/issueDetails/streamline/featureFlagUtils';
 import {useOrganizationFlagLog} from 'sentry/views/issueDetails/streamline/hooks/useOrganizationFlagLog';
 import TextBlock from 'sentry/views/settings/components/text/textBlock';
 
@@ -22,7 +24,7 @@ const BASE_COLUMNS: Array<GridColumnOrder<ColumnKey>> = [
   {key: 'provider', name: t('Provider')},
   {key: 'flag', name: t('Feature Flag'), width: 600},
   {key: 'action', name: t('Action')},
-  {key: 'createdAt', name: t('Created')},
+  {key: 'createdAt', name: t('Date')},
 ];
 
 export function OrganizationFeatureFlagsAuditLogTable({
@@ -83,19 +85,7 @@ export function OrganizationFeatureFlagsAuditLogTable({
       case 'createdAt':
         return FIELD_FORMATTERS.date.renderFunc('createdAt', dataRow);
       case 'action': {
-        const type =
-          dataRow.action === 'created'
-            ? 'info'
-            : dataRow.action === 'deleted'
-              ? 'error'
-              : undefined;
-        const capitalized =
-          dataRow.action.charAt(0).toUpperCase() + dataRow.action.slice(1);
-        return (
-          <div style={{alignSelf: 'flex-start'}}>
-            <Tag type={type}>{capitalized}</Tag>
-          </div>
-        );
+        return getFlagActionLabel(dataRow.action);
       }
       default:
         return dataRow[column.key!];

From ec89ac0b6c8816b242460e49a669eb9855340cc3 Mon Sep 17 00:00:00 2001
From: Michelle Zhang <56095982+michellewzhang@users.noreply.github.com>
Date: Mon, 17 Mar 2025 16:11:48 -0700
Subject: [PATCH 02/23] :white_check_mark: tests

---
 .../flagDetailsDrawerContent.spec.tsx         | 116 ++++++++++++++++++
 1 file changed, 116 insertions(+)
 create mode 100644 static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.spec.tsx

diff --git a/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.spec.tsx b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.spec.tsx
new file mode 100644
index 00000000000000..f85749c00331cd
--- /dev/null
+++ b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.spec.tsx
@@ -0,0 +1,116 @@
+import {GroupFixture} from 'sentry-fixture/group';
+
+import {initializeOrg} from 'sentry-test/initializeOrg';
+import {
+  render,
+  screen,
+  userEvent,
+  waitForElementToBeRemoved,
+} from 'sentry-test/reactTestingLibrary';
+
+import {FlagDetailsDrawerContent} from './flagDetailsDrawerContent';
+
+const mockNavigate = jest.fn();
+jest.mock('sentry/utils/useNavigate', () => ({
+  useNavigate: () => mockNavigate,
+}));
+
+const group = GroupFixture();
+
+function init(tagKey: string) {
+  return initializeOrg({
+    router: {
+      location: {
+        pathname: '/organizations/:orgId/issues/:groupId/',
+        query: {},
+      },
+      params: {orgId: 'org-slug', groupId: group.id, tagKey},
+    },
+  });
+}
+
+describe('FlagDetailsDrawerContent', () => {
+  beforeEach(() => {
+    MockApiClient.addMockResponse({
+      url: '/organizations/org-slug/flags/logs/',
+      query: {flag: 'test-flag-key'},
+      body: {
+        data: [
+          {
+            id: '1',
+            provider: 'test-provider',
+            flag: 'test-flag-key',
+            action: 'updated',
+            createdAt: '2021-01-01T00:00:00Z',
+          },
+        ],
+      },
+    });
+  });
+
+  afterEach(() => {
+    MockApiClient.clearMockResponses();
+  });
+
+  it('renders a list of tag values', async () => {
+    const {router} = init('test-flag-key');
+    render(<FlagDetailsDrawerContent />, {router});
+
+    await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator'));
+
+    expect(screen.getByText('Provider')).toBeInTheDocument();
+    expect(screen.getByText('Flag Name')).toBeInTheDocument();
+    expect(screen.getByText('Date')).toBeInTheDocument();
+    expect(screen.getByText('Action')).toBeInTheDocument();
+
+    // Displays dropdown menu
+    await userEvent.hover(screen.getByText('test-flag-key'));
+    expect(
+      screen.getByRole('button', {name: 'Tag Value Actions Menu'})
+    ).toBeInTheDocument();
+    await userEvent.click(screen.getByRole('button', {name: 'Tag Value Actions Menu'}));
+    expect(
+      screen.getByRole('menuitemradio', {
+        name: 'Search issues where this flag value is FALSE',
+      })
+    ).toBeInTheDocument();
+    expect(
+      screen.getByRole('menuitemradio', {
+        name: 'Search issues where this flag value is TRUE',
+      })
+    ).toBeInTheDocument();
+    expect(
+      await screen.findByRole('menuitemradio', {name: 'Copy tag value to clipboard'})
+    ).toBeInTheDocument();
+  });
+
+  it('renders an error message if flag values request fails', async () => {
+    const {router} = init('test-flag-key');
+
+    MockApiClient.addMockResponse({
+      url: '/organizations/org-slug/flags/logs/',
+      statusCode: 500,
+    });
+
+    render(<FlagDetailsDrawerContent />, {router});
+
+    expect(
+      await screen.findByText('There was an error loading feature flag details.')
+    ).toBeInTheDocument();
+  });
+
+  it('renders an empty state message if audit log values are empty', async () => {
+    const {router} = init('test-flag-key');
+
+    MockApiClient.addMockResponse({
+      url: '/organizations/org-slug/flags/logs/',
+      body: {data: []},
+    });
+
+    render(<FlagDetailsDrawerContent />, {router});
+
+    expect(
+      await screen.findByText('No audit log events were found for this flag.')
+    ).toBeInTheDocument();
+  });
+});

From db8a3ac2673940b3a0f2d73ccfb827a568d41810 Mon Sep 17 00:00:00 2001
From: Michelle Zhang <56095982+michellewzhang@users.noreply.github.com>
Date: Mon, 17 Mar 2025 16:21:31 -0700
Subject: [PATCH 03/23] :twisted_rightwards_arrows: merge fix

---
 .../issueDetails/groupTags/groupTagsDrawer.tsx      | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx b/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
index 2333864b6e9bd7..44e25504da05c4 100644
--- a/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
+++ b/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
@@ -83,6 +83,7 @@ function getHeaderTitle(
 }
 
 function DrawerContent({
+  data,
   tagKey,
   tab,
   group,
@@ -94,6 +95,7 @@ function DrawerContent({
   refetch,
   displayTags,
 }: {
+  data: GroupTag[];
   displayTags: GroupTag[];
   environments: string[];
   group: Group;
@@ -136,6 +138,16 @@ function DrawerContent({
     );
   }
 
+  if (displayTags.length === 0) {
+    return (
+      <StyledEmptyStateWarning withIcon>
+        {data.length === 0
+          ? t('No tags were found for this issue')
+          : t('No tags were found for this search')}
+      </StyledEmptyStateWarning>
+    );
+  }
+
   return (
     <Wrapper>
       <Container>
@@ -352,6 +364,7 @@ export function GroupTagsDrawer({
       </EventNavigator>
       <EventDrawerBody>
         <DrawerContent
+          data={data}
           tagKey={tagKey}
           tab={tab as DrawerTab}
           group={group}

From f8e8f6a4c980a073c1220591f26553ff1a8b0f3a Mon Sep 17 00:00:00 2001
From: Michelle Zhang <56095982+michellewzhang@users.noreply.github.com>
Date: Mon, 17 Mar 2025 16:27:19 -0700
Subject: [PATCH 04/23] :pencil2: wording

---
 .../groupFeatureFlags/flagDetailsDrawerContent.spec.tsx   | 8 +++++---
 .../groupFeatureFlags/flagDetailsDrawerContent.tsx        | 4 ++--
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.spec.tsx b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.spec.tsx
index f85749c00331cd..f41b9986ed4b4d 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.spec.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.spec.tsx
@@ -66,9 +66,11 @@ describe('FlagDetailsDrawerContent', () => {
     // Displays dropdown menu
     await userEvent.hover(screen.getByText('test-flag-key'));
     expect(
-      screen.getByRole('button', {name: 'Tag Value Actions Menu'})
+      screen.getByRole('button', {name: 'Flag Audit Log Actions Menu'})
     ).toBeInTheDocument();
-    await userEvent.click(screen.getByRole('button', {name: 'Tag Value Actions Menu'}));
+    await userEvent.click(
+      screen.getByRole('button', {name: 'Flag Audit Log Actions Menu'})
+    );
     expect(
       screen.getByRole('menuitemradio', {
         name: 'Search issues where this flag value is FALSE',
@@ -80,7 +82,7 @@ describe('FlagDetailsDrawerContent', () => {
       })
     ).toBeInTheDocument();
     expect(
-      await screen.findByRole('menuitemradio', {name: 'Copy tag value to clipboard'})
+      await screen.findByRole('menuitemradio', {name: 'Copy flag value to clipboard'})
     ).toBeInTheDocument();
   });
 
diff --git a/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx
index 34f7ae59f92b7c..1ed611c711964d 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx
@@ -144,7 +144,7 @@ function FlagValueActionsMenu({flagValue}: {flagValue: RawFlag}) {
       className={isVisible ? '' : 'invisible'}
       onOpenChange={isOpen => setIsVisible(isOpen)}
       triggerProps={{
-        'aria-label': t('Tag Value Actions Menu'),
+        'aria-label': t('Flag Audit Log Actions Menu'),
         icon: <IconEllipsis />,
         showChevron: false,
         size: 'xs',
@@ -168,7 +168,7 @@ function FlagValueActionsMenu({flagValue}: {flagValue: RawFlag}) {
         },
         {
           key: 'copy-value',
-          label: t('Copy tag value to clipboard'),
+          label: t('Copy flag value to clipboard'),
           onAction: handleCopy,
         },
       ]}

From 31e352b260e148992173e8409e13205270c9d361 Mon Sep 17 00:00:00 2001
From: Michelle Zhang <56095982+michellewzhang@users.noreply.github.com>
Date: Tue, 18 Mar 2025 11:42:33 -0700
Subject: [PATCH 05/23] :recycle: pr comments 1

---
 .../flagDetailsDrawerContent.spec.tsx                |  2 +-
 .../groupFeatureFlags/flagDetailsDrawerContent.tsx   |  2 +-
 .../views/issueDetails/groupTags/groupTagsDrawer.tsx |  3 ++-
 .../issueDetails/streamline/featureFlagUtils.tsx     | 12 +++++++++---
 4 files changed, 13 insertions(+), 6 deletions(-)

diff --git a/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.spec.tsx b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.spec.tsx
index f41b9986ed4b4d..4dbee606450f92 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.spec.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.spec.tsx
@@ -112,7 +112,7 @@ describe('FlagDetailsDrawerContent', () => {
     render(<FlagDetailsDrawerContent />, {router});
 
     expect(
-      await screen.findByText('No audit log events were found for this flag.')
+      await screen.findByText('No audit logs were found for this feature flag.')
     ).toBeInTheDocument();
   });
 });
diff --git a/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx
index 1ed611c711964d..2e9476156af9e4 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx
@@ -76,7 +76,7 @@ export function FlagDetailsDrawerContent() {
   if (!flagLog.data.length) {
     return (
       <EmptyStateWarning withIcon={false} small>
-        {t('No audit log events were found for this flag.')}
+        {t('No audit logs were found for this feature flag.')}
       </EmptyStateWarning>
     );
   }
diff --git a/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx b/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
index 44e25504da05c4..b2f687121ff3a7 100644
--- a/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
+++ b/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
@@ -173,6 +173,7 @@ export function GroupTagsDrawer({
   const location = useLocation();
   const organization = useOrganization();
   const environments = useEnvironmentsFromUrl();
+  // XXX: tagKey param is re-used for feature flag details drawer
   const {tagKey} = useParams<{tagKey: string}>();
   const drawerRef = useRef<HTMLDivElement>(null);
   const {projects} = useProjects();
@@ -346,7 +347,7 @@ export function GroupTagsDrawer({
                       to: tagKey
                         ? {
                             pathname: `${baseUrl}${TabPaths[Tab.TAGS]}`,
-                            query: {tab: FEATURE_FLAGS_TAB, ...location.query},
+                            query: {...location.query, tab: FEATURE_FLAGS_TAB},
                           }
                         : undefined,
                     },
diff --git a/static/app/views/issueDetails/streamline/featureFlagUtils.tsx b/static/app/views/issueDetails/streamline/featureFlagUtils.tsx
index b64c06eba0f9fa..448bdb07e4ad4b 100644
--- a/static/app/views/issueDetails/streamline/featureFlagUtils.tsx
+++ b/static/app/views/issueDetails/streamline/featureFlagUtils.tsx
@@ -1,3 +1,5 @@
+import styled from '@emotion/styled';
+
 import {Tag} from 'sentry/components/core/badge/tag';
 
 export type RawFlag = {
@@ -45,11 +47,15 @@ export function getFlagActionLabel(action: string) {
   const labelType =
     action === 'created' ? 'info' : action === 'deleted' ? 'error' : undefined;
 
-  const capitalized = action.toUpperCase();
+  const capitalized = action.charAt(0).toUpperCase() + action.slice(1);
 
   return (
-    <div style={{alignSelf: 'flex-start'}}>
+    <ActionLabel>
       <Tag type={labelType}>{capitalized}</Tag>
-    </div>
+    </ActionLabel>
   );
 }
+
+const ActionLabel = styled('div')`
+  align-self: flex-start;
+`;

From e28485f14ebe165a3e9df930cb7bffd5bbe92b9d Mon Sep 17 00:00:00 2001
From: Michelle Zhang <56095982+michellewzhang@users.noreply.github.com>
Date: Tue, 18 Mar 2025 12:12:18 -0700
Subject: [PATCH 06/23] :sparkles: add back button for empty state

---
 .../flagDetailsDrawerContent.tsx              | 36 ++++++++++++++++---
 .../groupTags/groupTagsDrawer.tsx             | 29 ++++++++-------
 2 files changed, 48 insertions(+), 17 deletions(-)

diff --git a/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx
index 2e9476156af9e4..a364d77c50b2e6 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx
@@ -1,6 +1,7 @@
 import {Fragment, useMemo, useState} from 'react';
 import styled from '@emotion/styled';
 
+import {LinkButton} from 'sentry/components/core/button';
 import {DateTime} from 'sentry/components/dateTime';
 import {DropdownMenu} from 'sentry/components/dropdownMenu';
 import EmptyStateWarning from 'sentry/components/emptyStateWarning';
@@ -13,21 +14,27 @@ import {space} from 'sentry/styles/space';
 import {decodeScalar} from 'sentry/utils/queryString';
 import useLocationQuery from 'sentry/utils/url/useLocationQuery';
 import useCopyToClipboard from 'sentry/utils/useCopyToClipboard';
+import {useLocation} from 'sentry/utils/useLocation';
 import {useNavigate} from 'sentry/utils/useNavigate';
 import useOrganization from 'sentry/utils/useOrganization';
 import {useParams} from 'sentry/utils/useParams';
+import {DrawerTab} from 'sentry/views/issueDetails/groupTags/groupTagsDrawer';
 import {
   getFlagActionLabel,
   type RawFlag,
 } from 'sentry/views/issueDetails/streamline/featureFlagUtils';
 import {useOrganizationFlagLog} from 'sentry/views/issueDetails/streamline/hooks/useOrganizationFlagLog';
+import {Tab, TabPaths} from 'sentry/views/issueDetails/types';
+import {useGroupDetailsRoute} from 'sentry/views/issueDetails/useGroupDetailsRoute';
 
 export function FlagDetailsDrawerContent() {
   const navigate = useNavigate();
   const organization = useOrganization();
   const {tagKey} = useParams<{tagKey: string}>();
-  const sortArrow = <IconArrow color="gray300" size="xs" direction="down" />;
+  const {baseUrl} = useGroupDetailsRoute();
+  const location = useLocation();
 
+  const sortArrow = <IconArrow color="gray300" size="xs" direction="down" />;
   const locationQuery = useLocationQuery({
     fields: {
       cursor: decodeScalar,
@@ -75,9 +82,20 @@ export function FlagDetailsDrawerContent() {
 
   if (!flagLog.data.length) {
     return (
-      <EmptyStateWarning withIcon={false} small>
-        {t('No audit logs were found for this feature flag.')}
-      </EmptyStateWarning>
+      <EmptyStateContainer>
+        <StyledEmptyStateWarning withIcon={false} small>
+          {t('No audit logs were found for this feature flag.')}
+        </StyledEmptyStateWarning>
+        <LinkButton
+          size="sm"
+          to={{
+            pathname: `${baseUrl}${TabPaths[Tab.TAGS]}`,
+            query: {...location.query, tab: DrawerTab.FEATURE_FLAGS},
+          }}
+        >
+          {t('See all flags')}
+        </LinkButton>
+      </EmptyStateContainer>
     );
   }
 
@@ -230,3 +248,13 @@ const Row = styled(Body)`
 const LeftAlignedValue = styled('div')`
   text-align: left;
 `;
+
+const EmptyStateContainer = styled('div')`
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+`;
+
+const StyledEmptyStateWarning = styled(EmptyStateWarning)`
+  padding: ${space(3)};
+`;
diff --git a/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx b/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
index b2f687121ff3a7..8822bd66625ce4 100644
--- a/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
+++ b/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
@@ -46,14 +46,15 @@ import {useGroupDetailsRoute} from 'sentry/views/issueDetails/useGroupDetailsRou
 import {useEnvironmentsFromUrl} from 'sentry/views/issueDetails/utils';
 
 // Used for `tab` state and URL param.
-const TAGS_TAB = 'tags';
-const FEATURE_FLAGS_TAB = 'featureFlags';
-type DrawerTab = 'tags' | 'featureFlags';
+export enum DrawerTab {
+  TAGS = 'tags',
+  FEATURE_FLAGS = 'featureFlags',
+}
 
 function useDrawerTab({enabled}: {enabled: boolean}) {
   const {getParamValue: getTabParam, setParamValue: setTabParam} = useUrlParams('tab');
   const [tab, setTab] = useState<DrawerTab>(
-    getTabParam() === FEATURE_FLAGS_TAB ? FEATURE_FLAGS_TAB : TAGS_TAB
+    getTabParam() === DrawerTab.FEATURE_FLAGS ? DrawerTab.FEATURE_FLAGS : DrawerTab.TAGS
   );
 
   useEffect(() => {
@@ -63,7 +64,7 @@ function useDrawerTab({enabled}: {enabled: boolean}) {
   }, [tab, setTabParam, enabled]);
 
   if (!enabled) {
-    return {tab: TAGS_TAB, setTab: (_tab: string) => {}};
+    return {tab: DrawerTab.TAGS, setTab: (_tab: string) => {}};
   }
   return {tab, setTab};
 }
@@ -74,7 +75,7 @@ function getHeaderTitle(
   includeFeatureFlagsTab: boolean
 ) {
   if (tagKey) {
-    return tab === TAGS_TAB
+    return tab === DrawerTab.TAGS
       ? tct('Tag Details - [tagKey]', {tagKey})
       : tct('Feature Flag Details - [tagKey]', {tagKey});
   }
@@ -108,14 +109,14 @@ function DrawerContent({
   tagKey: string | undefined;
 }) {
   if (tagKey) {
-    return tab === TAGS_TAB ? (
+    return tab === DrawerTab.TAGS ? (
       <TagDetailsDrawerContent group={group} />
     ) : (
       <FlagDetailsDrawerContent />
     );
   }
 
-  if (tab === FEATURE_FLAGS_TAB) {
+  if (tab === DrawerTab.FEATURE_FLAGS) {
     return (
       <GroupFeatureFlagsDrawerContent
         group={group}
@@ -305,8 +306,10 @@ export function GroupTagsDrawer({
             setSearch('');
           }}
         >
-          <SegmentedControl.Item key={TAGS_TAB}>{t('All Tags')}</SegmentedControl.Item>
-          <SegmentedControl.Item key={FEATURE_FLAGS_TAB}>
+          <SegmentedControl.Item key={DrawerTab.TAGS}>
+            {t('All Tags')}
+          </SegmentedControl.Item>
+          <SegmentedControl.Item key={DrawerTab.FEATURE_FLAGS}>
             {t('All Feature Flags')}
           </SegmentedControl.Item>
         </SegmentedControl>
@@ -327,7 +330,7 @@ export function GroupTagsDrawer({
                 </CrumbContainer>
               ),
             },
-            ...(tab === TAGS_TAB
+            ...(tab === DrawerTab.TAGS
               ? [
                   {
                     label: t('All Tags'),
@@ -340,14 +343,14 @@ export function GroupTagsDrawer({
                   },
                   ...(tagKey ? [{label: tagKey}] : []),
                 ]
-              : tab === FEATURE_FLAGS_TAB
+              : tab === DrawerTab.FEATURE_FLAGS
                 ? [
                     {
                       label: t('All Feature Flags'),
                       to: tagKey
                         ? {
                             pathname: `${baseUrl}${TabPaths[Tab.TAGS]}`,
-                            query: {...location.query, tab: FEATURE_FLAGS_TAB},
+                            query: {...location.query, tab: DrawerTab.FEATURE_FLAGS},
                           }
                         : undefined,
                     },

From 5a9488f96bc451e5e277373ba0a6358bf53ebb3f Mon Sep 17 00:00:00 2001
From: Michelle Zhang <56095982+michellewzhang@users.noreply.github.com>
Date: Tue, 18 Mar 2025 13:24:29 -0700
Subject: [PATCH 07/23] :art: simplify props

---
 .../groupTags/groupTagsDrawer.tsx             | 27 +++++++++----------
 1 file changed, 12 insertions(+), 15 deletions(-)

diff --git a/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx b/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
index 8822bd66625ce4..937d35d927bcdd 100644
--- a/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
+++ b/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
@@ -84,25 +84,23 @@ function getHeaderTitle(
 }
 
 function DrawerContent({
-  data,
-  tagKey,
-  tab,
+  displayTags,
   group,
   environments,
   search,
-  isPending,
-  isHighlightsPending,
+  isLoading,
   isError,
   refetch,
-  displayTags,
+  tab,
+  tagKey,
+  data,
 }: {
   data: GroupTag[];
   displayTags: GroupTag[];
   environments: string[];
   group: Group;
   isError: boolean;
-  isHighlightsPending: boolean;
-  isPending: boolean;
+  isLoading: boolean;
   refetch: () => void;
   search: string;
   tab: DrawerTab;
@@ -126,7 +124,7 @@ function DrawerContent({
     );
   }
 
-  if (isPending || isHighlightsPending) {
+  if (isLoading) {
     return <LoadingIndicator />;
   }
 
@@ -368,17 +366,16 @@ export function GroupTagsDrawer({
       </EventNavigator>
       <EventDrawerBody>
         <DrawerContent
-          data={data}
-          tagKey={tagKey}
-          tab={tab as DrawerTab}
+          displayTags={displayTags}
           group={group}
           environments={environments}
           search={search}
-          isPending={isPending}
-          isHighlightsPending={isHighlightsPending}
+          isLoading={isPending || isHighlightsPending}
           isError={isError}
           refetch={refetch}
-          displayTags={displayTags}
+          tab={tab as DrawerTab}
+          tagKey={tagKey}
+          data={data}
         />
       </EventDrawerBody>
     </EventDrawerContainer>

From fe137fd88952e84ab32f8309992229c0626ab825 Mon Sep 17 00:00:00 2001
From: Michelle Zhang <56095982+michellewzhang@users.noreply.github.com>
Date: Tue, 18 Mar 2025 13:43:15 -0700
Subject: [PATCH 08/23] :lipstick: style comments

---
 .../flagDetailsDrawerContent.tsx              |  3 ---
 .../groupFeatureFlagsDrawerContent.tsx        | 17 +++++-------
 .../groupTags/groupTagsDrawer.tsx             | 26 +++++++------------
 .../groupTags/tagDetailsDrawerContent.tsx     |  3 ---
 4 files changed, 16 insertions(+), 33 deletions(-)

diff --git a/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx
index a364d77c50b2e6..1eedd72d6cfdcf 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx
@@ -219,9 +219,6 @@ const Body = styled('div')`
 `;
 
 const Header = styled(Body)`
-  display: grid;
-  grid-column: 1 / -1;
-  grid-template-columns: subgrid;
   border-bottom: 1px solid ${p => p.theme.border};
   margin: 0 ${space(1)};
 `;
diff --git a/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
index 592764733b5dc1..a2a6e02f32bd13 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
@@ -8,7 +8,6 @@ import useGroupFeatureFlags from 'sentry/views/issueDetails/groupFeatureFlags/us
 import {
   Container,
   StyledEmptyStateWarning,
-  Wrapper,
 } from 'sentry/views/issueDetails/groupTags/groupTagsDrawer';
 import TagDetailsLink from 'sentry/views/issueDetails/groupTags/tagDetailsLink';
 import {TagDistribution} from 'sentry/views/issueDetails/groupTags/tagDistribution';
@@ -76,14 +75,12 @@ export default function GroupFeatureFlagsDrawerContent({
         : t('No feature flags were found for this search')}
     </StyledEmptyStateWarning>
   ) : (
-    <Wrapper>
-      <Container>
-        {displayTags.map((tag, tagIdx) => (
-          <TagDetailsLink tag={tag} groupId={group.id} key={tagIdx}>
-            <TagDistribution tag={tag} key={tagIdx} />
-          </TagDetailsLink>
-        ))}
-      </Container>
-    </Wrapper>
+    <Container>
+      {displayTags.map((tag, tagIdx) => (
+        <TagDetailsLink tag={tag} groupId={group.id} key={tagIdx}>
+          <TagDistribution tag={tag} key={tagIdx} />
+        </TagDetailsLink>
+      ))}
+    </Container>
   );
 }
diff --git a/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx b/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
index 937d35d927bcdd..61905106a773f4 100644
--- a/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
+++ b/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
@@ -148,17 +148,15 @@ function DrawerContent({
   }
 
   return (
-    <Wrapper>
-      <Container>
-        {displayTags.map(tag => (
-          <div key={tag.name}>
-            <TagDetailsLink tag={tag} groupId={group.id}>
-              <TagDistribution tag={tag} />
-            </TagDetailsLink>
-          </div>
-        ))}
-      </Container>
-    </Wrapper>
+    <Container>
+      {displayTags.map(tag => (
+        <div key={tag.name}>
+          <TagDetailsLink tag={tag} groupId={group.id}>
+            <TagDistribution tag={tag} />
+          </TagDetailsLink>
+        </div>
+      ))}
+    </Container>
   );
 }
 
@@ -382,12 +380,6 @@ export function GroupTagsDrawer({
   );
 }
 
-export const Wrapper = styled('div')`
-  display: flex;
-  flex-direction: column;
-  gap: ${space(2)};
-`;
-
 export const Container = styled('div')`
   display: grid;
   grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
diff --git a/static/app/views/issueDetails/groupTags/tagDetailsDrawerContent.tsx b/static/app/views/issueDetails/groupTags/tagDetailsDrawerContent.tsx
index 4018899d7a766a..c7656894fed00c 100644
--- a/static/app/views/issueDetails/groupTags/tagDetailsDrawerContent.tsx
+++ b/static/app/views/issueDetails/groupTags/tagDetailsDrawerContent.tsx
@@ -345,9 +345,6 @@ const Body = styled('div')`
 `;
 
 const Header = styled(Body)`
-  display: grid;
-  grid-column: 1 / -1;
-  grid-template-columns: subgrid;
   border-bottom: 1px solid ${p => p.theme.border};
   margin: 0 ${space(1)};
 `;

From 50dc510bd41bf20df8a3d0e56277149e5836903e Mon Sep 17 00:00:00 2001
From: Michelle Zhang <56095982+michellewzhang@users.noreply.github.com>
Date: Tue, 18 Mar 2025 13:52:50 -0700
Subject: [PATCH 09/23] :recycle: simplify tags path

---
 .../groupTags/groupTagsDrawer.tsx             | 47 ++++++++-----------
 1 file changed, 20 insertions(+), 27 deletions(-)

diff --git a/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx b/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
index 61905106a773f4..fb1588399f701c 100644
--- a/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
+++ b/static/app/views/issueDetails/groupTags/groupTagsDrawer.tsx
@@ -326,33 +326,26 @@ export function GroupTagsDrawer({
                 </CrumbContainer>
               ),
             },
-            ...(tab === DrawerTab.TAGS
-              ? [
-                  {
-                    label: t('All Tags'),
-                    to: tagKey
-                      ? {
-                          pathname: `${baseUrl}${TabPaths[Tab.TAGS]}`,
-                          query: location.query,
-                        }
-                      : undefined,
-                  },
-                  ...(tagKey ? [{label: tagKey}] : []),
-                ]
-              : tab === DrawerTab.FEATURE_FLAGS
-                ? [
-                    {
-                      label: t('All Feature Flags'),
-                      to: tagKey
-                        ? {
-                            pathname: `${baseUrl}${TabPaths[Tab.TAGS]}`,
-                            query: {...location.query, tab: DrawerTab.FEATURE_FLAGS},
-                          }
-                        : undefined,
-                    },
-                    ...(tagKey ? [{label: tagKey}] : []),
-                  ]
-                : []),
+            tab === DrawerTab.TAGS
+              ? {
+                  label: t('All Tags'),
+                  to: tagKey
+                    ? {
+                        pathname: `${baseUrl}${TabPaths[Tab.TAGS]}`,
+                        query: {...location.query, tab: DrawerTab.TAGS},
+                      }
+                    : undefined,
+                }
+              : {
+                  label: t('All Feature Flags'),
+                  to: tagKey
+                    ? {
+                        pathname: `${baseUrl}${TabPaths[Tab.TAGS]}`,
+                        query: {...location.query, tab: DrawerTab.FEATURE_FLAGS},
+                      }
+                    : undefined,
+                },
+            ...(tagKey ? [{label: tagKey}] : []),
           ]}
         />
       </EventDrawerHeader>

From e73a5c4da6b711fd159d98435a424532b8ec985d Mon Sep 17 00:00:00 2001
From: Michelle Zhang <56095982+michellewzhang@users.noreply.github.com>
Date: Tue, 18 Mar 2025 13:59:48 -0700
Subject: [PATCH 10/23] :fire: rm location params

---
 .../flagDetailsDrawerContent.tsx              | 24 ++++---------------
 1 file changed, 5 insertions(+), 19 deletions(-)

diff --git a/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx
index 1eedd72d6cfdcf..fcb7688cbb8a6c 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsDrawerContent.tsx
@@ -11,8 +11,6 @@ import Pagination from 'sentry/components/pagination';
 import {IconArrow, IconEllipsis} from 'sentry/icons';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
-import {decodeScalar} from 'sentry/utils/queryString';
-import useLocationQuery from 'sentry/utils/url/useLocationQuery';
 import useCopyToClipboard from 'sentry/utils/useCopyToClipboard';
 import {useLocation} from 'sentry/utils/useLocation';
 import {useNavigate} from 'sentry/utils/useNavigate';
@@ -35,29 +33,17 @@ export function FlagDetailsDrawerContent() {
   const location = useLocation();
 
   const sortArrow = <IconArrow color="gray300" size="xs" direction="down" />;
-  const locationQuery = useLocationQuery({
-    fields: {
-      cursor: decodeScalar,
-      end: decodeScalar,
-      flag: decodeScalar,
-      sort: (value: any) => decodeScalar(value, '-created_at'),
-      start: decodeScalar,
-      statsPeriod: decodeScalar,
-      utc: decodeScalar,
-    },
-  });
 
   const flagQuery = useMemo(() => {
-    const filteredFields = Object.fromEntries(
-      Object.entries(locationQuery).filter(([_key, val]) => val !== '')
-    );
     return {
-      ...filteredFields,
       flag: tagKey,
-      per_page: 15,
+      per_page: 50,
       queryReferrer: 'featureFlagDetailsDrawer',
+      statsPeriod: '90d',
+      sort: '-created_at',
+      cursor: location.query.flagDrawerCursor,
     };
-  }, [locationQuery, tagKey]);
+  }, [tagKey, location.query.flagDrawerCursor]);
 
   const {
     data: flagLog,

From 2ab9e7a5efe81c5241441fea41c96a526ba3e3b7 Mon Sep 17 00:00:00 2001
From: Michelle Zhang <56095982+michellewzhang@users.noreply.github.com>
Date: Tue, 18 Mar 2025 14:17:55 -0700
Subject: [PATCH 11/23] :art: clear cursor on drawer close & new link component

---
 .../groupFeatureFlags/flagDetailsLink.tsx     | 36 +++++++++++++++++++
 .../groupFeatureFlagsDrawerContent.tsx        |  6 ++--
 .../groupTags/useGroupTagsDrawer.tsx          |  1 +
 3 files changed, 40 insertions(+), 3 deletions(-)
 create mode 100644 static/app/views/issueDetails/groupFeatureFlags/flagDetailsLink.tsx

diff --git a/static/app/views/issueDetails/groupFeatureFlags/flagDetailsLink.tsx b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsLink.tsx
new file mode 100644
index 00000000000000..bbfe36e5083555
--- /dev/null
+++ b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsLink.tsx
@@ -0,0 +1,36 @@
+import styled from '@emotion/styled';
+
+import Link from 'sentry/components/links/link';
+import {useLocation} from 'sentry/utils/useLocation';
+import type {GroupTag} from 'sentry/views/issueDetails/groupTags/useGroupTags';
+
+export default function FlagDetailsLink({
+  tag,
+  children,
+}: {
+  children: React.ReactNode;
+  groupId: string;
+  tag: GroupTag;
+}) {
+  const location = useLocation();
+
+  return (
+    <StyledLink
+      to={{
+        pathname: `${location.pathname}${tag.key}/`,
+        query: location.query,
+      }}
+    >
+      {children}
+    </StyledLink>
+  );
+}
+
+const StyledLink = styled(Link)`
+  border-radius: ${p => p.theme.borderRadius};
+  display: block;
+
+  &:hover h5 {
+    text-decoration: underline;
+  }
+`;
diff --git a/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
index a2a6e02f32bd13..8b46043f47cf2d 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
@@ -4,12 +4,12 @@ import LoadingError from 'sentry/components/loadingError';
 import LoadingIndicator from 'sentry/components/loadingIndicator';
 import {t} from 'sentry/locale';
 import type {Group} from 'sentry/types/group';
+import FlagDetailsLink from 'sentry/views/issueDetails/groupFeatureFlags/flagDetailsLink';
 import useGroupFeatureFlags from 'sentry/views/issueDetails/groupFeatureFlags/useGroupFeatureFlags';
 import {
   Container,
   StyledEmptyStateWarning,
 } from 'sentry/views/issueDetails/groupTags/groupTagsDrawer';
-import TagDetailsLink from 'sentry/views/issueDetails/groupTags/tagDetailsLink';
 import {TagDistribution} from 'sentry/views/issueDetails/groupTags/tagDistribution';
 import type {GroupTag} from 'sentry/views/issueDetails/groupTags/useGroupTags';
 
@@ -77,9 +77,9 @@ export default function GroupFeatureFlagsDrawerContent({
   ) : (
     <Container>
       {displayTags.map((tag, tagIdx) => (
-        <TagDetailsLink tag={tag} groupId={group.id} key={tagIdx}>
+        <FlagDetailsLink tag={tag} groupId={group.id} key={tagIdx}>
           <TagDistribution tag={tag} key={tagIdx} />
-        </TagDetailsLink>
+        </FlagDetailsLink>
       ))}
     </Container>
   );
diff --git a/static/app/views/issueDetails/groupTags/useGroupTagsDrawer.tsx b/static/app/views/issueDetails/groupTags/useGroupTagsDrawer.tsx
index ec0d31a36fe199..6b1b9a516b308a 100644
--- a/static/app/views/issueDetails/groupTags/useGroupTagsDrawer.tsx
+++ b/static/app/views/issueDetails/groupTags/useGroupTagsDrawer.tsx
@@ -35,6 +35,7 @@ export function useGroupTagsDrawer({
                 ...location.query,
                 tagDrawerSort: undefined,
                 tab: undefined,
+                flagDrawerCursor: undefined,
               },
             },
             {replace: true}

From 551bd35e809593392e7b03a4947477d903e67cf9 Mon Sep 17 00:00:00 2001
From: Michelle Zhang <56095982+michellewzhang@users.noreply.github.com>
Date: Tue, 18 Mar 2025 14:25:35 -0700
Subject: [PATCH 12/23] :bug: ugh copy and paste

---
 .../views/issueDetails/groupFeatureFlags/flagDetailsLink.tsx    | 1 -
 .../groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx        | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/static/app/views/issueDetails/groupFeatureFlags/flagDetailsLink.tsx b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsLink.tsx
index bbfe36e5083555..9308b00339d8f2 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/flagDetailsLink.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/flagDetailsLink.tsx
@@ -9,7 +9,6 @@ export default function FlagDetailsLink({
   children,
 }: {
   children: React.ReactNode;
-  groupId: string;
   tag: GroupTag;
 }) {
   const location = useLocation();
diff --git a/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
index 8b46043f47cf2d..c728a6e93aa7a8 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
@@ -77,7 +77,7 @@ export default function GroupFeatureFlagsDrawerContent({
   ) : (
     <Container>
       {displayTags.map((tag, tagIdx) => (
-        <FlagDetailsLink tag={tag} groupId={group.id} key={tagIdx}>
+        <FlagDetailsLink tag={tag} key={tagIdx}>
           <TagDistribution tag={tag} key={tagIdx} />
         </FlagDetailsLink>
       ))}

From 24d93432ae3add5935f4b212b06a34b664232006 Mon Sep 17 00:00:00 2001
From: Michelle Zhang <56095982+michellewzhang@users.noreply.github.com>
Date: Tue, 18 Mar 2025 14:34:59 -0700
Subject: [PATCH 13/23] :bug: add div

---
 .../groupFeatureFlagsDrawerContent.tsx                 | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
index c728a6e93aa7a8..06095ce9212af4 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
@@ -76,10 +76,12 @@ export default function GroupFeatureFlagsDrawerContent({
     </StyledEmptyStateWarning>
   ) : (
     <Container>
-      {displayTags.map((tag, tagIdx) => (
-        <FlagDetailsLink tag={tag} key={tagIdx}>
-          <TagDistribution tag={tag} key={tagIdx} />
-        </FlagDetailsLink>
+      {displayTags.map(tag => (
+        <div key={tag.name}>
+          <FlagDetailsLink tag={tag} key={tag.name}>
+            <TagDistribution tag={tag} key={tag.name} />
+          </FlagDetailsLink>
+        </div>
       ))}
     </Container>
   );

From e3c6f06253bb203967f707a535435df07dba93fa Mon Sep 17 00:00:00 2001
From: Andrew Liu <159852527+aliu39@users.noreply.github.com>
Date: Tue, 18 Mar 2025 15:24:07 -0700
Subject: [PATCH 14/23] feat(flags): add CTA to flag drawer

---
 .../featureFlags/eventFeatureFlagList.tsx     |  9 +--
 .../components/events/featureFlags/utils.tsx  | 11 +++
 .../components/globalDrawer/components.tsx    |  2 +-
 .../groupFeatureFlags/flagDrawerCTA.tsx       | 78 +++++++++++++++++++
 .../groupFeatureFlagsDrawerContent.tsx        | 11 +++
 5 files changed, 104 insertions(+), 7 deletions(-)
 create mode 100644 static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx

diff --git a/static/app/components/events/featureFlags/eventFeatureFlagList.tsx b/static/app/components/events/featureFlags/eventFeatureFlagList.tsx
index 0faebe53f13b7d..7017a251951431 100644
--- a/static/app/components/events/featureFlags/eventFeatureFlagList.tsx
+++ b/static/app/components/events/featureFlags/eventFeatureFlagList.tsx
@@ -14,12 +14,12 @@ import FeatureFlagSort from 'sentry/components/events/featureFlags/featureFlagSo
 import {
   FlagControlOptions,
   OrderBy,
+  shouldShowFeatureFlagCTA,
   SortBy,
   sortedFlags,
 } from 'sentry/components/events/featureFlags/utils';
 import useDrawer from 'sentry/components/globalDrawer';
 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';
@@ -219,11 +219,8 @@ export function EventFeatureFlagList({
   }
 
   // contexts.flags is not set and project has not ingested flags
-  if (!hasFlagContext && !project.hasFlags) {
-    const showCTA =
-      featureFlagOnboardingPlatforms.includes(project.platform ?? 'other') &&
-      organization.features.includes('feature-flag-cta');
-    return showCTA ? <FeatureFlagInlineCTA projectId={event.projectID} /> : null;
+  if (!hasFlagContext && shouldShowFeatureFlagCTA(organization, project)) {
+    return <FeatureFlagInlineCTA projectId={event.projectID} />;
   }
 
   const actions = (
diff --git a/static/app/components/events/featureFlags/utils.tsx b/static/app/components/events/featureFlags/utils.tsx
index c32510a7ebd609..2e2a2451d61aca 100644
--- a/static/app/components/events/featureFlags/utils.tsx
+++ b/static/app/components/events/featureFlags/utils.tsx
@@ -1,5 +1,8 @@
 import type {KeyValueDataContentProps} from 'sentry/components/keyValueData';
+import {featureFlagOnboardingPlatforms} from 'sentry/data/platformCategories';
 import {t} from 'sentry/locale';
+import type {Organization} from 'sentry/types/organization';
+import type {Project} from 'sentry/types/project';
 
 export enum OrderBy {
   NEWEST = 'newest',
@@ -139,3 +142,11 @@ export const PROVIDER_TO_SETUP_WEBHOOK_URL: Record<WebhookProviderEnum, string>
   [WebhookProviderEnum.UNLEASH]:
     'https://docs.sentry.io/organization/integrations/feature-flag/unleash/#set-up-change-tracking',
 };
+
+export function shouldShowFeatureFlagCTA(organization: Organization, project: Project) {
+  return (
+    !project.hasFlags &&
+    featureFlagOnboardingPlatforms.includes(project.platform ?? 'other') &&
+    organization.features.includes('feature-flag-cta')
+  );
+}
diff --git a/static/app/components/globalDrawer/components.tsx b/static/app/components/globalDrawer/components.tsx
index b2525ce2c049cc..8890e308570dcd 100644
--- a/static/app/components/globalDrawer/components.tsx
+++ b/static/app/components/globalDrawer/components.tsx
@@ -19,7 +19,7 @@ const DrawerContentContext = createContext<DrawerContentContextType>({
   ariaLabel: 'slide out drawer',
 });
 
-function useDrawerContentContext() {
+export function useDrawerContentContext() {
   return useContext(DrawerContentContext);
 }
 
diff --git a/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx b/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
new file mode 100644
index 00000000000000..c43e5cc0571278
--- /dev/null
+++ b/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
@@ -0,0 +1,78 @@
+import styled from '@emotion/styled';
+
+import {Button, LinkButton} from 'sentry/components/core/button';
+import {useFeatureFlagOnboarding} from 'sentry/components/events/featureFlags/useFeatureFlagOnboarding';
+import {useDrawerContentContext} from 'sentry/components/globalDrawer/components';
+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 FlagDrawerCTA() {
+  const organization = useOrganization();
+  const {activateSidebar} = useFeatureFlagOnboarding();
+  const {onClose: closeDrawer} = useDrawerContentContext();
+
+  function handleSetupButtonClick(e: any) {
+    trackAnalytics('flags.setup_modal_opened', {organization});
+    trackAnalytics('flags.cta_setup_button_clicked', {organization});
+    closeDrawer?.();
+    setTimeout(() => {
+      // Wait for global drawer state to update
+      activateSidebar(e);
+    }, 100);
+  }
+
+  return (
+    <BannerWrapper>
+      <BannerTitle>{t('Set Up Feature Flags')}</BannerTitle>
+      <BannerDescription>
+        {t(
+          'Want to know which feature flags were associated with this error? Set up your feature flag integration.'
+        )}
+      </BannerDescription>
+      <ActionButton>
+        <Button onClick={handleSetupButtonClick} priority="primary">
+          {t('Set Up Now')}
+        </Button>
+        <LinkButton
+          priority="default"
+          href="https://docs.sentry.io/product/explore/feature-flags/"
+          external
+        >
+          {t('Read More')}
+        </LinkButton>
+      </ActionButton>
+    </BannerWrapper>
+  );
+}
+
+const BannerTitle = styled('div')`
+  font-size: ${p => p.theme.fontSizeExtraLarge};
+  margin-bottom: ${space(1)};
+  font-weight: ${p => p.theme.fontWeightBold};
+`;
+
+const BannerDescription = styled('div')`
+  margin-bottom: ${space(1.5)};
+  max-width: 340px;
+`;
+
+const ActionButton = styled('div')`
+  display: flex;
+  gap: ${space(1)};
+`;
+
+const BannerWrapper = styled('div')`
+  position: relative;
+  border: 1px solid ${p => p.theme.border};
+  border-radius: ${p => p.theme.borderRadius};
+  padding: ${space(2)};
+  margin: ${space(1)} 0;
+  background: linear-gradient(
+    90deg,
+    ${p => p.theme.backgroundSecondary}00 0%,
+    ${p => p.theme.backgroundSecondary}FF 70%,
+    ${p => p.theme.backgroundSecondary}FF 100%
+  );
+`;
diff --git a/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
index a2a6e02f32bd13..0877ff72b6df82 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
@@ -1,9 +1,13 @@
 import {useMemo} from 'react';
 
+import {shouldShowFeatureFlagCTA} from 'sentry/components/events/featureFlags/utils';
 import LoadingError from 'sentry/components/loadingError';
 import LoadingIndicator from 'sentry/components/loadingIndicator';
 import {t} from 'sentry/locale';
 import type {Group} from 'sentry/types/group';
+import useOrganization from 'sentry/utils/useOrganization';
+import useProjectFromSlug from 'sentry/utils/useProjectFromSlug';
+import FlagDrawerCTA from 'sentry/views/issueDetails/groupFeatureFlags/flagDrawerCTA';
 import useGroupFeatureFlags from 'sentry/views/issueDetails/groupFeatureFlags/useGroupFeatureFlags';
 import {
   Container,
@@ -61,6 +65,13 @@ export default function GroupFeatureFlagsDrawerContent({
     return searchedTags;
   }, [data, search, tagValues]);
 
+  const organization = useOrganization();
+  const project = useProjectFromSlug({organization, projectSlug: group.project?.slug});
+
+  if (data.length === 0 && project && shouldShowFeatureFlagCTA(organization, project)) {
+    return <FlagDrawerCTA />;
+  }
+
   return isPending ? (
     <LoadingIndicator />
   ) : isError ? (

From 0d452754e1a283d2a7062e39be947703145dcd14 Mon Sep 17 00:00:00 2001
From: Andrew Liu <159852527+aliu39@users.noreply.github.com>
Date: Tue, 18 Mar 2025 16:02:32 -0700
Subject: [PATCH 15/23] Fix setup sidebar analytics event

---
 .../events/featureFlags/featureFlagInlineCTA.tsx           | 5 ++++-
 static/app/utils/analytics/featureFlagAnalyticsEvents.tsx  | 4 ++++
 .../views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx | 7 +++++--
 3 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/static/app/components/events/featureFlags/featureFlagInlineCTA.tsx b/static/app/components/events/featureFlags/featureFlagInlineCTA.tsx
index 1b61b3cef6b1ed..c859adc982654b 100644
--- a/static/app/components/events/featureFlags/featureFlagInlineCTA.tsx
+++ b/static/app/components/events/featureFlags/featureFlagInlineCTA.tsx
@@ -20,7 +20,10 @@ export default function FeatureFlagInlineCTA({projectId}: {projectId: string}) {
   const {activateSidebar} = useFeatureFlagOnboarding();
 
   function handleSetupButtonClick(e: any) {
-    trackAnalytics('flags.setup_modal_opened', {organization});
+    trackAnalytics('flags.setup_sidebar_opened', {
+      organization,
+      surface: 'issue_details.flags_section',
+    });
     trackAnalytics('flags.cta_setup_button_clicked', {organization});
     activateSidebar(e);
   }
diff --git a/static/app/utils/analytics/featureFlagAnalyticsEvents.tsx b/static/app/utils/analytics/featureFlagAnalyticsEvents.tsx
index a61509db4a7e91..b222d06aa17f9e 100644
--- a/static/app/utils/analytics/featureFlagAnalyticsEvents.tsx
+++ b/static/app/utils/analytics/featureFlagAnalyticsEvents.tsx
@@ -10,6 +10,9 @@ export type FeatureFlagEventParameters = {
     direction: 'next' | 'prev';
     surface: 'settings';
   };
+  'flags.setup_sidebar_opened': {
+    surface: 'issue_details.flags_section' | 'issue_details.flags_drawer';
+  };
   'flags.sort_flags': {sortMethod: string};
   'flags.table_rendered': {
     numFlags: number;
@@ -31,4 +34,5 @@ export const featureFlagEventMap: Record<FeatureFlagEventKey, string | null> = {
   'flags.cta_dismissed': 'Flag CTA Dismissed',
   'flags.logs-paginated': 'Feature Flag Logs Paginated',
   'flags.view-setup-sidebar': 'Viewed Feature Flag Onboarding Sidebar',
+  'flags.setup_sidebar_opened': 'Feature Flag Setup Sidebar Opened',
 };
diff --git a/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx b/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
index c43e5cc0571278..2b944f74412947 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
@@ -14,7 +14,10 @@ export default function FlagDrawerCTA() {
   const {onClose: closeDrawer} = useDrawerContentContext();
 
   function handleSetupButtonClick(e: any) {
-    trackAnalytics('flags.setup_modal_opened', {organization});
+    trackAnalytics('flags.setup_sidebar_opened', {
+      organization,
+      surface: 'issue_details.flags_drawer',
+    });
     trackAnalytics('flags.cta_setup_button_clicked', {organization});
     closeDrawer?.();
     setTimeout(() => {
@@ -55,7 +58,6 @@ const BannerTitle = styled('div')`
 
 const BannerDescription = styled('div')`
   margin-bottom: ${space(1.5)};
-  max-width: 340px;
 `;
 
 const ActionButton = styled('div')`
@@ -65,6 +67,7 @@ const ActionButton = styled('div')`
 
 const BannerWrapper = styled('div')`
   position: relative;
+  max-width: 600px;
   border: 1px solid ${p => p.theme.border};
   border-radius: ${p => p.theme.borderRadius};
   padding: ${space(2)};

From ddd625608fd29d0c7272a1b0441c30ba49cc837d Mon Sep 17 00:00:00 2001
From: Andrew Liu <159852527+aliu39@users.noreply.github.com>
Date: Tue, 18 Mar 2025 16:15:08 -0700
Subject: [PATCH 16/23] Don't check cta ff

---
 .../events/featureFlags/eventFeatureFlagList.tsx      |  9 +++++++--
 static/app/components/events/featureFlags/utils.tsx   | 11 -----------
 .../groupFeatureFlagsDrawerContent.tsx                |  9 +++++++--
 3 files changed, 14 insertions(+), 15 deletions(-)

diff --git a/static/app/components/events/featureFlags/eventFeatureFlagList.tsx b/static/app/components/events/featureFlags/eventFeatureFlagList.tsx
index 7017a251951431..8f139de6b486fe 100644
--- a/static/app/components/events/featureFlags/eventFeatureFlagList.tsx
+++ b/static/app/components/events/featureFlags/eventFeatureFlagList.tsx
@@ -14,12 +14,12 @@ import FeatureFlagSort from 'sentry/components/events/featureFlags/featureFlagSo
 import {
   FlagControlOptions,
   OrderBy,
-  shouldShowFeatureFlagCTA,
   SortBy,
   sortedFlags,
 } from 'sentry/components/events/featureFlags/utils';
 import useDrawer from 'sentry/components/globalDrawer';
 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';
@@ -219,7 +219,12 @@ export function EventFeatureFlagList({
   }
 
   // contexts.flags is not set and project has not ingested flags
-  if (!hasFlagContext && shouldShowFeatureFlagCTA(organization, project)) {
+  if (
+    !hasFlagContext &&
+    !project.hasFlags &&
+    featureFlagOnboardingPlatforms.includes(project.platform ?? 'other') &&
+    organization.features.includes('feature-flag-cta')
+  ) {
     return <FeatureFlagInlineCTA projectId={event.projectID} />;
   }
 
diff --git a/static/app/components/events/featureFlags/utils.tsx b/static/app/components/events/featureFlags/utils.tsx
index 2e2a2451d61aca..c32510a7ebd609 100644
--- a/static/app/components/events/featureFlags/utils.tsx
+++ b/static/app/components/events/featureFlags/utils.tsx
@@ -1,8 +1,5 @@
 import type {KeyValueDataContentProps} from 'sentry/components/keyValueData';
-import {featureFlagOnboardingPlatforms} from 'sentry/data/platformCategories';
 import {t} from 'sentry/locale';
-import type {Organization} from 'sentry/types/organization';
-import type {Project} from 'sentry/types/project';
 
 export enum OrderBy {
   NEWEST = 'newest',
@@ -142,11 +139,3 @@ export const PROVIDER_TO_SETUP_WEBHOOK_URL: Record<WebhookProviderEnum, string>
   [WebhookProviderEnum.UNLEASH]:
     'https://docs.sentry.io/organization/integrations/feature-flag/unleash/#set-up-change-tracking',
 };
-
-export function shouldShowFeatureFlagCTA(organization: Organization, project: Project) {
-  return (
-    !project.hasFlags &&
-    featureFlagOnboardingPlatforms.includes(project.platform ?? 'other') &&
-    organization.features.includes('feature-flag-cta')
-  );
-}
diff --git a/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
index b4252998b0e737..312a487ad7db8f 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
@@ -1,8 +1,8 @@
 import {useMemo} from 'react';
 
-import {shouldShowFeatureFlagCTA} from 'sentry/components/events/featureFlags/utils';
 import LoadingError from 'sentry/components/loadingError';
 import LoadingIndicator from 'sentry/components/loadingIndicator';
+import {featureFlagOnboardingPlatforms} from 'sentry/data/platformCategories';
 import {t} from 'sentry/locale';
 import type {Group} from 'sentry/types/group';
 import useOrganization from 'sentry/utils/useOrganization';
@@ -68,7 +68,12 @@ export default function GroupFeatureFlagsDrawerContent({
   const organization = useOrganization();
   const project = useProjectFromSlug({organization, projectSlug: group.project?.slug});
 
-  if (data.length === 0 && project && shouldShowFeatureFlagCTA(organization, project)) {
+  if (
+    data.length === 0 &&
+    project &&
+    !project.hasFlags &&
+    featureFlagOnboardingPlatforms.includes(project.platform ?? 'other')
+  ) {
     return <FlagDrawerCTA />;
   }
 

From d02d4e36b00167282f40dc77d9f0b9e6471f024d Mon Sep 17 00:00:00 2001
From: Andrew Liu <159852527+aliu39@users.noreply.github.com>
Date: Tue, 18 Mar 2025 16:34:02 -0700
Subject: [PATCH 17/23] Style

---
 .../groupFeatureFlags/flagDrawerCTA.tsx       | 66 +++++++++++++------
 1 file changed, 46 insertions(+), 20 deletions(-)

diff --git a/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx b/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
index 2b944f74412947..0822c99bfe733e 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
@@ -1,5 +1,7 @@
 import styled from '@emotion/styled';
 
+import onboardingInstall from 'sentry-images/spot/onboarding-install.svg';
+
 import {Button, LinkButton} from 'sentry/components/core/button';
 import {useFeatureFlagOnboarding} from 'sentry/components/events/featureFlags/useFeatureFlagOnboarding';
 import {useDrawerContentContext} from 'sentry/components/globalDrawer/components';
@@ -28,24 +30,27 @@ export default function FlagDrawerCTA() {
 
   return (
     <BannerWrapper>
-      <BannerTitle>{t('Set Up Feature Flags')}</BannerTitle>
-      <BannerDescription>
-        {t(
-          'Want to know which feature flags were associated with this error? Set up your feature flag integration.'
-        )}
-      </BannerDescription>
-      <ActionButton>
-        <Button onClick={handleSetupButtonClick} priority="primary">
-          {t('Set Up Now')}
-        </Button>
-        <LinkButton
-          priority="default"
-          href="https://docs.sentry.io/product/explore/feature-flags/"
-          external
-        >
-          {t('Read More')}
-        </LinkButton>
-      </ActionButton>
+      <CardContent>
+        <BannerTitle>{t('Set Up Feature Flags')}</BannerTitle>
+        <BannerDescription>
+          {t(
+            'Want to know which feature flags were associated with this error? Set up your feature flag integration.'
+          )}
+        </BannerDescription>
+        <ActionButton>
+          <Button onClick={handleSetupButtonClick} priority="primary">
+            {t('Set Up Now')}
+          </Button>
+          <LinkButton
+            priority="default"
+            href="https://docs.sentry.io/product/explore/feature-flags/"
+            external
+          >
+            {t('Read More')}
+          </LinkButton>
+        </ActionButton>
+      </CardContent>
+      <BannerIllustration src={onboardingInstall} alt="Install" />
     </BannerWrapper>
   );
 }
@@ -58,6 +63,7 @@ const BannerTitle = styled('div')`
 
 const BannerDescription = styled('div')`
   margin-bottom: ${space(1.5)};
+  max-width: 340px;
 `;
 
 const ActionButton = styled('div')`
@@ -67,10 +73,8 @@ const ActionButton = styled('div')`
 
 const BannerWrapper = styled('div')`
   position: relative;
-  max-width: 600px;
   border: 1px solid ${p => p.theme.border};
   border-radius: ${p => p.theme.borderRadius};
-  padding: ${space(2)};
   margin: ${space(1)} 0;
   background: linear-gradient(
     90deg,
@@ -78,4 +82,26 @@ const BannerWrapper = styled('div')`
     ${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)};
+`;
+
+const BannerIllustration = styled('img')`
+  height: 100%;
+  object-fit: contain;
+  max-width: 30%;
+  margin-right: 10px;
+  margin-bottom: -${space(2)};
+  padding: ${space(2)};
+`;
+
+const CardContent = styled('div')`
+  padding: ${space(2)};
+
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
 `;

From 62e7f9ff0a93f52b4f9c38080d32e795c545c842 Mon Sep 17 00:00:00 2001
From: Andrew Liu <159852527+aliu39@users.noreply.github.com>
Date: Tue, 18 Mar 2025 16:40:52 -0700
Subject: [PATCH 18/23] Rename CardContent

---
 .../groupFeatureFlags/flagDrawerCTA.tsx       | 42 +++++++++----------
 1 file changed, 21 insertions(+), 21 deletions(-)

diff --git a/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx b/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
index 0822c99bfe733e..c3967af6257352 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
@@ -30,7 +30,7 @@ export default function FlagDrawerCTA() {
 
   return (
     <BannerWrapper>
-      <CardContent>
+      <BannerContent>
         <BannerTitle>{t('Set Up Feature Flags')}</BannerTitle>
         <BannerDescription>
           {t(
@@ -49,12 +49,17 @@ export default function FlagDrawerCTA() {
             {t('Read More')}
           </LinkButton>
         </ActionButton>
-      </CardContent>
+      </BannerContent>
       <BannerIllustration src={onboardingInstall} alt="Install" />
     </BannerWrapper>
   );
 }
 
+const ActionButton = styled('div')`
+  display: flex;
+  gap: ${space(1)};
+`;
+
 const BannerTitle = styled('div')`
   font-size: ${p => p.theme.fontSizeExtraLarge};
   margin-bottom: ${space(1)};
@@ -66,9 +71,21 @@ const BannerDescription = styled('div')`
   max-width: 340px;
 `;
 
-const ActionButton = styled('div')`
+const BannerContent = styled('div')`
+  padding: ${space(2)};
+
   display: flex;
-  gap: ${space(1)};
+  flex-direction: column;
+  justify-content: center;
+`;
+
+const BannerIllustration = styled('img')`
+  height: 100%;
+  object-fit: contain;
+  max-width: 30%;
+  margin-right: 10px;
+  margin-bottom: -${space(2)};
+  padding: ${space(2)};
 `;
 
 const BannerWrapper = styled('div')`
@@ -88,20 +105,3 @@ const BannerWrapper = styled('div')`
   justify-content: space-between;
   gap: ${space(1)};
 `;
-
-const BannerIllustration = styled('img')`
-  height: 100%;
-  object-fit: contain;
-  max-width: 30%;
-  margin-right: 10px;
-  margin-bottom: -${space(2)};
-  padding: ${space(2)};
-`;
-
-const CardContent = styled('div')`
-  padding: ${space(2)};
-
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-`;

From 0c0ddfa61c2c97d7ef10d13db79a005d1a701053 Mon Sep 17 00:00:00 2001
From: Andrew Liu <159852527+aliu39@users.noreply.github.com>
Date: Wed, 19 Mar 2025 10:47:42 -0700
Subject: [PATCH 19/23] Update inline cta style and reword drawer cta

---
 .../featureFlags/featureFlagInlineCTA.tsx     | 28 +++++++++++++++++--
 .../groupFeatureFlags/flagDrawerCTA.tsx       |  2 +-
 2 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/static/app/components/events/featureFlags/featureFlagInlineCTA.tsx b/static/app/components/events/featureFlags/featureFlagInlineCTA.tsx
index c859adc982654b..55e9dbddb6232b 100644
--- a/static/app/components/events/featureFlags/featureFlagInlineCTA.tsx
+++ b/static/app/components/events/featureFlags/featureFlagInlineCTA.tsx
@@ -1,5 +1,7 @@
 import styled from '@emotion/styled';
 
+import onboardingInstall from 'sentry-images/spot/onboarding-install.svg';
+
 import {usePrompt} from 'sentry/actionCreators/prompts';
 import ButtonBar from 'sentry/components/buttonBar';
 import {Button, LinkButton} from 'sentry/components/core/button';
@@ -77,7 +79,7 @@ export default function FeatureFlagInlineCTA({projectId}: {projectId: string}) {
       actions={actions}
     >
       <BannerWrapper>
-        <div>
+        <BannerContent>
           <BannerTitle>{t('Set Up Feature Flags')}</BannerTitle>
           <BannerDescription>
             {t(
@@ -96,7 +98,7 @@ export default function FeatureFlagInlineCTA({projectId}: {projectId: string}) {
               {t('Read More')}
             </LinkButton>
           </ActionButton>
-        </div>
+        </BannerContent>
         <CloseDropdownMenu
           position="bottom-end"
           triggerProps={{
@@ -130,6 +132,7 @@ export default function FeatureFlagInlineCTA({projectId}: {projectId: string}) {
             },
           ]}
         />
+        <BannerIllustration src={onboardingInstall} alt="Install" />
       </BannerWrapper>
     </InterimSection>
   );
@@ -146,6 +149,22 @@ const BannerDescription = styled('div')`
   max-width: 340px;
 `;
 
+const BannerContent = styled('div')`
+  padding: ${space(2)};
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+`;
+
+const BannerIllustration = styled('img')`
+  height: 100%;
+  object-fit: contain;
+  max-width: 30%;
+  margin-right: 10px;
+  margin-bottom: -${space(2)};
+  padding: ${space(2)};
+`;
+
 const CloseDropdownMenu = styled(DropdownMenu)`
   position: absolute;
   display: block;
@@ -173,4 +192,9 @@ const BannerWrapper = styled('div')`
     ${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)};
 `;
diff --git a/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx b/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
index c3967af6257352..47faf991831476 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
@@ -34,7 +34,7 @@ export default function FlagDrawerCTA() {
         <BannerTitle>{t('Set Up Feature Flags')}</BannerTitle>
         <BannerDescription>
           {t(
-            'Want to know which feature flags were associated with this error? Set up your feature flag integration.'
+            'Want to know which feature flags were associated with this issue? Set up your feature flag integration.'
           )}
         </BannerDescription>
         <ActionButton>

From 103e9830dff7140aa9506b9ee1f3c2d19b0aa849 Mon Sep 17 00:00:00 2001
From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com>
Date: Wed, 19 Mar 2025 17:52:02 +0000
Subject: [PATCH 20/23] :hammer_and_wrench: apply pre-commit fixes

---
 .../groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx         | 1 -
 1 file changed, 1 deletion(-)

diff --git a/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
index 8f59d243269638..312a487ad7db8f 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
@@ -9,7 +9,6 @@ import useOrganization from 'sentry/utils/useOrganization';
 import useProjectFromSlug from 'sentry/utils/useProjectFromSlug';
 import FlagDetailsLink from 'sentry/views/issueDetails/groupFeatureFlags/flagDetailsLink';
 import FlagDrawerCTA from 'sentry/views/issueDetails/groupFeatureFlags/flagDrawerCTA';
-import FlagDetailsLink from 'sentry/views/issueDetails/groupFeatureFlags/flagDetailsLink';
 import useGroupFeatureFlags from 'sentry/views/issueDetails/groupFeatureFlags/useGroupFeatureFlags';
 import {
   Container,

From 716e33986c282f5ffe3f8be39d34467a685892ae Mon Sep 17 00:00:00 2001
From: Andrew Liu <159852527+aliu39@users.noreply.github.com>
Date: Thu, 20 Mar 2025 14:48:17 -0700
Subject: [PATCH 21/23] Share content component

---
 .../featureFlags/featureFlagInlineCTA.tsx     | 88 +++++++++++--------
 .../groupFeatureFlags/flagDrawerCTA.tsx       | 84 ++----------------
 2 files changed, 55 insertions(+), 117 deletions(-)

diff --git a/static/app/components/events/featureFlags/featureFlagInlineCTA.tsx b/static/app/components/events/featureFlags/featureFlagInlineCTA.tsx
index 55e9dbddb6232b..b79436de8eefb3 100644
--- a/static/app/components/events/featureFlags/featureFlagInlineCTA.tsx
+++ b/static/app/components/events/featureFlags/featureFlagInlineCTA.tsx
@@ -1,3 +1,4 @@
+import {Fragment} from 'react';
 import styled from '@emotion/styled';
 
 import onboardingInstall from 'sentry-images/spot/onboarding-install.svg';
@@ -17,6 +18,38 @@ 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;
+}) {
+  return (
+    <Fragment>
+      <BannerContent>
+        <BannerTitle>{t('Set Up Feature Flags')}</BannerTitle>
+        <BannerDescription>
+          {t(
+            'Want to know which feature flags were associated with this issue? Set up your feature flag integration.'
+          )}
+        </BannerDescription>
+        <ActionButton>
+          <Button onClick={handleSetupButtonClick} priority="primary">
+            {t('Set Up Now')}
+          </Button>
+          <LinkButton
+            priority="default"
+            href="https://docs.sentry.io/product/explore/feature-flags/"
+            external
+          >
+            {t('Read More')}
+          </LinkButton>
+        </ActionButton>
+      </BannerContent>
+      <BannerIllustration src={onboardingInstall} alt="Install" />
+    </Fragment>
+  );
+}
+
 export default function FeatureFlagInlineCTA({projectId}: {projectId: string}) {
   const organization = useOrganization();
   const {activateSidebar} = useFeatureFlagOnboarding();
@@ -79,26 +112,7 @@ export default function FeatureFlagInlineCTA({projectId}: {projectId: string}) {
       actions={actions}
     >
       <BannerWrapper>
-        <BannerContent>
-          <BannerTitle>{t('Set Up Feature Flags')}</BannerTitle>
-          <BannerDescription>
-            {t(
-              'Want to know which feature flags were associated with this error? Set up your feature flag integration.'
-            )}
-          </BannerDescription>
-          <ActionButton>
-            <Button onClick={handleSetupButtonClick} priority="primary">
-              {t('Set Up Now')}
-            </Button>
-            <LinkButton
-              priority="default"
-              href="https://docs.sentry.io/product/explore/feature-flags/"
-              external
-            >
-              {t('Read More')}
-            </LinkButton>
-          </ActionButton>
-        </BannerContent>
+        <FeatureFlagCTAContent handleSetupButtonClick={handleSetupButtonClick} />
         <CloseDropdownMenu
           position="bottom-end"
           triggerProps={{
@@ -132,12 +146,16 @@ export default function FeatureFlagInlineCTA({projectId}: {projectId: string}) {
             },
           ]}
         />
-        <BannerIllustration src={onboardingInstall} alt="Install" />
       </BannerWrapper>
     </InterimSection>
   );
 }
 
+const ActionButton = styled('div')`
+  display: flex;
+  gap: ${space(1)};
+`;
+
 const BannerTitle = styled('div')`
   font-size: ${p => p.theme.fontSizeExtraLarge};
   margin-bottom: ${space(1)};
@@ -165,26 +183,10 @@ const BannerIllustration = styled('img')`
   padding: ${space(2)};
 `;
 
-const CloseDropdownMenu = styled(DropdownMenu)`
-  position: absolute;
-  display: block;
-  top: ${space(1)};
-  right: ${space(1)};
-  color: ${p => p.theme.white};
-  cursor: pointer;
-  z-index: 1;
-`;
-
-const ActionButton = styled('div')`
-  display: flex;
-  gap: ${space(1)};
-`;
-
-const BannerWrapper = styled('div')`
+export const BannerWrapper = styled('div')`
   position: relative;
   border: 1px solid ${p => p.theme.border};
   border-radius: ${p => p.theme.borderRadius};
-  padding: ${space(2)};
   margin: ${space(1)} 0;
   background: linear-gradient(
     90deg,
@@ -198,3 +200,13 @@ const BannerWrapper = styled('div')`
   justify-content: space-between;
   gap: ${space(1)};
 `;
+
+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/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx b/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
index 47faf991831476..9a6c97cd3d3f75 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/flagDrawerCTA.tsx
@@ -1,12 +1,9 @@
-import styled from '@emotion/styled';
-
-import onboardingInstall from 'sentry-images/spot/onboarding-install.svg';
-
-import {Button, LinkButton} from 'sentry/components/core/button';
+import {
+  BannerWrapper,
+  FeatureFlagCTAContent,
+} from 'sentry/components/events/featureFlags/featureFlagInlineCTA';
 import {useFeatureFlagOnboarding} from 'sentry/components/events/featureFlags/useFeatureFlagOnboarding';
 import {useDrawerContentContext} from 'sentry/components/globalDrawer/components';
-import {t} from 'sentry/locale';
-import {space} from 'sentry/styles/space';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import useOrganization from 'sentry/utils/useOrganization';
 
@@ -30,78 +27,7 @@ export default function FlagDrawerCTA() {
 
   return (
     <BannerWrapper>
-      <BannerContent>
-        <BannerTitle>{t('Set Up Feature Flags')}</BannerTitle>
-        <BannerDescription>
-          {t(
-            'Want to know which feature flags were associated with this issue? Set up your feature flag integration.'
-          )}
-        </BannerDescription>
-        <ActionButton>
-          <Button onClick={handleSetupButtonClick} priority="primary">
-            {t('Set Up Now')}
-          </Button>
-          <LinkButton
-            priority="default"
-            href="https://docs.sentry.io/product/explore/feature-flags/"
-            external
-          >
-            {t('Read More')}
-          </LinkButton>
-        </ActionButton>
-      </BannerContent>
-      <BannerIllustration src={onboardingInstall} alt="Install" />
+      <FeatureFlagCTAContent handleSetupButtonClick={handleSetupButtonClick} />
     </BannerWrapper>
   );
 }
-
-const ActionButton = styled('div')`
-  display: flex;
-  gap: ${space(1)};
-`;
-
-const BannerTitle = styled('div')`
-  font-size: ${p => p.theme.fontSizeExtraLarge};
-  margin-bottom: ${space(1)};
-  font-weight: ${p => p.theme.fontWeightBold};
-`;
-
-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')`
-  height: 100%;
-  object-fit: contain;
-  max-width: 30%;
-  margin-right: 10px;
-  margin-bottom: -${space(2)};
-  padding: ${space(2)};
-`;
-
-const BannerWrapper = styled('div')`
-  position: relative;
-  border: 1px solid ${p => p.theme.border};
-  border-radius: ${p => p.theme.borderRadius};
-  margin: ${space(1)} 0;
-  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)};
-`;

From e316a654484d4f67d4a96dfbda1bcbadc6f27da1 Mon Sep 17 00:00:00 2001
From: Andrew Liu <159852527+aliu39@users.noreply.github.com>
Date: Thu, 20 Mar 2025 15:19:31 -0700
Subject: [PATCH 22/23] Fix event flags cta vs hide conditions

---
 .../featureFlags/eventFeatureFlagList.spec.tsx   |  2 +-
 .../events/featureFlags/eventFeatureFlagList.tsx | 16 ++++++----------
 .../components/events/featureFlags/testUtils.tsx |  2 +-
 3 files changed, 8 insertions(+), 12 deletions(-)

diff --git a/static/app/components/events/featureFlags/eventFeatureFlagList.spec.tsx b/static/app/components/events/featureFlags/eventFeatureFlagList.spec.tsx
index a66baa739709c0..57e9cf38d1eb16 100644
--- a/static/app/components/events/featureFlags/eventFeatureFlagList.spec.tsx
+++ b/static/app/components/events/featureFlags/eventFeatureFlagList.spec.tsx
@@ -217,7 +217,7 @@ describe('EventFeatureFlagList', function () {
     ).toBe(document.DOCUMENT_POSITION_FOLLOWING);
   });
 
-  it('renders empty state', function () {
+  it('renders empty state if project has flags', function () {
     render(<EventFeatureFlagList {...EMPTY_STATE_SECTION_PROPS} />);
 
     const control = screen.queryByRole('button', {name: 'Sort Flags'});
diff --git a/static/app/components/events/featureFlags/eventFeatureFlagList.tsx b/static/app/components/events/featureFlags/eventFeatureFlagList.tsx
index 8f139de6b486fe..ab21a4832f361a 100644
--- a/static/app/components/events/featureFlags/eventFeatureFlagList.tsx
+++ b/static/app/components/events/featureFlags/eventFeatureFlagList.tsx
@@ -127,8 +127,6 @@ export function EventFeatureFlagList({
       : new Set(suspectFlags.map(f => f.flag));
   }, [isSuspectError, isSuspectPending, suspectFlags]);
 
-  const hasFlagContext = Boolean(event.contexts?.flags?.values);
-
   const eventFlags: Array<Required<FeatureFlag>> = useMemo(() => {
     // At runtime there's no type guarantees on the event flags. So we have to
     // explicitly validate against SDK developer error or user-provided contexts.
@@ -218,14 +216,12 @@ export function EventFeatureFlagList({
     return null;
   }
 
-  // contexts.flags is not set and project has not ingested flags
-  if (
-    !hasFlagContext &&
-    !project.hasFlags &&
-    featureFlagOnboardingPlatforms.includes(project.platform ?? 'other') &&
-    organization.features.includes('feature-flag-cta')
-  ) {
-    return <FeatureFlagInlineCTA projectId={event.projectID} />;
+  // If the project has never ingested flags, either show a CTA or hide the section entirely.
+  if (!hasFlags && !project.hasFlags) {
+    const showCTA =
+      featureFlagOnboardingPlatforms.includes(project.platform ?? 'other') &&
+      organization.features.includes('feature-flag-cta');
+    return showCTA ? <FeatureFlagInlineCTA projectId={event.projectID} /> : null;
   }
 
   const actions = (
diff --git a/static/app/components/events/featureFlags/testUtils.tsx b/static/app/components/events/featureFlags/testUtils.tsx
index 5e52a8329ce92d..94f286dab0127c 100644
--- a/static/app/components/events/featureFlags/testUtils.tsx
+++ b/static/app/components/events/featureFlags/testUtils.tsx
@@ -150,7 +150,7 @@ export const EMPTY_STATE_SECTION_PROPS = {
     id: 'abc123def456ghi789jkl',
     contexts: {flags: {values: []}},
   }),
-  project: ProjectFixture(),
+  project: ProjectFixture({hasFlags: true}),
   group: GroupFixture(),
 };
 

From 990a44f9e70adc734998da2b7685f0856d9764c2 Mon Sep 17 00:00:00 2001
From: Andrew Liu <159852527+aliu39@users.noreply.github.com>
Date: Fri, 21 Mar 2025 16:44:43 -0700
Subject: [PATCH 23/23] Update drawer showCTA condition and jest coverage

---
 .../groupFeatureFlagsDrawerContent.spec.tsx   | 179 ++++++++++++++++++
 .../groupFeatureFlagsDrawerContent.tsx        |  16 +-
 tests/js/fixtures/featureFlags.ts             |  42 ++++
 3 files changed, 228 insertions(+), 9 deletions(-)
 create mode 100644 static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.spec.tsx
 create mode 100644 tests/js/fixtures/featureFlags.ts

diff --git a/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.spec.tsx b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.spec.tsx
new file mode 100644
index 00000000000000..c1662aa7f5e6d8
--- /dev/null
+++ b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.spec.tsx
@@ -0,0 +1,179 @@
+import {FeatureFlagsFixture} from 'sentry-fixture/featureFlags';
+import {GroupFixture} from 'sentry-fixture/group';
+import {ProjectFixture} from 'sentry-fixture/project';
+
+import {render, screen, waitFor} from 'sentry-test/reactTestingLibrary';
+
+import ProjectsStore from 'sentry/stores/projectsStore';
+import GroupFeatureFlagsDrawerContent from 'sentry/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent';
+
+describe('GroupFeatureFlagsDrawerContent', function () {
+  function getEmptyState() {
+    return screen.queryByTestId('empty-state') ?? screen.getByTestId('empty-message');
+  }
+
+  beforeEach(function () {
+    jest.resetAllMocks();
+    MockApiClient.addMockResponse({
+      url: `/organizations/org-slug/issues/1/tags/`,
+      body: [],
+    });
+
+    ProjectsStore.init();
+    ProjectsStore.loadInitialData([
+      ProjectFixture({platform: 'javascript', hasFlags: false}),
+    ]);
+  });
+
+  it('calls flags backend and renders distribution cards', async function () {
+    const mockTagsEndpoint = MockApiClient.addMockResponse({
+      url: `/organizations/org-slug/issues/1/tags/`,
+      body: FeatureFlagsFixture(),
+    });
+
+    render(
+      <GroupFeatureFlagsDrawerContent
+        environments={[]}
+        group={GroupFixture()}
+        search=""
+      />
+    );
+
+    await waitFor(() => {
+      expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
+    });
+
+    expect(mockTagsEndpoint).toHaveBeenCalledWith(
+      '/organizations/org-slug/issues/1/tags/',
+      expect.objectContaining({
+        query: expect.objectContaining({useFlagsBackend: '1'}),
+      })
+    );
+
+    expect(screen.getByText('feature.organizations:my-feature')).toBeInTheDocument();
+    expect(screen.getByText('my-rolled-out-feature')).toBeInTheDocument();
+  });
+
+  it('renders error state', async function () {
+    MockApiClient.addMockResponse({
+      url: `/organizations/org-slug/issues/1/tags/`,
+      statusCode: 400,
+      body: {
+        detail: 'Bad request',
+      },
+    });
+
+    render(
+      <GroupFeatureFlagsDrawerContent
+        environments={[]}
+        group={GroupFixture()}
+        search=""
+      />
+    );
+
+    await waitFor(() => {
+      expect(screen.getByTestId('loading-error')).toBeInTheDocument();
+    });
+  });
+
+  it('renders empty state when no flags match the search', async function () {
+    MockApiClient.addMockResponse({
+      url: `/organizations/org-slug/issues/1/tags/`,
+      body: FeatureFlagsFixture(),
+    });
+
+    render(
+      <GroupFeatureFlagsDrawerContent
+        environments={[]}
+        group={GroupFixture()}
+        search="zxf"
+      />
+    );
+
+    await waitFor(() => {
+      expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
+    });
+
+    const emptyState = getEmptyState();
+    expect(emptyState).toBeInTheDocument();
+    expect(emptyState).toHaveTextContent('No feature flags were found for this search');
+  });
+
+  it('renders empty state when no flags returned and hasFlags', async function () {
+    ProjectsStore.reset();
+    ProjectsStore.loadInitialData([
+      ProjectFixture({platform: 'javascript', hasFlags: true}),
+    ]);
+
+    render(
+      <GroupFeatureFlagsDrawerContent
+        environments={[]}
+        group={GroupFixture()}
+        search=""
+      />
+    );
+
+    await waitFor(() => {
+      expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
+    });
+
+    const emptyState = getEmptyState();
+    expect(emptyState).toBeInTheDocument();
+    expect(emptyState).toHaveTextContent('No feature flags were found for this issue');
+  });
+
+  it('renders CTA when no flags returned and hasFlags is false', async function () {
+    render(
+      <GroupFeatureFlagsDrawerContent
+        environments={[]}
+        group={GroupFixture()}
+        search=""
+      />
+    );
+
+    await waitFor(() => {
+      expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
+    });
+
+    expect(screen.getByText('Set Up Feature Flags')).toBeInTheDocument();
+  });
+
+  it('does not render CTA when no flags returned and platform unsupported', async function () {
+    ProjectsStore.reset();
+    ProjectsStore.loadInitialData([
+      ProjectFixture({platform: 'dotnet-awslambda', hasFlags: false}),
+    ]);
+
+    render(
+      <GroupFeatureFlagsDrawerContent
+        environments={[]}
+        group={GroupFixture()}
+        search=""
+      />
+    );
+
+    await waitFor(() => {
+      expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
+    });
+
+    expect(getEmptyState()).toBeInTheDocument();
+  });
+
+  it('does not render CTA when project not found', async function () {
+    ProjectsStore.reset();
+
+    render(
+      <GroupFeatureFlagsDrawerContent
+        environments={[]}
+        group={GroupFixture()}
+        search=""
+      />
+    );
+
+    await waitFor(() => {
+      expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
+    });
+
+    expect(getEmptyState()).toBeInTheDocument();
+  });
+});
diff --git a/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
index 312a487ad7db8f..ff77c05edeb05c 100644
--- a/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
+++ b/static/app/views/issueDetails/groupFeatureFlags/groupFeatureFlagsDrawerContent.tsx
@@ -5,8 +5,7 @@ import LoadingIndicator from 'sentry/components/loadingIndicator';
 import {featureFlagOnboardingPlatforms} from 'sentry/data/platformCategories';
 import {t} from 'sentry/locale';
 import type {Group} from 'sentry/types/group';
-import useOrganization from 'sentry/utils/useOrganization';
-import useProjectFromSlug from 'sentry/utils/useProjectFromSlug';
+import useProjects from 'sentry/utils/useProjects';
 import FlagDetailsLink from 'sentry/views/issueDetails/groupFeatureFlags/flagDetailsLink';
 import FlagDrawerCTA from 'sentry/views/issueDetails/groupFeatureFlags/flagDrawerCTA';
 import useGroupFeatureFlags from 'sentry/views/issueDetails/groupFeatureFlags/useGroupFeatureFlags';
@@ -65,17 +64,14 @@ export default function GroupFeatureFlagsDrawerContent({
     return searchedTags;
   }, [data, search, tagValues]);
 
-  const organization = useOrganization();
-  const project = useProjectFromSlug({organization, projectSlug: group.project?.slug});
+  const {projects} = useProjects();
+  const project = projects.find(p => p.slug === group.project.slug)!;
 
-  if (
+  const showCTA =
     data.length === 0 &&
     project &&
     !project.hasFlags &&
-    featureFlagOnboardingPlatforms.includes(project.platform ?? 'other')
-  ) {
-    return <FlagDrawerCTA />;
-  }
+    featureFlagOnboardingPlatforms.includes(project.platform ?? 'other');
 
   return isPending ? (
     <LoadingIndicator />
@@ -84,6 +80,8 @@ export default function GroupFeatureFlagsDrawerContent({
       message={t('There was an error loading feature flags.')}
       onRetry={refetch}
     />
+  ) : showCTA ? (
+    <FlagDrawerCTA />
   ) : displayTags.length === 0 ? (
     <StyledEmptyStateWarning withIcon>
       {data.length === 0
diff --git a/tests/js/fixtures/featureFlags.ts b/tests/js/fixtures/featureFlags.ts
new file mode 100644
index 00000000000000..6712d506de8eb5
--- /dev/null
+++ b/tests/js/fixtures/featureFlags.ts
@@ -0,0 +1,42 @@
+import { GroupTag } from 'sentry/views/issueDetails/groupTags/useGroupTags';
+
+export function FeatureFlagsFixture(params: GroupTag[] = []): GroupTag[] {
+  return [
+    {
+      key: 'feature.organizations:my-feature',
+      name: 'Feature.Organizations:My-Feature',
+      totalValues: 11,
+      topValues: [
+        {
+          name: 'true',
+          value: 'true',
+          count: 7,
+          lastSeen: '2025-03-21T18:17:44Z',
+          firstSeen: '2025-03-20T16:05:25Z',
+        },
+        {
+          name: 'false',
+          value: 'false',
+          count: 4,
+          lastSeen: '2025-03-21T19:17:44Z',
+          firstSeen: '2025-03-15T16:00:00Z',
+        },
+      ],
+    },
+    {
+      key: 'my-rolled-out-feature',
+      name: 'My-Rolled-Out-Feature',
+      totalValues: 23,
+      topValues: [
+        {
+          name: 'true',
+          value: 'true',
+          count: 23,
+          lastSeen: '2025-03-21T18:17:44Z',
+          firstSeen: '2025-03-21T16:05:25Z',
+        },
+      ],
+    },
+    ...params,
+  ];
+}