diff --git a/static/app/components/profiling/profileEventsTable.tsx b/static/app/components/profiling/profileEventsTable.tsx index b33320da3da652..862aedcb48ebae 100644 --- a/static/app/components/profiling/profileEventsTable.tsx +++ b/static/app/components/profiling/profileEventsTable.tsx @@ -14,6 +14,7 @@ import {t} from 'sentry/locale'; import type {Organization} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; import {defined} from 'sentry/utils'; +import {trackAnalytics} from 'sentry/utils/analytics'; import {getTimeStampFromTableDateField} from 'sentry/utils/dates'; import EventView from 'sentry/utils/discover/eventView'; import {DURATION_UNITS} from 'sentry/utils/discover/fieldRenderers'; @@ -33,8 +34,7 @@ import { useDomainViewFilters, } from 'sentry/views/insights/pages/useFilters'; import {getTraceDetailsUrl} from 'sentry/views/performance/traceDetails/utils'; - -import {ProfilingTransactionHovercard} from './profilingTransactionHovercard'; +import {profilesRouteWithQuery} from 'sentry/views/performance/transactionSummary/transactionProfiles/utils'; interface ProfileEventsTableProps { columns: readonly F[]; @@ -236,13 +236,26 @@ function ProfileEventsCell(props: ProfileEventsCellProps const project = getProjectForRow(props.baggage, props.dataRow); if (defined(project)) { + const linkToSummary = profilesRouteWithQuery({ + query: props.baggage.location.query, + orgSlug: props.baggage.organization.slug, + projectID: project.id, + transaction: props.dataRow.transaction, + }); + return ( - + + trackAnalytics('profiling_views.go_to_transaction', { + organization: props.baggage.organization, + source: 'profiling.landing.transaction_table', + }) + } + > + {props.dataRow.transaction} + ); } diff --git a/static/app/components/profiling/profilingTransactionHovercard.tsx b/static/app/components/profiling/profilingTransactionHovercard.tsx deleted file mode 100644 index 0294088b7c9f8d..00000000000000 --- a/static/app/components/profiling/profilingTransactionHovercard.tsx +++ /dev/null @@ -1,270 +0,0 @@ -import {Fragment, useEffect} from 'react'; -import styled from '@emotion/styled'; - -import {LinkButton} from 'sentry/components/button'; -import {Flex} from 'sentry/components/container/flex'; -import {Hovercard} from 'sentry/components/hovercard'; -import { - FunctionsMiniGrid, - FunctionsMiniGridEmptyState, - FunctionsMiniGridLoading, -} from 'sentry/components/profiling/functionsMiniGrid'; -import {TextTruncateOverflow} from 'sentry/components/profiling/textTruncateOverflow'; -import {t} from 'sentry/locale'; -import {space} from 'sentry/styles/space'; -import type {Organization} from 'sentry/types/organization'; -import type {Project} from 'sentry/types/project'; -import {trackAnalytics} from 'sentry/utils/analytics'; -import {getShortEventId} from 'sentry/utils/events'; -import {useProfilingTransactionQuickSummary} from 'sentry/utils/profiling/hooks/useProfilingTransactionQuickSummary'; -import { - generateProfileFlamechartRouteWithQuery, - generateProfileSummaryRouteWithQuery, -} from 'sentry/utils/profiling/routes'; -import {useLocation} from 'sentry/utils/useLocation'; -import {profilesRouteWithQuery} from 'sentry/views/performance/transactionSummary/transactionProfiles/utils'; - -import Link from '../links/link'; -import LoadingIndicator from '../loadingIndicator'; -import PerformanceDuration from '../performanceDuration'; - -interface ProfilingTransactionHovercardProps { - organization: Organization; - project: Project; - transaction: string; -} - -export function ProfilingTransactionHovercard(props: ProfilingTransactionHovercardProps) { - const {project, transaction, organization} = props; - const {query} = useLocation(); - - if (!organization.features.includes('continuous-profiling-ui')) { - const linkToSummary = generateProfileSummaryRouteWithQuery({ - query, - orgSlug: organization.slug, - projectSlug: project.slug, - transaction, - }); - - const triggerLink = ( - - trackAnalytics('profiling_views.go_to_transaction', { - organization, - source: 'transaction_hovercard.trigger', - }) - } - > - {transaction} - - ); - - return ( - - {transaction} - - {t('View Profiles')} - - - } - body={ - - } - showUnderline - > - {triggerLink} - - ); - } - - const linkToSummary = profilesRouteWithQuery({ - query, - orgSlug: organization.slug, - projectID: project.id, - transaction, - }); - - return ( - - trackAnalytics('profiling_views.go_to_transaction', { - organization, - source: 'profiling.landing.transaction_table', - }) - } - > - {transaction} - - ); -} - -export function ProfilingTransactionHovercardBody({ - transaction, - project, - organization, -}: ProfilingTransactionHovercardProps) { - const { - slowestProfile, - slowestProfileQuery, - slowestProfileDurationMultiplier, - latestProfileQuery, - latestProfile, - functionsQuery, - functions, - } = useProfilingTransactionQuickSummary({ - transaction, - project, - referrer: 'api.profiling.transaction-hovercard', - }); - - const linkToFlamechartRoute = ( - profileId: string, - query?: {frameName: string; framePackage: string} - ) => { - return generateProfileFlamechartRouteWithQuery({ - orgSlug: organization.slug, - projectSlug: project.slug, - profileId, - query, - }); - }; - - useEffect(() => { - trackAnalytics('profiling_ui_events.transaction_hovercard_view', { - organization, - }); - }, [organization]); - - return ( - - - - {latestProfile ? ( - - trackAnalytics('profiling_views.go_to_flamegraph', { - organization, - source: 'transaction_hovercard.latest_profile', - }) - } - > - {getShortEventId(String(latestProfile!['profile.id']))} - - ) : ( - '-' - )} - - - - {slowestProfile ? ( - - - - trackAnalytics('profiling_views.go_to_flamegraph', { - organization, - source: 'transaction_hovercard.slowest_profile', - }) - } - > - ({getShortEventId(String(slowestProfile['profile.id']))}) - - - ) : ( - '-' - )} - - - - - - trackAnalytics('profiling_views.go_to_flamegraph', { - organization, - source: 'transaction_hovercard.suspect_function', - }) - } - /> - - - ); -} - -type ProfilingTransactionHovercardFunctionsProps = React.ComponentProps< - typeof FunctionsMiniGrid -> & {isLoading: boolean}; - -function ProfilingTransactionHovercardFunctions( - props: ProfilingTransactionHovercardFunctionsProps -) { - if (props.isLoading) { - return ; - } - - if (!props.functions || props.functions?.length === 0) { - return ; - } - return ; -} - -interface ContextDetailProps { - children: React.ReactNode; - isLoading: boolean; - title?: React.ReactNode; -} -function ContextDetail(props: ContextDetailProps) { - const {title, children, isLoading} = props; - - return ( - - {title && {title}} - - {isLoading ? ( - - - - ) : ( - children - )} - - - ); -} - -const UppercaseTitle = styled('span')` - text-transform: uppercase; - font-size: ${p => p.theme.fontSizeExtraSmall}; - font-weight: ${p => p.theme.fontWeightBold}; - color: ${p => p.theme.subText}; -`; - -const StyledHovercard = styled(Hovercard)` - width: 400px; -`; diff --git a/static/app/utils/profiling/hooks/useProfileEvents.spec.tsx b/static/app/utils/profiling/hooks/useProfileEvents.spec.tsx index 4be746eee5b2d3..dbbb4e3e6a231d 100644 --- a/static/app/utils/profiling/hooks/useProfileEvents.spec.tsx +++ b/static/app/utils/profiling/hooks/useProfileEvents.spec.tsx @@ -52,7 +52,7 @@ describe('useProfileEvents', function () { match: [ MockApiClient.matchQuery({ dataset: 'discover', - query: 'has:profile.id (transaction:foo)', + query: '(has:profile.id OR (has:profiler.id has:thread.id)) (transaction:foo)', }), ], }); diff --git a/static/app/utils/profiling/hooks/useProfileEvents.tsx b/static/app/utils/profiling/hooks/useProfileEvents.tsx index 8696daa42f5a4e..700a5f2e571128 100644 --- a/static/app/utils/profiling/hooks/useProfileEvents.tsx +++ b/static/app/utils/profiling/hooks/useProfileEvents.tsx @@ -14,7 +14,6 @@ export interface UseProfileEventsOptions fields: readonly F[]; referrer: string; sort: Sort; - continuousProfilingCompat?: boolean; cursor?: string; datetime?: PageFilters['datetime']; enabled?: boolean; @@ -30,7 +29,6 @@ export function useProfileEvents({ referrer, query, sort, - continuousProfilingCompat, cursor, enabled = true, refetchOnMount = true, @@ -40,11 +38,7 @@ export function useProfileEvents({ const organization = useOrganization(); const {selection} = usePageFilters(); - if (continuousProfilingCompat) { - query = `(has:profile.id OR (has:profiler.id has:thread.id)) ${query ? `(${query})` : ''}`; - } else { - query = `has:profile.id ${query ? `(${query})` : ''}`; - } + query = `(has:profile.id OR (has:profiler.id has:thread.id)) ${query ? `(${query})` : ''}`; const path = `/organizations/${organization.slug}/events/`; const endpointOptions = { diff --git a/static/app/utils/profiling/hooks/useProfileEventsStats.spec.tsx b/static/app/utils/profiling/hooks/useProfileEventsStats.spec.tsx index 6bc922b82f9ff9..7735fcb0f999e5 100644 --- a/static/app/utils/profiling/hooks/useProfileEventsStats.spec.tsx +++ b/static/app/utils/profiling/hooks/useProfileEventsStats.spec.tsx @@ -74,7 +74,7 @@ describe('useProfileEvents', function () { match: [ MockApiClient.matchQuery({ dataset: 'discover', - query: 'has:profile.id (transaction:foo)', + query: '(has:profile.id OR (has:profiler.id has:thread.id)) (transaction:foo)', }), ], }); @@ -135,7 +135,7 @@ describe('useProfileEvents', function () { match: [ MockApiClient.matchQuery({ dataset: 'discover', - query: 'has:profile.id (transaction:foo)', + query: '(has:profile.id OR (has:profiler.id has:thread.id)) (transaction:foo)', }), ], }); @@ -197,7 +197,7 @@ describe('useProfileEvents', function () { match: [ MockApiClient.matchQuery({ dataset: 'discover', - query: 'has:profile.id (transaction:foo)', + query: '(has:profile.id OR (has:profiler.id has:thread.id)) (transaction:foo)', }), ], }); diff --git a/static/app/utils/profiling/hooks/useProfileEventsStats.tsx b/static/app/utils/profiling/hooks/useProfileEventsStats.tsx index 4ef093b755d4b7..1bec0042bf2cff 100644 --- a/static/app/utils/profiling/hooks/useProfileEventsStats.tsx +++ b/static/app/utils/profiling/hooks/useProfileEventsStats.tsx @@ -11,7 +11,6 @@ interface UseProfileEventsStatsOptions { dataset: 'discover' | 'profiles' | 'profileFunctions'; referrer: string; yAxes: readonly F[]; - continuousProfilingCompat?: boolean; datetime?: PageFilters['datetime']; enabled?: boolean; interval?: string; @@ -19,7 +18,6 @@ interface UseProfileEventsStatsOptions { } export function useProfileEventsStats({ - continuousProfilingCompat, dataset, datetime, interval, @@ -38,11 +36,7 @@ export function useProfileEventsStats({ } if (dataset === 'discover') { - if (continuousProfilingCompat) { - query = `(has:profile.id OR (has:profiler.id has:thread.id)) ${query ? `(${query})` : ''}`; - } else { - query = `has:profile.id ${query ? `(${query})` : ''}`; - } + query = `(has:profile.id OR (has:profiler.id has:thread.id)) ${query ? `(${query})` : ''}`; } const path = `/organizations/${organization.slug}/events-stats/`; diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/styles.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/styles.tsx index 4a042fc71b0d73..4f6e32eb669439 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/styles.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/styles.tsx @@ -832,7 +832,7 @@ function NodeActions(props: { /> ) : null} - {organization.features.includes('continuous-profiling-ui') && !!profileLink ? ( + {profileLink ? ( { - traceAnalytics.trackViewContinuousProfile(organization); - navigate(props.profileLink!); - }, - label: t('Continuous Profile'), - } - : null; + const continuousProfileLink: MenuItemProps | null = props.profileLink + ? { + key: 'continuous-profile', + onAction: () => { + traceAnalytics.trackViewContinuousProfile(organization); + navigate(props.profileLink!); + }, + label: t('Continuous Profile'), + } + : null; if (isTransactionNode(props.node)) { return [showInView, jsonDetails, continuousProfileLink].filter(TypeSafeBoolean); @@ -955,8 +954,7 @@ function LegacyNodeActions(props: { return ( - {organization.features.includes('continuous-profiling-ui') && - !!props.profileLink ? ( + {props.profileLink ? ( {t('Continuous Profile')} diff --git a/static/app/views/performance/transactionSummary/transactionProfiles/content.tsx b/static/app/views/performance/transactionSummary/transactionProfiles/content.tsx index 937216ce8f758a..1f1f57ee9dce8d 100644 --- a/static/app/views/performance/transactionSummary/transactionProfiles/content.tsx +++ b/static/app/views/performance/transactionSummary/transactionProfiles/content.tsx @@ -287,16 +287,11 @@ const ALL_DIGESTS = [ ] satisfies ProfilingFieldType[]; function ProfileDigest({query}: TransactionProfilesContentProps) { - const organization = useOrganization(); - const profilesSummary = useProfileEvents({ fields: ALL_DIGESTS, query, sort: {key: 'last_seen()', order: 'desc'}, referrer: 'api.profiling.profile-summary-table', - continuousProfilingCompat: organization.features.includes( - 'continuous-profiling-compat' - ), }); const digestData = profilesSummary.data?.data?.[0]; @@ -419,9 +414,6 @@ function ProfileList({query: userQuery, transaction}: TransactionProfilesContent referrer: 'api.profiling.profile-summary-table', cursor, limit: 10, - continuousProfilingCompat: organization.features.includes( - 'continuous-profiling-compat' - ), }); const handleSort = useCallback( diff --git a/static/app/views/performance/transactionSummary/transactionProfiles/index.tsx b/static/app/views/performance/transactionSummary/transactionProfiles/index.tsx index c98ddcfb8b8a87..014b917c354a1a 100644 --- a/static/app/views/performance/transactionSummary/transactionProfiles/index.tsx +++ b/static/app/views/performance/transactionSummary/transactionProfiles/index.tsx @@ -7,7 +7,6 @@ import {DatePageFilter} from 'sentry/components/organizations/datePageFilter'; import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter'; import PageFilterBar from 'sentry/components/organizations/pageFilterBar'; import {TransactionSearchQueryBuilder} from 'sentry/components/performance/transactionSearchQueryBuilder'; -import {ProfileEventsTable} from 'sentry/components/profiling/profileEventsTable'; import type {SmartSearchBarProps} from 'sentry/components/smartSearchBar'; import {MAX_QUERY_LENGTH} from 'sentry/constants'; import {t} from 'sentry/locale'; @@ -16,12 +15,6 @@ import type {Organization} from 'sentry/types/organization'; import {browserHistory} from 'sentry/utils/browserHistory'; import EventView from 'sentry/utils/discover/eventView'; import {isAggregateField} from 'sentry/utils/discover/fields'; -import type {ProfilingFieldType} from 'sentry/utils/profiling/hooks/useProfileEvents'; -import { - getProfilesTableFields, - useProfileEvents, -} from 'sentry/utils/profiling/hooks/useProfileEvents'; -import {formatSort} from 'sentry/utils/profiling/hooks/utils'; import {decodeScalar} from 'sentry/utils/queryString'; import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import {useLocation} from 'sentry/utils/useLocation'; @@ -38,120 +31,6 @@ interface ProfilesProps { transaction: string; } -function ProfilesLegacy({organization, transaction}: ProfilesProps) { - const location = useLocation(); - const {projects} = useProjects(); - - const profilesCursor = useMemo( - () => decodeScalar(location.query.cursor), - [location.query.cursor] - ); - - const project = projects.find(p => p.id === location.query.project); - const fields = getProfilesTableFields(project?.platform); - const sortableFields = useMemo(() => new Set(fields), [fields]); - - const sort = formatSort(decodeScalar(location.query.sort), fields, { - key: 'timestamp', - order: 'desc', - }); - - const rawQuery = useMemo( - () => decodeScalar(location.query.query, ''), - [location.query.query] - ); - - const query = useMemo(() => { - const conditions = new MutableSearch(rawQuery); - conditions.setFilterValues('event.type', ['transaction']); - conditions.setFilterValues('transaction', [transaction]); - - Object.keys(conditions.filters).forEach(field => { - if (isAggregateField(field)) { - conditions.removeFilter(field); - } - }); - - return conditions.formatString(); - }, [transaction, rawQuery]); - - const profiles = useProfileEvents({ - cursor: profilesCursor, - fields, - query, - sort, - limit: 30, - referrer: 'api.profiling.transactions-profiles-table', - }); - - const handleSearch: SmartSearchBarProps['onSearch'] = useCallback( - (searchQuery: string) => { - browserHistory.push({ - ...location, - query: { - ...location.query, - cursor: undefined, - query: searchQuery || undefined, - }, - }); - }, - [location] - ); - - const projectIds = useMemo( - () => (project ? [parseInt(project?.id, 10)] : undefined), - [project] - ); - - return ( - EventView.fromLocation(location)} - getDocumentTitle={() => t(`Profile: %s`, transaction)} - childComponent={() => { - return ( - - - - - - - {organization.features.includes('search-query-builder-performance') ? ( - - ) : ( - parseInt(p.id, 10))} - query={rawQuery} - onSearch={handleSearch} - maxQueryLength={MAX_QUERY_LENGTH} - /> - )} - - - - ); - }} - /> - ); -} - function Profiles({organization, transaction}: ProfilesProps) { const location = useLocation(); const {projects} = useProjects(); @@ -262,11 +141,7 @@ function ProfilesIndex() { return null; } - if (organization.features.includes('continuous-profiling-compat')) { - return ; - } - - return ; + return ; } export default ProfilesIndex; diff --git a/static/app/views/profiling/content.tsx b/static/app/views/profiling/content.tsx index 9cc1b1a3679d74..989cdae0fae4f4 100644 --- a/static/app/views/profiling/content.tsx +++ b/static/app/views/profiling/content.tsx @@ -61,239 +61,6 @@ interface ProfilingContentProps { location: Location; } -function ProfilingContentLegacy({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} - - )} - - - - - - - {organization.features.includes('search-query-builder-performance') ? ( - - ) : ( - - )} - - {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')} - - - {t('Read Docs')} - - - - ) : ( - - {organization.features.includes( - 'profiling-global-suspect-functions' - ) ? ( - - - - - - - - ) : ( - - - - - )} - - - - )} -
-
-
-
-
- ); -} - function validateTab(tab: unknown): tab is 'flamegraph' | 'transactions' { return tab === 'flamegraph' || tab === 'transactions'; } @@ -304,7 +71,7 @@ function decodeTab(tab: unknown): 'flamegraph' | 'transactions' { return validateTab(tab) ? tab : 'transactions'; } -function ProfilingContent({location}: ProfilingContentProps) { +export default function ProfilingContent({location}: ProfilingContentProps) { const organization = useOrganization(); const {selection} = usePageFilters(); const {projects} = useProjects(); @@ -429,7 +196,6 @@ function ProfilingTransactionsContent(props: ProfilingTabContentProps) { query, sort, referrer: 'api.profiling.landing-table', - continuousProfilingCompat: true, }); const transactionsError = @@ -491,7 +257,6 @@ function ProfilingTransactionsContent(props: ProfilingTabContentProps) { referrer="api.profiling.landing-chart" userQuery={query} selection={selection} - continuousProfilingCompat /> ; - } - - return ; -} - -export default ProfilingContentWrapper; diff --git a/static/app/views/profiling/landing/profilesChartWidget.tsx b/static/app/views/profiling/landing/profilesChartWidget.tsx index 3d8c6cb63d8700..bc9f5a2dd5c968 100644 --- a/static/app/views/profiling/landing/profilesChartWidget.tsx +++ b/static/app/views/profiling/landing/profilesChartWidget.tsx @@ -9,7 +9,6 @@ import type {PageFilters} from 'sentry/types/core'; import type {Series} from 'sentry/types/echarts'; import {axisLabelFormatter, tooltipFormatter} from 'sentry/utils/discover/charts'; import {useProfileEventsStats} from 'sentry/utils/profiling/hooks/useProfileEventsStats'; -import useOrganization from 'sentry/utils/useOrganization'; import { ContentContainer, @@ -21,7 +20,6 @@ import { interface ProfilesChartWidgetProps { chartHeight: number; referrer: string; - continuousProfilingCompat?: boolean; header?: ReactNode; selection?: PageFilters; userQuery?: string; @@ -32,7 +30,6 @@ const SERIES_ORDER = ['p99()', 'p95()', 'p75()', 'p50()'] as const; export function ProfilesChartWidget({ chartHeight, - continuousProfilingCompat, header, referrer, selection, @@ -40,14 +37,12 @@ export function ProfilesChartWidget({ widgetHeight, }: ProfilesChartWidgetProps) { const theme = useTheme(); - const organization = useOrganization(); const profileStats = useProfileEventsStats({ dataset: 'profiles', query: userQuery, referrer, yAxes: SERIES_ORDER, - continuousProfilingCompat, }); const series: Series[] = useMemo(() => { @@ -119,11 +114,7 @@ export function ProfilesChartWidget({ {header ?? ( - - {organization.features.includes('continuous-profiling-compat') - ? t('Transactions by Percentiles') - : t('Profiles by Percentiles')} - + {t('Transactions by Percentiles')} )}