import {Fragment, useCallback, useEffect, useMemo} from 'react'; import {browserHistory} from 'react-router'; import styled from '@emotion/styled'; import {Location} from 'history'; import {Alert} from 'sentry/components/alert'; import {Button} from 'sentry/components/button'; import DatePageFilter from 'sentry/components/datePageFilter'; import EnvironmentPageFilter from 'sentry/components/environmentPageFilter'; import SearchBar from 'sentry/components/events/searchBar'; import * as Layout from 'sentry/components/layouts/thirds'; import PageFilterBar from 'sentry/components/organizations/pageFilterBar'; import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container'; import {PageHeadingQuestionTooltip} from 'sentry/components/pageHeadingQuestionTooltip'; import Pagination from 'sentry/components/pagination'; import { ProfilingAM1OrMMXUpgrade, ProfilingBetaAlertBanner, ProfilingUpgradeButton, } from 'sentry/components/profiling/billing/alerts'; import {ProfileEventsTable} from 'sentry/components/profiling/profileEventsTable'; import ProjectPageFilter from 'sentry/components/projectPageFilter'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import {SidebarPanelKey} from 'sentry/components/sidebar/types'; import SmartSearchBar, {SmartSearchBarProps} from 'sentry/components/smartSearchBar'; import {MAX_QUERY_LENGTH} from 'sentry/constants'; import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters'; import {t} from 'sentry/locale'; import SidebarPanelStore from 'sentry/stores/sidebarPanelStore'; import {space} from 'sentry/styles/space'; import {trackAnalytics} from 'sentry/utils/analytics'; import EventView from 'sentry/utils/discover/eventView'; import {useProfileEvents} from 'sentry/utils/profiling/hooks/useProfileEvents'; import {useProfileFilters} from 'sentry/utils/profiling/hooks/useProfileFilters'; import {formatError, formatSort} from 'sentry/utils/profiling/hooks/utils'; import {decodeScalar} from 'sentry/utils/queryString'; import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import useProjects from 'sentry/utils/useProjects'; import {DEFAULT_PROFILING_DATETIME_SELECTION} from 'sentry/views/profiling/utils'; import {LandingWidgetSelector} from './landing/landingWidgetSelector'; import {ProfileCharts} from './landing/profileCharts'; import {ProfilesChartWidget} from './landing/profilesChartWidget'; import {ProfilingSlowestTransactionsPanel} from './landing/profilingSlowestTransactionsPanel'; import {ProfilingOnboardingPanel} from './profilingOnboardingPanel'; const LEFT_WIDGET_CURSOR = 'leftCursor'; const RIGHT_WIDGET_CURSOR = 'rightCursor'; const CURSOR_PARAMS = [LEFT_WIDGET_CURSOR, RIGHT_WIDGET_CURSOR]; interface ProfilingContentProps { location: Location; } function ProfilingContent({location}: ProfilingContentProps) { const organization = useOrganization(); const {selection} = usePageFilters(); const cursor = decodeScalar(location.query.cursor); const query = decodeScalar(location.query.query, ''); const profilingUsingTransactions = organization.features.includes( 'profiling-using-transactions' ); const fields = profilingUsingTransactions ? ALL_FIELDS : BASE_FIELDS; const sort = formatSort(decodeScalar(location.query.sort), fields, { key: 'p95()', order: 'desc', }); const profileFilters = useProfileFilters({ query: '', selection, disabled: profilingUsingTransactions, }); const {projects} = useProjects(); const transactions = useProfileEvents({ cursor, fields, query, sort, referrer: 'api.profiling.landing-table', }); const transactionsError = transactions.status === 'error' ? formatError(transactions.error) : null; useEffect(() => { trackAnalytics('profiling_views.landing', { organization, }); }, [organization]); const handleSearch: SmartSearchBarProps['onSearch'] = useCallback( (searchQuery: string) => { browserHistory.push({ ...location, query: { ...location.query, cursor: undefined, query: searchQuery || undefined, }, }); }, [location] ); // Open the modal on demand const onSetupProfilingClick = useCallback(() => { trackAnalytics('profiling_views.onboarding', { organization, }); SidebarPanelStore.activatePanel(SidebarPanelKey.PROFILING_ONBOARDING); }, [organization]); const shouldShowProfilingOnboardingPanel = useMemo((): boolean => { // if it's My Projects or All projects, only show onboarding if we can't // find any projects with profiles if ( selection.projects.length === 0 || selection.projects[0] === ALL_ACCESS_PROJECTS ) { return projects.every(project => !project.hasProfiles); } // otherwise, only show onboarding if we can't find any projects with profiles // from those that were selected const projectsWithProfiles = new Set( projects.filter(project => project.hasProfiles).map(project => project.id) ); return selection.projects.every( project => !projectsWithProfiles.has(String(project)) ); }, [selection.projects, projects]); const eventView = useMemo(() => { const _eventView = EventView.fromNewQueryWithLocation( { id: undefined, version: 2, name: t('Profiling'), fields: [], query, projects: selection.projects, }, location ); _eventView.additionalConditions.setFilterValues('has', ['profile.id']); return _eventView; }, [location, query, selection.projects]); return ( {t('Profiling')} {transactionsError && ( {transactionsError} )} {profilingUsingTransactions ? ( ) : ( )} {shouldShowProfilingOnboardingPanel ? ( // If user is on m2, show default

{t('Function level insights')}

{t( 'Discover slow-to-execute or resource intensive functions within your application' )}

} /> } > {t('Set Up Profiling')} } > {t('Update plan')}
) : ( {organization.features.includes( 'profiling-global-suspect-functions' ) ? ( ) : ( )} )}
); } const BASE_FIELDS = [ 'transaction', 'project.id', 'last_seen()', 'p75()', 'p95()', 'p99()', 'count()', ] as const; // user misery is only available with the profiling-using-transactions feature const ALL_FIELDS = [...BASE_FIELDS, 'user_misery()'] as const; type FieldType = (typeof ALL_FIELDS)[number]; const ActionBar = styled('div')` display: grid; gap: ${space(2)}; grid-template-columns: min-content auto; margin-bottom: ${space(2)}; `; // TODO: another simple primitive that can easily be const PanelsGrid = styled('div')` display: grid; grid-template-columns: minmax(0, 1fr) 1fr; gap: ${space(2)}; @media (max-width: ${p => p.theme.breakpoints.small}) { grid-template-columns: minmax(0, 1fr); } `; const WidgetsContainer = styled('div')` display: grid; grid-template-columns: 1fr 1fr; gap: ${space(2)}; @media (max-width: ${p => p.theme.breakpoints.small}) { grid-template-columns: 1fr; } `; export default ProfilingContent;