import {Fragment, useEffect, useMemo, useState} from 'react'; import styled from '@emotion/styled'; import qs from 'qs'; import HighlightTopRightPattern from 'sentry-images/pattern/highlight-top-right.svg'; import {Button} from 'sentry/components/button'; import type {MenuItemProps} from 'sentry/components/dropdownMenu'; import {DropdownMenu} from 'sentry/components/dropdownMenu'; import IdBadge from 'sentry/components/idBadge'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import {shouldShowPerformanceTasks} from 'sentry/components/onboardingWizard/filterSupportedTasks'; import useOnboardingDocs from 'sentry/components/onboardingWizard/useOnboardingDocs'; import OnboardingStep from 'sentry/components/sidebar/onboardingStep'; import SidebarPanel from 'sentry/components/sidebar/sidebarPanel'; import type {CommonSidebarProps} from 'sentry/components/sidebar/types'; import {SidebarPanelKey} from 'sentry/components/sidebar/types'; import {withoutPerformanceSupport} from 'sentry/data/platformCategories'; import platforms from 'sentry/data/platforms'; import {t, tct} from 'sentry/locale'; import PageFiltersStore from 'sentry/stores/pageFiltersStore'; import {useLegacyStore} from 'sentry/stores/useLegacyStore'; import pulsingIndicatorStyles from 'sentry/styles/pulsingIndicator'; import {space} from 'sentry/styles/space'; import type {Project} from 'sentry/types/project'; import EventWaiter from 'sentry/utils/eventWaiter'; import useApi from 'sentry/utils/useApi'; import useOrganization from 'sentry/utils/useOrganization'; import usePrevious from 'sentry/utils/usePrevious'; import useProjects from 'sentry/utils/useProjects'; import {filterProjects, generateDocKeys, isPlatformSupported} from './utils'; function decodeProjectIds(projectIds: unknown): string[] | null { if (Array.isArray(projectIds)) { return projectIds; } if (typeof projectIds === 'string') { return [projectIds]; } return null; } function PerformanceOnboardingSidebar(props: CommonSidebarProps) { const {currentPanel, collapsed, hidePanel, orientation} = props; const isActive = currentPanel === SidebarPanelKey.PERFORMANCE_ONBOARDING; const organization = useOrganization(); const hasProjectAccess = organization.access.includes('project:read'); const {projects, initiallyLoaded: projectsLoaded} = useProjects(); const [currentProject, setCurrentProject] = useState(undefined); const {selection} = useLegacyStore(PageFiltersStore); const {projectsWithoutFirstTransactionEvent, projectsForOnboarding} = filterProjects(projects); const priorityProjectIds: Set | null = useMemo(() => { const queryParams = qs.parse(location.search); const decodedProjectIds = decodeProjectIds(queryParams.project); return decodedProjectIds === null ? null : new Set(decodedProjectIds); }, []); useEffect(() => { if ( currentProject || projects.length === 0 || !isActive || projectsWithoutFirstTransactionEvent.length <= 0 ) { return; } // Establish current project if (priorityProjectIds) { const projectMap: Record = projects.reduce((acc, project) => { acc[project.id] = project; return acc; }, {}); const priorityProjects: Project[] = []; priorityProjectIds.forEach(projectId => { priorityProjects.push(projectMap[String(projectId)]); }); // Among the project selection, find a project that has performance onboarding docs support, and has not sent // a first transaction event. const maybeProject = priorityProjects.find(project => projectsForOnboarding.includes(project) ); if (maybeProject) { setCurrentProject(maybeProject); return; } // Among the project selection, find a project that has not sent a first transaction event const maybeProjectFallback = priorityProjects.find(project => projectsWithoutFirstTransactionEvent.includes(project) ); if (maybeProjectFallback) { setCurrentProject(maybeProjectFallback); return; } } // Among the projects, find a project that has performance onboarding docs support, and has not sent // a first transaction event. if (projectsForOnboarding.length) { setCurrentProject(projectsForOnboarding[0]); return; } // Otherwise, pick a first project that has not sent a first transaction event. setCurrentProject(projectsWithoutFirstTransactionEvent[0]); }, [ selection.projects, projects, isActive, projectsForOnboarding, projectsWithoutFirstTransactionEvent, currentProject, priorityProjectIds, ]); if ( !isActive || !hasProjectAccess || currentProject === undefined || !shouldShowPerformanceTasks(projects) || !projectsLoaded || !projects || projects.length <= 0 || projectsWithoutFirstTransactionEvent.length <= 0 ) { return null; } const items: MenuItemProps[] = projectsWithoutFirstTransactionEvent.reduce( (acc: MenuItemProps[], project) => { const itemProps: MenuItemProps = { key: project.id, label: ( ), onAction: function switchProject() { setCurrentProject(project); }, }; if (priorityProjectIds?.has(String(project.id))) { acc.unshift(itemProps); } else { acc.push(itemProps); } return acc; }, [] ); return ( {t('Boost Performance')} } triggerProps={{'aria-label': currentProject.slug}} position="bottom-end" /> ); } function OnboardingContent({currentProject}: {currentProject: Project}) { const api = useApi(); const organization = useOrganization(); const previousProject = usePrevious(currentProject); const [received, setReceived] = useState(false); useEffect(() => { if (previousProject.id !== currentProject.id) { setReceived(false); } }, [previousProject.id, currentProject.id]); const currentPlatform = currentProject.platform ? platforms.find(p => p.id === currentProject.platform) : undefined; const docKeys = useMemo(() => { return currentPlatform ? generateDocKeys(currentPlatform.id) : []; }, [currentPlatform]); const {docContents, isLoading, hasOnboardingContents} = useOnboardingDocs({ project: currentProject, docKeys, isPlatformSupported: isPlatformSupported(currentPlatform), }); if (isLoading) { return ; } const doesNotSupportPerformance = currentProject.platform ? withoutPerformanceSupport.has(currentProject.platform) : false; if (doesNotSupportPerformance) { return (
{tct( 'Fiddlesticks. Performance isn’t available for your [platform] project yet but we’re definitely still working on it. Stay tuned.', {platform: currentPlatform?.name || currentProject.slug} )}
); } if (!currentPlatform || !hasOnboardingContents) { return (
{tct( 'Fiddlesticks. This checklist isn’t available for your [project] project yet, but for now, go to Sentry docs for installation details.', {project: currentProject.slug} )}
); } return (
{tct( `Adding Performance to your [platform] project is simple. Make sure you've got these basics down.`, {platform: currentPlatform?.name || currentProject.slug} )}
{docKeys.map((docKey, index) => { let footer: React.ReactNode = null; if (index === docKeys.length - 1) { footer = ( { setReceived(true); }} > {() => (received ? : )} ); } return (
{footer}
); })}
); } const TaskSidebarPanel = styled(SidebarPanel)` width: 450px; `; const TopRightBackgroundImage = styled('img')` position: absolute; top: 0; right: 0; width: 60%; user-select: none; `; const TaskList = styled('div')` display: grid; grid-auto-flow: row; grid-template-columns: 100%; gap: ${space(1)}; margin: 50px ${space(4)} ${space(4)} ${space(4)}; `; const Heading = styled('div')` display: flex; color: ${p => p.theme.activeText}; font-size: ${p => p.theme.fontSizeExtraSmall}; text-transform: uppercase; font-weight: ${p => p.theme.fontWeightBold}; line-height: 1; margin-top: ${space(3)}; `; const StyledIdBadge = styled(IdBadge)` overflow: hidden; white-space: nowrap; flex-shrink: 1; `; const PulsingIndicator = styled('div')` ${pulsingIndicatorStyles}; margin-right: ${space(1)}; `; const EventWaitingIndicator = styled((p: React.HTMLAttributes) => (
{t("Waiting for this project's first transaction event")}
))` display: flex; align-items: center; flex-grow: 1; font-size: ${p => p.theme.fontSizeMedium}; color: ${p => p.theme.pink400}; `; const EventReceivedIndicator = styled((p: React.HTMLAttributes) => (
{'🎉 '} {t("We've received this project's first transaction event!")}
))` display: flex; align-items: center; flex-grow: 1; font-size: ${p => p.theme.fontSizeMedium}; color: ${p => p.theme.successText}; `; export default PerformanceOnboardingSidebar;