Skip to content

Commit 683a0ce

Browse files
committed
Merge branch 'master' into srest2021/ENG-6194-frontend
2 parents 1d6f4ee + 0dc92ed commit 683a0ce

File tree

14 files changed

+534
-125
lines changed

14 files changed

+534
-125
lines changed

src/sentry/options/defaults.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3825,15 +3825,6 @@
38253825
flags=FLAG_ALLOW_EMPTY | FLAG_AUTOMATOR_MODIFIABLE,
38263826
)
38273827

3828-
# Project ID allowlist to enable truncation of group IDs in Snuba query
3829-
# when search filters are selective.
3830-
register(
3831-
"snuba.search.truncate-group-ids-for-selective-filters-project-allowlist",
3832-
type=Sequence,
3833-
default=[],
3834-
flags=FLAG_ALLOW_EMPTY | FLAG_AUTOMATOR_MODIFIABLE,
3835-
)
3836-
38373828
# Option to enable truncation of group IDs in Snuba query
38383829
# when search filters are selective.
38393830
register(

src/sentry/workflow_engine/endpoints/organization_workflow_index.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,13 @@ def filter_workflows(self, request: Request, organization: Organization) -> Quer
121121
# If specific IDs are provided, skip query and project filtering
122122
return queryset
123123

124+
if raw_detectorlist := request.GET.getlist("detector"):
125+
try:
126+
detector_ids = [int(id) for id in raw_detectorlist]
127+
except ValueError:
128+
raise ValidationError({"detector": ["Invalid detector ID format"]})
129+
queryset = queryset.filter(detectorworkflow__detector_id__in=detector_ids).distinct()
130+
124131
if raw_query := request.GET.get("query"):
125132
for filter in parse_workflow_query(raw_query):
126133
assert isinstance(filter, SearchFilter)

static/app/components/events/autofix/utils.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export function formatRootCauseWithEvent(
7474
return rootCauseText;
7575
}
7676

77-
const eventText = '\n# Raw Event Data\n' + formatEventToMarkdown(event);
77+
const eventText = '\n# Raw Event Data\n' + formatEventToMarkdown(event, undefined);
7878
return rootCauseText + eventText;
7979
}
8080

