Skip to content

Commit 4ee1ea9

Browse files
authored
feat(related_issues): Free tier support for trace timeline and related issues (#72933)
If we fetch the events endpoint without specifying a project it queries across all projects. Organizations without the `global-views` feature (e.g. free plan) would fail to return any events, thus, the trace timeline would not be displayed. This changes the `useTraceTimelineEvents` to always include the `project` query parameter by either setting it to `-1` (which queries all projects) or to the current project (e.g. the organization is on the free plan). This will enable the trace timeline and related issues for free plans for events/issues within the same project. If a trace contains events for other projects, we will only show the link to the trace but not the trace timeline or related issues.
1 parent 408d2d9 commit 4ee1ea9

File tree

2 files changed

+60
-18
lines changed

2 files changed

+60
-18
lines changed

static/app/views/issueDetails/traceTimeline/traceTimeline.spec.tsx

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ jest.mock('sentry/utils/routeAnalytics/useRouteAnalyticsParams');
1515
jest.mock('sentry/utils/analytics');
1616

1717
describe('TraceTimeline', () => {
18-
const organization = OrganizationFixture();
18+
// Paid plans have global-views enabled
19+
// Include project: -1 in all matchQuery calls to ensure we are looking at all projects
20+
const organization = OrganizationFixture({
21+
features: ['global-views'],
22+
});
1923
// This creates the ApiException event
2024
const event = EventFixture({
2125
dateCreated: '2024-01-24T09:09:03+00:00',
@@ -72,12 +76,12 @@ describe('TraceTimeline', () => {
7276
MockApiClient.addMockResponse({
7377
url: `/organizations/${organization.slug}/events/`,
7478
body: issuePlatformBody,
75-
match: [MockApiClient.matchQuery({dataset: 'issuePlatform'})],
79+
match: [MockApiClient.matchQuery({dataset: 'issuePlatform', project: -1})],
7680
});
7781
MockApiClient.addMockResponse({
7882
url: `/organizations/${organization.slug}/events/`,
7983
body: discoverBody,
80-
match: [MockApiClient.matchQuery({dataset: 'discover'})],
84+
match: [MockApiClient.matchQuery({dataset: 'discover', project: -1})],
8185
});
8286
render(<TraceTimeline event={event} />, {organization});
8387
expect(await screen.findByLabelText('Current Event')).toBeInTheDocument();
@@ -94,12 +98,12 @@ describe('TraceTimeline', () => {
9498
MockApiClient.addMockResponse({
9599
url: `/organizations/${organization.slug}/events/`,
96100
body: emptyBody,
97-
match: [MockApiClient.matchQuery({dataset: 'issuePlatform'})],
101+
match: [MockApiClient.matchQuery({dataset: 'issuePlatform', project: -1})],
98102
});
99103
MockApiClient.addMockResponse({
100104
url: `/organizations/${organization.slug}/events/`,
101105
body: discoverBody,
102-
match: [MockApiClient.matchQuery({dataset: 'discover'})],
106+
match: [MockApiClient.matchQuery({dataset: 'discover', project: -1})],
103107
});
104108
const {container} = render(<TraceTimeline event={event} />, {organization});
105109
await waitFor(() =>
@@ -115,12 +119,12 @@ describe('TraceTimeline', () => {
115119
MockApiClient.addMockResponse({
116120
url: `/organizations/${organization.slug}/events/`,
117121
body: emptyBody,
118-
match: [MockApiClient.matchQuery({dataset: 'issuePlatform'})],
122+
match: [MockApiClient.matchQuery({dataset: 'issuePlatform', project: -1})],
119123
});
120124
MockApiClient.addMockResponse({
121125
url: `/organizations/${organization.slug}/events/`,
122126
body: emptyBody,
123-
match: [MockApiClient.matchQuery({dataset: 'discover'})],
127+
match: [MockApiClient.matchQuery({dataset: 'discover', project: -1})],
124128
});
125129
const {container} = render(<TraceTimeline event={event} />, {organization});
126130
await waitFor(() =>
@@ -136,12 +140,12 @@ describe('TraceTimeline', () => {
136140
MockApiClient.addMockResponse({
137141
url: `/organizations/${organization.slug}/events/`,
138142
body: issuePlatformBody,
139-
match: [MockApiClient.matchQuery({dataset: 'issuePlatform'})],
143+
match: [MockApiClient.matchQuery({dataset: 'issuePlatform', project: -1})],
140144
});
141145
MockApiClient.addMockResponse({
142146
url: `/organizations/${organization.slug}/events/`,
143147
body: emptyBody,
144-
match: [MockApiClient.matchQuery({dataset: 'discover'})],
148+
match: [MockApiClient.matchQuery({dataset: 'discover', project: -1})],
145149
});
146150
render(<TraceTimeline event={event} />, {organization});
147151
// Checking for the presence of seconds
@@ -152,12 +156,12 @@ describe('TraceTimeline', () => {
152156
MockApiClient.addMockResponse({
153157
url: `/organizations/${organization.slug}/events/`,
154158
body: issuePlatformBody,
155-
match: [MockApiClient.matchQuery({dataset: 'issuePlatform'})],
159+
match: [MockApiClient.matchQuery({dataset: 'issuePlatform', project: -1})],
156160
});
157161
MockApiClient.addMockResponse({
158162
url: `/organizations/${organization.slug}/events/`,
159163
body: emptyBody,
160-
match: [MockApiClient.matchQuery({dataset: 'discover'})],
164+
match: [MockApiClient.matchQuery({dataset: 'discover', project: -1})],
161165
});
162166
render(<TraceTimeline event={event} />, {organization});
163167
expect(await screen.findByLabelText('Current Event')).toBeInTheDocument();
@@ -167,12 +171,12 @@ describe('TraceTimeline', () => {
167171
MockApiClient.addMockResponse({
168172
url: `/organizations/${organization.slug}/events/`,
169173
body: issuePlatformBody,
170-
match: [MockApiClient.matchQuery({dataset: 'issuePlatform'})],
174+
match: [MockApiClient.matchQuery({dataset: 'issuePlatform', project: -1})],
171175
});
172176
MockApiClient.addMockResponse({
173177
url: `/organizations/${organization.slug}/events/`,
174178
body: emptyBody,
175-
match: [MockApiClient.matchQuery({dataset: 'discover'})],
179+
match: [MockApiClient.matchQuery({dataset: 'discover', project: -1})],
176180
});
177181
// I believe the call to projects is to determine what projects a user belongs to
178182
MockApiClient.addMockResponse({
@@ -182,7 +186,7 @@ describe('TraceTimeline', () => {
182186

183187
render(<TraceTimeline event={event} />, {
184188
organization: OrganizationFixture({
185-
features: ['related-issues-issue-details-page'],
189+
features: ['related-issues-issue-details-page', 'global-views'],
186190
}),
187191
});
188192

@@ -201,7 +205,7 @@ describe('TraceTimeline', () => {
201205
{
202206
group_id: issuePlatformBody.data[0]['issue.id'],
203207
organization: OrganizationFixture({
204-
features: ['related-issues-issue-details-page'],
208+
features: ['related-issues-issue-details-page', 'global-views'],
205209
}),
206210
}
207211
);
@@ -211,13 +215,13 @@ describe('TraceTimeline', () => {
211215
MockApiClient.addMockResponse({
212216
url: `/organizations/${organization.slug}/events/`,
213217
body: emptyBody,
214-
match: [MockApiClient.matchQuery({dataset: 'issuePlatform'})],
218+
match: [MockApiClient.matchQuery({dataset: 'issuePlatform', project: -1})],
215219
});
216220
MockApiClient.addMockResponse({
217221
url: `/organizations/${organization.slug}/events/`,
218222
// Only 1 issue
219223
body: discoverBody,
220-
match: [MockApiClient.matchQuery({dataset: 'discover'})],
224+
match: [MockApiClient.matchQuery({dataset: 'discover', project: -1})],
221225
});
222226
// I believe the call to projects is to determine what projects a user belongs to
223227
MockApiClient.addMockResponse({
@@ -227,7 +231,7 @@ describe('TraceTimeline', () => {
227231

228232
render(<TraceTimeline event={event} />, {
229233
organization: OrganizationFixture({
230-
features: ['related-issues-issue-details-page'],
234+
features: ['related-issues-issue-details-page', 'global-views'],
231235
}),
232236
});
233237

@@ -244,4 +248,36 @@ describe('TraceTimeline', () => {
244248
trace_timeline_status: 'empty',
245249
});
246250
});
251+
252+
it('works for plans with no global-views feature', async () => {
253+
MockApiClient.addMockResponse({
254+
url: `/organizations/${organization.slug}/events/`,
255+
body: issuePlatformBody,
256+
match: [
257+
MockApiClient.matchQuery({
258+
dataset: 'issuePlatform',
259+
// Since we don't have global-views, we only look at the current project
260+
project: event.projectID,
261+
}),
262+
],
263+
});
264+
MockApiClient.addMockResponse({
265+
url: `/organizations/${organization.slug}/events/`,
266+
body: emptyBody,
267+
match: [
268+
MockApiClient.matchQuery({
269+
dataset: 'discover',
270+
// Since we don't have global-views, we only look at the current project
271+
project: event.projectID,
272+
}),
273+
],
274+
});
275+
276+
render(<TraceTimeline event={event} />, {
277+
organization: OrganizationFixture({
278+
features: [], // No global-views feature
279+
}),
280+
});
281+
expect(await screen.findByLabelText('Current Event')).toBeInTheDocument();
282+
});
247283
});

static/app/views/issueDetails/traceTimeline/useTraceTimelineEvents.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ export function useTraceTimelineEvents({event}: UseTraceTimelineEventsOptions):
4343
traceEvents: TimelineEvent[];
4444
} {
4545
const organization = useOrganization();
46+
// If the org has global views, we want to look across all projects,
47+
// otherwise, just look at the current project.
48+
const hasGlobalViews = organization.features.includes('global-views');
49+
const project = hasGlobalViews ? -1 : event.projectID;
4650
const {start, end} = getTraceTimeRangeFromEvent(event);
4751

4852
const traceId = event.contexts?.trace?.trace_id ?? '';
@@ -65,6 +69,7 @@ export function useTraceTimelineEvents({event}: UseTraceTimelineEventsOptions):
6569
sort: '-timestamp',
6670
start,
6771
end,
72+
project: project,
6873
},
6974
},
7075
],
@@ -100,6 +105,7 @@ export function useTraceTimelineEvents({event}: UseTraceTimelineEventsOptions):
100105
sort: '-timestamp',
101106
start,
102107
end,
108+
project: project,
103109
},
104110
},
105111
],

0 commit comments

Comments
 (0)