import {Fragment, useCallback, useEffect, useMemo} from 'react'; import {browserHistory} from 'react-router'; import styled from '@emotion/styled'; import {Location} from 'history'; 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 IdBadge from 'sentry/components/idBadge'; import * as Layout from 'sentry/components/layouts/thirds'; import PageFilterBar from 'sentry/components/organizations/pageFilterBar'; import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container'; import { ProfilingBreadcrumbs, ProfilingBreadcrumbsProps, } from 'sentry/components/profiling/profilingBreadcrumbs'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import SmartSearchBar, {SmartSearchBarProps} from 'sentry/components/smartSearchBar'; import {MAX_QUERY_LENGTH} from 'sentry/constants'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {PageFilters, Project} from 'sentry/types'; import {defined, generateQueryWithTag} from 'sentry/utils'; import {trackAnalytics} from 'sentry/utils/analytics'; import EventView from 'sentry/utils/discover/eventView'; import {formatTagKey, isAggregateField} from 'sentry/utils/discover/fields'; import {useCurrentProjectFromRouteParam} from 'sentry/utils/profiling/hooks/useCurrentProjectFromRouteParam'; import {useProfileEvents} from 'sentry/utils/profiling/hooks/useProfileEvents'; import {useProfileFilters} from 'sentry/utils/profiling/hooks/useProfileFilters'; import {decodeScalar} from 'sentry/utils/queryString'; import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import useOrganization from 'sentry/utils/useOrganization'; import withPageFilters from 'sentry/utils/withPageFilters'; import Tags from 'sentry/views/discover/tags'; import {transactionSummaryRouteWithQuery} from 'sentry/views/performance/transactionSummary/utils'; import {DEFAULT_PROFILING_DATETIME_SELECTION} from 'sentry/views/profiling/utils'; import {ProfileSummaryContent} from './content'; interface ProfileSummaryPageProps { location: Location; params: { projectId?: Project['slug']; }; selection: PageFilters; } function ProfileSummaryPage(props: ProfileSummaryPageProps) { const organization = useOrganization(); const project = useCurrentProjectFromRouteParam(); const profilingUsingTransactions = organization.features.includes( 'profiling-using-transactions' ); useEffect(() => { trackAnalytics('profiling_views.profile_summary', { organization, project_platform: project?.platform, project_id: project?.id, }); // ignore currentProject so we don't block the analytics event // or fire more than once unnecessarily // eslint-disable-next-line react-hooks/exhaustive-deps }, [organization]); const transaction = decodeScalar(props.location.query.transaction); const rawQuery = useMemo( () => decodeScalar(props.location.query.query, ''), [props.location.query.query] ); const query = useMemo(() => { const search = new MutableSearch(rawQuery); if (defined(transaction)) { search.setFilterValues('transaction', [transaction]); } // there are no aggregations happening on this page, // so remove any aggregate filters Object.keys(search.filters).forEach(field => { if (isAggregateField(field)) { search.removeFilter(field); } }); return search.formatString(); }, [rawQuery, transaction]); const profilesAggregateQuery = useProfileEvents<'count()'>({ fields: ['count()'], sort: {key: 'count()', order: 'desc'}, referrer: 'api.profiling.profile-summary-totals', query, enabled: profilingUsingTransactions, }); const profilesCount = useMemo(() => { if (profilesAggregateQuery.status !== 'success') { return null; } return (profilesAggregateQuery.data?.data?.[0]?.['count()'] as number) || null; }, [profilesAggregateQuery]); const filtersQuery = useMemo(() => { // To avoid querying for the filters each time the query changes, // do not pass the user query to get the filters. const search = new MutableSearch(''); if (defined(transaction)) { search.setFilterValues('transaction_name', [transaction]); } return search.formatString(); }, [transaction]); const profileFilters = useProfileFilters({ query: filtersQuery, selection: props.selection, disabled: profilingUsingTransactions, }); const transactionSummaryTarget = project && transaction && transactionSummaryRouteWithQuery({ orgSlug: organization.slug, transaction, projectID: project.id, query: {query}, }); const handleSearch: SmartSearchBarProps['onSearch'] = useCallback( (searchQuery: string) => { browserHistory.push({ ...props.location, query: { ...props.location.query, query: searchQuery || undefined, cursor: undefined, }, }); }, [props.location] ); const breadcrumbTrails: ProfilingBreadcrumbsProps['trails'] = useMemo(() => { return [ { type: 'landing', payload: { query: props.location.query, }, }, { type: 'profile summary', payload: { projectSlug: project?.slug ?? '', query: props.location.query, transaction: transaction ?? '', }, }, ]; }, [props.location.query, project?.slug, transaction]); const eventView = useMemo(() => { const _eventView = EventView.fromNewQueryWithLocation( { id: undefined, version: 2, name: transaction || '', fields: [], query, projects: project ? [parseInt(project.id, 10)] : [], }, props.location ); _eventView.additionalConditions.setFilterValues('has', ['profile.id']); return _eventView; }, [props.location, project, query, transaction]); function generateTagUrl(key: string, value: string) { return { ...props.location, query: generateQueryWithTag(props.location.query, {key: formatTagKey(key), value}), }; } return ( {project && transaction && ( {project ? ( ) : null} {transaction} {transactionSummaryTarget && ( )} {profilingUsingTransactions ? ( ) : ( )} {profilingUsingTransactions && ( )} )} ); } const ActionBar = styled('div')` display: grid; gap: ${space(2)}; grid-template-columns: min-content auto; margin-bottom: ${space(2)}; `; export default withPageFilters(ProfileSummaryPage);