Repository: getsentry/sentry
Pull request #86070: ref(flags): split FF settings into eval/change tracking
Author: login: aliu39
Labels: name: Scope: Frontend
description: Automatically applied to PRs that change frontend components
Created: 2025-02-27T21:57:12Z | Merged: 2025-03-03T19:21:09Z

Figma: [settings page v2](https://www.figma.com/design/xZ22Ox8OqhKVkJB2TGi1Vo/Specs%3A-Settings-Page-v2?node-id=2033-3118&p=f&m=dev)
Closes https://github.com/getsentry/team-replay/issues/544

https://github.com/user-attachments/assets/ccfc4b18-0f9e-45ac-8b09-0be2ebb62f4f

Updated flyout: 
![image](https://github.com/user-attachments/assets/815e80af-42c4-4b48-8bd0-d0c1c2f783aa)
New Change Tracking link takes you to change tracking settings.



Diff:
diff --git a/static/app/components/events/featureFlags/featureFlagOnboardingLayout.tsx b/static/app/components/events/featureFlags/featureFlagOnboardingLayout.tsx
index 65c4601d2613..d1921158ae7f 100644
--- a/static/app/components/events/featureFlags/featureFlagOnboardingLayout.tsx
+++ b/static/app/components/events/featureFlags/featureFlagOnboardingLayout.tsx
@@ -1,10 +1,10 @@
 import {useMemo, useState} from 'react';
 import styled from '@emotion/styled';
 
-import {Button} from 'sentry/components/button';
+import {Button, LinkButton} from 'sentry/components/button';
 import {Flex} from 'sentry/components/container/flex';
 import {Alert} from 'sentry/components/core/alert';
-import OnboardingIntegrationSection from 'sentry/components/events/featureFlags/onboardingIntegrationSection';
+import OnboardingAdditionalFeatures from 'sentry/components/events/featureFlags/onboardingAdditionalInfo';
 import {AuthTokenGeneratorProvider} from 'sentry/components/onboarding/gettingStartedDoc/authTokenGenerator';
 import type {OnboardingLayoutProps} from 'sentry/components/onboarding/gettingStartedDoc/onboardingLayout';
 import {Step} from 'sentry/components/onboarding/gettingStartedDoc/step';
@@ -20,8 +20,7 @@ import useOrganization from 'sentry/utils/useOrganization';
 
 interface FeatureFlagOnboardingLayoutProps extends OnboardingLayoutProps {
   integration?: string;
-  provider?: string;
-  skipConfig?: boolean;
+  skipEvalTracking?: boolean;
 }
 
 export function FeatureFlagOnboardingLayout({
@@ -33,8 +32,7 @@ export function FeatureFlagOnboardingLayout({
   projectKeyId,
   configType = 'onboarding',
   integration = '',
-  provider = '',
-  skipConfig,
+  skipEvalTracking,
 }: FeatureFlagOnboardingLayoutProps) {
   const api = useApi();
   const organization = useOrganization();
@@ -42,7 +40,7 @@ export function FeatureFlagOnboardingLayout({
     useSourcePackageRegistries(organization);
   const selectedOptions = useUrlPlatformOptions(docsConfig.platformOptions);
   const {isSelfHosted, urlPrefix} = useLegacyStore(ConfigStore);
-  const [skipSteps, setSkipSteps] = useState(skipConfig);
+  const [hideSteps, setHideSteps] = useState(skipEvalTracking);
 
   const {steps} = useMemo(() => {
     const doc = docsConfig[configType] ?? docsConfig.onboarding;
@@ -95,28 +93,32 @@ export function FeatureFlagOnboardingLayout({
   return (
     <AuthTokenGeneratorProvider projectSlug={projectSlug}>
       <Wrapper>
-        {!skipConfig ? null : (
+        {skipEvalTracking ? (
           <Alert.Container>
             <Alert type="info" showIcon>
               <Flex gap={space(3)}>
                 {t(
                   'Feature flag integration detected. Please follow the remaining steps.'
                 )}
-                <Button onClick={() => setSkipSteps(!skipSteps)}>
-                  {skipSteps ? t('Show Full Guide') : t('Hide Full Guide')}
+                <Button onClick={() => setHideSteps(!hideSteps)}>
+                  {hideSteps ? t('Show Full Guide') : t('Hide Full Guide')}
                 </Button>
               </Flex>
             </Alert>
           </Alert.Container>
-        )}
-        {!skipSteps && (
+        ) : null}
+        {hideSteps ? null : (
           <Steps>
             {steps.map(step => (
               <Step key={step.title ?? step.type} {...step} />
             ))}
+            <StyledLinkButton to="/issues/" priority="primary">
+              {t('Take me to Issues')}
+            </StyledLinkButton>
           </Steps>
         )}
-        <OnboardingIntegrationSection provider={provider} integration={integration} />
+        <Divider />
+        <OnboardingAdditionalFeatures organization={organization} />
       </Wrapper>
     </AuthTokenGeneratorProvider>
   );
@@ -128,6 +130,10 @@ const Steps = styled('div')`
   gap: 1.5rem;
 `;
 
+const StyledLinkButton = styled(LinkButton)`
+  align-self: flex-start;
+`;
+
 const Wrapper = styled('div')`
   h4 {
     margin-bottom: 0.5em;
@@ -141,3 +147,17 @@ const Wrapper = styled('div')`
     }
   }
 `;
+
+const Divider = styled('div')`
+  position: relative;
+  margin-top: ${space(3)};
+  &:before {
+    display: block;
+    position: absolute;
+    content: '';
+    height: 1px;
+    left: 0;
+    right: 0;
+    background: ${p => p.theme.border};
+  }
+`;
diff --git a/static/app/components/events/featureFlags/featureFlagOnboardingSidebar.tsx b/static/app/components/events/featureFlags/featureFlagOnboardingSidebar.tsx
index f58a9a72ac95..38e2b6374c0e 100644
--- a/static/app/components/events/featureFlags/featureFlagOnboardingSidebar.tsx
+++ b/static/app/components/events/featureFlags/featureFlagOnboardingSidebar.tsx
@@ -34,6 +34,7 @@ import type {SelectValue} from 'sentry/types/core';
 import type {Project} from 'sentry/types/project';
 import useOrganization from 'sentry/utils/useOrganization';
 import useUrlParams from 'sentry/utils/useUrlParams';
+import TextBlock from 'sentry/views/settings/components/text/textBlock';
 
 export function useFeatureFlagOnboardingDrawer() {
   const organization = useOrganization();
@@ -88,8 +89,6 @@ function LegacyFeatureFlagOnboardingSidebar(props: CommonSidebarProps) {
 
 function SidebarContent() {
   const {
-    hasDocs,
-    projects,
     allProjects,
     currentProject,
     setCurrentProject,
@@ -139,12 +138,6 @@ function SidebarContent() {
     ];
   }, [supportedProjects, unsupportedProjects]);
 
-  const selectedProject = currentProject ?? projects[0] ?? allProjects[0];
-
-  if (!selectedProject) {
-    return <LoadingIndicator />;
-  }
-
   return (
     <Fragment>
       <TopRightBackgroundImage src={HighlightTopRightPattern} />
@@ -183,19 +176,19 @@ function SidebarContent() {
             />
           </div>
         </HeaderActions>
-        <OnboardingContent currentProject={selectedProject} hasDocs={hasDocs} />
+        {currentProject ? (
+          <OnboardingContent currentProject={currentProject} />
+        ) : (
+          <TextBlock>
+            {t('Select a project from the drop-down to view set up instructions.')}
+          </TextBlock>
+        )}
       </TaskList>
     </Fragment>
   );
 }
 
-function OnboardingContent({
-  currentProject,
-  hasDocs,
-}: {
-  currentProject: Project;
-  hasDocs: boolean;
-}) {
+function OnboardingContent({currentProject}: {currentProject: Project}) {
   const organization = useOrganization();
 
   // useMemo is needed to remember the original hash
@@ -203,7 +196,7 @@ function OnboardingContent({
   const ORIGINAL_HASH = useMemo(() => {
     return window.location.hash;
   }, []);
-  const skipConfig = ORIGINAL_HASH === FLAG_HASH_SKIP_CONFIG;
+  const skipEvalTracking = ORIGINAL_HASH === FLAG_HASH_SKIP_CONFIG;
 
   // First dropdown: OpenFeature providers
   const openFeatureProviderOptions = Object.values(WebhookProviderEnum).map(provider => {
@@ -329,7 +322,10 @@ function OnboardingContent({
         {t(
           'To see which feature flags changed over time, visit the settings page to set up a webhook for your Feature Flag provider.'
         )}
-        <LinkButton size="sm" href={`/settings/${organization.slug}/feature-flags/`}>
+        <LinkButton
+          size="sm"
+          href={`/settings/${organization.slug}/feature-flags/change-tracking/`}
+        >
           {t('Go to Feature Flag Settings')}
         </LinkButton>
       </StyledDefaultContent>
@@ -369,14 +365,7 @@ function OnboardingContent({
   }
 
   // Platform is not supported, no platform, docs import failed, no DSN, or the platform doesn't have onboarding yet
-  if (
-    doesNotSupportFeatureFlags ||
-    !currentPlatform ||
-    !docs ||
-    !dsn ||
-    !hasDocs ||
-    !projectKeyId
-  ) {
+  if (doesNotSupportFeatureFlags || !currentPlatform || !docs || !dsn || !projectKeyId) {
     return defaultMessage;
   }
 
@@ -384,7 +373,7 @@ function OnboardingContent({
     <Fragment>
       {radioButtons}
       <FeatureFlagOnboardingLayout
-        skipConfig={skipConfig}
+        skipEvalTracking={skipEvalTracking}
         docsConfig={docs}
         dsn={dsn}
         projectKeyId={projectKeyId}
@@ -396,10 +385,6 @@ function OnboardingContent({
           // either OpenFeature or the SDK selected from the second dropdown
           setupMode() === 'openFeature' ? SdkProviderEnum.OPENFEATURE : sdkProvider.value
         }
-        provider={
-          // dropdown value (from either dropdown)
-          setupMode() === 'openFeature' ? openFeatureProvider.value : sdkProvider.value
-        }
         configType="featureFlagOnboarding"
       />
     </Fragment>
diff --git a/static/app/components/events/featureFlags/onboardingAdditionalInfo.tsx b/static/app/components/events/featureFlags/onboardingAdditionalInfo.tsx
new file mode 100644
index 000000000000..11180d132a9f
--- /dev/null
+++ b/static/app/components/events/featureFlags/onboardingAdditionalInfo.tsx
@@ -0,0 +1,25 @@
+import {Fragment} from 'react';
+
+import Link from 'sentry/components/links/link';
+import {t, tct} from 'sentry/locale';
+import type {Organization} from 'sentry/types/organization';
+
+export default function OnboardingAdditionalFeatures({
+  organization,
+}: {
+  organization: Organization;
+}) {
+  return (
+    <Fragment>
+      <h4 style={{marginTop: '40px'}}>{t('Additional Features')}</h4>
+      {tct(
+        '[link:Change Tracking]: Configure Sentry to listen for additions, removals, and modifications to your feature flags.',
+        {
+          link: (
+            <Link to={`/settings/${organization.slug}/feature-flags/change-tracking/`} />
+          ),
+        }
+      )}
+    </Fragment>
+  );
+}
diff --git a/static/app/components/events/featureFlags/onboardingIntegrationSection.tsx b/static/app/components/events/featureFlags/onboardingIntegrationSection.tsx
index b664f7ffb471..f5598887119a 100644
--- a/static/app/components/events/featureFlags/onboardingIntegrationSection.tsx
+++ b/static/app/components/events/featureFlags/onboardingIntegrationSection.tsx
@@ -26,11 +26,11 @@ import {useMutation, useQueryClient} from 'sentry/utils/queryClient';
 import type RequestError from 'sentry/utils/requestError/requestError';
 import useApi from 'sentry/utils/useApi';
 import useOrganization from 'sentry/utils/useOrganization';
-import {makeFetchSecretQueryKey} from 'sentry/views/settings/featureFlags';
+import {makeFetchSecretQueryKey} from 'sentry/views/settings/featureFlags/changeTracking';
 import type {
   CreateSecretQueryVariables,
   CreateSecretResponse,
-} from 'sentry/views/settings/featureFlags/newProviderForm';
+} from 'sentry/views/settings/featureFlags/changeTracking/newProviderForm';
 
 export default function OnboardingIntegrationSection({
   provider,
diff --git a/static/app/components/events/featureFlags/useFeatureFlagOnboarding.tsx b/static/app/components/events/featureFlags/useFeatureFlagOnboarding.tsx
index abfe3af57313..e1ebf9499053 100644
--- a/static/app/components/events/featureFlags/useFeatureFlagOnboarding.tsx
+++ b/static/app/components/events/featureFlags/useFeatureFlagOnboarding.tsx
@@ -31,7 +31,7 @@ export function useFeatureFlagOnboarding() {
   }, []);
 
   // if we detect that event.contexts.flags is set, use this hook instead
-  // to skip the configure step
+  // to hide the eval tracking SDK configuration.
   const activateSidebarSkipConfigure = useCallback(
     (event: React.MouseEvent, projectId: string) => {
       event.preventDefault();
diff --git a/static/app/routes.tsx b/static/app/routes.tsx
index a0c23f55871a..6302a4e962fb 100644
--- a/static/app/routes.tsx
+++ b/static/app/routes.tsx
@@ -1010,16 +1010,23 @@ function buildRoutes() {
         <IndexRoute
           component={make(() => import('sentry/views/settings/featureFlags'))}
         />
-        <Route
-          path="new-provider/"
-          name={t('Add New Provider')}
-          component={make(
-            () =>
-              import(
-                'sentry/views/settings/featureFlags/organizationFeatureFlagsNewSecret'
-              )
-          )}
-        />
+        <Route path="change-tracking/" name={t('Change Tracking')}>
+          <IndexRoute
+            component={make(
+              () => import('sentry/views/settings/featureFlags/changeTracking')
+            )}
+          />
+          <Route
+            path="new-provider/"
+            name={t('Add New Provider')}
+            component={make(
+              () =>
+                import(
+                  'sentry/views/settings/featureFlags/changeTracking/organizationFeatureFlagsNewSecret'
+                )
+            )}
+          />
+        </Route>
       </Route>
       <Route path="stats/" name={t('Stats')}>
         {statsChildRoutes}
diff --git a/static/app/views/settings/featureFlags/index.spec.tsx b/static/app/views/settings/featureFlags/changeTracking/index.spec.tsx
similarity index 93%
rename from static/app/views/settings/featureFlags/index.spec.tsx
rename to static/app/views/settings/featureFlags/changeTracking/index.spec.tsx
index 74fe75d71c95..4f104058499d 100644
--- a/static/app/views/settings/featureFlags/index.spec.tsx
+++ b/static/app/views/settings/featureFlags/changeTracking/index.spec.tsx
@@ -13,9 +13,9 @@ import {
 import * as indicators from 'sentry/actionCreators/indicator';
 import OrganizationsStore from 'sentry/stores/organizationsStore';
 import {
-  OrganizationFeatureFlagsIndex,
+  OrganizationFeatureFlagsChangeTracking,
   type Secret,
-} from 'sentry/views/settings/featureFlags';
+} from 'sentry/views/settings/featureFlags/changeTracking';
 
 describe('OrganizationFeatureFlagsIndex', function () {
   const SECRETS_ENDPOINT = '/organizations/org-slug/flags/signing-secrets/';
@@ -51,7 +51,7 @@ describe('OrganizationFeatureFlagsIndex', function () {
       body: {data: secrets},
     });
 
-    render(<OrganizationFeatureFlagsIndex />);
+    render(<OrganizationFeatureFlagsChangeTracking />);
 
     const secretsTable = within(screen.getByTestId('secrets-table'));
 
@@ -76,7 +76,7 @@ describe('OrganizationFeatureFlagsIndex', function () {
       statusCode: 400,
     });
 
-    render(<OrganizationFeatureFlagsIndex />);
+    render(<OrganizationFeatureFlagsChangeTracking />);
 
     const secretsTable = within(screen.getByTestId('secrets-table'));
 
@@ -98,7 +98,7 @@ describe('OrganizationFeatureFlagsIndex', function () {
       body: {data: secrets},
     });
 
-    render(<OrganizationFeatureFlagsIndex />);
+    render(<OrganizationFeatureFlagsChangeTracking />);
 
     const secretsTable = within(screen.getByTestId('secrets-table'));
 
@@ -127,7 +127,7 @@ describe('OrganizationFeatureFlagsIndex', function () {
         method: 'DELETE',
       });
 
-      render(<OrganizationFeatureFlagsIndex />);
+      render(<OrganizationFeatureFlagsChangeTracking />);
       renderGlobalModal();
 
       const secretsTable = within(screen.getByTestId('secrets-table'));
@@ -174,7 +174,7 @@ describe('OrganizationFeatureFlagsIndex', function () {
         body: {data: secrets},
       });
 
-      render(<OrganizationFeatureFlagsIndex />, {organization: org});
+      render(<OrganizationFeatureFlagsChangeTracking />, {organization: org});
 
       const secretsTable = within(screen.getByTestId('secrets-table'));
 
diff --git a/static/app/views/settings/featureFlags/changeTracking/index.tsx b/static/app/views/settings/featureFlags/changeTracking/index.tsx
new file mode 100644
index 000000000000..6e8f18570ce4
--- /dev/null
+++ b/static/app/views/settings/featureFlags/changeTracking/index.tsx
@@ -0,0 +1,215 @@
+import {Fragment} from 'react';
+import styled from '@emotion/styled';
+
+import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
+import {hasEveryAccess} from 'sentry/components/acl/access';
+import {LinkButton} from 'sentry/components/button';
+import {Flex} from 'sentry/components/container/flex';
+import ExternalLink from 'sentry/components/links/externalLink';
+import LoadingError from 'sentry/components/loadingError';
+import {PanelTable} from 'sentry/components/panels/panelTable';
+import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
+import {Tooltip} from 'sentry/components/tooltip';
+import {t, tct} from 'sentry/locale';
+import {space} from 'sentry/styles/space';
+import {handleXhrErrorResponse} from 'sentry/utils/handleXhrErrorResponse';
+import {
+  setApiQueryData,
+  useApiQuery,
+  useMutation,
+  useQueryClient,
+} from 'sentry/utils/queryClient';
+import type RequestError from 'sentry/utils/requestError/requestError';
+import useApi from 'sentry/utils/useApi';
+import useOrganization from 'sentry/utils/useOrganization';
+import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
+import TextBlock from 'sentry/views/settings/components/text/textBlock';
+import {OrganizationFeatureFlagsAuditLogTable} from 'sentry/views/settings/featureFlags/changeTracking/organizationFeatureFlagsAuditLogTable';
+import {OrganizationFeatureFlagsProviderRow} from 'sentry/views/settings/featureFlags/changeTracking/organizationFeatureFlagsProviderRow';
+
+export type Secret = {
+  createdAt: string;
+  createdBy: number;
+  id: number;
+  provider: string;
+  secret: string;
+};
+
+type FetchSecretResponse = {data: Secret[]};
+
+type FetchSecretParameters = {
+  orgSlug: string;
+};
+
+type RemoveSecretQueryVariables = {
+  id: number;
+};
+
+export const makeFetchSecretQueryKey = ({orgSlug}: FetchSecretParameters) =>
+  [`/organizations/${orgSlug}/flags/signing-secrets/`] as const;
+
+function SecretList({
+  secretList,
+  isRemoving,
+  removeSecret,
+}: {
+  isRemoving: boolean;
+  secretList: Secret[];
+  removeSecret?: (data: {id: number}) => void;
+}) {
+  return (
+    <Fragment>
+      {secretList.map(secret => {
+        return (
+          <OrganizationFeatureFlagsProviderRow
+            key={secret.id}
+            secret={secret}
+            isRemoving={isRemoving}
+            removeSecret={removeSecret ? () => removeSecret({id: secret.id}) : undefined}
+          />
+        );
+      })}
+    </Fragment>
+  );
+}
+
+export function OrganizationFeatureFlagsChangeTracking() {
+  const organization = useOrganization();
+  const api = useApi();
+  const queryClient = useQueryClient();
+
+  const {
+    isPending,
+    isError,
+    data: secretList,
+    refetch: refetchSecretList,
+  } = useApiQuery<FetchSecretResponse>(
+    makeFetchSecretQueryKey({orgSlug: organization.slug}),
+    {
+      staleTime: Infinity,
+    }
+  );
+
+  const {mutate: handleRemoveSecret, isPending: isRemoving} = useMutation<
+    unknown,
+    RequestError,
+    RemoveSecretQueryVariables
+  >({
+    mutationFn: ({id}) =>
+      api.requestPromise(
+        `/organizations/${organization.slug}/flags/signing-secrets/${id}/`,
+        {
+          method: 'DELETE',
+        }
+      ),
+
+    onSuccess: (_data, {id}) => {
+      addSuccessMessage(
+        t('Removed the provider and signing secret for the organization.')
+      );
+
+      setApiQueryData(
+        queryClient,
+        makeFetchSecretQueryKey({orgSlug: organization.slug}),
+        (oldData: FetchSecretResponse) => {
+          return {data: oldData.data.filter(oldSecret => oldSecret.id !== id)};
+        }
+      );
+    },
+    onError: error => {
+      const message = t('Failed to remove the provider or signing secret.');
+      handleXhrErrorResponse(message, error);
+      addErrorMessage(message);
+    },
+  });
+
+  const addNewProvider = (hasAccess: any) => (
+    <Tooltip
+      title={t('You must be an organization member to add a provider.')}
+      disabled={hasAccess}
+    >
+      <LinkButton
+        priority="primary"
+        size="sm"
+        to={`/settings/${organization.slug}/feature-flags/change-tracking/new-provider/`}
+        data-test-id="create-new-provider"
+        disabled={!hasAccess}
+      >
+        {t('Add New Provider')}
+      </LinkButton>
+    </Tooltip>
+  );
+
+  const canRead = hasEveryAccess(['org:read'], {organization});
+  const canWrite = hasEveryAccess(['org:write'], {organization});
+  const canAdmin = hasEveryAccess(['org:admin'], {organization});
+  const hasAccess = canRead || canWrite || canAdmin;
+  const hasDeleteAccess = canWrite || canAdmin;
+
+  return (
+    <Fragment>
+      <SentryDocumentTitle title={t('Change Tracking')} orgSlug={organization.slug} />
+      <SettingsPageHeader title={t('Change Tracking')} />
+      <TextBlock>
+        {tct(
+          'Integrating Sentry with your feature flag provider enables Sentry to correlate feature flag changes with new error events and mark certain changes as suspicious. Learn more about how to interact with feature flag insights within the Sentry UI by reading the [link:documentation].',
+          {
+            link: (
+              <ExternalLink href="https://docs.sentry.io/product/explore/feature-flags/#change-tracking" />
+            ),
+          }
+        )}
+      </TextBlock>
+
+      <Flex justify="space-between">
+        <h5>{t('Providers')}</h5>
+        {addNewProvider(hasAccess)}
+      </Flex>
+
+      <TextBlock>
+        {t(
+          'Look below for a list of the webhooks you have set up with external providers. Note that each provider can only have one associated signing secret.'
+        )}
+      </TextBlock>
+      <ResponsivePanelTable
+        isLoading={isPending || isError}
+        isEmpty={!isPending && !secretList?.data?.length}
+        loader={
+          isError ? (
+            <LoadingError
+              message={t('Failed to load secrets and providers for the organization.')}
+              onRetry={refetchSecretList}
+            />
+          ) : undefined
+        }
+        emptyMessage={t("You haven't linked any providers yet.")}
+        headers={[t('Provider'), t('Created'), t('Created by'), '']}
+        data-test-id="secrets-table"
+      >
+        {!isError && !isPending && !!secretList?.data?.length && (
+          <SecretList
+            secretList={secretList.data}
+            isRemoving={isRemoving}
+            removeSecret={hasDeleteAccess ? handleRemoveSecret : undefined}
+          />
+        )}
+      </ResponsivePanelTable>
+
+      <OrganizationFeatureFlagsAuditLogTable />
+    </Fragment>
+  );
+}
+
+export default OrganizationFeatureFlagsChangeTracking;
+
+const ResponsivePanelTable = styled(PanelTable)`
+  @media (max-width: ${p => p.theme.breakpoints.small}) {
+    grid-template-columns: 1fr 1fr;
+
+    > *:nth-child(4n + 2),
+    > *:nth-child(4n + 3) {
+      display: none;
+    }
+  }
+  margin-bottom: ${space(3)};
+`;
diff --git a/static/app/views/settings/featureFlags/newProviderForm.spec.tsx b/static/app/views/settings/featureFlags/changeTracking/newProviderForm.spec.tsx
similarity index 90%
rename from static/app/views/settings/featureFlags/newProviderForm.spec.tsx
rename to static/app/views/settings/featureFlags/changeTracking/newProviderForm.spec.tsx
index 141dc0457570..51b5c3d1797b 100644
--- a/static/app/views/settings/featureFlags/newProviderForm.spec.tsx
+++ b/static/app/views/settings/featureFlags/changeTracking/newProviderForm.spec.tsx
@@ -1,6 +1,6 @@
 import {render} from 'sentry-test/reactTestingLibrary';
 
-import NewProviderForm from 'sentry/views/settings/featureFlags/newProviderForm';
+import NewProviderForm from 'sentry/views/settings/featureFlags/changeTracking/newProviderForm';
 
 describe('NewProviderForm', () => {
   it('renders', () => {
diff --git a/static/app/views/settings/featureFlags/newProviderForm.tsx b/static/app/views/settings/featureFlags/changeTracking/newProviderForm.tsx
similarity index 97%
rename from static/app/views/settings/featureFlags/newProviderForm.tsx
rename to static/app/views/settings/featureFlags/changeTracking/newProviderForm.tsx
index 47772184d475..5fdd5d78d859 100644
--- a/static/app/views/settings/featureFlags/newProviderForm.tsx
+++ b/static/app/views/settings/featureFlags/changeTracking/newProviderForm.tsx
@@ -26,7 +26,7 @@ import normalizeUrl from 'sentry/utils/url/normalizeUrl';
 import useApi from 'sentry/utils/useApi';
 import {useNavigate} from 'sentry/utils/useNavigate';
 import useOrganization from 'sentry/utils/useOrganization';
-import {makeFetchSecretQueryKey} from 'sentry/views/settings/featureFlags';
+import {makeFetchSecretQueryKey} from 'sentry/views/settings/featureFlags/changeTracking';
 
 export type CreateSecretQueryVariables = {
   provider: string;
@@ -54,7 +54,9 @@ export default function NewProviderForm({
   const [selectedProvider, setSelectedProvider] = useState('<provider_name>');
 
   const handleGoBack = useCallback(() => {
-    navigate(normalizeUrl(`/settings/${organization.slug}/feature-flags/`));
+    navigate(
+      normalizeUrl(`/settings/${organization.slug}/feature-flags/change-tracking/`)
+    );
   }, [organization.slug, navigate]);
 
   const {mutate: submitSecret, isPending} = useMutation<
diff --git a/static/app/views/settings/featureFlags/newSecretHandler.spec.tsx b/static/app/views/settings/featureFlags/changeTracking/newSecretHandler.spec.tsx
similarity index 92%
rename from static/app/views/settings/featureFlags/newSecretHandler.spec.tsx
rename to static/app/views/settings/featureFlags/changeTracking/newSecretHandler.spec.tsx
index 618bbeb5dc65..0624518f2ed9 100644
--- a/static/app/views/settings/featureFlags/newSecretHandler.spec.tsx
+++ b/static/app/views/settings/featureFlags/changeTracking/newSecretHandler.spec.tsx
@@ -2,7 +2,7 @@ import {SecretFixture} from 'sentry-fixture/secret';
 
 import {render} from 'sentry-test/reactTestingLibrary';
 
-import NewSecretHandler from 'sentry/views/settings/featureFlags/newSecretHandler';
+import NewSecretHandler from 'sentry/views/settings/featureFlags/changeTracking/newSecretHandler';
 
 describe('NewSecretHandler', () => {
   it('renders', () => {
diff --git a/static/app/views/settings/featureFlags/newSecretHandler.tsx b/static/app/views/settings/featureFlags/changeTracking/newSecretHandler.tsx
similarity index 100%
rename from static/app/views/settings/featureFlags/newSecretHandler.tsx
rename to static/app/views/settings/featureFlags/changeTracking/newSecretHandler.tsx
diff --git a/static/app/views/settings/featureFlags/organizationFeatureFlagsAuditLogTable.tsx b/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsAuditLogTable.tsx
similarity index 100%
rename from static/app/views/settings/featureFlags/organizationFeatureFlagsAuditLogTable.tsx
rename to static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsAuditLogTable.tsx
diff --git a/static/app/views/settings/featureFlags/organizationFeatureFlagsNewSecret.spec.tsx b/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsNewSecret.spec.tsx
similarity index 97%
rename from static/app/views/settings/featureFlags/organizationFeatureFlagsNewSecret.spec.tsx
rename to static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsNewSecret.spec.tsx
index ca24a24f50c9..3e9521047f75 100644
--- a/static/app/views/settings/featureFlags/organizationFeatureFlagsNewSecret.spec.tsx
+++ b/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsNewSecret.spec.tsx
@@ -5,7 +5,7 @@ import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
 
 import * as indicators from 'sentry/actionCreators/indicator';
 import OrganizationsStore from 'sentry/stores/organizationsStore';
-import OrganizationFeatureFlagsNewSecet from 'sentry/views/settings/featureFlags/organizationFeatureFlagsNewSecret';
+import OrganizationFeatureFlagsNewSecet from 'sentry/views/settings/featureFlags/changeTracking/organizationFeatureFlagsNewSecret';
 
 describe('OrganizationFeatureFlagsNewSecret', function () {
   const ENDPOINT = '/organizations/org-slug/flags/signing-secrets/';
diff --git a/static/app/views/settings/featureFlags/organizationFeatureFlagsNewSecret.tsx b/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsNewSecret.tsx
similarity index 93%
rename from static/app/views/settings/featureFlags/organizationFeatureFlagsNewSecret.tsx
rename to static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsNewSecret.tsx
index 3dd58e89d941..340834fb1cfb 100644
--- a/static/app/views/settings/featureFlags/organizationFeatureFlagsNewSecret.tsx
+++ b/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsNewSecret.tsx
@@ -12,8 +12,8 @@ import {useNavigate} from 'sentry/utils/useNavigate';
 import useOrganization from 'sentry/utils/useOrganization';
 import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
 import TextBlock from 'sentry/views/settings/components/text/textBlock';
-import NewProviderForm from 'sentry/views/settings/featureFlags/newProviderForm';
-import NewSecretHandler from 'sentry/views/settings/featureFlags/newSecretHandler';
+import NewProviderForm from 'sentry/views/settings/featureFlags/changeTracking/newProviderForm';
+import NewSecretHandler from 'sentry/views/settings/featureFlags/changeTracking/newSecretHandler';
 
 export function OrganizationFeatureFlagsNewSecet() {
   const [newSecret, setNewSecret] = useState<string | null>(null);
@@ -22,7 +22,9 @@ export function OrganizationFeatureFlagsNewSecet() {
   const navigate = useNavigate();
 
   const handleGoBack = useCallback(() => {
-    navigate(normalizeUrl(`/settings/${organization.slug}/feature-flags/`));
+    navigate(
+      normalizeUrl(`/settings/${organization.slug}/feature-flags/change-tracking/`)
+    );
   }, [organization.slug, navigate]);
 
   return (
diff --git a/static/app/views/settings/featureFlags/organizationFeatureFlagsProviderRow.spec.tsx b/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsProviderRow.spec.tsx
similarity index 94%
rename from static/app/views/settings/featureFlags/organizationFeatureFlagsProviderRow.spec.tsx
rename to static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsProviderRow.spec.tsx
index 1ce8e63c7dae..499046d323dc 100644
--- a/static/app/views/settings/featureFlags/organizationFeatureFlagsProviderRow.spec.tsx
+++ b/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsProviderRow.spec.tsx
@@ -9,8 +9,8 @@ import {
 } from 'sentry-test/reactTestingLibrary';
 
 import OrganizationsStore from 'sentry/stores/organizationsStore';
-import type {Secret} from 'sentry/views/settings/featureFlags';
-import {OrganizationFeatureFlagsProviderRow} from 'sentry/views/settings/featureFlags/organizationFeatureFlagsProviderRow';
+import type {Secret} from 'sentry/views/settings/featureFlags/changeTracking';
+import {OrganizationFeatureFlagsProviderRow} from 'sentry/views/settings/featureFlags/changeTracking/organizationFeatureFlagsProviderRow';
 
 describe('OrganizationFeatureFlagsProviderRow', function () {
   const {organization, router} = initializeOrg();
diff --git a/static/app/views/settings/featureFlags/organizationFeatureFlagsProviderRow.tsx b/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsProviderRow.tsx
similarity index 96%
rename from static/app/views/settings/featureFlags/organizationFeatureFlagsProviderRow.tsx
rename to static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsProviderRow.tsx
index 2de05154f371..888b2e2aeb1d 100644
--- a/static/app/views/settings/featureFlags/organizationFeatureFlagsProviderRow.tsx
+++ b/static/app/views/settings/featureFlags/changeTracking/organizationFeatureFlagsProviderRow.tsx
@@ -11,7 +11,7 @@ import {IconSubtract} from 'sentry/icons';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import useUserFromId from 'sentry/utils/useUserFromId';
-import type {Secret} from 'sentry/views/settings/featureFlags';
+import type {Secret} from 'sentry/views/settings/featureFlags/changeTracking';
 
 export function OrganizationFeatureFlagsProviderRow({
   isRemoving,
diff --git a/static/app/views/settings/featureFlags/index.tsx b/static/app/views/settings/featureFlags/index.tsx
index 0b6a64b58f32..97d69a9c68bb 100644
--- a/static/app/views/settings/featureFlags/index.tsx
+++ b/static/app/views/settings/featureFlags/index.tsx
@@ -1,150 +1,22 @@
 import {Fragment} from 'react';
 import styled from '@emotion/styled';
 
-import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
-import {hasEveryAccess} from 'sentry/components/acl/access';
-import {LinkButton} from 'sentry/components/button';
-import {Flex} from 'sentry/components/container/flex';
+import {Button, LinkButton} from 'sentry/components/button';
+import {useFeatureFlagOnboarding} from 'sentry/components/events/featureFlags/useFeatureFlagOnboarding';
+import FieldGroup from 'sentry/components/forms/fieldGroup';
 import ExternalLink from 'sentry/components/links/externalLink';
-import LoadingError from 'sentry/components/loadingError';
-import {PanelTable} from 'sentry/components/panels/panelTable';
+import Panel from 'sentry/components/panels/panel';
+import PanelBody from 'sentry/components/panels/panelBody';
+import PanelHeader from 'sentry/components/panels/panelHeader';
 import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
-import {Tooltip} from 'sentry/components/tooltip';
 import {t, tct} from 'sentry/locale';
-import {space} from 'sentry/styles/space';
-import {handleXhrErrorResponse} from 'sentry/utils/handleXhrErrorResponse';
-import {
-  setApiQueryData,
-  useApiQuery,
-  useMutation,
-  useQueryClient,
-} from 'sentry/utils/queryClient';
-import type RequestError from 'sentry/utils/requestError/requestError';
-import useApi from 'sentry/utils/useApi';
 import useOrganization from 'sentry/utils/useOrganization';
 import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
 import TextBlock from 'sentry/views/settings/components/text/textBlock';
-import {OrganizationFeatureFlagsAuditLogTable} from 'sentry/views/settings/featureFlags/organizationFeatureFlagsAuditLogTable';
-import {OrganizationFeatureFlagsProviderRow} from 'sentry/views/settings/featureFlags/organizationFeatureFlagsProviderRow';
 
-export type Secret = {
-  createdAt: string;
-  createdBy: number;
-  id: number;
-  provider: string;
-  secret: string;
-};
-
-type FetchSecretResponse = {data: Secret[]};
-
-type FetchSecretParameters = {
-  orgSlug: string;
-};
-
-type RemoveSecretQueryVariables = {
-  id: number;
-};
-
-export const makeFetchSecretQueryKey = ({orgSlug}: FetchSecretParameters) =>
-  [`/organizations/${orgSlug}/flags/signing-secrets/`] as const;
-
-function SecretList({
-  secretList,
-  isRemoving,
-  removeSecret,
-}: {
-  isRemoving: boolean;
-  secretList: Secret[];
-  removeSecret?: (data: {id: number}) => void;
-}) {
-  return (
-    <Fragment>
-      {secretList.map(secret => {
-        return (
-          <OrganizationFeatureFlagsProviderRow
-            key={secret.id}
-            secret={secret}
-            isRemoving={isRemoving}
-            removeSecret={removeSecret ? () => removeSecret({id: secret.id}) : undefined}
-          />
-        );
-      })}
-    </Fragment>
-  );
-}
-
-export function OrganizationFeatureFlagsIndex() {
+export default function OrganizationFeatureFlagsIndex() {
   const organization = useOrganization();
-  const api = useApi();
-  const queryClient = useQueryClient();
-
-  const {
-    isPending,
-    isError,
-    data: secretList,
-    refetch: refetchSecretList,
-  } = useApiQuery<FetchSecretResponse>(
-    makeFetchSecretQueryKey({orgSlug: organization.slug}),
-    {
-      staleTime: Infinity,
-    }
-  );
-
-  const {mutate: handleRemoveSecret, isPending: isRemoving} = useMutation<
-    unknown,
-    RequestError,
-    RemoveSecretQueryVariables
-  >({
-    mutationFn: ({id}) =>
-      api.requestPromise(
-        `/organizations/${organization.slug}/flags/signing-secrets/${id}/`,
-        {
-          method: 'DELETE',
-        }
-      ),
-
-    onSuccess: (_data, {id}) => {
-      addSuccessMessage(
-        t('Removed the provider and signing secret for the organization.')
-      );
-
-      setApiQueryData(
-        queryClient,
-        makeFetchSecretQueryKey({orgSlug: organization.slug}),
-        (oldData: FetchSecretResponse) => {
-          return {data: oldData.data.filter(oldSecret => oldSecret.id !== id)};
-        }
-      );
-    },
-    onError: error => {
-      const message = t('Failed to remove the provider or signing secret.');
-      handleXhrErrorResponse(message, error);
-      addErrorMessage(message);
-    },
-  });
-
-  const addNewProvider = (hasAccess: any) => (
-    <Tooltip
-      title={t('You must be an organization member to add a provider.')}
-      disabled={hasAccess}
-    >
-      <LinkButton
-        priority="primary"
-        size="sm"
-        to={`/settings/${organization.slug}/feature-flags/new-provider/`}
-        data-test-id="create-new-provider"
-        disabled={!hasAccess}
-      >
-        {t('Add New Provider')}
-      </LinkButton>
-    </Tooltip>
-  );
-
-  const canRead = hasEveryAccess(['org:read'], {organization});
-  const canWrite = hasEveryAccess(['org:write'], {organization});
-  const canAdmin = hasEveryAccess(['org:admin'], {organization});
-  const hasAccess = canRead || canWrite || canAdmin;
-  const hasDeleteAccess = canWrite || canAdmin;
+  const {activateSidebar} = useFeatureFlagOnboarding();
 
   return (
     <Fragment>
@@ -152,63 +24,55 @@ export function OrganizationFeatureFlagsIndex() {
       <SettingsPageHeader title={t('Feature Flags')} />
       <TextBlock>
         {tct(
-          'Integrating Sentry with your feature flag provider enables Sentry to correlate feature flag changes with new error events and mark certain changes as suspicious. Learn more about how to interact with feature flag insights within the Sentry UI by reading the [link:documentation].',
+          'Integrating Sentry with your feature flag provider enables Sentry to correlate feature flag changes with new error events and mark certain changes as suspicious. To learn more about our feature flag features, [link:read our docs].',
           {
             link: (
-              <ExternalLink href="https://docs.sentry.io/product/explore/feature-flags/#change-tracking" />
+              <ExternalLink href="https://docs.sentry.io/product/issues/issue-details/feature-flags/" />
             ),
           }
         )}
       </TextBlock>
 
-      <Flex justify="space-between">
-        <h5>{t('Providers')}</h5>
-        {addNewProvider(hasAccess)}
-      </Flex>
-      <TextBlock>
-        {t(
-          'Look below for a list of the webhooks you have set up with external providers. Note that each provider can only have one associated signing secret.'
-        )}
-      </TextBlock>
-      <ResponsivePanelTable
-        isLoading={isPending || isError}
-        isEmpty={!isPending && !secretList?.data?.length}
-        loader={
-          isError ? (
-            <LoadingError
-              message={t('Failed to load secrets and providers for the organization.')}
-              onRetry={refetchSecretList}
-            />
-          ) : undefined
-        }
-        emptyMessage={t("You haven't linked any providers yet.")}
-        headers={[t('Provider'), t('Created'), t('Created by'), '']}
-        data-test-id="secrets-table"
-      >
-        {!isError && !isPending && !!secretList?.data?.length && (
-          <SecretList
-            secretList={secretList.data}
-            isRemoving={isRemoving}
-            removeSecret={hasDeleteAccess ? handleRemoveSecret : undefined}
-          />
-        )}
-      </ResponsivePanelTable>
-
-      <OrganizationFeatureFlagsAuditLogTable />
+      <Panel>
+        <PanelHeader>{t('Features')}</PanelHeader>
+        <PanelBody>
+          <FieldGroup
+            alignRight
+            flexibleControlStateSize
+            label={<Large>{t('Evaluation Tracking')}</Large>}
+            help={t(
+              'Evaluation tracking enables Sentry to capture flag values on your error events. Flag evaluations will appear in the "Feature Flags" section of the Issue Details page.'
+            )}
+          >
+            <Button
+              aria-label={t('Set Up Evaluation Tracking')}
+              onClick={mouseEvent => {
+                activateSidebar(mouseEvent);
+              }}
+            >
+              {t('Set Up Project')}
+            </Button>
+          </FieldGroup>
+          <FieldGroup
+            alignRight
+            flexibleControlStateSize
+            label={<Large>{t('Change Tracking')}</Large>}
+            help={t(
+              'Change tracking enables Sentry to listen for additions, removals, and modifications to your feature flags.'
+            )}
+          >
+            <LinkButton
+              to={`/settings/${organization.slug}/feature-flags/change-tracking/`}
+            >
+              {t('Manage Providers')}
+            </LinkButton>
+          </FieldGroup>
+        </PanelBody>
+      </Panel>
     </Fragment>
   );
 }
 
-export default OrganizationFeatureFlagsIndex;
-
-const ResponsivePanelTable = styled(PanelTable)`
-  @media (max-width: ${p => p.theme.breakpoints.small}) {
-    grid-template-columns: 1fr 1fr;
-
-    > *:nth-child(4n + 2),
-    > *:nth-child(4n + 3) {
-      display: none;
-    }
-  }
-  margin-bottom: ${space(3)};
+const Large = styled('span')`
+  font-size: ${p => p.theme.fontSizeLarge};
 `;
diff --git a/static/app/views/settings/organization/navigationConfiguration.tsx b/static/app/views/settings/organization/navigationConfiguration.tsx
index f003cf8b4c3a..e65216e9e53f 100644
--- a/static/app/views/settings/organization/navigationConfiguration.tsx
+++ b/static/app/views/settings/organization/navigationConfiguration.tsx
@@ -135,7 +135,7 @@ export function getOrganizationNavigationConfiguration({
         {
           path: `${organizationSettingsPathPrefix}/feature-flags/`,
           title: t('Feature Flags'),
-          description: t('Set up your provider webhooks'),
+          description: t('Set up feature flag integrations'),
           badge: () => 'beta',
           show: ({organization}) =>
             !!organization && organization.features.includes('feature-flag-ui'),
diff --git a/static/app/views/settings/organization/userOrgNavigationConfiguration.tsx b/static/app/views/settings/organization/userOrgNavigationConfiguration.tsx
index be9d4040c22a..703cc9599c38 100644
--- a/static/app/views/settings/organization/userOrgNavigationConfiguration.tsx
+++ b/static/app/views/settings/organization/userOrgNavigationConfiguration.tsx
@@ -173,7 +173,7 @@ export function getUserOrgNavigationConfiguration({
         {
           path: `${organizationSettingsPathPrefix}/feature-flags/`,
           title: t('Feature Flags'),
-          description: t('Set up your provider webhooks'),
+          description: t('Set up feature flag integrations'),
           badge: () => 'beta',
           show: ({organization}) =>
             !!organization && organization.features.includes('feature-flag-ui'),
diff --git a/tests/js/fixtures/secret.ts b/tests/js/fixtures/secret.ts
index b834b432c26b..3596be5bd5bf 100644
--- a/tests/js/fixtures/secret.ts
+++ b/tests/js/fixtures/secret.ts
@@ -1,4 +1,4 @@
-import { Secret } from "sentry/views/settings/featureFlags";
+import { Secret } from "sentry/views/settings/featureFlags/changeTracking";
 
 
 export function SecretFixture(


Reviews:
user: login: aliu39
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T01:42:29Z
user: login: ryan953
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T17:34:27Z
user: login: ryan953
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T17:34:55Z
user: login: ryan953
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T17:37:13Z
user: login: michellewzhang
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T18:31:50Z
user: login: aliu39
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T18:32:55Z
user: login: michellewzhang
body: looks good
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T18:35:10Z
user: login: aliu39
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T18:36:49Z
user: login: michellewzhang
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T18:38:52Z
user: login: aliu39
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T18:41:54Z
user: login: aliu39
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T18:49:55Z
user: login: aliu39
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T18:55:17Z
user: login: michellewzhang
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T18:56:45Z
user: login: michellewzhang
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T18:57:17Z
user: login: aliu39
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T19:02:30Z

Review comments:
diff_hunk: @@ -135,7 +135,7 @@ export function getOrganizationNavigationConfiguration({
         {
           path: `${organizationSettingsPathPrefix}/feature-flags/`,
           title: t('Feature Flags'),
-          description: t('Set up your provider webhooks'),
+          description: t('Set up feature flag integrations'),
path: static/app/views/settings/organization/navigationConfiguration.tsx
user: login: aliu39
body: Not sure if we should also add `/feature-flags/change-tracking/` here, seems like other paths are top-level only
created_at: 2025-03-03T01:42:29Z
author_association: MEMBER
diff_hunk: @@ -95,28 +93,32 @@ export function FeatureFlagOnboardingLayout({
   return (
     <AuthTokenGeneratorProvider projectSlug={projectSlug}>
       <Wrapper>
-        {!skipConfig ? null : (
+        {!skipEvalTracking ? null : (
path: static/app/components/events/featureFlags/featureFlagOnboardingLayout.tsx
user: login: ryan953
body: can this be like `{skipEvalTracking ? <Alert>...</Alert> : null}` instead of `!skipEvalTracking`

phrase the condition in the positive.
created_at: 2025-03-03T17:34:27Z
author_association: MEMBER
diff_hunk: @@ -95,28 +93,32 @@ export function FeatureFlagOnboardingLayout({
   return (
     <AuthTokenGeneratorProvider projectSlug={projectSlug}>
       <Wrapper>
-        {!skipConfig ? null : (
+        {!skipEvalTracking ? null : (
           <Alert.Container>
             <Alert type="info" showIcon>
               <Flex gap={space(3)}>
                 {t(
                   'Feature flag integration detected. Please follow the remaining steps.'
                 )}
-                <Button onClick={() => setSkipSteps(!skipSteps)}>
-                  {skipSteps ? t('Show Full Guide') : t('Hide Full Guide')}
+                <Button onClick={() => setHideSteps(!hideSteps)}>
+                  {hideSteps ? t('Show Full Guide') : t('Hide Full Guide')}
                 </Button>
               </Flex>
             </Alert>
           </Alert.Container>
         )}
-        {!skipSteps && (
+        {!hideSteps && (
path: static/app/components/events/featureFlags/featureFlagOnboardingLayout.tsx
user: login: ryan953
body: can we phrase the condition in the positive here? `{hideSteps ? null : <Steps>...</Steps>}`
created_at: 2025-03-03T17:34:55Z
author_association: MEMBER
diff_hunk: @@ -95,28 +93,32 @@ export function FeatureFlagOnboardingLayout({
   return (
     <AuthTokenGeneratorProvider projectSlug={projectSlug}>
       <Wrapper>
-        {!skipConfig ? null : (
+        {!skipEvalTracking ? null : (
           <Alert.Container>
             <Alert type="info" showIcon>
               <Flex gap={space(3)}>
                 {t(
                   'Feature flag integration detected. Please follow the remaining steps.'
                 )}
-                <Button onClick={() => setSkipSteps(!skipSteps)}>
-                  {skipSteps ? t('Show Full Guide') : t('Hide Full Guide')}
+                <Button onClick={() => setHideSteps(!hideSteps)}>
+                  {hideSteps ? t('Show Full Guide') : t('Hide Full Guide')}
                 </Button>
               </Flex>
             </Alert>
           </Alert.Container>
         )}
-        {!skipSteps && (
+        {!hideSteps && (
           <Steps>
             {steps.map(step => (
               <Step key={step.title ?? step.type} {...step} />
             ))}
+            <StyledLinkButton to={`/issues/`} priority="primary">
path: static/app/components/events/featureFlags/featureFlagOnboardingLayout.tsx
user: login: ryan953
body: ```suggestion
            <StyledLinkButton to="/issues/" priority="primary">
```
created_at: 2025-03-03T17:37:13Z
author_association: MEMBER
diff_hunk: @@ -135,7 +135,7 @@ export function getOrganizationNavigationConfiguration({
         {
           path: `${organizationSettingsPathPrefix}/feature-flags/`,
           title: t('Feature Flags'),
-          description: t('Set up your provider webhooks'),
+          description: t('Set up feature flag integrations'),
path: static/app/views/settings/organization/navigationConfiguration.tsx
user: login: michellewzhang
body: i think only add the link to the index page -- does it show up in the sidebar if you add the change tracking link too?
created_at: 2025-03-03T18:31:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -135,7 +135,7 @@ export function getOrganizationNavigationConfiguration({
         {
           path: `${organizationSettingsPathPrefix}/feature-flags/`,
           title: t('Feature Flags'),
-          description: t('Set up your provider webhooks'),
+          description: t('Set up feature flag integrations'),
path: static/app/views/settings/organization/navigationConfiguration.tsx
user: login: aliu39
body: oh I see this is for the sidebar, I won't add it then
created_at: 2025-03-03T18:32:55Z
author_association: MEMBER
diff_hunk: @@ -183,27 +175,21 @@ function SidebarContent() {
             />
           </div>
         </HeaderActions>
-        <OnboardingContent currentProject={selectedProject} hasDocs={hasDocs} />
+        {currentProject && <OnboardingContent currentProject={currentProject} />}
path: static/app/components/events/featureFlags/featureFlagOnboardingSidebar.tsx
user: login: aliu39
body: Deciding to hide here if `currentProject` is undef so user is forced to select a project. New empty state:
![Screenshot 2025-03-03 at 10 35 10 AM](https://github.com/user-attachments/assets/ac26d5c0-33ae-4a58-b537-c6fc07291f72)

Before there'd be some cases where `selectedProject` was defined, but docs failed to load and it'd mistakenly say "platform is not supported"
created_at: 2025-03-03T18:36:49Z
author_association: MEMBER
diff_hunk: @@ -183,27 +175,21 @@ function SidebarContent() {
             />
           </div>
         </HeaderActions>
-        <OnboardingContent currentProject={selectedProject} hasDocs={hasDocs} />
+        {currentProject && <OnboardingContent currentProject={currentProject} />}
path: static/app/components/events/featureFlags/featureFlagOnboardingSidebar.tsx
user: login: michellewzhang
body: we should have some empty state text here at least, not just a blank page

>  there'd be some cases where selectedProject was defined, but docs failed to load and it'd mistakenly say "platform is not supported"

can you give an example / video of this happening?
created_at: 2025-03-03T18:38:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -183,27 +175,21 @@ function SidebarContent() {
             />
           </div>
         </HeaderActions>
-        <OnboardingContent currentProject={selectedProject} hasDocs={hasDocs} />
+        {currentProject && <OnboardingContent currentProject={currentProject} />}
path: static/app/components/events/featureFlags/featureFlagOnboardingSidebar.tsx
user: login: aliu39
body: Maybe "Select a project from the drop-down to view set up instructions for your platform."?
created_at: 2025-03-03T18:41:54Z
author_association: MEMBER
diff_hunk: @@ -183,27 +175,21 @@ function SidebarContent() {
             />
           </div>
         </HeaderActions>
-        <OnboardingContent currentProject={selectedProject} hasDocs={hasDocs} />
+        {currentProject && <OnboardingContent currentProject={currentProject} />}
path: static/app/components/events/featureFlags/featureFlagOnboardingSidebar.tsx
user: login: aliu39
body: https://github.com/user-attachments/assets/f7b917e4-344c-485a-b3bd-aeb8dc62c2e1

Using dev-ui on master with an `activateSidebar` button on the current settings page. It's getting platform = React from the first project in the list, advent of bugs, but failing to import docs
created_at: 2025-03-03T18:49:55Z
author_association: MEMBER
diff_hunk: @@ -183,27 +175,21 @@ function SidebarContent() {
             />
           </div>
         </HeaderActions>
-        <OnboardingContent currentProject={selectedProject} hasDocs={hasDocs} />
+        {currentProject && <OnboardingContent currentProject={currentProject} />}
path: static/app/components/events/featureFlags/featureFlagOnboardingSidebar.tsx
user: login: aliu39
body: Think this is from the discrepancy between [selectedProject](https://github.com/getsentry/sentry/blob/1c6082133202c1936a43ec89877d03280bd83ada/static/app/components/events/featureFlags/featureFlagOnboardingSidebar.tsx#L142) and the [useCurrentProjectState](https://github.com/getsentry/sentry/blob/1c6082133202c1936a43ec89877d03280bd83ada/static/app/components/events/featureFlags/featureFlagOnboardingSidebar.tsx#L90-L103) used to load `hasDocs`
created_at: 2025-03-03T18:55:17Z
author_association: MEMBER
diff_hunk: @@ -183,27 +175,21 @@ function SidebarContent() {
             />
           </div>
         </HeaderActions>
-        <OnboardingContent currentProject={selectedProject} hasDocs={hasDocs} />
+        {currentProject && <OnboardingContent currentProject={currentProject} />}
path: static/app/components/events/featureFlags/featureFlagOnboardingSidebar.tsx
user: login: michellewzhang
body: do you mean it fails to import at first since the settings page is org-based and we haven't picked a project?
created_at: 2025-03-03T18:56:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -183,27 +175,21 @@ function SidebarContent() {
             />
           </div>
         </HeaderActions>
-        <OnboardingContent currentProject={selectedProject} hasDocs={hasDocs} />
+        {currentProject && <OnboardingContent currentProject={currentProject} />}
path: static/app/components/events/featureFlags/featureFlagOnboardingSidebar.tsx
user: login: michellewzhang
body: in that case you can hardcode the first project in the list to be auto-selected if no selected project found right? 
created_at: 2025-03-03T18:57:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -183,27 +175,21 @@ function SidebarContent() {
             />
           </div>
         </HeaderActions>
-        <OnboardingContent currentProject={selectedProject} hasDocs={hasDocs} />
+        {currentProject && <OnboardingContent currentProject={currentProject} />}
path: static/app/components/events/featureFlags/featureFlagOnboardingSidebar.tsx
user: login: aliu39
body: Yeah I could update the auto-selection, but for settings page I prefer empty state -> select a proj
New empty state
![Screenshot 2025-03-03 at 11 02 14 AM](https://github.com/user-attachments/assets/a7b3a5d4-0897-4903-a2c6-dbd70004b46a)
created_at: 2025-03-03T19:02:30Z
author_association: MEMBER

PR comments:
user: login: codecov[bot]
created_at: 2025-03-03T01:48:03Z
body: ## [Codecov](https://app.codecov.io/gh/getsentry/sentry/pull/86070?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry) Report
All modified and coverable lines are covered by tests :white_check_mark:

:white_check_mark: All tests successful. No failed tests found.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master   #86070      +/-   ##
==========================================
- Coverage   87.92%   87.89%   -0.03%     
==========================================
  Files        9719     9682      -37     
  Lines      552476   547788    -4688     
  Branches    21445    21250     -195     
==========================================
- Hits       485777   481501    -4276     
+ Misses      66319    65980     -339     
+ Partials      380      307      -73     
```

</details>
author_association: NONE
user: login: ryan953
created_at: 2025-03-03T16:11:43Z
body: Can you throw up a screenshot of what got built? linking to the figma is helpful, but if people come back to this PR then they'll be curious to know what things looked like in the browser at this point in time.
author_association: MEMBER
user: login: codecov[bot]
created_at: 2025-03-03T18:35:36Z
body: ## [Bundle](https://app.codecov.io/gh/getsentry/sentry/pull/86070?dropdown=bundle) Report

Changes will increase total bundle size by 48.56kB (0.12%) :arrow_up:. This is within the [configured](https://docs.codecov.com/docs/javascript-bundle-analysis#main-features) threshold :white_check_mark:

<details><summary>Detailed changes</summary>

| Bundle name | Size | Change |
| ----------- | ---- | ------ |
| app-webpack-bundle-array-push | 41.73MB | 48.56kB (0.12%) :arrow_up: |

</details>

### Affected Assets, Files, and Routes:

<details>
<summary>view changes for bundle: app-webpack-bundle-array-push</summary>

#### **Assets Changed:**
| Asset Name | Size Change | Total Size | Change (%) |
| ---------- | ----------- | ---------- | ---------- |
| ```chunks/app_components_avatar_actorAvatar_tsx-app_components_avatar_avatarList_tsx-app_components_cha-e41958.*.js``` | 800 bytes | 1.37MB | 0.06%  |
| ```chunks/app_actionCreators_redirectToProject_tsx-app_actionCreators_sudoModal_tsx-app_components_aler-ee9c33.*.js``` | 46 bytes | 452.36kB | 0.01%  |
| ```chunks/gsApp_registerHooks_tsx.*.js``` | -4.46kB | 355.82kB | -1.24%  |
| ```chunks/app_actionCreators_members_tsx-app_components_avatar_avatarList_tsx-app_components_deprecated-c50dcc.*.js``` | 665 bytes | 156.64kB | 0.43%  |
| ```chunks/app_views_traces_content_tsx-node_modules_lodash_omitBy_js-node_modules_lodash_partialRight_j-d4a98b.*.js``` | 14.53kB | 135.31kB | 12.03% :warning: |
| **```chunks/app_components_feedback_widget_us*kWidget_tsx-app_views_projectDetail_index_tsx.e29a72a98843bbc12ea2.js```** _(New)_ | 129.73kB | 129.73kB | 100.0% :rocket: |
| ```entrypoints/app.js``` | 141 bytes | 109.15kB | 0.13%  |
| ```chunks/app_components_search_index_tsx.*.js``` | 6 bytes | 72.25kB | 0.01%  |
| ```chunks/app_views_stories_index_tsx.*.js``` | 150 bytes | 43.4kB | 0.35%  |
| **```chunks/app_components_charts_eventsRequest_tsx-app_components_feedback_widget_us*kWidget_tsx--fbb1b9.7747a02eb654e50295e6.js```** _(New)_ | 31.1kB | 31.1kB | 100.0% :rocket: |
| **```chunks/app_components_tours_tour_stories_tsx.*.js```** _(New)_ | 27.46kB | 27.46kB | 100.0% :rocket: |
| ```chunks/app_components_charts_components_dataZoomInside_tsx-app_components_charts_components_toolBox_-440387.*.js``` | 3.8kB | 26.97kB | 16.4% :warning: |
| **```chunks/app_components_feedback_widget_us*kWidget_tsx-app_views_performance_transactionSummary-51d702.8edfb8fa22cd2c8af028.js```** _(New)_ | 25.69kB | 25.69kB | 100.0% :rocket: |
| **```chunks/app_components_feedback_widget_us*kWidget_tsx-app_components_performance_transactionSe-feae7d.c95ee360f043b14ea134.js```** _(New)_ | 24.18kB | 24.18kB | 100.0% :rocket: |
| **```chunks/app_components_feedback_widget_us*kWidget_tsx-app_utils_discover_discoverQuery_tsx-app-c5fefe.0475d49c6ea33b4c2511.js```** _(New)_ | 17.41kB | 17.41kB | 100.0% :rocket: |
| **```chunks/app_components_feedback_widget_us*kWidget_tsx-app_components_gridEditable_sortLink_tsx-d372f4.4a93445975672c7ae010.js```** _(New)_ | 16.21kB | 16.21kB | 100.0% :rocket: |
| **```chunks/app_components_feedback_widget_us*kWidget_tsx-app_components_performance_transactionSe-f61270.82a01a0a41d43c3b6d9b.js```** _(New)_ | 13.36kB | 13.36kB | 100.0% :rocket: |
| **```chunks/app_components_charts_lineChart_tsx-app_components_feedback_widget_us*kWidget_tsx-app_-26fc43.a6c2e9d66fb6f45fa082.js```** _(New)_ | 12.34kB | 12.34kB | 100.0% :rocket: |
| ```chunks/app_views_replays_detail_useAllMobileProj_tsx-app_views_replays_replayTable_index_tsx-app_vie-c01670.*.js``` | 567 bytes | 10.8kB | 5.54% :warning: |
| **```chunks/app_views_settings_featureFlags_changeTracking_organizationFeatureFlagsNewSecret_tsx.*.js```** _(New)_ | 10.59kB | 10.59kB | 100.0% :rocket: |
| **```chunks/app_views_settings_featureFlags_index_tsx.*.js```** _(New)_ | 4.76kB | 4.76kB | 100.0% :rocket: |
| ~~**```chunks/app_components_feedback_widget_us*kWidget_tsx-app_views_projectDetail_index_tsx.552edbaab65367bd32a4.js```**~~ _(Deleted)_ | -129.73kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/app_components_charts_eventsRequest_tsx-app_components_feedback_widget_us*kWidget_tsx--fbb1b9.cf06900e4f7fcc86a111.js```**~~ _(Deleted)_ | -31.1kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/app_components_feedback_widget_us*kWidget_tsx-app_views_performance_transactionSummary-51d702.aa01909c631e36113d41.js```**~~ _(Deleted)_ | -25.69kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/app_components_feedback_widget_us*kWidget_tsx-app_components_performance_transactionSe-feae7d.cd5d875f0e4327c0c148.js```**~~ _(Deleted)_ | -24.18kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/app_components_feedback_widget_us*kWidget_tsx-app_utils_discover_discoverQuery_tsx-app-c5fefe.dbc5cb7f824f5b39405e.js```**~~ _(Deleted)_ | -17.41kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/app_components_feedback_widget_us*kWidget_tsx-app_components_gridEditable_sortLink_tsx-d372f4.ce48f2b5da4beb27a438.js```**~~ _(Deleted)_ | -16.21kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/app_components_feedback_widget_us*kWidget_tsx-app_components_performance_transactionSe-f61270.cef1f8878f1f42052bc0.js```**~~ _(Deleted)_ | -13.36kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/app_components_charts_lineChart_tsx-app_components_feedback_widget_us*kWidget_tsx-app_-26fc43.cba87ab54956e52d7281.js```**~~ _(Deleted)_ | -12.34kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/app_views_settings_featureFlags_organizationFeatureFlagsNewSecret_tsx.*.js```**~~ _(Deleted)_ | -10.5kB | 0 bytes | -100.0% :wastebasket: |


**Files in** **```chunks/app_components_avatar_actorAvatar_tsx-app_components_avatar_avatarList_tsx-app_components_cha-e41958.*.js```**:

- ```./app/components/events/featureFlags/useFeatureFlagOnboarding.tsx``` → Total Size: **1.88kB**















**Files in** **```chunks/app_components_search_index_tsx.*.js```**:

- ```./app/views/settings/organization/navigationConfiguration.tsx``` → Total Size: **5.57kB**

- ```./app/views/settings/organization/userOrgNavigationConfiguration.tsx``` → Total Size: **7.26kB**



























**Files in** **```chunks/app_views_settings_featureFlags_index_tsx.*.js```**:

- ```./app/views/settings/featureFlags/index.tsx``` → Total Size: **9.34kB**























</details>

author_association: NONE