Browse Source

fix(functions): Missing function examples in profile summary (#64378)

With the change to process non indexed profiles, it's possible that we
don't have any example profiles to link to anymore. Make sure to handle
that case.
Tony Xiao 1 year ago
parent
commit
a750e74676

+ 33 - 25
static/app/views/performance/trends/changeExplorerUtils/functionsList.tsx

@@ -1,4 +1,4 @@
-import {useMemo} from 'react';
+import {Fragment, useMemo} from 'react';
 import type {Location} from 'history';
 import moment from 'moment';
 
@@ -8,6 +8,7 @@ import {IconWarning} from 'sentry/icons';
 import {t, tct} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import type {Organization, Project} from 'sentry/types';
+import {defined} from 'sentry/utils';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import {parsePeriodToHours} from 'sentry/utils/dates';
 import {useDiscoverQuery} from 'sentry/utils/discover/discoverQuery';
@@ -388,28 +389,37 @@ export function NumberedFunctionsList(props: NumberedFunctionsListProps) {
     .map((func, index) => {
       const profiles = func['examples()'] as string[];
 
-      const functionSummaryView = generateProfileFlamechartRouteWithQuery({
-        orgSlug: organization.slug,
-        projectSlug: project?.slug || '',
-        profileId: profiles[0],
-        query: {
-          frameName: func.function as string,
-          framePackage: func.package as string,
-        },
-      });
-
-      const handleClickAnalytics = () => {
-        trackAnalytics(
-          'performance_views.performance_change_explorer.function_link_clicked',
-          {
-            organization,
-            transaction: transactionName,
-            package: func.package as string,
-            function: func.function as string,
-            profile_id: profiles[0],
-          }
+      let rendered = <Fragment>{func.function}</Fragment>;
+      if (defined(profiles[0])) {
+        const functionSummaryView = generateProfileFlamechartRouteWithQuery({
+          orgSlug: organization.slug,
+          projectSlug: project?.slug || '',
+          profileId: profiles[0],
+          query: {
+            frameName: func.function as string,
+            framePackage: func.package as string,
+          },
+        });
+
+        const handleClickAnalytics = () => {
+          trackAnalytics(
+            'performance_views.performance_change_explorer.function_link_clicked',
+            {
+              organization,
+              transaction: transactionName,
+              package: func.package as string,
+              function: func.function as string,
+              profile_id: profiles[0],
+            }
+          );
+        };
+
+        rendered = (
+          <ListLink to={functionSummaryView} onClick={handleClickAnalytics}>
+            {rendered}
+          </ListLink>
         );
-      };
+      }
 
       return (
         <li key={`list-item-${index}`}>
@@ -417,9 +427,7 @@ export function NumberedFunctionsList(props: NumberedFunctionsListProps) {
             <p style={{marginLeft: space(2)}}>
               {tct('[changeType] suspect function', {changeType: func.changeType})}
             </p>
-            <ListLink to={functionSummaryView} onClick={handleClickAnalytics}>
-              {func.function}
-            </ListLink>
+            {rendered}
             <TimeDifference difference={func.avgTimeDifference / 1000000} />
           </ListItemWrapper>
         </li>

+ 24 - 18
static/app/views/profiling/profileSummary/regressedProfileFunctions.tsx

@@ -13,6 +13,7 @@ import {TextTruncateOverflow} from 'sentry/components/profiling/textTruncateOver
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import type {Organization, Project} from 'sentry/types';
+import {defined} from 'sentry/utils';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import {formatPercentage} from 'sentry/utils/formatters';
 import type {FunctionTrend, TrendType} from 'sentry/utils/profiling/hooks/types';
@@ -321,26 +322,31 @@ function RegressedFunctionBeforeAfterFlamechart(
     });
   }, [props.organization]);
 
+  let rendered = <TextTruncateOverflow>{props.fn.function}</TextTruncateOverflow>;
+  if (defined(props.fn['examples()']?.[0])) {
+    rendered = (
+      <Link
+        onClick={onRegressedFunctionClick}
+        to={generateProfileFlamechartRouteWithQuery({
+          orgSlug: props.organization.slug,
+          projectSlug: props.project?.slug ?? '',
+          profileId: (props.fn['examples()']?.[0] as string) ?? '',
+          query: {
+            // specify the frame to focus, the flamegraph will switch
+            // to the appropriate thread when these are specified
+            frameName: props.fn.function as string,
+            framePackage: props.fn.package as string,
+          },
+        })}
+      >
+        {rendered}
+      </Link>
+    );
+  }
+
   return (
     <RegressedFunctionMainRow>
-      <div>
-        <Link
-          onClick={onRegressedFunctionClick}
-          to={generateProfileFlamechartRouteWithQuery({
-            orgSlug: props.organization.slug,
-            projectSlug: props.project?.slug ?? '',
-            profileId: (props.fn['examples()']?.[0] as string) ?? '',
-            query: {
-              // specify the frame to focus, the flamegraph will switch
-              // to the appropriate thread when these are specified
-              frameName: props.fn.function as string,
-              framePackage: props.fn.package as string,
-            },
-          })}
-        >
-          <TextTruncateOverflow>{props.fn.function}</TextTruncateOverflow>
-        </Link>
-      </div>
+      <div>{rendered}</div>
       <div>
         <Link
           onClick={onRegressedFunctionClick}

+ 24 - 18
static/app/views/profiling/profileSummary/slowestProfileFunctions.tsx

@@ -13,6 +13,7 @@ import {TextTruncateOverflow} from 'sentry/components/profiling/textTruncateOver
 import {t, tn} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import type {Organization, Project} from 'sentry/types';
+import {defined} from 'sentry/utils';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import {Frame} from 'sentry/utils/profiling/frame';
 import type {EventsResultsDataRow} from 'sentry/utils/profiling/hooks/types';
@@ -172,27 +173,32 @@ function SlowestFunctionEntry(props: SlowestFunctionEntryProps) {
     );
   }, [props.func, props.project]);
 
+  let rendered = <TextTruncateOverflow>{frame.name}</TextTruncateOverflow>;
+  if (defined(props.func['examples()']?.[0])) {
+    rendered = (
+      <Link
+        onClick={props.onSlowestFunctionClick}
+        to={generateProfileFlamechartRouteWithQuery({
+          orgSlug: props.organization.slug,
+          projectSlug: props.project?.slug ?? '',
+          profileId: props.func['examples()']?.[0] as string,
+          query: {
+            // specify the frame to focus, the flamegraph will switch
+            // to the appropriate thread when these are specified
+            frameName: frame.name as string,
+            framePackage: frame.package as string,
+          },
+        })}
+      >
+        {rendered}
+      </Link>
+    );
+  }
+
   return (
     <SlowestFunctionRow>
       <SlowestFunctionMainRow>
-        <div>
-          <Link
-            onClick={props.onSlowestFunctionClick}
-            to={generateProfileFlamechartRouteWithQuery({
-              orgSlug: props.organization.slug,
-              projectSlug: props.project?.slug ?? '',
-              profileId: (props.func['examples()']?.[0] as string) ?? '',
-              query: {
-                // specify the frame to focus, the flamegraph will switch
-                // to the appropriate thread when these are specified
-                frameName: frame.name as string,
-                framePackage: frame.package as string,
-              },
-            })}
-          >
-            <TextTruncateOverflow>{frame.name}</TextTruncateOverflow>
-          </Link>
-        </div>
+        <div>{rendered}</div>
         <div>
           <PerformanceDuration nanoseconds={props.func['sum()'] as number} abbreviation />
         </div>