Skip to content

Commit 38ff36c

Browse files
committed
Fully enable reprocessing-v2 (frontend) and update logic showing "Reprocess Event" action
This fully enables the feature by simply removing all the checks and early returns for the feature flag. The logic was updated so that all kinds of events that have debug files are eligible for reprocessing.
1 parent 5f0e188 commit 38ff36c

File tree

17 files changed

+163
-192
lines changed

17 files changed

+163
-192
lines changed

src/sentry/api/endpoints/event_reprocessable.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from rest_framework.request import Request
33
from rest_framework.response import Response
44

5-
from sentry import features
65
from sentry.api.api_owners import ApiOwner
76
from sentry.api.api_publish_status import ApiPublishStatus
87
from sentry.api.base import region_silo_endpoint
@@ -56,14 +55,6 @@ def get(self, request: Request, project, event_id) -> Response:
5655
:auth: required
5756
"""
5857

59-
if not features.has(
60-
"organizations:reprocessing-v2", project.organization, actor=request.user
61-
):
62-
return self.respond(
63-
{"error": "This project does not have the reprocessing v2 feature"},
64-
status=404,
65-
)
66-
6758
try:
6859
pull_event_data(project.id, event_id)
6960
except CannotReprocess as e:

src/sentry/api/endpoints/group_reprocessing.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from rest_framework.request import Request
22
from rest_framework.response import Response
33

4-
from sentry import features
54
from sentry.api.api_publish_status import ApiPublishStatus
65
from sentry.api.base import region_silo_endpoint
76
from sentry.api.bases import GroupEndpoint
@@ -27,14 +26,6 @@ def post(self, request: Request, group) -> Response:
2726
:auth: required
2827
"""
2928

30-
if not features.has(
31-
"organizations:reprocessing-v2", group.project.organization, actor=request.user
32-
):
33-
return self.respond(
34-
{"error": "This project does not have the reprocessing v2 feature"},
35-
status=404,
36-
)
37-
3829
max_events = request.data.get("maxEvents")
3930
if max_events:
4031
max_events = int(max_events)

static/app/components/events/interfaces/debugMeta/debugImageDetails/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ export function DebugImageDetails({
256256
);
257257
const hasReprocessWarning =
258258
haveCandidatesUnappliedDebugFile &&
259-
displayReprocessEventAction(organization.features, event) &&
259+
displayReprocessEventAction(event) &&
260260
!!onReprocessEvent;
261261

262262
if (isError) {

static/app/components/modals/reprocessEventModal.spec.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ describe('ReprocessEventModal', function () {
2424
organization: {
2525
id: '4660',
2626
slug: 'org',
27-
features: ['reprocessing-v2'],
2827
},
2928
});
3029

@@ -73,7 +72,6 @@ describe('ReprocessEventModal', function () {
7372
organization: {
7473
id: '4660',
7574
slug: 'org',
76-
features: ['reprocessing-v2'],
7775
},
7876
});
7977

static/app/utils/displayReprocessEventAction.spec.tsx

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,18 @@ import {
66
import {displayReprocessEventAction} from 'sentry/utils/displayReprocessEventAction';
77

88
describe('DisplayReprocessEventAction', function () {
9-
const orgFeatures = ['reprocessing-v2'];
10-
11-
it('returns false in case of no reprocessing-v2 feature', function () {
12-
const event = EventStacktraceMessageFixture();
13-
expect(displayReprocessEventAction([], event)).toBe(false);
14-
});
15-
169
it('returns false in case of no event', function () {
17-
expect(displayReprocessEventAction(orgFeatures)).toBe(false);
10+
expect(displayReprocessEventAction()).toBe(false);
1811
});
1912

2013
it('returns false if no exception entry is found', function () {
2114
const event = EventStacktraceMessageFixture();
22-
expect(displayReprocessEventAction(orgFeatures, event)).toBe(false);
15+
expect(displayReprocessEventAction(event)).toBe(false);
2316
});
2417

2518
it('returns false if the event is not a mini-dump event or an Apple crash report event or a Native event', function () {
2619
const event = EventStacktraceExceptionFixture();
27-
expect(displayReprocessEventAction(orgFeatures, event)).toBe(false);
20+
expect(displayReprocessEventAction(event)).toBe(false);
2821
});
2922

3023
describe('returns true', function () {
@@ -35,15 +28,15 @@ describe('DisplayReprocessEventAction', function () {
3528
platform: 'native',
3629
});
3730

38-
expect(displayReprocessEventAction(orgFeatures, event)).toBe(true);
31+
expect(displayReprocessEventAction(event)).toBe(true);
3932
});
4033

4134
it('cocoa', function () {
4235
const event = EventStacktraceExceptionFixture({
4336
platform: 'cocoa',
4437
});
4538

46-
expect(displayReprocessEventAction(orgFeatures, event)).toBe(true);
39+
expect(displayReprocessEventAction(event)).toBe(true);
4740
});
4841
});
4942

@@ -55,7 +48,7 @@ describe('DisplayReprocessEventAction', function () {
5548

5649
event.entries[0].data.values[0].stacktrace.frames[0].platform = 'native';
5750

58-
expect(displayReprocessEventAction(orgFeatures, event)).toBe(true);
51+
expect(displayReprocessEventAction(event)).toBe(true);
5952
});
6053

6154
it('cocoa', function () {
@@ -65,7 +58,7 @@ describe('DisplayReprocessEventAction', function () {
6558

6659
event.entries[0].data.values[0].stacktrace.frames[0].platform = 'cocoa';
6760

68-
expect(displayReprocessEventAction(orgFeatures, event)).toBe(true);
61+
expect(displayReprocessEventAction(event)).toBe(true);
6962
});
7063
});
7164
});
@@ -82,7 +75,7 @@ describe('DisplayReprocessEventAction', function () {
8275
},
8376
};
8477

85-
expect(displayReprocessEventAction(orgFeatures, event)).toBe(true);
78+
expect(displayReprocessEventAction(event)).toBe(true);
8679
});
8780

