@@ -7,15 +7,20 @@ import {Container, Flex, Stack} from '@sentry/scraps/layout';
77import { Text } from '@sentry/scraps/text' ;
88
99import { addErrorMessage } from 'sentry/actionCreators/indicator' ;
10+ import { update } from 'sentry/actionCreators/projects' ;
1011import { useOnboardingContext } from 'sentry/components/onboarding/onboardingContext' ;
1112import { useCreateProjectAndRules } from 'sentry/components/onboarding/useCreateProjectAndRules' ;
13+ import { useCreateProjectRules } from 'sentry/components/onboarding/useCreateProjectRules' ;
1214import { TeamSelector } from 'sentry/components/teamSelector' ;
1315import { IconGroup , IconProject , IconSiren } from 'sentry/icons' ;
1416import { t } from 'sentry/locale' ;
17+ import type { OnboardingSelectedSDK } from 'sentry/types/onboarding' ;
1518import type { Team } from 'sentry/types/organization' ;
1619import { trackAnalytics } from 'sentry/utils/analytics' ;
1720import { slugify } from 'sentry/utils/slugify' ;
21+ import { useApi } from 'sentry/utils/useApi' ;
1822import { useOrganization } from 'sentry/utils/useOrganization' ;
23+ import { useProjects } from 'sentry/utils/useProjects' ;
1924import { useTeams } from 'sentry/utils/useTeams' ;
2025import {
2126 DEFAULT_ISSUE_ALERT_OPTIONS_VALUES ,
@@ -32,16 +37,20 @@ import type {StepProps} from './types';
3237const PROJECT_DETAILS_WIDTH = '285px' ;
3338
3439export function ScmProjectDetails ( { onComplete} : StepProps ) {
40+ const api = useApi ( ) ;
3541 const organization = useOrganization ( ) ;
3642 const {
3743 selectedPlatform,
3844 selectedFeatures,
45+ createdProjectSlug,
3946 setCreatedProjectSlug,
4047 projectDetailsForm,
4148 setProjectDetailsForm,
4249 } = useOnboardingContext ( ) ;
43- const { teams} = useTeams ( ) ;
50+ const { teams, fetching : isLoadingTeams } = useTeams ( ) ;
51+ const { projects, initiallyLoaded : projectsLoaded } = useProjects ( ) ;
4452 const createProjectAndRules = useCreateProjectAndRules ( ) ;
53+ const createProjectRules = useCreateProjectRules ( ) ;
4554 useEffect ( ( ) => {
4655 trackAnalytics ( 'onboarding.scm_project_details_step_viewed' , { organization} ) ;
4756 } , [ organization ] ) ;
@@ -58,6 +67,12 @@ export function ScmProjectDetails({onComplete}: StepProps) {
5867 projectDetailsForm ?. teamSlug ?? null
5968 ) ;
6069
70+ const [ isSaving , setIsSaving ] = useState ( false ) ;
71+
72+ // Safety-net: verify the previously created project still exists in the store.
73+ const projectExists =
74+ ! ! createdProjectSlug && projects . some ( p => p . slug === createdProjectSlug ) ;
75+
6176 const projectNameResolved = projectName ?? defaultName ;
6277 const teamSlugResolved = teamSlug ?? firstAdminTeam ?. slug ?? '' ;
6378
@@ -104,34 +119,35 @@ export function ScmProjectDetails({onComplete}: StepProps) {
104119 projectNameResolved . length > 0 &&
105120 teamSlugResolved . length > 0 &&
106121 ! ! selectedPlatform &&
107- ! createProjectAndRules . isPending ;
122+ ! isSaving &&
123+ ! isLoadingTeams &&
124+ projectsLoaded ;
108125
109- async function handleCreateProject ( ) {
110- if ( ! selectedPlatform || ! canSubmit ) {
126+ function persistFormState ( ) {
127+ if ( ! selectedPlatform ) {
111128 return ;
112129 }
130+ setProjectDetailsForm ( {
131+ projectName : projectNameResolved ,
132+ teamSlug : teamSlugResolved ,
133+ alertRuleConfig,
134+ platform : selectedPlatform . key ,
135+ } ) ;
136+ }
113137
114- trackAnalytics ( 'onboarding.scm_project_details_create_clicked' , { organization } ) ;
115-
138+ async function createNewProject ( platform : OnboardingSelectedSDK ) {
139+ setIsSaving ( true ) ;
116140 try {
117141 const { project} = await createProjectAndRules . mutateAsync ( {
118142 projectName : projectNameResolved ,
119- platform : selectedPlatform ,
143+ platform,
120144 team : teamSlugResolved ,
121145 alertRuleConfig : getRequestDataFragment ( alertRuleConfig ) ,
122146 createNotificationAction : ( ) => undefined ,
123147 } ) ;
124148
125- // Store the project slug separately so onboarding.tsx can find
126- // the project via useRecentCreatedProject without corrupting
127- // selectedPlatform.key (which the platform features step needs).
128149 setCreatedProjectSlug ( project . slug ) ;
129- setProjectDetailsForm ( {
130- projectName : projectNameResolved ,
131- teamSlug : teamSlugResolved ,
132- alertRuleConfig,
133- platform : selectedPlatform . key ,
134- } ) ;
150+ persistFormState ( ) ;
135151
136152 trackAnalytics ( 'onboarding.scm_project_details_create_succeeded' , {
137153 organization,
@@ -143,6 +159,91 @@ export function ScmProjectDetails({onComplete}: StepProps) {
143159 trackAnalytics ( 'onboarding.scm_project_details_create_failed' , { organization} ) ;
144160 addErrorMessage ( t ( 'Failed to create project' ) ) ;
145161 Sentry . captureException ( error ) ;
162+ } finally {
163+ setIsSaving ( false ) ;
164+ }
165+ }
166+
167+ async function updateExistingProject ( slug : string ) {
168+ setIsSaving ( true ) ;
169+ try {
170+ const updated = await update ( api , {
171+ orgId : organization . slug ,
172+ projectId : slug ,
173+ data : { name : projectNameResolved } ,
174+ } ) ;
175+
176+ // If the alert config changed, replace the existing rule.
177+ // Onboarding only ever creates a single alert rule.
178+ if ( ! alertConfigUnchanged ) {
179+ const [ existingRule ] = await api . requestPromise (
180+ `/projects/${ organization . slug } /${ updated . slug } /rules/`
181+ ) ;
182+ if ( existingRule ) {
183+ await api . requestPromise (
184+ `/projects/${ organization . slug } /${ updated . slug } /rules/${ existingRule . id } /` ,
185+ { method : 'DELETE' }
186+ ) ;
187+ }
188+
189+ const fragment = getRequestDataFragment ( alertRuleConfig ) ;
190+ if ( fragment . shouldCreateCustomRule ) {
191+ await createProjectRules . mutateAsync ( {
192+ projectSlug : updated . slug ,
193+ name : updated . name ,
194+ conditions : fragment . conditions ,
195+ actions : fragment . actions ,
196+ actionMatch : fragment . actionMatch ,
197+ frequency : fragment . frequency ,
198+ } ) ;
199+ }
200+ }
201+
202+ setCreatedProjectSlug ( updated . slug ) ;
203+ persistFormState ( ) ;
204+ onComplete ( undefined , selectedFeatures ? { product : selectedFeatures } : undefined ) ;
205+ } catch ( error ) {
206+ addErrorMessage ( t ( 'Failed to update project' ) ) ;
207+ Sentry . captureException ( error ) ;
208+ } finally {
209+ setIsSaving ( false ) ;
210+ }
211+ }
212+
213+ const savedAlert = projectDetailsForm ?. alertRuleConfig ;
214+ const alertConfigUnchanged =
215+ alertRuleConfig . alertSetting === savedAlert ?. alertSetting &&
216+ alertRuleConfig . interval === savedAlert ?. interval &&
217+ alertRuleConfig . metric === savedAlert ?. metric &&
218+ alertRuleConfig . threshold === savedAlert ?. threshold ;
219+
220+ function handleCreateProject ( ) {
221+ if ( ! selectedPlatform || ! canSubmit ) {
222+ return ;
223+ }
224+
225+ trackAnalytics ( 'onboarding.scm_project_details_create_clicked' , { organization} ) ;
226+
227+ if ( projectExists && createdProjectSlug ) {
228+ const platformChanged = projectDetailsForm ?. platform !== selectedPlatform . key ;
229+
230+ if ( platformChanged ) {
231+ // Platform changed — abandon the old project and create a new one,
232+ // matching legacy onboarding behavior.
233+ createNewProject ( selectedPlatform ) ;
234+ } else if (
235+ projectNameResolved === projectDetailsForm ?. projectName &&
236+ teamSlugResolved === projectDetailsForm ?. teamSlug &&
237+ alertConfigUnchanged
238+ ) {
239+ // Nothing changed — reuse the existing project as-is.
240+ onComplete ( undefined , selectedFeatures ? { product : selectedFeatures } : undefined ) ;
241+ } else {
242+ // Same platform but settings changed — update in place.
243+ updateExistingProject ( createdProjectSlug ) ;
244+ }
245+ } else {
246+ createNewProject ( selectedPlatform ) ;
146247 }
147248 }
148249
@@ -218,7 +319,7 @@ export function ScmProjectDetails({onComplete}: StepProps) {
218319 priority = "primary"
219320 onClick = { handleCreateProject }
220321 disabled = { ! canSubmit }
221- busy = { createProjectAndRules . isPending }
322+ busy = { isSaving }
222323 icon = { < IconProject /> }
223324 >
224325 { t ( 'Create project' ) }
0 commit comments