Skip to content

Commit bdc7810

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. Refs VDY-82
1 parent d3a87b1 commit bdc7810

File tree

4 files changed

+76
-4
lines changed

4 files changed

+76
-4
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: 62 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,54 @@ export function ScmPlatformFeatures({onComplete}: StepProps) {
306321
}
307322
}
308323

309-
function handleContinue() {
324+
async function handleContinue() {
310325
// Persist derived defaults to context if user accepted them
311326
if (currentPlatformKey && !selectedPlatform?.key) {
312327
setPlatform(currentPlatformKey);
313328
}
314329
if (!selectedFeatures) {
315330
setSelectedFeatures(currentFeatures);
316331
}
332+
333+
if (!hasProjectDetailsStep) {
334+
// Auto-create project with defaults when SCM_PROJECT_DETAILS step is skipped
335+
const platform =
336+
selectedPlatform ??
337+
(currentPlatformKey
338+
? toSelectedSdk(getPlatformInfo(currentPlatformKey)!)
339+
: undefined);
340+
if (!platform) {
341+
return;
342+
}
343+
344+
// If the onboarding context still holds a created project slug (e.g.
345+
// the user went back after the project received its first event),
346+
// reuse it instead of creating a duplicate.
347+
if (createdProjectSlug && projects.some(p => p.slug === createdProjectSlug)) {
348+
onComplete(undefined, {product: currentFeatures});
349+
return;
350+
}
351+
352+
const firstAdminTeam = teams.find((team: Team) =>
353+
team.access.includes('team:admin')
354+
);
355+
356+
try {
357+
const project = await createProject.mutateAsync({
358+
name: platform.key,
359+
platform,
360+
default_rules: true,
361+
firstTeamSlug: firstAdminTeam?.slug,
362+
});
363+
setCreatedProjectSlug(project.slug);
364+
onComplete(undefined, {product: currentFeatures});
365+
} catch (error) {
366+
addErrorMessage(t('Failed to create project'));
367+
Sentry.captureException(error);
368+
}
369+
return;
370+
}
371+
317372
onComplete();
318373
}
319374

@@ -459,7 +514,12 @@ export function ScmPlatformFeatures({onComplete}: StepProps) {
459514
features: currentFeatures,
460515
}}
461516
onClick={handleContinue}
462-
disabled={!currentPlatformKey}
517+
disabled={
518+
!currentPlatformKey ||
519+
createProject.isPending ||
520+
(!hasProjectDetailsStep && (isLoadingTeams || !projectsLoaded))
521+
}
522+
busy={createProject.isPending}
463523
>
464524
{t('Continue')}
465525
</Button>

0 commit comments

Comments
 (0)