Skip to content

Commit

Permalink
ref(flags): split FF settings into eval/change tracking (#86070)
Browse files Browse the repository at this point in the history
  • Loading branch information
aliu39 authored Mar 3, 2025
1 parent 7e2f866 commit 0dc6dfd
Show file tree
Hide file tree
Showing 21 changed files with 382 additions and 262 deletions.
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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({
Expand All @@ -33,16 +32,15 @@ export function FeatureFlagOnboardingLayout({
projectKeyId,
configType = 'onboarding',
integration = '',
provider = '',
skipConfig,
skipEvalTracking,
}: FeatureFlagOnboardingLayoutProps) {
const api = useApi();
const organization = useOrganization();
const {isPending: isLoadingRegistry, data: registryData} =
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;
Expand Down Expand Up @@ -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>
);
Expand All @@ -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;
Expand All @@ -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};
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -88,8 +89,6 @@ function LegacyFeatureFlagOnboardingSidebar(props: CommonSidebarProps) {

function SidebarContent() {
const {
hasDocs,
projects,
allProjects,
currentProject,
setCurrentProject,
Expand Down Expand Up @@ -139,12 +138,6 @@ function SidebarContent() {
];
}, [supportedProjects, unsupportedProjects]);

const selectedProject = currentProject ?? projects[0] ?? allProjects[0];

if (!selectedProject) {
return <LoadingIndicator />;
}

return (
<Fragment>
<TopRightBackgroundImage src={HighlightTopRightPattern} />
Expand Down Expand Up @@ -183,27 +176,27 @@ 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
// in case window.location.hash disappears
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 => {
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -369,22 +365,15 @@ 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;
}

return (
<Fragment>
{radioButtons}
<FeatureFlagOnboardingLayout
skipConfig={skipConfig}
skipEvalTracking={skipEvalTracking}
docsConfig={docs}
dsn={dsn}
projectKeyId={projectKeyId}
Expand All @@ -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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
27 changes: 17 additions & 10 deletions static/app/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/';
Expand Down Expand Up @@ -51,7 +51,7 @@ describe('OrganizationFeatureFlagsIndex', function () {
body: {data: secrets},
});

render(<OrganizationFeatureFlagsIndex />);
render(<OrganizationFeatureFlagsChangeTracking />);

const secretsTable = within(screen.getByTestId('secrets-table'));

Expand All @@ -76,7 +76,7 @@ describe('OrganizationFeatureFlagsIndex', function () {
statusCode: 400,
});

render(<OrganizationFeatureFlagsIndex />);
render(<OrganizationFeatureFlagsChangeTracking />);

const secretsTable = within(screen.getByTestId('secrets-table'));

Expand All @@ -98,7 +98,7 @@ describe('OrganizationFeatureFlagsIndex', function () {
body: {data: secrets},
});

render(<OrganizationFeatureFlagsIndex />);
render(<OrganizationFeatureFlagsChangeTracking />);

const secretsTable = within(screen.getByTestId('secrets-table'));

Expand Down Expand Up @@ -127,7 +127,7 @@ describe('OrganizationFeatureFlagsIndex', function () {
method: 'DELETE',
});

render(<OrganizationFeatureFlagsIndex />);
render(<OrganizationFeatureFlagsChangeTracking />);
renderGlobalModal();

const secretsTable = within(screen.getByTestId('secrets-table'));
Expand Down Expand Up @@ -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'));

Expand Down
Loading

0 comments on commit 0dc6dfd

Please sign in to comment.