Browse Source

feat: add continuous profile link to actions (#76668)

Add continuous profile link to actions
Jonas 6 months ago
parent
commit
e070747f29

+ 5 - 0
static/app/views/performance/newTraceDetails/traceAnalytics.tsx

@@ -61,6 +61,10 @@ const trackViewEventJSON = (organization: Organization) =>
   trackAnalytics('trace.trace_layout.view_event_json', {
     organization,
   });
+const trackViewContinuousProfile = (organization: Organization) =>
+  trackAnalytics('trace.trace_layout.view_continuous_profile', {
+    organization,
+  });
 
 const trackTabPin = (organization: Organization) =>
   trackAnalytics('trace.trace_layout.tab_pin', {
@@ -147,6 +151,7 @@ const traceAnalytics = {
   // Drawer actions
   trackShowInView,
   trackViewEventJSON,
+  trackViewContinuousProfile,
   // Layout actions
   trackLayoutChange,
   trackDrawerMinimize,

+ 46 - 29
static/app/views/performance/newTraceDetails/traceDrawer/details/styles.tsx

@@ -1,4 +1,5 @@
 import {Fragment, type PropsWithChildren, useMemo} from 'react';
+import {browserHistory} from 'react-router';
 import styled from '@emotion/styled';
 import type {LocationDescriptor} from 'history';
 
@@ -399,6 +400,10 @@ function DropdownMenuWithPortal(props: DropdownMenuProps) {
   );
 }
 
+function TypeSafeBoolean<T>(value: T | null | undefined): value is NonNullable<T> {
+  return value !== null && value !== undefined;
+}
+
 function NodeActions(props: {
   node: TraceTreeNode<any>;
   onTabScrollToNode: (
@@ -412,7 +417,31 @@ function NodeActions(props: {
   eventSize?: number | undefined;
 }) {
   const organization = useOrganization();
-  const items = useMemo(() => {
+  const params = useParams<{traceSlug?: string}>();
+
+  const {data: transaction} = useTransaction({
+    node: isTransactionNode(props.node) ? props.node : null,
+    organization,
+  });
+
+  const profilerId = useMemo(() => {
+    if (isTransactionNode(props.node)) {
+      return props.node.value.profiler_id;
+    }
+    if (isSpanNode(props.node)) {
+      return props.node.value.sentry_tags?.profiler_id ?? '';
+    }
+    return '';
+  }, [props]);
+
+  const profileLink = makeTraceContinuousProfilingLink(props.node, profilerId, {
+    orgSlug: props.organization.slug,
+    projectSlug: props.node.metadata.project_slug ?? '',
+    traceId: params.traceSlug ?? '',
+    threadId: getThreadIdFromNode(props.node, transaction),
+  });
+
+  const items = useMemo((): MenuItemProps[] => {
     const showInView: MenuItemProps = {
       key: 'show-in-view',
       label: t('Show in View'),
@@ -443,17 +472,28 @@ function NodeActions(props: {
         (typeof eventSize === 'number' ? ` (${formatBytesBase10(eventSize, 0)})` : ''),
     };
 
+    const continuousProfileLink: MenuItemProps | null = profileLink
+      ? {
+          key: 'continuous-profile',
+          onAction: () => {
+            traceAnalytics.trackViewContinuousProfile(props.organization);
+            browserHistory.push(profileLink!);
+          },
+          label: t('Continuous Profile'),
+        }
+      : null;
+
     if (isTransactionNode(props.node)) {
-      return [showInView, jsonDetails];
+      return [showInView, jsonDetails, continuousProfileLink].filter(TypeSafeBoolean);
     }
     if (isSpanNode(props.node)) {
-      return [showInView];
+      return [showInView, continuousProfileLink].filter(TypeSafeBoolean);
     }
     if (isMissingInstrumentationNode(props.node)) {
-      return [showInView];
+      return [showInView, continuousProfileLink].filter(TypeSafeBoolean);
     }
     if (isTraceErrorNode(props.node)) {
-      return [showInView];
+      return [showInView, continuousProfileLink].filter(TypeSafeBoolean);
     }
     if (isRootNode(props.node)) {
       return [showInView];
@@ -463,30 +503,7 @@ function NodeActions(props: {
     }
 
     return [showInView];
-  }, [props]);
-
-  const profilerId = useMemo(() => {
-    if (isTransactionNode(props.node)) {
-      return props.node.value.profiler_id;
-    }
-    if (isSpanNode(props.node)) {
-      return props.node.value.sentry_tags?.profiler_id ?? '';
-    }
-    return '';
-  }, [props]);
-
-  const {data: transaction} = useTransaction({
-    node: isTransactionNode(props.node) ? props.node : null,
-    organization,
-  });
-
-  const params = useParams<{traceSlug?: string}>();
-  const profileLink = makeTraceContinuousProfilingLink(props.node, profilerId, {
-    orgSlug: props.organization.slug,
-    projectSlug: props.node.metadata.project_slug ?? '',
-    traceId: params.traceSlug ?? '',
-    threadId: getThreadIdFromNode(props.node, transaction),
-  });
+  }, [props, profileLink]);
 
   return (
     <ActionsContainer>