diff --git a/static/app/components/discover/transactionsList.spec.tsx b/static/app/components/discover/transactionsList.spec.tsx index 1e35b7d18f788d..36b5c197bf9074 100644 --- a/static/app/components/discover/transactionsList.spec.tsx +++ b/static/app/components/discover/transactionsList.spec.tsx @@ -72,10 +72,10 @@ describe('TransactionsList', function () { }, ]; generateLink = { - transaction: (org, row, query) => ({ + transaction: (org, row) => ({ pathname: `/${org.slug}`, query: { - ...query, + ...location.query, transaction: row.transaction, count: row.count, 'count()': row['count()'], diff --git a/static/app/components/discover/transactionsList.tsx b/static/app/components/discover/transactionsList.tsx index c8a1f1ac8517e1..33f64f67f9b9cb 100644 --- a/static/app/components/discover/transactionsList.tsx +++ b/static/app/components/discover/transactionsList.tsx @@ -1,7 +1,7 @@ import {Component, Fragment, useContext, useEffect} from 'react'; import {browserHistory} from 'react-router'; import styled from '@emotion/styled'; -import type {Location, LocationDescriptor, Query} from 'history'; +import type {Location, LocationDescriptor} from 'history'; import GuideAnchor from 'sentry/components/assistant/guideAnchor'; import {Button} from 'sentry/components/button'; @@ -100,7 +100,7 @@ type Props = { ( organization: Organization, tableRow: TableDataRow, - query: Query + location: Location ) => LocationDescriptor >; generatePerformanceTransactionEventsView?: () => EventView; diff --git a/static/app/components/discover/transactionsTable.tsx b/static/app/components/discover/transactionsTable.tsx index 911a8db5c85800..e98b80f2863246 100644 --- a/static/app/components/discover/transactionsTable.tsx +++ b/static/app/components/discover/transactionsTable.tsx @@ -1,6 +1,6 @@ import {Fragment, PureComponent} from 'react'; import styled from '@emotion/styled'; -import type {Location, LocationDescriptor, Query} from 'history'; +import type {Location, LocationDescriptor} from 'history'; import SortLink from 'sentry/components/gridEditable/sortLink'; import Link from 'sentry/components/links/link'; @@ -39,7 +39,7 @@ type Props = { ( organization: Organization, tableRow: TableDataRow, - query: Query + location: Location ) => LocationDescriptor >; handleCellAction?: ( @@ -143,7 +143,7 @@ class TransactionsTable extends PureComponent { const fieldRenderer = getFieldRenderer(field, tableMeta, useAggregateAlias); let rendered = fieldRenderer(row, {organization, location}); - const target = generateLink?.[field]?.(organization, row, location.query); + const target = generateLink?.[field]?.(organization, row, location); if (target && !objectIsEmpty(target)) { if (fields[index] === 'replayId') { diff --git a/static/app/components/events/interfaces/spans/aggregateSpanDetail.tsx b/static/app/components/events/interfaces/spans/aggregateSpanDetail.tsx index 1ab4e74cfc26f0..d8208c5fd64c2b 100644 --- a/static/app/components/events/interfaces/spans/aggregateSpanDetail.tsx +++ b/static/app/components/events/interfaces/spans/aggregateSpanDetail.tsx @@ -1,10 +1,13 @@ import styled from '@emotion/styled'; +import type {Location} from 'history'; import Link from 'sentry/components/links/link'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Organization, Project} from 'sentry/types'; import type {AggregateEventTransaction} from 'sentry/types/event'; +import EventView from 'sentry/utils/discover/eventView'; +import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls'; import {formatPercentage, getDuration} from 'sentry/utils/formatters'; import type { QuickTraceEvent, @@ -27,20 +30,32 @@ type Props = { trace: Readonly; }; -function renderSpanSamples(span: AggregateSpanType, project: Project | undefined) { +function renderSpanSamples( + aggSpan: AggregateSpanType, + project: Project | undefined, + location: Location, + organization: Organization +) { if (!project) { return null; } - return span.samples?.map(([transactionId, spanId], index) => ( + return aggSpan.samples?.map(({transaction, span, trace, timestamp}, index) => ( {`${spanId}${index < span.samples.length - 1 ? ', ' : ''}`} + key={`${transaction}-${span}`} + to={generateLinkToEventInTraceView({ + organization, + eventSlug: `${project.slug}:${transaction}`, + dataRow: {id: transaction, trace, timestamp}, + location, + eventView: EventView.fromLocation(location), + spanId: span, + })} + >{`${span}${index < aggSpan.samples.length - 1 ? ', ' : ''}`} )); } -function AggregateSpanDetail({span}: Props) { +function AggregateSpanDetail({span, organization}: Props) { const location = useLocation(); const {projects} = useProjects(); @@ -62,7 +77,9 @@ function AggregateSpanDetail({span}: Props) { {getDuration(avgDuration)} {frequency && formatPercentage(frequency)} - {renderSpanSamples(span, project)} + + {renderSpanSamples(span, project, location, organization)} + diff --git a/static/app/components/events/interfaces/spans/types.tsx b/static/app/components/events/interfaces/spans/types.tsx index eb1e99866dcc25..8d014d0ad2c557 100644 --- a/static/app/components/events/interfaces/spans/types.tsx +++ b/static/app/components/events/interfaces/spans/types.tsx @@ -66,7 +66,12 @@ export type RawSpanType = { export type AggregateSpanType = RawSpanType & { count: number; frequency: number; - samples: Array<[string, string]>; + samples: Array<{ + span: string; + timestamp: number; + trace: string; + transaction: string; + }>; total: number; type: 'aggregate'; }; diff --git a/static/app/components/events/interfaces/spans/useSpanWaterfallModelFromTransaction.tsx b/static/app/components/events/interfaces/spans/useSpanWaterfallModelFromTransaction.tsx index 7d8737f920ff45..256d811c66ba69 100644 --- a/static/app/components/events/interfaces/spans/useSpanWaterfallModelFromTransaction.tsx +++ b/static/app/components/events/interfaces/spans/useSpanWaterfallModelFromTransaction.tsx @@ -22,7 +22,8 @@ export function useSpanWaterfallModelFromTransaction( 'avg(absolute_offset)': start_timestamp, 'count()': count, 'avg(duration)': duration, - samples, + sample_spans, + trace, ...rest } = span; return { @@ -33,11 +34,11 @@ export function useSpanWaterfallModelFromTransaction( exclusive_time, timestamp: (start_timestamp + duration) / 1000, start_timestamp: start_timestamp / 1000, - trace_id: '1', // not actually trace_id just a placeholder + trace, count, total, duration, - samples, + samples: sample_spans, frequency: count / total, type: 'aggregate', }; diff --git a/static/app/utils/discover/urls.tsx b/static/app/utils/discover/urls.tsx index 7887acdfa7ef66..7cc7a97d6a915b 100644 --- a/static/app/utils/discover/urls.tsx +++ b/static/app/utils/discover/urls.tsx @@ -48,6 +48,7 @@ export function generateLinkToEventInTraceView({ location, spanId, eventSlug, + transactionName, type = 'performance', }: { dataRow: TableDataRow; @@ -57,6 +58,7 @@ export function generateLinkToEventInTraceView({ organization: Organization; isHomepage?: boolean; spanId?: string; + transactionName?: string; type?: 'performance' | 'discover'; }) { const dateSelection = eventView.normalizeDateSelection(location); @@ -78,7 +80,7 @@ export function generateLinkToEventInTraceView({ return getTransactionDetailsUrl( organization.slug, eventSlug, - undefined, + transactionName, location.query, spanId ); diff --git a/static/app/utils/performance/suspectSpans/types.tsx b/static/app/utils/performance/suspectSpans/types.tsx index 39a0adae21ef74..ceab671d6b4db9 100644 --- a/static/app/utils/performance/suspectSpans/types.tsx +++ b/static/app/utils/performance/suspectSpans/types.tsx @@ -3,6 +3,7 @@ export type ExampleSpan = { finishTimestamp: number; id: string; startTimestamp: number; + trace: string; }; export type ExampleTransaction = { diff --git a/static/app/views/performance/transactionSummary/transactionEvents/eventsTable.tsx b/static/app/views/performance/transactionSummary/transactionEvents/eventsTable.tsx index 372c5ec9d9d3ad..7bdae612ce5f7f 100644 --- a/static/app/views/performance/transactionSummary/transactionEvents/eventsTable.tsx +++ b/static/app/views/performance/transactionSummary/transactionEvents/eventsTable.tsx @@ -18,8 +18,7 @@ import type {IssueAttachment, Organization} from 'sentry/types'; import {trackAnalytics} from 'sentry/utils/analytics'; import type {TableData, TableDataRow} from 'sentry/utils/discover/discoverQuery'; import DiscoverQuery from 'sentry/utils/discover/discoverQuery'; -import type EventView from 'sentry/utils/discover/eventView'; -import {isFieldSortable} from 'sentry/utils/discover/eventView'; +import EventView, {isFieldSortable} from 'sentry/utils/discover/eventView'; import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers'; import { fieldAlignment, @@ -27,6 +26,10 @@ import { isSpanOperationBreakdownField, SPAN_OP_RELATIVE_BREAKDOWN_FIELD, } from 'sentry/utils/discover/fields'; +import { + generateEventSlug, + generateLinkToEventInTraceView, +} from 'sentry/utils/discover/urls'; import ViewReplayLink from 'sentry/utils/discover/viewReplayLink'; import parseLinkHeader from 'sentry/utils/parseLinkHeader'; import {VisuallyCompleteWithData} from 'sentry/utils/performanceForSentry'; @@ -38,7 +41,6 @@ import { generateProfileLink, generateReplayLink, generateTraceLink, - generateTransactionLink, normalizeSearchConditions, } from '../utils'; @@ -164,8 +166,18 @@ class EventsTable extends Component { if (isIssue && !isRegressionIssue && field === 'id') { target.pathname = `/organizations/${organization.slug}/issues/${issueId}/events/${dataRow.id}/`; } else { - const generateLink = field === 'id' ? generateTransactionLink : generateTraceLink; - target = generateLink(transactionName)(organization, dataRow, location.query); + if (field === 'id') { + target = generateLinkToEventInTraceView({ + eventSlug: generateEventSlug(dataRow), + dataRow: dataRow, + eventView: EventView.fromLocation(location), + location, + organization, + transactionName: transactionName, + }); + } else { + target = generateTraceLink(transactionName)(organization, dataRow, location); + } } return ( diff --git a/static/app/views/performance/transactionSummary/transactionOverview/content.tsx b/static/app/views/performance/transactionSummary/transactionOverview/content.tsx index c9f9e7c64ca4e5..20e114fce02018 100644 --- a/static/app/views/performance/transactionSummary/transactionOverview/content.tsx +++ b/static/app/views/performance/transactionSummary/transactionOverview/content.tsx @@ -57,7 +57,7 @@ import { generateProfileLink, generateReplayLink, generateTraceLink, - generateTransactionLink, + generateTransactionIdLink, normalizeSearchConditions, SidebarSpacer, TransactionFilterOptions, @@ -391,7 +391,7 @@ function SummaryContent({ titles={transactionsListTitles} handleDropdownChange={handleTransactionsListSortChange} generateLink={{ - id: generateTransactionLink(transactionName), + id: generateTransactionIdLink(transactionName), trace: generateTraceLink(eventView.normalizeDateSelection(location)), replayId: generateReplayLink(routes), 'profile.id': generateProfileLink(), diff --git a/static/app/views/performance/transactionSummary/transactionSpans/spanDetails/spanDetailsTable.tsx b/static/app/views/performance/transactionSummary/transactionSpans/spanDetails/spanDetailsTable.tsx index 891f677d35b55d..60285511679a41 100644 --- a/static/app/views/performance/transactionSummary/transactionSpans/spanDetails/spanDetailsTable.tsx +++ b/static/app/views/performance/transactionSummary/transactionSpans/spanDetails/spanDetailsTable.tsx @@ -15,9 +15,14 @@ import {t, tct} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Organization, Project} from 'sentry/types'; import {defined} from 'sentry/utils'; +import EventView from 'sentry/utils/discover/eventView'; import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers'; import type {ColumnType} from 'sentry/utils/discover/fields'; import {fieldAlignment} from 'sentry/utils/discover/fields'; +import { + generateEventSlug, + generateLinkToEventInTraceView, +} from 'sentry/utils/discover/urls'; import {formatPercentage} from 'sentry/utils/formatters'; import toPercent from 'sentry/utils/number/toPercent'; import type { @@ -26,8 +31,6 @@ import type { } from 'sentry/utils/performance/suspectSpans/types'; import {VisuallyCompleteWithData} from 'sentry/utils/performanceForSentry'; -import {generateTransactionLink} from '../../utils'; - type TableColumnKeys = | 'id' | 'timestamp' @@ -59,9 +62,9 @@ export default function SpanTable(props: Props) { project, examples, suspectSpan, - transactionName, isLoading, pageLinks, + transactionName, } = props; if (!defined(examples)) { @@ -152,17 +155,22 @@ function renderBodyCellWithMeta( let rendered = fieldRenderer(dataRow, {location, organization}); if (column.key === 'id') { + const traceSlug = dataRow.spans[0] ? dataRow.spans[0].trace : ''; const worstSpan = dataRow.spans.length ? dataRow.spans.reduce((worst, span) => worst.exclusiveTime >= span.exclusiveTime ? worst : span ) : null; - const target = generateTransactionLink(transactionName)( + + const target = generateLinkToEventInTraceView({ + eventSlug: generateEventSlug(dataRow), + dataRow: {...dataRow, trace: traceSlug}, + eventView: EventView.fromLocation(location), + location, organization, - dataRow, - location.query, - worstSpan.id - ); + spanId: worstSpan.id, + transactionName: transactionName, + }); rendered = {rendered}; } diff --git a/static/app/views/performance/transactionSummary/transactionSpans/utils.tsx b/static/app/views/performance/transactionSummary/transactionSpans/utils.tsx index 188e4941f781a1..4e9f0c9160ab2c 100644 --- a/static/app/views/performance/transactionSummary/transactionSpans/utils.tsx +++ b/static/app/views/performance/transactionSummary/transactionSpans/utils.tsx @@ -152,7 +152,11 @@ export function generateSpansEventView({ id: undefined, version: 2, name: transactionName, - fields: [...Object.values(SpanSortOthers), ...Object.values(SpanSortPercentiles)], + fields: [ + ...Object.values(SpanSortOthers), + ...Object.values(SpanSortPercentiles), + 'trace', + ], query: conditions.formatString(), projects: [], }, diff --git a/static/app/views/performance/transactionSummary/transactionTags/tagsHeatMap.tsx b/static/app/views/performance/transactionSummary/transactionTags/tagsHeatMap.tsx index d5eba24005fe67..b59afe72ebf48e 100644 --- a/static/app/views/performance/transactionSummary/transactionTags/tagsHeatMap.tsx +++ b/static/app/views/performance/transactionSummary/transactionTags/tagsHeatMap.tsx @@ -27,7 +27,11 @@ import {space} from 'sentry/styles/space'; import type {Organization, Project} from 'sentry/types'; import type {ReactEchartsRef, Series} from 'sentry/types/echarts'; import {axisLabelFormatter} from 'sentry/utils/discover/charts'; -import type EventView from 'sentry/utils/discover/eventView'; +import EventView from 'sentry/utils/discover/eventView'; +import { + generateEventSlug, + generateLinkToEventInTraceView, +} from 'sentry/utils/discover/urls'; import {formatAbbreviatedNumber} from 'sentry/utils/formatters'; import getDynamicText from 'sentry/utils/getDynamicText'; import type { @@ -39,7 +43,6 @@ import {decodeScalar} from 'sentry/utils/queryString'; import {getPerformanceDuration} from 'sentry/views/performance/utils/getPerformanceDuration'; import {eventsRouteWithQuery} from '../transactionEvents/utils'; -import {generateTransactionLink} from '../utils'; import {parseHistogramBucketInfo, trackTagPageInteraction} from './utils'; @@ -348,11 +351,14 @@ function TagsHeatMap(
{!transactionTableData?.data.length ? : null} {[...(transactionTableData?.data ?? [])].slice(0, 3).map(row => { - const target = generateTransactionLink(transactionName)( + const target = generateLinkToEventInTraceView({ + eventSlug: generateEventSlug(row), + dataRow: row, + eventView: EventView.fromLocation(location), + location, organization, - row, - location.query - ); + transactionName, + }); return ( diff --git a/static/app/views/performance/transactionSummary/utils.tsx b/static/app/views/performance/transactionSummary/utils.tsx index ca1a867900762d..f62e76945e752e 100644 --- a/static/app/views/performance/transactionSummary/utils.tsx +++ b/static/app/views/performance/transactionSummary/utils.tsx @@ -1,13 +1,16 @@ import type {PlainRoute} from 'react-router'; import styled from '@emotion/styled'; -import type {LocationDescriptor, Query} from 'history'; +import type {Location, LocationDescriptor, Query} from 'history'; import {space} from 'sentry/styles/space'; import type {Organization} from 'sentry/types'; import type {TableDataRow} from 'sentry/utils/discover/discoverQuery'; -import {generateEventSlug} from 'sentry/utils/discover/urls'; +import EventView from 'sentry/utils/discover/eventView'; +import { + generateEventSlug, + generateLinkToEventInTraceView, +} from 'sentry/utils/discover/urls'; import getRouteStringFromRoutes from 'sentry/utils/getRouteStringFromRoutes'; -import {getTransactionDetailsUrl} from 'sentry/utils/performance/urls'; import {generateProfileFlamechartRoute} from 'sentry/utils/profiling/routes'; import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import {normalizeUrl} from 'sentry/utils/withDomainRequired'; @@ -125,7 +128,7 @@ export function generateTraceLink(dateSelection) { return ( organization: Organization, tableRow: TableDataRow, - _query: Query + _location: Location ): LocationDescriptor => { const traceId = `${tableRow.trace}`; if (!traceId) { @@ -142,21 +145,22 @@ export function generateTraceLink(dateSelection) { }; } -export function generateTransactionLink(transactionName: string) { +export function generateTransactionIdLink(transactionName?: string) { return ( organization: Organization, tableRow: TableDataRow, - query: Query, + location: Location, spanId?: string ): LocationDescriptor => { - const eventSlug = generateEventSlug(tableRow); - return getTransactionDetailsUrl( - organization.slug, - eventSlug, + return generateLinkToEventInTraceView({ + eventSlug: generateEventSlug(tableRow), + dataRow: tableRow, + eventView: EventView.fromLocation(location), + location, + organization, + spanId, transactionName, - query, - spanId - ); + }); }; } @@ -164,7 +168,7 @@ export function generateProfileLink() { return ( organization: Organization, tableRow: TableDataRow, - _query: Query | undefined + _location: Location | undefined ) => { const profileId = tableRow['profile.id']; if (!profileId) { @@ -184,7 +188,7 @@ export function generateReplayLink(routes: PlainRoute[]) { return ( organization: Organization, tableRow: TableDataRow, - _query: Query | undefined + _location: Location | undefined ): LocationDescriptor => { const replayId = tableRow.replayId; if (!replayId) { diff --git a/static/app/views/releases/detail/overview/index.tsx b/static/app/views/releases/detail/overview/index.tsx index 6310314e8ea101..c1745ef95e698d 100644 --- a/static/app/views/releases/detail/overview/index.tsx +++ b/static/app/views/releases/detail/overview/index.tsx @@ -2,7 +2,7 @@ import {Fragment} from 'react'; import type {RouteComponentProps} from 'react-router'; import {browserHistory} from 'react-router'; import styled from '@emotion/styled'; -import type {Location, LocationDescriptor, Query} from 'history'; +import type {Location, LocationDescriptor} from 'history'; import moment from 'moment'; import {restoreRelease} from 'sentry/actionCreators/release'; @@ -665,7 +665,7 @@ function generateTransactionLink( return ( organization: Organization, tableRow: TableDataRow, - _query: Query + _location: Location ): LocationDescriptor => { const {transaction} = tableRow; const trendTransaction = ['regression', 'improved'].includes(value); diff --git a/tests/js/sentry-test/performance/initializePerformanceData.ts b/tests/js/sentry-test/performance/initializePerformanceData.ts index c9c0a10cb6219b..ca0a409bc2972b 100644 --- a/tests/js/sentry-test/performance/initializePerformanceData.ts +++ b/tests/js/sentry-test/performance/initializePerformanceData.ts @@ -123,6 +123,7 @@ function makeSpan(opt: SpanOpt): ExampleSpan { const {id} = opt; return { id, + trace: 'trace', startTimestamp: 10100, finishTimestamp: 10200, exclusiveTime: 100,