Skip to content

Commit 7590847

Browse files
mjqclaude
andauthored
feat(performance): Add search support to EAP txn summary sample events (#111349)
Extract `EAPSpansQueryBuilder` into a shared component and wire up search/filtering in the Sample Events tab when using EAP mode. The spans table now respects the search query from URL params, and non-applicable controls (percentile filter, Open in Discover) are hidden in EAP mode. Fixes BROWSE-463. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 895ba61 commit 7590847

5 files changed

Lines changed: 142 additions & 69 deletions

File tree

static/app/views/performance/eap/overviewSpansTable.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {t, tct} from 'sentry/locale';
1212
import type {Organization} from 'sentry/types/organization';
1313
import type {EventsMetaType, EventView} from 'sentry/utils/discover/eventView';
1414
import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
15+
import {decodeScalar} from 'sentry/utils/queryString';
1516
import type {Theme} from 'sentry/utils/theme';
1617
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
1718
import {useLocation} from 'sentry/utils/useLocation';
@@ -50,13 +51,15 @@ export function OverviewSpansTable({eventView, totalValues, transactionName}: Pr
5051
const projectSlug = projects.find(p => p.id === `${eventView.project}`)?.slug;
5152

5253
const p95 = totalValues?.['p95()'] ?? 0;
53-
const defaultQuery = new MutableSearch('');
54-
defaultQuery.addFilterValue('is_transaction', '1');
55-
defaultQuery.addFilterValue('transaction', transactionName);
54+
const searchQuery = decodeScalar(location.query.query, '');
5655

57-
const countQuery = new MutableSearch('');
58-
countQuery.addFilterValue('is_transaction', '1');
59-
countQuery.addFilterValue('transaction', transactionName);
56+
const defaultQuery = new MutableSearch(searchQuery);
57+
defaultQuery.setFilterValues('is_transaction', ['true']);
58+
defaultQuery.setFilterValues('transaction', [transactionName]);
59+
60+
const countQuery = new MutableSearch(searchQuery);
61+
countQuery.setFilterValues('is_transaction', ['true']);
62+
countQuery.setFilterValues('transaction', [transactionName]);
6063

6164
const {data: numEvents, error: numEventsError} = useSpans(
6265
{

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

Lines changed: 69 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {DatePageFilter} from 'sentry/components/pageFilters/date/datePageFilter'
1313
import {EnvironmentPageFilter} from 'sentry/components/pageFilters/environment/environmentPageFilter';
1414
import {PageFilterBar} from 'sentry/components/pageFilters/pageFilterBar';
1515
import {normalizeDateTimeParams} from 'sentry/components/pageFilters/parse';
16+
import {useSpanSearchQueryBuilderProps} from 'sentry/components/performance/spanSearchQueryBuilder';
1617
import {TransactionSearchQueryBuilder} from 'sentry/components/performance/transactionSearchQueryBuilder';
1718
import {t} from 'sentry/locale';
1819
import {DataCategory} from 'sentry/types/core';
@@ -29,6 +30,7 @@ import {useMaxPickableDays} from 'sentry/utils/useMaxPickableDays';
2930
import {useNavigate} from 'sentry/utils/useNavigate';
3031
import {useRoutes} from 'sentry/utils/useRoutes';
3132
import {hasDatasetSelector} from 'sentry/views/dashboards/utils';
33+
import {TraceItemSearchQueryBuilder} from 'sentry/views/explore/components/traceItemSearchQueryBuilder';
3234
import {useDomainViewFilters} from 'sentry/views/insights/pages/useFilters';
3335
import {OverviewSpansTable} from 'sentry/views/performance/eap/overviewSpansTable';
3436
import {useTransactionSummaryEAP} from 'sentry/views/performance/eap/useTransactionSummaryEAP';
@@ -48,6 +50,27 @@ import {EventsTable} from './eventsTable';
4850
import type {EventsDisplayFilterName} from './utils';
4951
import {getEventsFilterOptions} from './utils';
5052

53+
function EAPSearchBar({
54+
projects,
55+
initialQuery,
56+
onSearch,
57+
}: {
58+
initialQuery: string;
59+
onSearch: (query: string) => void;
60+
projects: number[];
61+
}) {
62+
const {spanSearchQueryBuilderProps} = useSpanSearchQueryBuilderProps({
63+
projects,
64+
initialQuery,
65+
onSearch,
66+
searchSource: 'transaction_events',
67+
});
68+
69+
return (
70+
<TraceItemSearchQueryBuilder {...spanSearchQueryBuilderProps} disallowFreeText />
71+
);
72+
}
73+
5174
type Props = {
5275
eventView: EventView;
5376
eventsDisplayFilterName: EventsDisplayFilterName;
@@ -90,8 +113,13 @@ export function EventsContent(props: Props) {
90113
const routes = useRoutes();
91114
const theme = useTheme();
92115
const domainViewFilters = useDomainViewFilters();
116+
const shouldUseEAP = useTransactionSummaryEAP();
93117

94118
const {eventView, titles} = useMemo(() => {
119+
if (shouldUseEAP) {
120+
return {eventView: originalEventView, titles: []};
121+
}
122+
95123
const eventViewClone = originalEventView.clone();
96124
const transactionsListTitles = TRANSACTIONS_LIST_TITLES.slice();
97125
const project = projects.find(p => p.id === projectId);
@@ -164,6 +192,7 @@ export function EventsContent(props: Props) {
164192
titles: transactionsListTitles,
165193
};
166194
}, [
195+
shouldUseEAP,
167196
originalEventView,
168197
location,
169198
organization,
@@ -173,8 +202,6 @@ export function EventsContent(props: Props) {
173202
webVital,
174203
]);
175204

176-
const shouldUseEAP = useTransactionSummaryEAP();
177-
178205
const table = shouldUseEAP ? (
179206
<OverviewSpansTable
180207
eventView={eventView}
@@ -250,7 +277,7 @@ function Search(props: Props) {
250277
const shouldUseEAP = useTransactionSummaryEAP();
251278

252279
const maxPickableDays = useMaxPickableDays({
253-
dataCategories: [DataCategory.TRANSACTIONS],
280+
dataCategories: [shouldUseEAP ? DataCategory.SPANS : DataCategory.TRANSACTIONS],
254281
});
255282
const datePageFilterProps = useDatePageFilterProps(maxPickableDays);
256283

@@ -270,34 +297,46 @@ function Search(props: Props) {
270297
<DatePageFilter {...datePageFilterProps} />
271298
</PageFilterBar>
272299
<StyledSearchBarWrapper>
273-
<TransactionSearchQueryBuilder
274-
projects={projectIds}
275-
initialQuery={query}
276-
onSearch={handleSearch}
277-
searchSource="transaction_events"
278-
/>
279-
</StyledSearchBarWrapper>
280-
<CompactSelect
281-
trigger={triggerProps => (
282-
<OverlayTrigger.Button {...triggerProps} prefix={t('Percentile')} />
283-
)}
284-
value={eventsDisplayFilterName}
285-
onChange={opt => onChangeEventsDisplayFilter(opt.value)}
286-
options={Object.entries(eventsFilterOptions).map(([name, filter]) => ({
287-
value: name as EventsDisplayFilterName,
288-
label: filter.label,
289-
}))}
290-
/>
291-
<LinkButton
292-
to={eventView.getResultsViewUrlTarget(
293-
organization,
294-
false,
295-
hasDatasetSelector(organization) ? SavedQueryDatasets.TRANSACTIONS : undefined
300+
{shouldUseEAP ? (
301+
<EAPSearchBar
302+
projects={projectIds ?? []}
303+
initialQuery={query}
304+
onSearch={handleSearch}
305+
/>
306+
) : (
307+
<TransactionSearchQueryBuilder
308+
projects={projectIds}
309+
initialQuery={query}
310+
onSearch={handleSearch}
311+
searchSource="transaction_events"
312+
/>
296313
)}
297-
onClick={handleDiscoverButtonClick}
298-
>
299-
{t('Open in Discover')}
300-
</LinkButton>
314+
</StyledSearchBarWrapper>
315+
{!shouldUseEAP && (
316+
<CompactSelect
317+
trigger={triggerProps => (
318+
<OverlayTrigger.Button {...triggerProps} prefix={t('Percentile')} />
319+
)}
320+
value={eventsDisplayFilterName}
321+
onChange={opt => onChangeEventsDisplayFilter(opt.value)}
322+
options={Object.entries(eventsFilterOptions).map(([name, filter]) => ({
323+
value: name as EventsDisplayFilterName,
324+
label: filter.label,
325+
}))}
326+
/>
327+
)}
328+
{!shouldUseEAP && (
329+
<LinkButton
330+
to={eventView.getResultsViewUrlTarget(
331+
organization,
332+
false,
333+
hasDatasetSelector(organization) ? SavedQueryDatasets.TRANSACTIONS : undefined
334+
)}
335+
onClick={handleDiscoverButtonClick}
336+
>
337+
{t('Open in Discover')}
338+
</LinkButton>
339+
)}
301340
</FilterActions>
302341
);
303342
}

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {removeHistogramQueryStrings} from 'sentry/utils/performance/histogram';
88
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
99
import {useLocation} from 'sentry/utils/useLocation';
1010
import {useNavigate} from 'sentry/utils/useNavigate';
11+
import {useTransactionSummaryEAP} from 'sentry/views/performance/eap/useTransactionSummaryEAP';
1112
import {
1213
decodeFilterFromLocation,
1314
filterToLocationQuery,
@@ -38,6 +39,7 @@ function TransactionEvents() {
3839

3940
const navigate = useNavigate();
4041
const location = useLocation();
42+
const shouldUseEAP = useTransactionSummaryEAP();
4143
const eventsDisplayFilterName = decodeEventsDisplayFilterFromLocation(location);
4244
const spanOperationBreakdownFilter = decodeFilterFromLocation(location);
4345
const webVital = getWebVital(location);
@@ -121,6 +123,26 @@ function TransactionEvents() {
121123
});
122124
};
123125

126+
if (shouldUseEAP) {
127+
return (
128+
<EventsContent
129+
location={location}
130+
organization={organization}
131+
eventView={eventView}
132+
transactionName={transactionName}
133+
spanOperationBreakdownFilter={spanOperationBreakdownFilter}
134+
onChangeSpanOperationBreakdownFilter={onChangeSpanOperationBreakdownFilter}
135+
eventsDisplayFilterName={EventsDisplayFilterName.P100}
136+
onChangeEventsDisplayFilter={onChangeEventsDisplayFilter}
137+
percentileValues={undefined}
138+
projectId={projectId}
139+
projects={projects}
140+
webVital={webVital}
141+
setError={setError}
142+
/>
143+
);
144+
}
145+
124146
return (
125147
<DiscoverQuery
126148
eventView={percentilesView}

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
SPAN_OP_RELATIVE_BREAKDOWN_FIELD,
1111
type QueryFieldValue,
1212
} from 'sentry/utils/discover/fields';
13+
import {DiscoverDatasets} from 'sentry/utils/discover/types';
1314
import {WebVital} from 'sentry/utils/fields';
1415
import {decodeScalar} from 'sentry/utils/queryString';
1516
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
@@ -212,6 +213,7 @@ export function getPercentilesEventView(eventView: EventView): EventView {
212213
export function generateTransactionEventsEventView({
213214
location,
214215
transactionName,
216+
shouldUseEAP,
215217
}: {
216218
location: Location;
217219
organization: Organization;
@@ -221,7 +223,11 @@ export function generateTransactionEventsEventView({
221223
const query = decodeScalar(location.query.query, '');
222224
const conditions = new MutableSearch(query);
223225

224-
conditions.setFilterValues('event.type', ['transaction']);
226+
if (shouldUseEAP) {
227+
conditions.setFilterValues('is_transaction', ['true']);
228+
} else {
229+
conditions.setFilterValues('event.type', ['transaction']);
230+
}
225231
conditions.setFilterValues('transaction', [transactionName]);
226232

227233
Object.keys(conditions.filters).forEach(field => {
@@ -230,7 +236,29 @@ export function generateTransactionEventsEventView({
230236
}
231237
});
232238

233-
const orderby = decodeScalar(location.query.sort, '-timestamp');
239+
let orderby = decodeScalar(location.query.sort, '-timestamp');
240+
241+
if (shouldUseEAP) {
242+
orderby = orderby.replace('transaction.duration', 'span.duration');
243+
244+
return EventView.fromNewQueryWithLocation(
245+
{
246+
id: undefined,
247+
version: 2,
248+
name: transactionName,
249+
// TODO(mjq): `fields` is never actually read - the relevant query comes
250+
// from `useSegmentSpansQuery` instead. Confusingly, other fields of
251+
// this EventView _are_ used in various places. Untangling this is a job
252+
// for after the non-EAP branches are removed.
253+
fields: [],
254+
query: conditions.formatString(),
255+
projects: [],
256+
orderby,
257+
dataset: DiscoverDatasets.SPANS,
258+
},
259+
location
260+
);
261+
}
234262

235263
// Default fields for relative span view
236264
const fields = [

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

Lines changed: 12 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -100,27 +100,6 @@ type Props = {
100100

101101
export const SEGMENT_SPANS_CURSOR_NAME = 'segmentSpansCursor';
102102

103-
function EAPSearchQueryBuilder({
104-
projects,
105-
initialQuery,
106-
onSearch,
107-
}: {
108-
initialQuery: string;
109-
onSearch: (query: string) => void;
110-
projects: number[];
111-
}) {
112-
const {spanSearchQueryBuilderProps} = useSpanSearchQueryBuilderProps({
113-
projects,
114-
initialQuery,
115-
onSearch,
116-
searchSource: 'transaction_summary',
117-
});
118-
119-
return (
120-
<TraceItemSearchQueryBuilder {...spanSearchQueryBuilderProps} disallowFreeText />
121-
);
122-
}
123-
124103
function EAPSummaryContentInner({
125104
eventView,
126105
location,
@@ -258,15 +237,12 @@ function EAPSummaryContentInner({
258237
});
259238
const datePageFilterProps = useDatePageFilterProps(maxPickableDays);
260239

261-
function renderSearchBar() {
262-
return (
263-
<EAPSearchQueryBuilder
264-
projects={projectIds}
265-
initialQuery={query}
266-
onSearch={handleSearch}
267-
/>
268-
);
269-
}
240+
const {spanSearchQueryBuilderProps} = useSpanSearchQueryBuilderProps({
241+
projects: projectIds,
242+
initialQuery: query,
243+
onSearch: handleSearch,
244+
searchSource: 'transaction_summary',
245+
});
270246

271247
return (
272248
<Fragment>
@@ -277,7 +253,12 @@ function EAPSummaryContentInner({
277253
<EnvironmentPageFilter />
278254
<DatePageFilter {...datePageFilterProps} />
279255
</PageFilterBar>
280-
<StyledSearchBarWrapper>{renderSearchBar()}</StyledSearchBarWrapper>
256+
<StyledSearchBarWrapper>
257+
<TraceItemSearchQueryBuilder
258+
{...spanSearchQueryBuilderProps}
259+
disallowFreeText
260+
/>
261+
</StyledSearchBarWrapper>
281262
</FilterActions>
282263
<EAPChartsWidgetContainer>
283264
<EAPChartsWidget transactionName={transactionName} query={query} />

0 commit comments

Comments
 (0)