Skip to content

Commit 91b183f

Browse files
authored
feat(profiling): Support both profile formats in transaction samples tab (#75165)
Similar to #75099. This will render a profiling icon to link to the transaction/continuous profile automatically based on the data ingested.
1 parent 8fbdb17 commit 91b183f

File tree

2 files changed

+103
-48
lines changed

2 files changed

+103
-48
lines changed

static/app/views/performance/transactionSummary/transactionEvents/content.tsx

Lines changed: 71 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {useMemo} from 'react';
12
import styled from '@emotion/styled';
23
import type {Location} from 'history';
34
import omit from 'lodash/omit';
@@ -72,57 +73,84 @@ function EventsContent(props: Props) {
7273
projects,
7374
} = props;
7475
const routes = useRoutes();
75-
const eventView = originalEventView.clone();
76-
const transactionsListTitles = TRANSACTIONS_LIST_TITLES.slice();
77-
const project = projects.find(p => p.id === projectId);
7876

79-
const fields = [...eventView.fields];
77+
const {eventView, titles} = useMemo(() => {
78+
const eventViewClone = originalEventView.clone();
79+
const transactionsListTitles = TRANSACTIONS_LIST_TITLES.slice();
80+
const project = projects.find(p => p.id === projectId);
8081

81-
if (webVital) {
82-
transactionsListTitles.splice(3, 0, webVital);
83-
}
82+
const fields = [...eventViewClone.fields];
8483

85-
const spanOperationBreakdownConditions = filterToSearchConditions(
86-
spanOperationBreakdownFilter,
87-
location
88-
);
84+
if (webVital) {
85+
transactionsListTitles.splice(3, 0, webVital);
86+
}
8987

90-
if (spanOperationBreakdownConditions) {
91-
eventView.query = `${eventView.query} ${spanOperationBreakdownConditions}`.trim();
92-
transactionsListTitles.splice(2, 1, t('%s duration', spanOperationBreakdownFilter));
93-
}
88+
const spanOperationBreakdownConditions = filterToSearchConditions(
89+
spanOperationBreakdownFilter,
90+
location
91+
);
9492

95-
const platform = platformToPerformanceType(projects, eventView.project);
96-
if (platform === ProjectPerformanceType.BACKEND) {
97-
const userIndex = transactionsListTitles.indexOf('user');
98-
if (userIndex > 0) {
99-
transactionsListTitles.splice(userIndex + 1, 0, 'http.method');
100-
fields.splice(userIndex + 1, 0, {field: 'http.method'});
93+
if (spanOperationBreakdownConditions) {
94+
eventViewClone.query =
95+
`${eventViewClone.query} ${spanOperationBreakdownConditions}`.trim();
96+
transactionsListTitles.splice(2, 1, t('%s duration', spanOperationBreakdownFilter));
10197
}
102-
}
10398

104-
if (
105-
organization.features.includes('profiling') &&
106-
project &&
107-
// only show for projects that already sent a profile
108-
// once we have a more compact design we will show this for
109-
// projects that support profiling as well
110-
project.hasProfiles
111-
) {
112-
transactionsListTitles.push(t('profile'));
113-
fields.push({field: 'profile.id'});
114-
}
99+
const platform = platformToPerformanceType(projects, eventViewClone.project);
100+
if (platform === ProjectPerformanceType.BACKEND) {
101+
const userIndex = transactionsListTitles.indexOf('user');
102+
if (userIndex > 0) {
103+
transactionsListTitles.splice(userIndex + 1, 0, 'http.method');
104+
fields.splice(userIndex + 1, 0, {field: 'http.method'});
105+
}
106+
}
115107

116-
if (
117-
organization.features.includes('session-replay') &&
118-
project &&
119-
projectSupportsReplay(project)
120-
) {
121-
transactionsListTitles.push(t('replay'));
122-
fields.push({field: 'replayId'});
123-
}
108+
if (
109+
// only show for projects that already sent a profile
110+
// once we have a more compact design we will show this for
111+
// projects that support profiling as well
112+
project?.hasProfiles &&
113+
(organization.features.includes('profiling') ||
114+
organization.features.includes('continuous-profiling'))
115+
) {
116+
transactionsListTitles.push(t('profile'));
124117

125-
eventView.fields = fields;
118+
if (organization.features.includes('profiling')) {
119+
fields.push({field: 'profile.id'});
120+
}
121+
122+
if (organization.features.includes('continuous-profiling')) {
123+
fields.push({field: 'profiler.id'});
124+
fields.push({field: 'thread.id'});
125+
fields.push({field: 'precise.start_ts'});
126+
fields.push({field: 'precise.finish_ts'});
127+
}
128+
}
129+
130+
if (
131+
organization.features.includes('session-replay') &&
132+
project &&
133+
projectSupportsReplay(project)
134+
) {
135+
transactionsListTitles.push(t('replay'));
136+
fields.push({field: 'replayId'});
137+
}
138+
139+
eventViewClone.fields = fields;
140+
141+
return {
142+
eventView: eventViewClone,
143+
titles: transactionsListTitles,
144+
};
145+
}, [
146+
originalEventView,
147+
location,
148+
organization,
149+
projects,
150+
projectId,
151+
spanOperationBreakdownFilter,
152+
webVital,
153+
]);
126154

127155
return (
128156
<Layout.Main fullWidth>
@@ -133,7 +161,7 @@ function EventsContent(props: Props) {
133161
routes={routes}
134162
location={location}
135163
setError={setError}
136-
columnTitles={transactionsListTitles}
164+
columnTitles={titles}
137165
transactionName={transactionName}
138166
/>
139167
</Layout.Main>

static/app/views/performance/transactionSummary/transactionEvents/eventsTable.tsx

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import type {Location, LocationDescriptor, LocationDescriptorObject} from 'histo
55
import groupBy from 'lodash/groupBy';
66

77
import {Client} from 'sentry/api';
8+
import {LinkButton} from 'sentry/components/button';
89
import type {GridColumn} from 'sentry/components/gridEditable';
910
import GridEditable, {COL_WIDTH_UNDEFINED} from 'sentry/components/gridEditable';
1011
import SortLink from 'sentry/components/gridEditable/sortLink';
1112
import Link from 'sentry/components/links/link';
1213
import Pagination from 'sentry/components/pagination';
1314
import QuestionTooltip from 'sentry/components/questionTooltip';
1415
import {Tooltip} from 'sentry/components/tooltip';
16+
import {IconProfiling} from 'sentry/icons';
1517
import {t, tct} from 'sentry/locale';
1618
import type {IssueAttachment, Organization} from 'sentry/types';
1719
import {trackAnalytics} from 'sentry/utils/analytics';
@@ -29,6 +31,7 @@ import {
2931
} from 'sentry/utils/discover/fields';
3032
import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls';
3133
import ViewReplayLink from 'sentry/utils/discover/viewReplayLink';
34+
import {isEmptyObject} from 'sentry/utils/object/isEmptyObject';
3235
import parseLinkHeader from 'sentry/utils/parseLinkHeader';
3336
import {VisuallyCompleteWithData} from 'sentry/utils/performanceForSentry';
3437
import CellAction, {Actions, updateQuery} from 'sentry/views/discover/table/cellAction';
@@ -47,6 +50,23 @@ import {
4750
import type {TitleProps} from './operationSort';
4851
import OperationSort from './operationSort';
4952

53+
function shouldRenderColumn(containsSpanOpsBreakdown: boolean, col: string): boolean {
54+
if (containsSpanOpsBreakdown && isSpanOperationBreakdownField(col)) {
55+
return false;
56+
}
57+
58+
if (
59+
col === 'profiler.id' ||
60+
col === 'thread.id' ||
61+
col === 'precise.start_ts' ||
62+
col === 'precise.finish_ts'
63+
) {
64+
return false;
65+
}
66+
67+
return true;
68+
}
69+
5070
function OperationTitle({onClick}: TitleProps) {
5171
return (
5272
<div onClick={onClick}>
@@ -242,7 +262,15 @@ class EventsTable extends Component<Props, State> {
242262
handleCellAction={this.handleCellAction(column)}
243263
allowActions={allowActions}
244264
>
245-
{target ? <Link to={target}>{rendered}</Link> : rendered}
265+
<div>
266+
<LinkButton
267+
disabled={!target || isEmptyObject(target)}
268+
to={target || {}}
269+
size="xs"
270+
>
271+
<IconProfiling size="xs" />
272+
</LinkButton>
273+
</div>
246274
</CellAction>
247275
</Tooltip>
248276
);
@@ -379,7 +407,7 @@ class EventsTable extends Component<Props, State> {
379407
totalEventsView.fields = [{field: 'count()', width: -1}];
380408

381409
const {widths} = this.state;
382-
const containsSpanOpsBreakdown = eventView
410+
const containsSpanOpsBreakdown = !!eventView
383411
.getColumns()
384412
.find(
385413
(col: TableColumn<React.ReactText>) =>
@@ -388,9 +416,8 @@ class EventsTable extends Component<Props, State> {
388416

389417
const columnOrder = eventView
390418
.getColumns()
391-
.filter(
392-
(col: TableColumn<React.ReactText>) =>
393-
!containsSpanOpsBreakdown || !isSpanOperationBreakdownField(col.name)
419+
.filter((col: TableColumn<React.ReactText>) =>
420+
shouldRenderColumn(containsSpanOpsBreakdown, col.name)
394421
)
395422
.map((col: TableColumn<React.ReactText>, i: number) => {
396423
if (typeof widths[i] === 'number') {

0 commit comments

Comments
 (0)