import {Fragment} from 'react'; import {RouteComponentProps} from 'react-router'; import AsyncComponent from 'sentry/components/asyncComponent'; import Button 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 * 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 {TransactionToProfileButton} from 'sentry/components/profiling/transactionToProfileButton'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import {TagsTable} from 'sentry/components/tagsTable'; import {IconOpen} from 'sentry/icons'; import {t} from 'sentry/locale'; import {Organization, Project} from 'sentry/types'; import {Event, EventTag} from 'sentry/types/event'; import {trackAnalyticsEvent} from 'sentry/utils/analytics'; import {formatTagKey} from 'sentry/utils/discover/fields'; 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} 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 Breadcrumb from 'sentry/views/performance/breadcrumb'; import {transactionSummaryRouteWithQuery} from '../transactionSummary/utils'; import {getSelectedProjectPlatforms} from '../utils'; import EventMetas from './eventMetas'; import FinishSetupAlert from './finishSetupAlert'; type Props = Pick< RouteComponentProps<{eventSlug: string}, {}>, 'params' | 'location' | 'router' | 'route' > & { eventSlug: string; organization: Organization; projects: Project[]; }; type State = { event: Event | undefined; isSidebarVisible: boolean; } & AsyncComponent['state']; class EventDetailsContent extends AsyncComponent { 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]]; } 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, route, router, projects} = this.props; // metrics trackAnalyticsEvent({ eventKey: 'performance.event_details', eventName: 'Performance: Opened Event Details', event_type: event.type, organization_id: parseInt(organization.id, 10), project_platforms: getSelectedProjectPlatforms(location, projects), }); 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'); return ( {metaResults => ( {results => ( {event.title} {results && ( )} {hasProfilingFeature && ( )} {results && ( )} {({projects: _projects}) => ( { return getTransactionDetailsUrl( organization.slug, childTransactionProps.eventSlug, childTransactionProps.transaction, location.query ); }, }} > )} {isSidebarVisible && ( {results === undefined && ( )} {(organization.features.includes('dashboards-mep') || organization.features.includes('mep-rollout-flag')) && ( )} )} )} )} ); } 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} ); } } export default EventDetailsContent;