8881
it('apple crash report event', function () {
@@ -97,7 +90,7 @@ describe('DisplayReprocessEventAction', function () {
9790
},
9891
};
9992

100-
expect(displayReprocessEventAction(orgFeatures, event)).toBe(true);
93+
expect(displayReprocessEventAction(event)).toBe(true);
10194
});
10295
});
10396
});

static/app/utils/displayReprocessEventAction.tsx

Lines changed: 107 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -8,106 +8,134 @@ import type {
88
} from 'sentry/types';
99
import {EntryType} from 'sentry/types/event';
1010

11-
const NATIVE_PLATFORMS: PlatformKey[] = ['cocoa', 'native'];
11+
/** All platforms that always use Debug Files. */
12+
const DEBUG_FILE_PLATFORMS: Set<PlatformKey> = new Set([
13+
'objc',
14+
'cocoa',
15+
'swift',
16+
'native',
17+
'c',
18+
]);
19+
/** Other platforms that may use Debug Files. */
20+
const MAYBE_DEBUG_FILE_PLATFORMS: Set<PlatformKey> = new Set(['csharp', 'java']);
21+
22+
/**
23+
* Returns whether to display the "Reprocess Event" action.
24+
*
25+
* That is the case when we have a "reprocessable" event, which is an event that needs
26+
* Debug Files for proper processing, as those Debug Files could have been uploaded *after*
27+
* the Event was ingested.
28+
*/
29+
export function displayReprocessEventAction(event?: Event): boolean {
30+
if (!event) {
31+
return false;
32+
}
1233

13-
// Finds all frames in a given data blob and returns it's platforms
14-
function getPlatforms(exceptionValue: ExceptionValue | StacktraceType | null) {
15-
const frames = exceptionValue?.frames ?? [];
16-
const stacktraceFrames = (exceptionValue as ExceptionValue)?.stacktrace?.frames ?? [];
34+
const eventPlatforms = getEventPlatform(event);
35+
// Check Events from platforms that always use Debug Files as a fast-path
36+
if (hasIntersection(eventPlatforms, DEBUG_FILE_PLATFORMS)) {
37+
return true;
38+
}
1739

18-
if (!frames.length && !stacktraceFrames.length) {
19-
return [];
40+
const hasDebugImages = (event?.entries ?? []).some(
41+
entry => entry.type === EntryType.DEBUGMETA && entry.data.images.length > 0
42+
);
43+
44+
// Otherwise, check alternative platforms if they actually have Debug Files
45+
if (hasIntersection(eventPlatforms, MAYBE_DEBUG_FILE_PLATFORMS) && hasDebugImages) {
46+
return true;
2047
}
2148

22-
return [...frames, ...stacktraceFrames]
23-
.map(frame => frame.platform)
24-
.filter(platform => !!platform);
25-
}
49+
// Finally, fall back to checking the `platform` of each frame
50+
const exceptionEntry = event.entries.find(
51+
entry => entry.type === EntryType.EXCEPTION
52+
) as EntryException | undefined;
53+
54+
if (!exceptionEntry) {
55+
return false;
56+
}
2657

27-
function getStackTracePlatforms(event: Event, exceptionEntry: EntryException) {
28-
// Fetch platforms in stack traces of an exception entry
29-
const exceptionEntryPlatforms = (exceptionEntry.data.values ?? []).flatMap(
30-
getPlatforms
58+
return hasIntersection(
59+
getStackTracePlatforms(event, exceptionEntry),
60+
DEBUG_FILE_PLATFORMS
3161
);
62+
}
63+
64+
/**
65+
* Returns whether the two Sets have intersecting elements.
66+
*/
67+
function hasIntersection<T>(set1: Set<T>, set2: Set<T>): boolean {
68+
for (const v of set1) {
69+
if (set2.has(v)) {
70+
return true;
71+
}
72+
}
73+
return false;
74+
}
75+
76+
/**
77+
* Returns the event platform as a Set.
78+
*/
79+
function getEventPlatform(event: Event): Set<PlatformKey> {
80+
const platforms = new Set<PlatformKey>();
81+
addPlatforms(platforms, [event]);
82+
return platforms;
83+
}
84+
85+
/**
86+
* Returns a Set of all platforms found in the `event` and `exceptionEntry`.
87+
*/
88+
function getStackTracePlatforms(
89+
event: Event,
90+
exceptionEntry: EntryException
91+
): Set<PlatformKey> {
92+
const platforms = new Set<PlatformKey>();
93+
94+
// Add platforms in stack traces of an exception entry
95+
(exceptionEntry.data.values ?? []).forEach(exc => addFramePlatforms(platforms, exc));
3296

33-
// Fetch platforms in an exception entry
97+
// Add platforms in a stack trace entry
3498
const stackTraceEntry = (event.entries.find(
3599
entry => entry.type === EntryType.STACKTRACE
36100
)?.data ?? {}) as StacktraceType;
37101

38-
// Fetch platforms in an exception entry
39-
const stackTraceEntryPlatforms = Object.keys(stackTraceEntry).flatMap(key =>
40-
getPlatforms(stackTraceEntry[key])
102+
Object.keys(stackTraceEntry).forEach(key =>
103+
addFramePlatforms(platforms, stackTraceEntry[key])
41104
);
42105

43-
// Fetch platforms in an thread entry
106+
// Add platforms in a thread entry
44107
const threadEntry = (event.entries.find(entry => entry.type === EntryType.THREADS)?.data
45108
.values ?? []) as Array<Thread>;
46109

47-
// Fetch platforms in a thread entry
48-
const threadEntryPlatforms = threadEntry.flatMap(({stacktrace}) =>
49-
getPlatforms(stacktrace)
50-
);
110+
threadEntry.forEach(({stacktrace}) => addFramePlatforms(platforms, stacktrace));
51111

52-
return new Set([
53-
...exceptionEntryPlatforms,
54-
...stackTraceEntryPlatforms,
55-
...threadEntryPlatforms,
56-
]);
112+
return platforms;
57113
}
58114

59-
// Checks whether an event indicates that it is a native event.
60-
function isNativeEvent(event: Event, exceptionEntry: EntryException) {
61-
const {platform} = event;
62-
63-
if (platform && NATIVE_PLATFORMS.includes(platform)) {
64-
return true;
65-
}
66-
67-
const stackTracePlatforms = getStackTracePlatforms(event, exceptionEntry);
68-
69-
return NATIVE_PLATFORMS.some(nativePlatform => stackTracePlatforms.has(nativePlatform));
70-
}
71-
72-
// Checks whether an event indicates that it has an associated minidump.
73-
function isMinidumpEvent(exceptionEntry: EntryException) {
74-
const {data} = exceptionEntry;
75-
return (data.values ?? []).some(value => value.mechanism?.type === 'minidump');
76-
}
115+
/**
116+
* Adds all the platforms in the frames of `exceptionValue` to the `platforms` Set.
117+
*/
118+
function addFramePlatforms(
119+
platforms: Set<PlatformKey>,
120+
exceptionValue: ExceptionValue | StacktraceType | null
121+
) {
122+
const frames = exceptionValue?.frames ?? [];
123+
const stacktraceFrames = (exceptionValue as ExceptionValue)?.stacktrace?.frames ?? [];
77124

78-
// Checks whether an event indicates that it has an apple crash report.
79-
function isAppleCrashReportEvent(exceptionEntry: EntryException) {
80-
const {data} = exceptionEntry;
81-
return (data.values ?? []).some(value => value.mechanism?.type === 'applecrashreport');
125+
addPlatforms(platforms, frames);
126+
addPlatforms(platforms, stacktraceFrames);
82127
}
83128

84-
export function displayReprocessEventAction(orgFeatures: Array<string>, event?: Event) {
85-
if (!event || !orgFeatures.includes('reprocessing-v2')) {
86-
return false;
87-
}
88-
89-
const {entries} = event;
90-
const exceptionEntry = entries.find(entry => entry.type === EntryType.EXCEPTION) as
91-
| EntryException
92-
| undefined;
93-
94-
if (!exceptionEntry) {
95-
return false;
129+
/**
130+
* Adds all the `platform` properties found in `iter` to the `platforms` Set.
131+
*/
132+
function addPlatforms(
133+
platforms: Set<PlatformKey>,
134+
iter: Array<{platform?: PlatformKey | null}>
135+
) {
136+
for (const o of iter) {
137+
if (o.platform) {
138+
platforms.add(o.platform);
139+
}
96140
}
97-
98-
// We want to show the reprocessing button if the issue in question is native or contains native frames.
99-
// The logic is taken from the symbolication pipeline in Python, where it is used to determine whether reprocessing
100-
// payloads should be stored:
101-
// https://github.com/getsentry/sentry/blob/cb7baef414890336881d67b7a8433ee47198c701/src/sentry/lang/native/processing.py#L425-L426
102-
// It is still not ideal as one can always merge native and non-native events together into one issue,
103-
// but it's the best approximation we have.
104-
if (
105-
!isMinidumpEvent(exceptionEntry) &&
106-
!isAppleCrashReportEvent(exceptionEntry) &&
107-
!isNativeEvent(event, exceptionEntry)
108-
) {
109-
return false;
110-
}
111-
112-
return true;
113141
}

static/app/views/issueDetails/actions/index.spec.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ const group = GroupFixture({
3737
const organization = OrganizationFixture({
3838
id: '4660',
3939
slug: 'org',
40-
features: ['reprocessing-v2'],
4140
});
4241

4342
describe('GroupActions', function () {
@@ -132,7 +131,7 @@ describe('GroupActions', function () {
132131
});
133132

134133
describe('reprocessing', function () {
135-
it('renders ReprocessAction component if org has feature flag reprocessing-v2 and native exception event', async function () {
134+
it('renders ReprocessAction component if org has native exception event', async function () {
136135
const event = EventStacktraceExceptionFixture({
137136
platform: 'native',
138137
});

static/app/views/issueDetails/actions/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ export function Actions(props: Props) {
412412
{
413413
key: 'reprocess',
414414
label: t('Reprocess events'),
415-
hidden: !displayReprocessEventAction(organization.features, event),
415+
hidden: !displayReprocessEventAction(event),
416416
onAction: onReprocessEvent,
417417
},
418418
{

0 commit comments

Comments
 (0)