import {Fragment, useEffect, useState} from 'react'; import styled from '@emotion/styled'; import Alert from 'sentry/components/alert'; import {Button, LinkButton} from 'sentry/components/button'; import ButtonBar from 'sentry/components/buttonBar'; import NotFound from 'sentry/components/errors/notFound'; import EventCustomPerformanceMetrics, { EventDetailPageSource, } from 'sentry/components/events/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'; import * as Layout from 'sentry/components/layouts/thirds'; import LoadingError from 'sentry/components/loadingError'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import {TransactionProfileIdProvider} from 'sentry/components/profiling/transactionProfileIdProvider'; import {TransactionToProfileButton} from 'sentry/components/profiling/transactionToProfileButton'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import {TagsTable} from 'sentry/components/tagsTable'; import {Tooltip} from 'sentry/components/tooltip'; import {IconOpen} from 'sentry/icons'; import {t} from 'sentry/locale'; import type {Event, EventTag, EventTransaction} from 'sentry/types/event'; import type {RouteComponentProps} from 'sentry/types/legacyReactRouter'; import type {Organization} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; import {formatTagKey} from 'sentry/utils/discover/fields'; import {getAnalyticsDataForEvent} from 'sentry/utils/events'; import {QuickTraceContext} from 'sentry/utils/performance/quickTrace/quickTraceContext'; import QuickTraceQuery from 'sentry/utils/performance/quickTrace/quickTraceQuery'; import TraceMetaQuery from 'sentry/utils/performance/quickTrace/traceMetaQuery'; import { getTraceTimeRangeFromEvent, isTransaction, } from 'sentry/utils/performance/quickTrace/utils'; import {getTransactionDetailsUrl} from 'sentry/utils/performance/urls'; import Projects from 'sentry/utils/projects'; import {useApiQuery} from 'sentry/utils/queryClient'; import {appendTagCondition, decodeScalar} from 'sentry/utils/queryString'; import type {WithRouteAnalyticsProps} from 'sentry/utils/routeAnalytics/withRouteAnalytics'; import withRouteAnalytics from 'sentry/utils/routeAnalytics/withRouteAnalytics'; import Breadcrumb from 'sentry/views/performance/breadcrumb'; import {ProfileGroupProvider} from 'sentry/views/profiling/profileGroupProvider'; import {ProfileContext, ProfilesProvider} from 'sentry/views/profiling/profilesProvider'; import TraceDetailsRouting from '../traceDetails/TraceDetailsRouting'; import {transactionSummaryRouteWithQuery} from '../transactionSummary/utils'; import {getSelectedProjectPlatforms} from '../utils'; import EventMetas from './eventMetas'; import FinishSetupAlert from './finishSetupAlert'; type Props = Pick, 'params' | 'location'> & WithRouteAnalyticsProps & { eventSlug: string; organization: Organization; projects: Project[]; }; function EventDetailsContent(props: Props) { const [isSidebarVisible, setIsSidebarVisible] = useState(true); const projectId = props.eventSlug.split(':')[0]; const {organization, eventSlug, location} = props; const { data: event, isPending, error, } = useApiQuery( [`/organizations/${organization.slug}/events/${eventSlug}/`], {staleTime: 2 * 60 * 1000} // 2 minutes in milliseonds ); useEffect(() => { if (event) { const {projects} = props; props.setEventNames( 'performance.event_details', 'Performance: Opened Event Details' ); props.setRouteAnalyticsParams({ event_type: event?.type, project_platforms: getSelectedProjectPlatforms(location, projects), ...getAnalyticsDataForEvent(event), }); } }, [event, props, location]); const generateTagUrl = (tag: EventTag) => { if (!event) { return ''; } const query = decodeScalar(location.query.query, ''); const newQuery = { ...location.query, query: appendTagCondition(query, formatTagKey(tag.key), tag.value), }; return transactionSummaryRouteWithQuery({ orgSlug: organization.slug, transaction: event.title, projectID: event.projectID, query: newQuery, }); }; function renderContent(transaction: Event) { const transactionName = transaction.title; const query = decodeScalar(location.query.query, ''); const eventJsonUrl = `/api/0/projects/${organization.slug}/${projectId}/events/${transaction.eventID}/json/`; const traceId = transaction.contexts?.trace?.trace_id ?? ''; const {start, end} = getTraceTimeRangeFromEvent(transaction); const hasProfilingFeature = organization.features.includes('profiling'); const profileId = (transaction as EventTransaction).contexts?.profile?.profile_id ?? null; const originatingUrl = getUrlFromEvent(transaction); return ( {metaResults => ( {results => ( {transaction.title} {originatingUrl && ( } href={originatingUrl} external translucentBorder borderless /> )} {results && ( } href={eventJsonUrl} external > {t('JSON')} () )} {hasProfilingFeature && isTransaction(transaction) && ( )} {results && ( )} {({projects: _projects}) => ( { return getTransactionDetailsUrl( organization.slug, childTransactionProps.eventSlug, childTransactionProps.transaction, location.query ); }, }} > {hasProfilingFeature ? ( {profiles => ( )} ) : ( )} )} {isSidebarVisible && ( {results === undefined && ( )} )} )} )} ); } function renderBody() { if (!event) { return ; } const isSampleTransaction = event.tags.some( tag => tag.key === 'sample_event' && tag.value === 'yes' ); return ( {isSampleTransaction && ( )} {renderContent(event)} ); } if (isPending) { return ; } if (error) { const notFound = error.status === 404; const permissionDenied = error.status === 403; if (notFound) { return ; } if (permissionDenied) { return ( ); } return ( {error.message} ); } return ( {renderBody() as React.ReactChild} ); } // 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')` display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; `; export default withRouteAnalytics(EventDetailsContent);