import {Fragment, useCallback, useContext, useEffect, useMemo, useState} from 'react'; import styled from '@emotion/styled'; import type {LocationDescriptorObject} from 'history'; import omit from 'lodash/omit'; import Feature from 'sentry/components/acl/feature'; import {Alert} from 'sentry/components/alert'; import {Button} from 'sentry/components/button'; import ButtonBar from 'sentry/components/buttonBar'; import NotFound from 'sentry/components/errors/notFound'; import HookOrDefault from 'sentry/components/hookOrDefault'; import {SdkDocumentation} from 'sentry/components/onboarding/gettingStartedDoc/sdkDocumentation'; import type {ProductSolution} from 'sentry/components/onboarding/productSelection'; import {platformProductAvailability} from 'sentry/components/onboarding/productSelection'; import {setPageFiltersStorage} from 'sentry/components/organizations/pageFilters/persistence'; import { performance as performancePlatforms, replayPlatforms, } from 'sentry/data/platformCategories'; import type {Platform} from 'sentry/data/platformPickerCategories'; import platforms from 'sentry/data/platforms'; import {t} from 'sentry/locale'; import ConfigStore from 'sentry/stores/configStore'; import PageFiltersStore from 'sentry/stores/pageFiltersStore'; import {space} from 'sentry/styles/space'; import type {IssueAlertRule} from 'sentry/types/alerts'; import type {OnboardingSelectedSDK} from 'sentry/types/onboarding'; import type {PlatformIntegration, PlatformKey, Project} from 'sentry/types/project'; import {trackAnalytics} from 'sentry/utils/analytics'; import {useApiQuery} from 'sentry/utils/queryClient'; import {decodeList} from 'sentry/utils/queryString'; import {useLocation} from 'sentry/utils/useLocation'; import {useNavigate} from 'sentry/utils/useNavigate'; import useOrganization from 'sentry/utils/useOrganization'; import {SetupDocsLoader} from 'sentry/views/onboarding/setupDocsLoader'; import {GettingStartedWithProjectContext} from 'sentry/views/projects/gettingStartedWithProjectContext'; import {OtherPlatformsInfo} from './otherPlatformsInfo'; import {PlatformDocHeader} from './platformDocHeader'; const ProductUnavailableCTAHook = HookOrDefault({ hookName: 'component:product-unavailable-cta', }); type Props = { currentPlatformKey: PlatformKey; loading: boolean; platform: PlatformIntegration | undefined; project: Project | undefined; }; export function ProjectInstallPlatform({ loading, project, currentPlatformKey, platform: currentPlatform, }: Props) { const organization = useOrganization(); const location = useLocation(); const navigate = useNavigate(); const gettingStartedWithProjectContext = useContext(GettingStartedWithProjectContext); const isSelfHosted = ConfigStore.get('isSelfHosted'); const isSelfHostedErrorsOnly = ConfigStore.get('isSelfHostedErrorsOnly'); const [showLoaderOnboarding, setShowLoaderOnboarding] = useState( currentPlatform?.id === 'javascript' ); const products = useMemo( () => decodeList(location.query.product ?? []) as ProductSolution[], [location.query.product] ); const { data: projectAlertRules, isPending: projectAlertRulesIsLoading, isError: projectAlertRulesIsError, } = useApiQuery( [`/projects/${organization.slug}/${project?.slug}/rules/`], { enabled: !!project?.slug, staleTime: 0, } ); useEffect(() => { setShowLoaderOnboarding(currentPlatform?.id === 'javascript'); }, [currentPlatform?.id]); useEffect(() => { if (!project || projectAlertRulesIsLoading || projectAlertRulesIsError) { return; } if (gettingStartedWithProjectContext.project?.id === project.id) { return; } const platformKey = Object.keys(platforms).find( key => platforms[key].id === project.platform ); if (!platformKey) { return; } gettingStartedWithProjectContext.setProject({ id: project.id, name: project.name, // sometimes the team slug here can be undefined teamSlug: project.team?.slug, alertRules: projectAlertRules, platform: { ...omit(platforms[platformKey], 'id'), key: platforms[platformKey].id, } as OnboardingSelectedSDK, }); }, [ gettingStartedWithProjectContext, project, projectAlertRules, projectAlertRulesIsLoading, projectAlertRulesIsError, ]); const platform: Platform = { key: currentPlatformKey, id: currentPlatform?.id, name: currentPlatform?.name, link: currentPlatform?.link, }; const hideLoaderOnboarding = useCallback(() => { setShowLoaderOnboarding(false); if (!project?.id || !currentPlatform) { return; } trackAnalytics('onboarding.js_loader_npm_docs_shown', { organization, platform: currentPlatform.id, project_id: project?.id, }); }, [organization, currentPlatform, project?.id]); const redirectWithProjectSelection = useCallback( (to: LocationDescriptorObject) => { if (!project?.id) { return; } // We need to persist and pin the project filter // so the selection does not reset on further navigation PageFiltersStore.updateProjects([Number(project?.id)], null); PageFiltersStore.pin('projects', true); setPageFiltersStorage(organization.slug, new Set(['projects'])); navigate({ ...to, query: { ...to.query, project: project?.id, }, }); }, [navigate, organization.slug, project?.id] ); if (!project) { return null; } if (!platform.id && platform.key !== 'other') { return ; } // because we fall back to 'other' this will always be defined if (!currentPlatform) { return null; } const issueStreamLink = `/organizations/${organization.slug}/issues/`; const performanceOverviewLink = `/organizations/${organization.slug}/performance/`; const replayLink = `/organizations/${organization.slug}/replays/`; const showPerformancePrompt = performancePlatforms.includes(platform.id as PlatformKey); const showReplayButton = replayPlatforms.includes(platform.id as PlatformKey); const isGettingStarted = window.location.href.indexOf('getting-started') > 0; const showDocsWithProductSelection = (platformProductAvailability[platform.key] ?? []).length > 0; return ( {!isSelfHosted && showDocsWithProductSelection && ( )} {platform.key === 'other' ? ( ) : showLoaderOnboarding ? ( ) : ( )}
{isGettingStarted && showPerformancePrompt && ( {({hasFeature}) => { if (hasFeature) { return null; } return ( {t( `Your selected platform supports performance, but your organization does not have performance enabled.` )} ); }} )} {!isSelfHostedErrorsOnly && ( )} {!isSelfHostedErrorsOnly && showReplayButton && ( )}
); } const StyledButtonBar = styled(ButtonBar)` margin-top: ${space(3)}; width: max-content; @media (max-width: ${p => p.theme.breakpoints.small}) { width: auto; grid-row-gap: ${space(1)}; grid-auto-flow: row; } `; const StyledAlert = styled(Alert)` margin-top: ${space(2)}; `;