import {Fragment} from 'react'; import {RouteComponentProps} from 'react-router'; import styled from '@emotion/styled'; import {Button} from 'sentry/components/button'; import ButtonBar from 'sentry/components/buttonBar'; import DeprecatedAsyncComponent from 'sentry/components/deprecatedAsyncComponent'; 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 {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 {Organization, Project} from 'sentry/types'; import {Event, EventTag, EventTransaction} from 'sentry/types/event'; 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 {appendTagCondition, decodeScalar} from 'sentry/utils/queryString'; import withRouteAnalytics, { WithRouteAnalyticsProps, } 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[]; }; type State = { event: Event | undefined; isSidebarVisible: boolean; } & DeprecatedAsyncComponent['state']; class EventDetailsContent extends DeprecatedAsyncComponent { state: State = { // AsyncComponent state loading: true, reloading: false, error: false, errors: {}, event: undefined, // local state isSidebarVisible: true, }; toggleSidebar = () => { this.setState({isSidebarVisible: !this.state.isSidebarVisible}); }; getEndpoints(): ReturnType { const {organization, params} = this.props; const {eventSlug} = params; const url = `/organizations/${organization.slug}/events/${eventSlug}/`; return [['event', url]]; } onLoadAllEndpointsSuccess() { const {location, projects} = this.props; const {event} = this.state; this.props.setEventNames( 'performance.event_details', 'Performance: Opened Event Details' ); this.props.setRouteAnalyticsParams({ event_type: event?.type, project_platforms: getSelectedProjectPlatforms(location, projects), ...getAnalyticsDataForEvent(event), }); } get projectId() { return this.props.eventSlug.split(':')[0]; } generateTagUrl = (tag: EventTag) => { const {location, organization} = this.props; const {event} = this.state; 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, }); }; renderBody() { const {event} = this.state; const {organization} = this.props; if (!event) { return ; } const isSampleTransaction = event.tags.some( tag => tag.key === 'sample_event' && tag.value === 'yes' ); return ( {isSampleTransaction && ( )} {this.renderContent(event)} ); } renderContent(event: Event) { const {organization, location, eventSlug} = this.props; const {isSidebarVisible} = this.state; const transactionName = event.title; const query = decodeScalar(location.query.query, ''); const eventJsonUrl = `/api/0/projects/${organization.slug}/${this.projectId}/events/${event.eventID}/json/`; const traceId = event.contexts?.trace?.trace_id ?? ''; const {start, end} = getTraceTimeRangeFromEvent(event); const hasProfilingFeature = organization.features.includes('profiling'); const profileId = (event as EventTransaction).contexts?.profile?.profile_id ?? null; const originatingUrl = getUrlFromEvent(event); return ( {metaResults => ( {results => ( {event.title} {originatingUrl && ( {results && ( )} {hasProfilingFeature && isTransaction(event) && ( )} {results && ( )} {({projects: _projects}) => ( { return getTransactionDetailsUrl( organization.slug, childTransactionProps.eventSlug, childTransactionProps.transaction, location.query ); }, }} > {hasProfilingFeature ? ( {profiles => ( )} ) : ( )} )} {isSidebarVisible && ( {results === undefined && ( )} )} )} )} ); } renderError(error: Error) { const notFound = Object.values(this.state.errors).find( resp => resp && resp.status === 404 ); const permissionDenied = Object.values(this.state.errors).find( resp => resp && resp.status === 403 ); if (notFound) { return ; } if (permissionDenied) { return ( ); } return super.renderError(error, true); } renderComponent() { const {organization} = this.props; return ( {super.renderComponent() 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);