import {Fragment, useCallback, useEffect, useMemo} from 'react'; import styled from '@emotion/styled'; import type {Location} from 'history'; import {Alert} from 'sentry/components/alert'; import {Button} from 'sentry/components/button'; import SearchBar from 'sentry/components/events/searchBar'; import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton'; import * as Layout from 'sentry/components/layouts/thirds'; import {DatePageFilter} from 'sentry/components/organizations/datePageFilter'; import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter'; import PageFilterBar from 'sentry/components/organizations/pageFilterBar'; import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container'; import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter'; 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 SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import {SidebarPanelKey} from 'sentry/components/sidebar/types'; import type {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 {browserHistory} from 'sentry/utils/browserHistory'; import {useProfileEvents} from 'sentry/utils/profiling/hooks/useProfileEvents'; 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 {ProfilesChart} 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 fields = ALL_FIELDS; const sort = formatSort(decodeScalar(location.query.sort), fields, { key: 'count()', order: 'desc', }); 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]); return ( {t('Profiling')} {transactionsError && ( {transactionsError} )} {shouldShowProfilingOnboardingPanel ? (

{t('Function level insights')}

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

} /> } > {t('Set Up Profiling')} } > {t('Set Up Profiling')} ) : ( {organization.features.includes( 'profiling-global-suspect-functions' ) ? ( {organization.features.includes( 'continuous-profiling-ui' ) ? null : ( )} ) : ( )} )}
); } const ALL_FIELDS = [ 'transaction', 'project.id', 'last_seen()', 'p50()', 'p75()', 'p95()', 'p99()', 'count()', ] as const; type FieldType = (typeof ALL_FIELDS)[number]; const StyledHeaderContent = styled(Layout.HeaderContent)` display: flex; align-items: center; justify-content: space-between; flex-direction: row; `; 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;