import {useMemo, useState} from 'react'; import styled from '@emotion/styled'; import {PlatformIcon} from 'platformicons'; import {Button} from 'sentry/components/button'; import EmptyStateWarning from 'sentry/components/emptyStateWarning'; import Link from 'sentry/components/links/link'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import Panel from 'sentry/components/panels/panel'; import PerformanceDuration from 'sentry/components/performanceDuration'; import {Flex} from 'sentry/components/profiling/flex'; import { FunctionsMiniGrid, FunctionsMiniGridEmptyState, FunctionsMiniGridLoading, } from 'sentry/components/profiling/functionsMiniGrid'; import {TextTruncateOverflow} from 'sentry/components/profiling/textTruncateOverflow'; import {IconChevron} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {Organization, Project} from 'sentry/types'; import {trackAnalytics} from 'sentry/utils/analytics'; import {getAggregateAlias} from 'sentry/utils/discover/fields'; import {EventsResults, EventsResultsDataRow} from 'sentry/utils/profiling/hooks/types'; import {useProfileEvents} from 'sentry/utils/profiling/hooks/useProfileEvents'; import {useProfilingTransactionQuickSummary} from 'sentry/utils/profiling/hooks/useProfilingTransactionQuickSummary'; import {generateProfileSummaryRouteWithQuery} from 'sentry/utils/profiling/routes'; import {makeFormatTo} from 'sentry/utils/profiling/units/units'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import useProjects from 'sentry/utils/useProjects'; const fields = ['transaction', 'project.id', 'last_seen()', 'p95()', 'count()'] as const; type SlowestTransactionsFields = (typeof fields)[number]; export function ProfilingSlowestTransactionsPanel() { const profilingTransactionsQuery = useProfileEvents({ fields, sort: { key: 'p95()', order: 'desc', }, limit: 3, query: 'count():>3', referrer: 'api.profiling.landing-slowest-transaction-panel', }); const [openPanel, setOpenPanel] = useState(null); const profilingTransactions = useMemo( () => profilingTransactionsQuery.data?.data ?? [], [profilingTransactionsQuery.data] ); const transactionNames = useMemo( () => profilingTransactions.map(txn => txn.transaction), [profilingTransactions] ); if (transactionNames.length > 0 && !transactionNames.includes(openPanel)) { const firstTransaction = transactionNames[0]; setOpenPanel(firstTransaction as string); } const {isLoading} = profilingTransactionsQuery; const hasProfilingTransactions = !isLoading && profilingTransactions && profilingTransactions.length > 0; return ( {t('Slowest Transactions')} {t('Slowest transactions that could use some optimization.')} {(isLoading || !hasProfilingTransactions) && ( {isLoading ? ( ) : ( !hasProfilingTransactions && (

{t('No results found')}

{t( 'Transactions may not be listed due to the filters above or a low number of profiles.' )}
) )}
)} {profilingTransactions?.map(transaction => { return ( setOpenPanel(transaction.transaction as string)} units={profilingTransactionsQuery.data?.meta.units} /> ); })}
); } interface SlowestTransactionPanelItemProps { onOpen: () => void; open: boolean; transaction: EventsResultsDataRow; units?: EventsResults['meta']['units']; } function SlowestTransactionPanelItem({ transaction, open, onOpen, units, }: SlowestTransactionPanelItemProps) { const {query} = useLocation(); const organization = useOrganization(); const projects = useProjects(); const transactionProject = useMemo( () => projects.projects.find(p => p.id === String(transaction['project.id'])), [projects.projects, transaction] ); if (!transactionProject && !projects.fetching && projects.projects.length > 0) { return null; } const key: SlowestTransactionsFields = 'p95()'; const formatter = makeFormatTo( units?.[key] ?? units?.[getAggregateAlias(key)] ?? 'nanoseconds', 'milliseconds' ); return (
{ trackAnalytics('profiling_views.go_to_transaction', { source: 'slowest_transaction_panel', organization, }); }} > {transaction.transaction as string}
{open && transactionProject && ( )}
); } interface PanelItemFunctionsMiniGridProps { organization: Organization; project: Project; transaction: string; } function PanelItemFunctionsMiniGrid(props: PanelItemFunctionsMiniGridProps) { const {transaction, project, organization} = props; const {functionsQuery, functions} = useProfilingTransactionQuickSummary({ transaction, project, referrer: 'api.profiling.landing-slowest-transaction-panel', skipLatestProfile: true, skipSlowestProfile: true, }); if (functionsQuery.isLoading) { return ; } if (!functions || (functions && functions.length === 0)) { return ; } return ( trackAnalytics('profiling_views.go_to_flamegraph', { organization, source: 'slowest_transaction_panel', }) } /> ); } const FlexPanel = styled(Panel)` display: flex; flex-direction: column; `; const PanelHeading = styled('span')` font-size: ${p => p.theme.text.cardTitle.fontSize}; font-weight: ${p => p.theme.text.cardTitle.fontWeight}; line-height: ${p => p.theme.text.cardTitle.lineHeight}; `; const PanelSubheading = styled('span')` color: ${p => p.theme.subText}; `; const PanelItem = styled('div')` padding: ${space(1)} ${space(1.5)}; border-top: 1px solid ${p => p.theme.border}; `; const PanelItemBody = styled('div')` transition: height 0.1s ease; width: 100%; overflow: hidden; `; // TODO: simple layout stuff like this should come from a primitive component and we should really stop this `styled` nonsense const PanelItemBodyInner = styled('div')` padding-top: ${space(1.5)}; `; const EmptyStateDescription = styled('div')` font-size: ${p => p.theme.fontSizeMedium}; `;