Skip to content

Commit 5dd3955

Browse files
committed
feat(onboarding): Gate SCM_PROJECT_DETAILS step with feature flag
When `onboarding-scm-project-details` flag is absent, skip the project details step and auto-create the project with defaults (platform key as name, first admin team, default alert rules) during the platform features Continue action. Also update useBackActions to use useExperiment for the SCM context preservation check, since the hardcoded step ID no longer works when the step is removed. Refs VDY-82
1 parent b349eb1 commit 5dd3955

File tree

5 files changed

+90
-8
lines changed

5 files changed

+90
-8
lines changed

static/app/views/onboarding/onboarding.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,7 @@ describe('Onboarding', () => {
630630

631631
describe('SCM onboarding flow', () => {
632632
const scmOrganization = OrganizationFixture({
633-
features: ['onboarding-scm-experiment'],
633+
features: ['onboarding-scm-experiment', 'onboarding-scm-project-details'],
634634
});
635635

636636
const githubProvider = GitHubIntegrationProviderFixture({

static/app/views/onboarding/onboarding.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,15 @@ export function OnboardingWithoutContext() {
170170
feature: 'onboarding-scm-experiment',
171171
});
172172

173-
const onboardingSteps = hasScmOnboarding ? scmOnboardingSteps : legacyOnboardingSteps;
173+
const hasProjectDetailsStep = organization.features.includes(
174+
'onboarding-scm-project-details'
175+
);
176+
177+
const scmSteps = hasProjectDetailsStep
178+
? scmOnboardingSteps
179+
: scmOnboardingSteps.filter(s => s.id !== OnboardingStepId.SCM_PROJECT_DETAILS);
180+
181+
const onboardingSteps = hasScmOnboarding ? scmSteps : legacyOnboardingSteps;
174182

175183
const stepObj = onboardingSteps.find(({id}) => stepId === id);
176184
const stepIndex = onboardingSteps.findIndex(({id}) => stepId === id);

static/app/views/onboarding/scmPlatformFeatures.spec.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
OnboardingContextProvider,
1111
type OnboardingSessionState,
1212
} from 'sentry/components/onboarding/onboardingContext';
13+
import {ProjectsStore} from 'sentry/stores/projectsStore';
14+
import {TeamStore} from 'sentry/stores/teamStore';
1315
import * as analytics from 'sentry/utils/analytics';
1416
import {sessionStorageWrapper} from 'sentry/utils/sessionStorage';
1517

@@ -72,6 +74,8 @@ describe('ScmPlatformFeatures', () => {
7274
beforeEach(() => {
7375
jest.clearAllMocks();
7476
sessionStorageWrapper.clear();
77+
ProjectsStore.loadInitialData([]);
78+
TeamStore.loadInitialData([]);
7579
});
7680

7781
afterEach(() => {

static/app/views/onboarding/scmPlatformFeatures.tsx

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {useCallback, useEffect, useMemo, useState} from 'react';
2+
import * as Sentry from '@sentry/react';
23
import {LayoutGroup, motion} from 'framer-motion';
34
import {PlatformIcon} from 'platformicons';
45

@@ -7,6 +8,7 @@ import {Container, Flex, Grid, Stack} from '@sentry/scraps/layout';
78
import {Select} from '@sentry/scraps/select';
89
import {Heading} from '@sentry/scraps/text';
910

11+
import {addErrorMessage} from 'sentry/actionCreators/indicator';
1012
import {closeModal, openConsoleModal, openModal} from 'sentry/actionCreators/modal';
1113
import {LoadingIndicator} from 'sentry/components/loadingIndicator';
1214
import {SupportedLanguages} from 'sentry/components/onboarding/frameworkSuggestionModal';
@@ -16,13 +18,17 @@ import {
1618
getDisabledProducts,
1719
platformProductAvailability,
1820
} from 'sentry/components/onboarding/productSelection';
21+
import {useCreateProject} from 'sentry/components/onboarding/useCreateProject';
1922
import {platforms} from 'sentry/data/platforms';
2023
import {t} from 'sentry/locale';
2124
import type {OnboardingSelectedSDK} from 'sentry/types/onboarding';
25+
import type {Team} from 'sentry/types/organization';
2226
import type {PlatformIntegration, PlatformKey} from 'sentry/types/project';
2327
import {trackAnalytics} from 'sentry/utils/analytics';
2428
import {isDisabledGamingPlatform} from 'sentry/utils/platform';
2529
import {useOrganization} from 'sentry/utils/useOrganization';
30+
import {useProjects} from 'sentry/utils/useProjects';
31+
import {useTeams} from 'sentry/utils/useTeams';
2632
import {ScmFeatureSelectionCards} from 'sentry/views/onboarding/components/scmFeatureSelectionCards';
2733
import {ScmPlatformCard} from 'sentry/views/onboarding/components/scmPlatformCard';
2834

@@ -84,8 +90,17 @@ export function ScmPlatformFeatures({onComplete}: StepProps) {
8490
setSelectedPlatform,
8591
selectedFeatures,
8692
setSelectedFeatures,
93+
createdProjectSlug,
94+
setCreatedProjectSlug,
8795
} = useOnboardingContext();
8896

97+
const {teams, fetching: isLoadingTeams} = useTeams();
98+
const {projects, initiallyLoaded: projectsLoaded} = useProjects();
99+
const createProject = useCreateProject();
100+
const hasProjectDetailsStep = organization.features.includes(
101+
'onboarding-scm-project-details'
102+
);
103+
89104
const [showManualPicker, setShowManualPicker] = useState(false);
90105

91106
useEffect(() => {
@@ -306,14 +321,59 @@ export function ScmPlatformFeatures({onComplete}: StepProps) {
306321
}
307322
}
308323

309-
function handleContinue() {
324+
const existingProject = createdProjectSlug
325+
? projects.find(p => p.slug === createdProjectSlug)
326+
: undefined;
327+
328+
async function handleContinue() {
310329
// Persist derived defaults to context if user accepted them
311330
if (currentPlatformKey && !selectedPlatform?.key) {
312331
setPlatform(currentPlatformKey);
313332
}
314333
if (!selectedFeatures) {
315334
setSelectedFeatures(currentFeatures);
316335
}
336+
337+
if (!hasProjectDetailsStep) {
338+
// Auto-create project with defaults when SCM_PROJECT_DETAILS step is skipped
339+
const platform =
340+
selectedPlatform ??
341+
(currentPlatformKey
342+
? toSelectedSdk(getPlatformInfo(currentPlatformKey)!)
343+
: undefined);
344+
if (!platform) {
345+
return;
346+
}
347+
348+
// If a project was already created for this platform (e.g. the user
349+
// went back after the project received its first event), reuse it.
350+
// If the platform changed, abandon the old project and create a new
351+
// one — matching legacy onboarding behavior.
352+
if (existingProject?.platform === platform.key) {
353+
onComplete(undefined, {product: currentFeatures});
354+
return;
355+
}
356+
357+
const firstAdminTeam = teams.find((team: Team) =>
358+
team.access.includes('team:admin')
359+
);
360+
361+
try {
362+
const project = await createProject.mutateAsync({
363+
name: platform.key,
364+
platform,
365+
default_rules: true,
366+
firstTeamSlug: firstAdminTeam?.slug,
367+
});
368+
setCreatedProjectSlug(project.slug);
369+
onComplete(undefined, {product: currentFeatures});
370+
} catch (error) {
371+
addErrorMessage(t('Failed to create project'));
372+
Sentry.captureException(error);
373+
}
374+
return;
375+
}
376+
317377
onComplete();
318378
}
319379

@@ -459,7 +519,12 @@ export function ScmPlatformFeatures({onComplete}: StepProps) {
459519
features: currentFeatures,
460520
}}
461521
onClick={handleContinue}
462-
disabled={!currentPlatformKey}
522+
disabled={
523+
!currentPlatformKey ||
524+
createProject.isPending ||
525+
(!hasProjectDetailsStep && (isLoadingTeams || !projectsLoaded))
526+
}
527+
busy={createProject.isPending}
463528
>
464529
{t('Continue')}
465530
</Button>

static/app/views/onboarding/useBackActions.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {handleXhrErrorResponse} from 'sentry/utils/handleXhrErrorResponse';
1010
import type {RequestError} from 'sentry/utils/requestError/requestError';
1111
import {normalizeUrl} from 'sentry/utils/url/normalizeUrl';
1212
import {useApi} from 'sentry/utils/useApi';
13+
import {useExperiment} from 'sentry/utils/useExperiment';
1314
import {useOrganization} from 'sentry/utils/useOrganization';
1415
import type {StepDescriptor} from 'sentry/views/onboarding/types';
1516

@@ -33,6 +34,9 @@ export function useBackActions({
3334
const api = useApi();
3435
const organization = useOrganization();
3536
const onboardingContext = useOnboardingContext();
37+
const {inExperiment: hasScmOnboarding} = useExperiment({
38+
feature: 'onboarding-scm-experiment',
39+
});
3640
const currentStep = onboardingSteps[stepIndex];
3741

3842
const deleteRecentCreatedProject = useCallback(
@@ -118,21 +122,22 @@ export function useBackActions({
118122
// store data and skip project creation.
119123
// In the SCM flow, preserve context so the user keeps their SCM
120124
// connection, repo selection, and feature choices.
121-
await deleteRecentCreatedProject(prevStep.id === 'scm-project-details');
125+
await deleteRecentCreatedProject(hasScmOnboarding);
122126
}
123127

124128
if (!browserBackButton) {
125129
goToStep(prevStep);
126130
}
127131
},
128132
[
129-
goToStep,
133+
currentStep,
130134
organization,
131-
onboardingContext,
132135
isRecentCreatedProjectActive,
133136
recentCreatedProject,
134-
currentStep,
137+
onboardingContext,
138+
goToStep,
135139
deleteRecentCreatedProject,
140+
hasScmOnboarding,
136141
]
137142
);
138143

0 commit comments

Comments
 (0)