@@ -95,7 +95,7 @@ export function formatSolutionWithEvent(
9595
combinedText += solutionText;
9696

9797
if (event) {
98-
const eventText = '\n# Raw Event Data\n' + formatEventToMarkdown(event);
98+
const eventText = '\n# Raw Event Data\n' + formatEventToMarkdown(event, undefined);
9999
combinedText += eventText;
100100
}
101101

static/app/components/events/interfaces/threads.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import type {PlatformKey, Project} from 'sentry/types/project';
3939
import {StackType, StackView} from 'sentry/types/stacktrace';
4040
import {defined} from 'sentry/utils';
4141
import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
42+
import {setActiveThreadId} from 'sentry/views/issueDetails/streamline/hooks/useCopyIssueDetails';
4243
import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection';
4344
import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils';
4445

@@ -178,6 +179,11 @@ export function Threads({data, event, projectSlug, groupingCurrentLevel, group}:
178179
const hasStreamlinedUI = useHasStreamlinedUI();
179180
const [activeThread, setActiveThread] = useActiveThreadState(event, threads);
180181

182+
// Sync active thread to module store for copy functionality
183+
useEffect(() => {
184+
setActiveThreadId(activeThread?.id);
185+
}, [activeThread?.id]);
186+
181187
const stackTraceNotFound = !threads.length;
182188

183189
const hasMoreThanOneThread = threads.length > 1;

static/app/utils/analytics/preprodBuildAnalyticsEvents.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type {PreprodBuildsDisplay} from 'sentry/components/preprod/preprodBuildsDisplay';
12
import type {Organization} from 'sentry/types/organization';
23

34
type BasePreprodBuildEvent = {
@@ -8,6 +9,11 @@ type BasePreprodBuildEvent = {
89
project_type?: string | null;
910
};
1011

12+
export type BuildListPageSource =
13+
| 'preprod_builds_list'
14+
| 'releases_mobile_builds_tab'
15+
| 'releases_details_preprod_builds';
16+
1117
export type PreprodBuildEventParameters = {
1218
'preprod.builds.compare.go_to_build_details': BasePreprodBuildEvent & {
1319
slot?: 'head' | 'base';
@@ -28,6 +34,18 @@ export type PreprodBuildEventParameters = {
2834
'preprod.builds.install_modal.opened': BasePreprodBuildEvent & {
2935
source: 'build_details_sidebar' | 'builds_table';
3036
};
37+
'preprod.builds.list.metadata': {
38+
build_count_on_page: number;
39+
datetime_selection: string;
40+
display: PreprodBuildsDisplay;
41+
has_search_query: boolean;
42+
is_empty: boolean;
43+
organization: Organization;
44+
page_source: BuildListPageSource;
45+
project_count: number;
46+
query_status: 'success' | 'error';
47+
cursor?: string | null;
48+
};
3149
'preprod.builds.release.build_row_clicked': BasePreprodBuildEvent;
3250
'preprod.releases.mobile-builds.tab-clicked': {
3351
organization: Organization;
@@ -37,6 +55,7 @@ export type PreprodBuildEventParameters = {
3755
type PreprodBuildAnalyticsKey = keyof PreprodBuildEventParameters;
3856

3957
export const preprodBuildEventMap: Record<PreprodBuildAnalyticsKey, string | null> = {
58+
'preprod.builds.list.metadata': 'Preprod Builds: List Metadata',
4059
'preprod.builds.release.build_row_clicked': 'Preprod Builds: Release Build Row Clicked',
4160
'preprod.builds.details.open_insights_sidebar':
4261
'Preprod Build Details: Insights Sidebar Opened',

static/app/views/issueDetails/streamline/eventTitle.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ import {Divider} from 'sentry/views/issueDetails/divider';
2626
import EventCreatedTooltip from 'sentry/views/issueDetails/eventCreatedTooltip';
2727
import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
2828
import {getFoldSectionKey} from 'sentry/views/issueDetails/streamline/foldSection';
29-
import {issueAndEventToMarkdown} from 'sentry/views/issueDetails/streamline/hooks/useCopyIssueDetails';
29+
import {
30+
issueAndEventToMarkdown,
31+
useActiveThreadId,
32+
} from 'sentry/views/issueDetails/streamline/hooks/useCopyIssueDetails';
3033
import {IssueDetailsJumpTo} from 'sentry/views/issueDetails/streamline/issueDetailsJumpTo';
3134

3235
type EventNavigationProps = {
@@ -45,14 +48,21 @@ export const MIN_NAV_HEIGHT = 44;
4548

4649
function GroupMarkdownButton({group, event}: {event: Event; group: Group}) {
4750
const organization = useOrganization();
51+
const activeThreadId = useActiveThreadId();
4852

4953
// Get data for markdown copy functionality
5054
const {data: groupSummaryData} = useGroupSummaryData(group);
5155
const {data: autofixData} = useAutofixData({groupId: group.id});
5256

5357
const markdownText = useMemo(() => {
54-
return issueAndEventToMarkdown(group, event, groupSummaryData, autofixData);
55-
}, [group, event, groupSummaryData, autofixData]);
58+
return issueAndEventToMarkdown(
59+
group,
60+
event,
61+
groupSummaryData,
62+
autofixData,
63+
activeThreadId
64+
);
65+
}, [group, event, groupSummaryData, autofixData, activeThreadId]);
5666
const markdownLines = markdownText.trim().split('\n').length.toLocaleString();
5767

5868
const {copy} = useCopyToClipboard();

static/app/views/issueDetails/streamline/hooks/useCopyIssueDetails.spec.tsx

Lines changed: 183 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,21 @@ describe('useCopyIssueDetails', () => {
8989
});
9090

9191
it('formats basic issue information correctly', () => {
92-
const result = issueAndEventToMarkdown(group, event, null, null);
92+
const result = issueAndEventToMarkdown(group, event, null, null, undefined);
9393

9494
expect(result).toContain(`# ${group.title}`);
9595
expect(result).toContain(`**Issue ID:** ${group.id}`);
9696
expect(result).toContain(`**Project:** ${group.project?.slug}`);
9797
});
9898

9999
it('includes group summary data when provided', () => {
100-
const result = issueAndEventToMarkdown(group, event, mockGroupSummaryData, null);
100+
const result = issueAndEventToMarkdown(
101+
group,
102+
event,
103+
mockGroupSummaryData,
104+
null,
105+
undefined
106+
);
101107

102108
expect(result).toContain('## Issue Summary');
103109
expect(result).toContain(mockGroupSummaryData.headline);
@@ -109,7 +115,13 @@ describe('useCopyIssueDetails', () => {
109115
});
110116

111117
it('includes autofix data when provided', () => {
112-
const result = issueAndEventToMarkdown(group, event, null, mockAutofixData);
118+
const result = issueAndEventToMarkdown(
119+
group,
120+
event,
121+
null,
122+
mockAutofixData,
123+
undefined
124+
);
113125

114126
expect(result).toContain('## Root Cause');
115127
expect(result).toContain('## Solution');
@@ -124,7 +136,7 @@ describe('useCopyIssueDetails', () => {
124136
],
125137
};
126138

127-
const result = issueAndEventToMarkdown(group, eventWithTags, null, null);
139+
const result = issueAndEventToMarkdown(group, eventWithTags, null, null, undefined);
128140

129141
expect(result).toContain('## Tags');
130142
expect(result).toContain('**browser:** Chrome');
@@ -162,20 +174,185 @@ describe('useCopyIssueDetails', () => {
162174
],
163175
});
164176

165-
const result = issueAndEventToMarkdown(group, eventWithException, null, null);
177+
const result = issueAndEventToMarkdown(
178+
group,
179+
eventWithException,
180+
null,
181+
null,
182+
undefined
183+
);
166184

167185
expect(result).toContain('## Exception');
168186
expect(result).toContain('**Type:** TypeError');
169187
expect(result).toContain('**Value:** Cannot read property of undefined');
170188
expect(result).toContain('#### Stacktrace');
171189
});
172190

191+
it('includes thread stacktrace when activeThreadId matches', () => {
192+
const eventWithThreads = EventFixture({
193+
...event,
194+
entries: [
195+
{
196+
type: EntryType.THREADS,
197+
data: {
198+
values: [
199+
{
200+
id: 1,
201+
name: 'Main Thread',
202+
crashed: true,
203+
current: true,
204+
stacktrace: {
205+
frames: [
206+
{
207+
function: 'mainFunction',
208+
filename: 'main.py',
209+
lineNo: 10,
210+
inApp: true,
211+
},
212+
],
213+
},
214+
},
215+
{
216+
id: 2,
217+
name: 'Worker Thread',
218+
crashed: false,
219+
current: false,
220+
stacktrace: {
221+
frames: [
222+
{
223+
function: 'workerFunction',
224+
filename: 'worker.py',
225+
lineNo: 25,
226+
inApp: true,
227+
},
228+
],
229+
},
230+
},
231+
],
232+
},
233+
},
234+
],
235+
});
236+
237+
// Pass activeThreadId = 1 to select Main Thread
238+
const result = issueAndEventToMarkdown(group, eventWithThreads, null, null, 1);
239+
240+
expect(result).toContain('## Thread: Main Thread');
241+
expect(result).toContain('(crashed)');
242+
expect(result).toContain('(current)');
243+
expect(result).toContain('mainFunction');
244+
expect(result).toContain('main.py');
245+
expect(result).not.toContain('Worker Thread');
246+
expect(result).not.toContain('workerFunction');
247+
});
248+
249+
it('includes different thread when activeThreadId changes', () => {
250+
const eventWithThreads = EventFixture({
251+
...event,
252+
entries: [
253+
{
254+
type: EntryType.THREADS,
255+
data: {
256+
values: [
257+
{
258+
id: 1,
259+
name: 'Main Thread',
260+
crashed: true,
261+
current: true,
262+
stacktrace: {
263+
frames: [
264+
{
265+
function: 'mainFunction',
266+
filename: 'main.py',
267+
lineNo: 10,
268+
inApp: true,
269+
},
270+
],
271+
},
272+
},
273+
{
274+
id: 2,
275+
name: 'Worker Thread',
276+
crashed: false,
277+
current: false,
278+
stacktrace: {
279+
frames: [
280+
{
281+
function: 'workerFunction',
282+
filename: 'worker.py',
283+
lineNo: 25,
284+
inApp: true,
285+
},
286+
],
287+
},
288+
},
289+
],
290+
},
291+
},
292+
],
293+
});
294+
295+
// Pass activeThreadId = 2 to select Worker Thread
296+
const result = issueAndEventToMarkdown(group, eventWithThreads, null, null, 2);
297+
298+
expect(result).toContain('## Thread: Worker Thread');
299+
expect(result).not.toContain('(crashed)');
300+
expect(result).not.toContain('(current)');
301+
expect(result).toContain('workerFunction');
302+
expect(result).toContain('worker.py');
303+
expect(result).not.toContain('Main Thread');
304+
expect(result).not.toContain('mainFunction');
305+
});
306+
307+
it('does not include thread stacktrace when activeThreadId is undefined', () => {
308+
const eventWithThreads = EventFixture({
309+
...event,
310+
entries: [
311+
{
312+
type: EntryType.THREADS,
313+
data: {
314+
values: [
315+
{
316+
id: 1,
317+
name: 'Main Thread',
318+
crashed: true,
319+
current: true,
320+
stacktrace: {
321+
frames: [
322+
{
323+
function: 'mainFunction',
324+
filename: 'main.py',
325+
lineNo: 10,
326+
inApp: true,
327+
},
328+
],
329+
},
330+
},
331+
],
332+
},
333+
},
334+
],
335+
});
336+
337+
const result = issueAndEventToMarkdown(
338+
group,
339+
eventWithThreads,
340+
null,
341+
null,
342+
undefined
343+
);
344+
345+
expect(result).not.toContain('## Thread');
346+
expect(result).not.toContain('mainFunction');
347+
});
348+
173349
it('prefers autofix rootCause over groupSummary possibleCause', () => {
174350
const result = issueAndEventToMarkdown(
175351
group,
176352
event,
177353
mockGroupSummaryData,
178-
mockAutofixData
354+
mockAutofixData,
355+
undefined
179356
);
180357

181358
expect(result).toContain('## Root Cause');

0 commit comments

Comments
 (0)