Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions static/app/components/events/autofix/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export function formatRootCauseWithEvent(
return rootCauseText;
}

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

Expand All @@ -95,7 +95,7 @@ export function formatSolutionWithEvent(
combinedText += solutionText;

if (event) {
const eventText = '\n# Raw Event Data\n' + formatEventToMarkdown(event);
const eventText = '\n# Raw Event Data\n' + formatEventToMarkdown(event, undefined);
combinedText += eventText;
}

Expand Down
6 changes: 6 additions & 0 deletions static/app/components/events/interfaces/threads.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import type {PlatformKey, Project} from 'sentry/types/project';
import {StackType, StackView} from 'sentry/types/stacktrace';
import {defined} from 'sentry/utils';
import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
import {setActiveThreadId} from 'sentry/views/issueDetails/streamline/hooks/useCopyIssueDetails';
import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection';
import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils';

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

// Sync active thread to module store for copy functionality
useEffect(() => {
setActiveThreadId(activeThread?.id);
}, [activeThread?.id]);

const stackTraceNotFound = !threads.length;

const hasMoreThanOneThread = threads.length > 1;
Expand Down
16 changes: 13 additions & 3 deletions static/app/views/issueDetails/streamline/eventTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ import {Divider} from 'sentry/views/issueDetails/divider';
import EventCreatedTooltip from 'sentry/views/issueDetails/eventCreatedTooltip';
import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
import {getFoldSectionKey} from 'sentry/views/issueDetails/streamline/foldSection';
import {issueAndEventToMarkdown} from 'sentry/views/issueDetails/streamline/hooks/useCopyIssueDetails';
import {
issueAndEventToMarkdown,
useActiveThreadId,
} from 'sentry/views/issueDetails/streamline/hooks/useCopyIssueDetails';
import {IssueDetailsJumpTo} from 'sentry/views/issueDetails/streamline/issueDetailsJumpTo';

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

function GroupMarkdownButton({group, event}: {event: Event; group: Group}) {
const organization = useOrganization();
const activeThreadId = useActiveThreadId();

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

const markdownText = useMemo(() => {
return issueAndEventToMarkdown(group, event, groupSummaryData, autofixData);
}, [group, event, groupSummaryData, autofixData]);
return issueAndEventToMarkdown(
group,
event,
groupSummaryData,
autofixData,
activeThreadId
);
}, [group, event, groupSummaryData, autofixData, activeThreadId]);
const markdownLines = markdownText.trim().split('\n').length.toLocaleString();

const {copy} = useCopyToClipboard();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,21 @@ describe('useCopyIssueDetails', () => {
});

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

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

it('includes group summary data when provided', () => {
const result = issueAndEventToMarkdown(group, event, mockGroupSummaryData, null);
const result = issueAndEventToMarkdown(
group,
event,
mockGroupSummaryData,
null,
undefined
);

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

it('includes autofix data when provided', () => {
const result = issueAndEventToMarkdown(group, event, null, mockAutofixData);
const result = issueAndEventToMarkdown(
group,
event,
null,
mockAutofixData,
undefined
);

expect(result).toContain('## Root Cause');
expect(result).toContain('## Solution');
Expand All @@ -124,7 +136,7 @@ describe('useCopyIssueDetails', () => {
],
};

const result = issueAndEventToMarkdown(group, eventWithTags, null, null);
const result = issueAndEventToMarkdown(group, eventWithTags, null, null, undefined);

expect(result).toContain('## Tags');
expect(result).toContain('**browser:** Chrome');
Expand Down Expand Up @@ -162,20 +174,185 @@ describe('useCopyIssueDetails', () => {
],
});

const result = issueAndEventToMarkdown(group, eventWithException, null, null);
const result = issueAndEventToMarkdown(
group,
eventWithException,
null,
null,
undefined
);

expect(result).toContain('## Exception');
expect(result).toContain('**Type:** TypeError');
expect(result).toContain('**Value:** Cannot read property of undefined');
expect(result).toContain('#### Stacktrace');
});

it('includes thread stacktrace when activeThreadId matches', () => {
const eventWithThreads = EventFixture({
...event,
entries: [
{
type: EntryType.THREADS,
data: {
values: [
{
id: 1,
name: 'Main Thread',
crashed: true,
current: true,
stacktrace: {
frames: [
{
function: 'mainFunction',
filename: 'main.py',
lineNo: 10,
inApp: true,
},
],
},
},
{
id: 2,
name: 'Worker Thread',
crashed: false,
current: false,
stacktrace: {
frames: [
{
function: 'workerFunction',
filename: 'worker.py',
lineNo: 25,
inApp: true,
},
],
},
},
],
},
},
],
});

// Pass activeThreadId = 1 to select Main Thread
const result = issueAndEventToMarkdown(group, eventWithThreads, null, null, 1);

expect(result).toContain('## Thread: Main Thread');
expect(result).toContain('(crashed)');
expect(result).toContain('(current)');
expect(result).toContain('mainFunction');
expect(result).toContain('main.py');
expect(result).not.toContain('Worker Thread');
expect(result).not.toContain('workerFunction');
});

it('includes different thread when activeThreadId changes', () => {
const eventWithThreads = EventFixture({
...event,
entries: [
{
type: EntryType.THREADS,
data: {
values: [
{
id: 1,
name: 'Main Thread',
crashed: true,
current: true,
stacktrace: {
frames: [
{
function: 'mainFunction',
filename: 'main.py',
lineNo: 10,
inApp: true,
},
],
},
},
{
id: 2,
name: 'Worker Thread',
crashed: false,
current: false,
stacktrace: {
frames: [
{
function: 'workerFunction',
filename: 'worker.py',
lineNo: 25,
inApp: true,
},
],
},
},
],
},
},
],
});

// Pass activeThreadId = 2 to select Worker Thread
const result = issueAndEventToMarkdown(group, eventWithThreads, null, null, 2);

expect(result).toContain('## Thread: Worker Thread');
expect(result).not.toContain('(crashed)');
expect(result).not.toContain('(current)');
expect(result).toContain('workerFunction');
expect(result).toContain('worker.py');
expect(result).not.toContain('Main Thread');
expect(result).not.toContain('mainFunction');
});

it('does not include thread stacktrace when activeThreadId is undefined', () => {
const eventWithThreads = EventFixture({
...event,
entries: [
{
type: EntryType.THREADS,
data: {
values: [
{
id: 1,
name: 'Main Thread',
crashed: true,
current: true,
stacktrace: {
frames: [
{
function: 'mainFunction',
filename: 'main.py',
lineNo: 10,
inApp: true,
},
],
},
},
],
},
},
],
});

const result = issueAndEventToMarkdown(
group,
eventWithThreads,
null,
null,
undefined
);

expect(result).not.toContain('## Thread');
expect(result).not.toContain('mainFunction');
});

it('prefers autofix rootCause over groupSummary possibleCause', () => {
const result = issueAndEventToMarkdown(
group,
event,
mockGroupSummaryData,
mockAutofixData
mockAutofixData,
undefined
);

expect(result).toContain('## Root Cause');
Expand Down
Loading
Loading