diff --git a/static/app/components/onboarding/frameworkSuggestionModal.tsx b/static/app/components/onboarding/frameworkSuggestionModal.tsx index c105ff24133658..00f717ff7c2d31 100644 --- a/static/app/components/onboarding/frameworkSuggestionModal.tsx +++ b/static/app/components/onboarding/frameworkSuggestionModal.tsx @@ -12,7 +12,7 @@ import {Radio} from 'sentry/components/core/radio'; import {RadioLineItem} from 'sentry/components/forms/controls/radioGroup'; import List from 'sentry/components/list'; import ListItem from 'sentry/components/list/listItem'; -import {useIsCreatingProject} from 'sentry/components/onboarding/useCreateProject'; +import {useIsCreatingProjectAndRules} from 'sentry/components/onboarding/useCreateProjectAndRules'; import Panel from 'sentry/components/panels/panel'; import PanelBody from 'sentry/components/panels/panelBody'; import categoryList, {createablePlatforms} from 'sentry/data/platformPickerCategories'; @@ -133,7 +133,8 @@ export function FrameworkSuggestionModal({ organization, newOrg, }: FrameworkSuggestionModalProps) { - const isCreatingProject = useIsCreatingProject(); + const isCreatingProjectAndRules = useIsCreatingProjectAndRules(); + const [selectedFramework, setSelectedFramework] = useState< OnboardingSelectedSDK | undefined >(selectedPlatform); @@ -306,7 +307,11 @@ export function FrameworkSuggestionModal({ diff --git a/static/app/components/onboarding/useCreateProject.tsx b/static/app/components/onboarding/useCreateProject.tsx index 178541f0255eb0..02b75ca679e628 100644 --- a/static/app/components/onboarding/useCreateProject.tsx +++ b/static/app/components/onboarding/useCreateProject.tsx @@ -1,13 +1,11 @@ import ProjectsStore from 'sentry/stores/projectsStore'; import type {OnboardingSelectedSDK} from 'sentry/types/onboarding'; import type {Project} from 'sentry/types/project'; -import {useIsMutating, useMutation} from 'sentry/utils/queryClient'; +import {useMutation} from 'sentry/utils/queryClient'; import type RequestError from 'sentry/utils/requestError/requestError'; import useApi from 'sentry/utils/useApi'; import useOrganization from 'sentry/utils/useOrganization'; -const MUTATION_KEY = 'create-project'; - interface Variables { platform: OnboardingSelectedSDK; default_rules?: boolean; @@ -20,7 +18,6 @@ export function useCreateProject() { const organization = useOrganization(); return useMutation({ - mutationKey: [MUTATION_KEY], mutationFn: ({firstTeamSlug, name, platform, default_rules}) => { return api.requestPromise( firstTeamSlug @@ -42,7 +39,3 @@ export function useCreateProject() { }, }); } - -export function useIsCreatingProject() { - return Boolean(useIsMutating({mutationKey: [MUTATION_KEY]})); -} diff --git a/static/app/components/onboarding/useCreateProjectAndRules.ts b/static/app/components/onboarding/useCreateProjectAndRules.ts new file mode 100644 index 00000000000000..6a45c4a0325b7a --- /dev/null +++ b/static/app/components/onboarding/useCreateProjectAndRules.ts @@ -0,0 +1,78 @@ +import {useCreateProject} from 'sentry/components/onboarding/useCreateProject'; +import {useCreateProjectRules} from 'sentry/components/onboarding/useCreateProjectRules'; +import type {OnboardingSelectedSDK} from 'sentry/types/onboarding'; +import type {Project} from 'sentry/types/project'; +import {defined} from 'sentry/utils'; +import {useIsMutating, useMutation} from 'sentry/utils/queryClient'; +import type RequestError from 'sentry/utils/requestError/requestError'; +import type {useCreateNotificationAction} from 'sentry/views/projectInstall/issueAlertNotificationOptions'; +import type {RequestDataFragment} from 'sentry/views/projectInstall/issueAlertOptions'; + +const MUTATION_KEY = 'create-project-and-rules'; + +type Variables = { + alertRuleConfig: Partial; + createNotificationAction: ReturnType< + typeof useCreateNotificationAction + >['createNotificationAction']; + platform: OnboardingSelectedSDK; + projectName: string; + team?: string; +}; + +type Response = { + project: Project; + ruleIds: string[]; +}; + +export function useCreateProjectAndRules() { + const createProject = useCreateProject(); + const createProjectRules = useCreateProjectRules(); + + return useMutation({ + mutationKey: [MUTATION_KEY], + mutationFn: async ({ + projectName, + platform, + alertRuleConfig, + team, + createNotificationAction, + }) => { + const project = await createProject.mutateAsync({ + name: projectName, + platform, + default_rules: alertRuleConfig?.defaultRules ?? true, + firstTeamSlug: team, + }); + + const customRulePromise = alertRuleConfig?.shouldCreateCustomRule + ? createProjectRules.mutateAsync({ + projectSlug: project.slug, + name: project.name, + conditions: alertRuleConfig?.conditions, + actions: alertRuleConfig?.actions, + actionMatch: alertRuleConfig?.actionMatch, + frequency: alertRuleConfig?.frequency, + }) + : undefined; + + const notificationRulePromise = createNotificationAction({ + shouldCreateRule: alertRuleConfig?.shouldCreateRule, + name: project.name, + projectSlug: project.slug, + conditions: alertRuleConfig?.conditions, + actionMatch: alertRuleConfig?.actionMatch, + frequency: alertRuleConfig?.frequency, + }); + + const rules = await Promise.all([customRulePromise, notificationRulePromise]); + const ruleIds = rules.filter(defined).map(rule => rule.id); + + return {project, ruleIds}; + }, + }); +} + +export function useIsCreatingProjectAndRules() { + return Boolean(useIsMutating({mutationKey: [MUTATION_KEY]})); +} diff --git a/static/app/views/projectInstall/createProject.tsx b/static/app/views/projectInstall/createProject.tsx index aae8b8b9b51b34..8c01c49e211f4f 100644 --- a/static/app/views/projectInstall/createProject.tsx +++ b/static/app/views/projectInstall/createProject.tsx @@ -1,6 +1,7 @@ import {useCallback, useEffect, useMemo, useState} from 'react'; import styled from '@emotion/styled'; import * as Sentry from '@sentry/react'; +import debounce from 'lodash/debounce'; import omit from 'lodash/omit'; import startCase from 'lodash/startCase'; import {PlatformIcon} from 'platformicons'; @@ -18,8 +19,7 @@ import ExternalLink from 'sentry/components/links/externalLink'; import List from 'sentry/components/list'; import ListItem from 'sentry/components/list/listItem'; import {SupportedLanguages} from 'sentry/components/onboarding/frameworkSuggestionModal'; -import {useCreateProject} from 'sentry/components/onboarding/useCreateProject'; -import {useCreateProjectRules} from 'sentry/components/onboarding/useCreateProjectRules'; +import {useCreateProjectAndRules} from 'sentry/components/onboarding/useCreateProjectAndRules'; import type {Platform} from 'sentry/components/platformPicker'; import PlatformPicker from 'sentry/components/platformPicker'; import TeamSelector from 'sentry/components/teamSelector'; @@ -135,13 +135,11 @@ const keyToErrorText: Record = { export function CreateProject() { const api = useApi(); const navigate = useNavigate(); - const [errors, setErrors] = useState(); const organization = useOrganization(); const location = useLocation(); const {createNotificationAction, notificationProps} = useCreateNotificationAction(); const canUserCreateProject = useCanCreateProject(); - const createProject = useCreateProject(); - const createProjectRules = useCreateProjectRules(); + const createProjectAndRules = useCreateProjectAndRules(); const {teams} = useTeams(); const accessTeams = teams.filter((team: Team) => team.access.includes('team:admin')); const referrer = decodeScalar(location.query.referrer); @@ -151,44 +149,6 @@ export function CreateProject() { null ); - const createRules = useCallback( - async ({ - project, - alertRuleConfig, - }: {project: Project} & Pick) => { - const ruleIds = []; - - if (alertRuleConfig?.shouldCreateCustomRule) { - const ruleData = await createProjectRules.mutateAsync({ - projectSlug: project.slug, - name: project.name, - conditions: alertRuleConfig?.conditions, - actions: alertRuleConfig?.actions, - actionMatch: alertRuleConfig?.actionMatch, - frequency: alertRuleConfig?.frequency, - }); - - ruleIds.push(ruleData.id); - } - - const notificationRule = await createNotificationAction({ - shouldCreateRule: alertRuleConfig?.shouldCreateRule, - name: project.name, - projectSlug: project.slug, - conditions: alertRuleConfig?.conditions, - actionMatch: alertRuleConfig?.actionMatch, - frequency: alertRuleConfig?.frequency, - }); - - if (notificationRule) { - ruleIds.push(notificationRule.id); - } - - return ruleIds; - }, - [createNotificationAction, createProjectRules] - ); - const autoFill = useMemo(() => { return referrer === 'getting-started' && projectId === createdProject?.id; }, [referrer, projectId, createdProject?.id]); @@ -235,9 +195,6 @@ export function CreateProject() { missingValues.isMissingMessagingIntegrationChannel, ].filter(value => value).length; - const canSubmitForm = - !createProject.isPending && canUserCreateProject && formErrorCount === 0; - const submitTooltipText = getSubmitTooltipText({ ...missingValues, formErrorCount, @@ -284,17 +241,15 @@ export function CreateProject() { let projectToRollback: Project | undefined; try { - const project = await createProject.mutateAsync({ - name: projectName, + const {project, ruleIds} = await createProjectAndRules.mutateAsync({ + projectName, platform: selectedPlatform, - default_rules: alertRuleConfig?.defaultRules ?? true, - firstTeamSlug: team, + team, + alertRuleConfig, + createNotificationAction, }); - projectToRollback = project; - const ruleIds = await createRules({project, alertRuleConfig}); - trackAnalytics('project_creation_page.created', { organization, issue_alert: alertRuleConfig?.defaultRules @@ -341,7 +296,6 @@ export function CreateProject() { ) ); } catch (error) { - setErrors(error.responseJSON); addErrorMessage(t('Failed to create project %s', `${projectName}`)); // Only log this if the error is something other than: @@ -373,7 +327,14 @@ export function CreateProject() { } } }, - [createRules, organization, createProject, setCreatedProject, navigate, api] + [ + organization, + setCreatedProject, + navigate, + api, + createProjectAndRules, + createNotificationAction, + ] ); const handleProjectCreation = useCallback( @@ -428,6 +389,11 @@ export function CreateProject() { [configurePlatform, organization] ); + const debounceHandleProjectCreation = useMemo( + () => debounce(handleProjectCreation, 2000, {leading: true, trailing: false}), + [handleProjectCreation] + ); + const handlePlatformChange = useCallback( (value: Platform | null) => { if (!value) { @@ -549,21 +515,22 @@ export function CreateProject() { - {errors && ( + {createProjectAndRules.isError && createProjectAndRules.error.responseJSON && ( - {Object.keys(errors).map(key => ( + {Object.keys(createProjectAndRules.error.responseJSON).map(key => (
{keyToErrorText[key] ?? startCase(key)}:{' '} - {(errors as any)[key]} + {(createProjectAndRules.error.responseJSON as any)[key]}
))}