Browse Source

feat(perf): Add a button to the originating url in event details (#55731)

Add a button with a link to the originating url:
<img width="1286" alt="image"
src="https://github.com/getsentry/sentry/assets/23648387/eb68d519-5fa3-4032-8ae2-52235ebc7f37">
Dameli Ushbayeva 1 year ago
parent
commit
6a037705ed

+ 33 - 0
static/app/components/events/interfaces/request/getUrlFromEvent.tsx

@@ -0,0 +1,33 @@
+import {getFullUrl} from 'sentry/components/events/interfaces/utils';
+import {EntryRequest, EntryType, Event} from 'sentry/types/event';
+import {isUrl} from 'sentry/utils';
+
+function getUrlFromEvent(event: Event): string {
+  const requestEntry = event.entries.find(
+    entry => entry.type === EntryType.REQUEST
+  ) as EntryRequest;
+
+  if (requestEntry) {
+    const {data} = requestEntry;
+    const isPartial =
+      // We assume we only have a partial interface is we're missing
+      // an HTTP method. This means we don't have enough information
+      // to reliably construct a full HTTP request.
+      !data.method || !data.url;
+
+    let fullUrl = getFullUrl(data);
+
+    if (!isUrl(fullUrl)) {
+      // Check if the url passed in is a safe url to avoid XSS
+      fullUrl = undefined;
+    }
+
+    if (fullUrl && !isPartial) {
+      return fullUrl;
+    }
+  }
+
+  return '';
+}
+
+export default getUrlFromEvent;

+ 20 - 1
static/app/views/performance/transactionDetails/content.tsx

@@ -12,6 +12,7 @@ import EventCustomPerformanceMetrics, {
 import {BorderlessEventEntries} from 'sentry/components/events/eventEntries';
 import EventMetadata from 'sentry/components/events/eventMetadata';
 import EventVitals from 'sentry/components/events/eventVitals';
+import getUrlFromEvent from 'sentry/components/events/interfaces/request/getUrlFromEvent';
 import * as SpanEntryContext from 'sentry/components/events/interfaces/spans/context';
 import RootSpanStatus from 'sentry/components/events/rootSpanStatus';
 import FileSize from 'sentry/components/fileSize';
@@ -159,6 +160,8 @@ class EventDetailsContent extends DeprecatedAsyncComponent<Props, State> {
 
     const profileId = (event as EventTransaction).contexts?.profile?.profile_id ?? null;
 
+    const originatingUrl = getUrlFromEvent(event);
+
     return (
       <TraceMetaQuery
         location={location}
@@ -190,6 +193,17 @@ class EventDetailsContent extends DeprecatedAsyncComponent<Props, State> {
                       <Tooltip showOnlyOnOverflow skipWrapper title={transactionName}>
                         <EventTitle>{event.title}</EventTitle>
                       </Tooltip>
+                      {originatingUrl && (
+                        <Button
+                          aria-label={t('Go to originating URL')}
+                          size="zero"
+                          icon={<IconOpen />}
+                          href={originatingUrl}
+                          external
+                          translucentBorder
+                          borderless
+                        />
+                      )}
                     </Layout.Title>
                   </Layout.HeaderContent>
                   <Layout.HeaderActions>
@@ -355,8 +369,13 @@ class EventDetailsContent extends DeprecatedAsyncComponent<Props, State> {
   }
 }
 
+// We can't use theme.overflowEllipsis so that width isn't set to 100%
+// since button withn a link has to immediately follow the text in the title
 const EventTitle = styled('div')`
-  ${p => p.theme.overflowEllipsis}
+  display: block;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
 `;
 
 export default withRouteAnalytics(EventDetailsContent);