Browse Source

chore(profiles): Fix up profiles <> transactions compatibility (#70994)

We're looking to swap over to the transactions table so making sure
everything is compatible.
Tony Xiao 10 months ago
parent
commit
7417e26fd2

+ 27 - 11
static/app/components/events/eventStatisticalDetector/eventFunctionComparisonList.tsx

@@ -142,10 +142,10 @@ function EventComparisonListInner({
 
   const profilesQuery = useProfileEvents({
     datetime,
-    fields: ['id', 'transaction', 'profile.duration'],
-    query: `id:[${[...beforeProfileIds, ...afterProfileIds].join(', ')}]`,
+    fields: ['profile.id', 'transaction', 'transaction.duration'],
+    query: `profile.id:[${[...beforeProfileIds, ...afterProfileIds].join(', ')}]`,
     sort: {
-      key: 'profile.duration',
+      key: 'transaction.duration',
       order: 'desc',
     },
     projects: [project.id],
@@ -161,7 +161,7 @@ function EventComparisonListInner({
 
     return (
       (profilesQuery.data?.data?.filter(row =>
-        profileIds.has(row.id as string)
+        profileIds.has(row['profile.id'] as string)
       ) as ProfileItem[]) ?? []
     );
   }, [beforeProfilesQuery, profilesQuery]);
@@ -173,11 +173,13 @@ function EventComparisonListInner({
 
     return (
       (profilesQuery.data?.data?.filter(row =>
-        profileIds.has(row.id as string)
+        profileIds.has(row['profile.id'] as string)
       ) as ProfileItem[]) ?? []
     );
   }, [afterProfilesQuery, profilesQuery]);
 
+  const durationUnit = profilesQuery.data?.meta?.units?.['transaction.duration'] ?? '';
+
   return (
     <Wrapper>
       <EventDataSection type="profiles-before" title={t('Example Profiles Before')}>
@@ -187,6 +189,7 @@ function EventComparisonListInner({
           organization={organization}
           profiles={beforeProfiles}
           project={project}
+          unit={durationUnit}
         />
       </EventDataSection>
       <EventDataSection type="profiles-after" title={t('Example Profiles After')}>
@@ -196,6 +199,7 @@ function EventComparisonListInner({
           organization={organization}
           profiles={afterProfiles}
           project={project}
+          unit={durationUnit}
         />
       </EventDataSection>
     </Wrapper>
@@ -203,10 +207,10 @@ function EventComparisonListInner({
 }
 
 interface ProfileItem {
-  id: string;
-  'profile.duration': number;
+  'profile.id': string;
   timestamp: string;
   transaction: string;
+  'transaction.duration': number;
 }
 
 interface EventListProps {
@@ -215,6 +219,7 @@ interface EventListProps {
   organization: Organization;
   profiles: ProfileItem[];
   project: Project;
+  unit: string;
 }
 
 function EventList({
@@ -223,6 +228,7 @@ function EventList({
   organization,
   profiles,
   project,
+  unit,
 }: EventListProps) {
   return (
     <ListContainer>
@@ -240,7 +246,7 @@ function EventList({
         const target = generateProfileFlamechartRouteWithQuery({
           orgSlug: organization.slug,
           projectSlug: project.slug,
-          profileId: item.id,
+          profileId: item['profile.id'],
           query: {
             frameName,
             framePackage,
@@ -248,7 +254,7 @@ function EventList({
         });
 
         return (
-          <Fragment key={item.id}>
+          <Fragment key={item['profile.id']}>
             <Container>
               <Link
                 to={target}
@@ -259,12 +265,22 @@ function EventList({
                   });
                 }}
               >
-                {getShortEventId(item.id)}
+                {getShortEventId(item['profile.id'])}
               </Link>
             </Container>
             <Container>{item.transaction}</Container>
             <NumberContainer>
-              <PerformanceDuration nanoseconds={item['profile.duration']} abbreviation />
+              {unit === 'millisecond' ? (
+                <PerformanceDuration
+                  milliseconds={item['transaction.duration']}
+                  abbreviation
+                />
+              ) : (
+                <PerformanceDuration
+                  nanoseconds={item['transaction.duration']}
+                  abbreviation
+                />
+              )}
             </NumberContainer>
           </Fragment>
         );

+ 2 - 6
static/app/components/profiling/transactionProfileIdProvider.tsx

@@ -39,10 +39,6 @@ export function TransactionProfileIdProvider({
     };
   }, [timestamp]);
 
-  const profileIdColumn = organization.features.includes('profiling-using-transactions')
-    ? 'profile.id'
-    : 'id';
-
   const transactionIdColumn = organization.features.includes(
     'profiling-using-transactions'
   )
@@ -51,7 +47,7 @@ export function TransactionProfileIdProvider({
 
   const {status, data, error} = useProfileEvents({
     projects: projectId ? [projectId] : undefined,
-    fields: [profileIdColumn],
+    fields: ['profile.id'],
     referrer: 'transactionToProfileProvider',
     limit: 1,
     sort: {
@@ -73,7 +69,7 @@ export function TransactionProfileIdProvider({
     }
   }, [status, error]);
 
-  const profileId = (data?.data[0]?.[profileIdColumn] as string | undefined) ?? null;
+  const profileId = (data?.data[0]?.['profile.id'] as string | undefined) ?? null;
 
   return (
     <TransactionProfileContext.Provider value={profileId}>

+ 1 - 1
static/app/components/profiling/transactonProfileIdProvider.spec.tsx

@@ -73,7 +73,7 @@ describe('TransactionProfileIdProvider', () => {
       body: {
         data: [
           {
-            id: MOCK_PROFILE_ID,
+            'profile.id': MOCK_PROFILE_ID,
           },
         ],
       },

+ 9 - 3
static/app/utils/profiling/hooks/useProfilingTransactionQuickSummary.tsx

@@ -29,8 +29,14 @@ export function useProfilingTransactionQuickSummary(
     skipSlowestProfile = false,
   } = options;
 
+  const profilesQueryString = useMemo(() => {
+    const conditions = new MutableSearch('');
+    conditions.setFilterValues('transaction', [transaction]);
+    return conditions.formatString();
+  }, [transaction]);
+
   const baseQueryOptions: Omit<UseProfileEventsOptions, 'sort' | 'referrer'> = {
-    query: `transaction:"${transaction}"`,
+    query: profilesQueryString,
     fields: getProfilesTableFields(project.platform),
     enabled: Boolean(transaction),
     limit: 1,
@@ -58,7 +64,7 @@ export function useProfilingTransactionQuickSummary(
     enabled: !skipLatestProfile,
   });
 
-  const query = useMemo(() => {
+  const functionsQueryString = useMemo(() => {
     const conditions = new MutableSearch('');
     conditions.setFilterValues('transaction', [transaction]);
     conditions.setFilterValues('is_application', ['1']);
@@ -72,7 +78,7 @@ export function useProfilingTransactionQuickSummary(
       key: 'sum()',
       order: 'desc',
     },
-    query,
+    query: functionsQueryString,
     limit: 5,
     enabled: !skipFunctions,
   });

+ 4 - 24
static/app/views/profiling/content.tsx

@@ -31,7 +31,6 @@ 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 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';
@@ -65,7 +64,7 @@ function ProfilingContent({location}: ProfilingContentProps) {
     'profiling-using-transactions'
   );
 
-  const fields = profilingUsingTransactions ? ALL_FIELDS : BASE_FIELDS;
+  const fields = ALL_FIELDS;
 
   const sort = formatSort<FieldType>(decodeScalar(location.query.sort), fields, {
     key: 'count()',
@@ -138,22 +137,6 @@ function ProfilingContent({location}: ProfilingContentProps) {
     );
   }, [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 (
     <SentryDocumentTitle title={t('Profiling')} orgSlug={organization.slug}>
       <PageFiltersContainer
@@ -194,9 +177,9 @@ function ProfilingContent({location}: ProfilingContentProps) {
                 </PageFilterBar>
                 {profilingUsingTransactions ? (
                   <SearchBar
-                    searchSource="profile_summary"
+                    searchSource="profile_landing"
                     organization={organization}
-                    projectIds={eventView.project}
+                    projectIds={selection.projects}
                     query={query}
                     onSearch={handleSearch}
                     maxQueryLength={MAX_QUERY_LENGTH}
@@ -317,7 +300,7 @@ function ProfilingContent({location}: ProfilingContentProps) {
   );
 }
 
-const BASE_FIELDS = [
+const ALL_FIELDS = [
   'transaction',
   'project.id',
   'last_seen()',
@@ -328,9 +311,6 @@ const BASE_FIELDS = [
   '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 StyledHeaderContent = styled(Layout.HeaderContent)`

+ 58 - 10
static/app/views/profiling/profileSummary/profilesTable.tsx

@@ -11,30 +11,60 @@ 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';
+import useOrganization from 'sentry/utils/useOrganization';
 
-const FIELDS = [
+const ALL_FIELDS = [
   'profile.id',
   'timestamp',
-  'profile.duration',
+  'transaction.duration',
   'release',
   'environment',
-  'os.name',
-  'os.version',
   'trace',
   'trace.transaction',
+  'id',
 ] as const;
 
-type FieldType = (typeof FIELDS)[number];
+type FieldType = (typeof ALL_FIELDS)[number];
+
+const FIELDS = [
+  'profile.id',
+  'timestamp',
+  'transaction.duration',
+  'release',
+  'environment',
+  'trace',
+  'trace.transaction',
+] as const;
 
 export function ProfilesTable() {
   const location = useLocation();
 
+  const organization = useOrganization();
+
+  const useTransactions = organization.features.includes('profiling-using-transactions');
+
+  const queryFields = useMemo(() => {
+    const transactionIdColumn = useTransactions
+      ? ('id' as const)
+      : ('trace.transaction' as const);
+
+    return [
+      'profile.id',
+      'timestamp',
+      'transaction.duration',
+      'release',
+      'environment',
+      'trace',
+      transactionIdColumn,
+    ] as const;
+  }, [useTransactions]);
+
   const sort = useMemo(() => {
-    return formatSort<FieldType>(decodeScalar(location.query.sort), FIELDS, {
+    return formatSort<FieldType>(decodeScalar(location.query.sort), queryFields, {
       key: 'timestamp',
       order: 'desc',
     });
-  }, [location.query.sort]);
+  }, [queryFields, location.query.sort]);
 
   const rawQuery = useMemo(() => {
     return decodeScalar(location.query.query, '');
@@ -58,7 +88,7 @@ export function ProfilesTable() {
 
   const profiles = useProfileEvents<FieldType>({
     cursor: profilesCursor,
-    fields: FIELDS,
+    fields: queryFields,
     query,
     sort,
     limit: 20,
@@ -66,15 +96,33 @@ export function ProfilesTable() {
   });
 
   const eventsTableProps = useMemo(() => {
-    return {columns: FIELDS.slice(), sortableColumns: new Set(FIELDS)};
+    return {columns: FIELDS, sortableColumns: new Set(FIELDS)};
   }, []);
 
+  // Transform the response so that the data is compatible with the renderer
+  // regardless if we're using the transactions or profiles table
+  const data = useMemo(() => {
+    if (!profiles.data) {
+      return null;
+    }
+    const _data = {
+      data: profiles.data.data.map(row => {
+        return {
+          ...row,
+          'trace.transaction': useTransactions ? row.id : row['trace.transaction'],
+        };
+      }),
+      meta: profiles.data.meta,
+    };
+    return _data;
+  }, [profiles.data, useTransactions]);
+
   return (
     <ProfileEvents>
       <ProfileEventsTableContainer>
         <ProfileEventsTable
           sort={sort}
-          data={profiles.status === 'success' ? profiles.data : null}
+          data={profiles.status === 'success' ? data : null}
           error={profiles.status === 'error' ? t('Unable to load profiles') : null}
           isLoading={profiles.status === 'loading'}
           {...eventsTableProps}