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
166 changes: 166 additions & 0 deletions static/app/components/events/autofix/v3/autofixEvidence.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,172 @@ describe('AutofixEvidence', () => {
).toBeNull();
});
});

describe('EvidenceGitSearch', () => {
const FULL_SHA = 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2';
const COMMIT_URL = 'https://github.com/org/repo/commit/a1b2c3d';
const COMMITS_URL =
'https://github.com/org/repo/commits?since=2024-01-01&until=2024-01-31';
const REPO_NAME = 'org/repo';
const START_DATE = '2024-01-01';
const END_DATE = '2024-01-31';

it('renders commit label with short hash for single commit', () => {
const props = resolveProps(
makeToolCall('git_search'),
makeToolLink('git_search', {
commit_url: COMMIT_URL,
sha: FULL_SHA,
})
);
render(<AutofixEvidence evidenceButtonProps={props!} />, {organization});
expect(screen.getByText('Commit: a1b2c3d')).toBeInTheDocument();
expect(screen.getByText('Commit: a1b2c3d').closest('a')).toHaveAttribute(
'href',
COMMIT_URL
);
});

it('renders full sha when sha is not a 40-char hex hash', () => {
const shortSha = 'abc1234';
const props = resolveProps(
makeToolCall('git_search'),
makeToolLink('git_search', {
commit_url: COMMIT_URL,
sha: shortSha,
})
);
render(<AutofixEvidence evidenceButtonProps={props!} />, {organization});
expect(screen.getByText('Commit: abc1234')).toBeInTheDocument();
});

it('renders commits label with repo name when file_path is absent', () => {
const props = resolveProps(
makeToolCall('git_search'),
makeToolLink('git_search', {
commits_url: COMMITS_URL,
repo_name: REPO_NAME,
start_date: START_DATE,
end_date: END_DATE,
})
);
render(<AutofixEvidence evidenceButtonProps={props!} />, {organization});
expect(screen.getByText(`Commits: ${REPO_NAME}`)).toBeInTheDocument();
expect(screen.getByText(`Commits: ${REPO_NAME}`).closest('a')).toHaveAttribute(
'href',
COMMITS_URL
);
});

it('renders commits label with short file_path without truncation', () => {
const props = resolveProps(
makeToolCall('git_search'),
makeToolLink('git_search', {
commits_url: COMMITS_URL,
repo_name: REPO_NAME,
start_date: START_DATE,
end_date: END_DATE,
file_path: 'src/foo/bar.py',
})
);
render(<AutofixEvidence evidenceButtonProps={props!} />, {organization});
expect(screen.getByText('Commits: bar.py')).toBeInTheDocument();
});

it('renders commits label with truncated file_path when file_path is long', () => {
const props = resolveProps(
makeToolCall('git_search'),
makeToolLink('git_search', {
commits_url: COMMITS_URL,
repo_name: REPO_NAME,
start_date: START_DATE,
end_date: END_DATE,
file_path: 'src/components/thisisalongfilename.tsx',
})
);
render(<AutofixEvidence evidenceButtonProps={props!} />, {organization});
expect(screen.getByText('Commits: thisisal\u2026name.tsx')).toBeInTheDocument();
});

it('prefers single commit path when both param sets are present', () => {
const props = resolveProps(
makeToolCall('git_search'),
makeToolLink('git_search', {
commit_url: COMMIT_URL,
sha: FULL_SHA,
commits_url: COMMITS_URL,
repo_name: REPO_NAME,
start_date: START_DATE,
end_date: END_DATE,
})
);
render(<AutofixEvidence evidenceButtonProps={props!} />, {organization});
expect(screen.getByText('Commit: a1b2c3d')).toBeInTheDocument();
});

it('returns null when toolLink is missing', () => {
expect(resolveProps(makeToolCall('git_search'))).toBeNull();
});

it('returns null when toolLink params are empty', () => {
expect(
resolveProps(makeToolCall('git_search'), makeToolLink('git_search', {}))
).toBeNull();
});

it('returns null when commit_url is present but sha is missing', () => {
expect(
resolveProps(
makeToolCall('git_search'),
makeToolLink('git_search', {commit_url: COMMIT_URL})
)
).toBeNull();
});

it('returns null when sha is present but commit_url is missing', () => {
expect(
resolveProps(
makeToolCall('git_search'),
makeToolLink('git_search', {sha: FULL_SHA})
)
).toBeNull();
});

it('returns null when commits_url path is missing end_date', () => {
expect(
resolveProps(
makeToolCall('git_search'),
makeToolLink('git_search', {
commits_url: COMMITS_URL,
repo_name: REPO_NAME,
start_date: START_DATE,
})
)
).toBeNull();
});

it('returns null when commits_url path is missing repo_name', () => {
expect(
resolveProps(
makeToolCall('git_search'),
makeToolLink('git_search', {
commits_url: COMMITS_URL,
start_date: START_DATE,
end_date: END_DATE,
})
)
).toBeNull();
});

it('returns null when commit_url is not a string', () => {
expect(
resolveProps(
makeToolCall('git_search'),
makeToolLink('git_search', {commit_url: 123, sha: FULL_SHA})
)
).toBeNull();
});
});
});

describe('useAutofixSectionEvidence', () => {
Expand Down
53 changes: 51 additions & 2 deletions static/app/components/events/autofix/v3/autofixEvidence.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {type ReactNode} from 'react';
import {Fragment, type ReactNode} from 'react';
import type {LocationDescriptor} from 'history';

import {LinkButton} from '@sentry/scraps/button';

import {IconCompass} from 'sentry/icons/iconCompass';
import {IconFile} from 'sentry/icons/iconFile';
import {IconGithub} from 'sentry/icons/iconGithub';
import {IconIssues} from 'sentry/icons/iconIssues';
import {IconPlay} from 'sentry/icons/iconPlay';
import {IconProfiling} from 'sentry/icons/iconProfiling';
Expand All @@ -14,6 +15,7 @@ import type {Organization} from 'sentry/types/organization';
import type {Project} from 'sentry/types/project';
import {defined} from 'sentry/utils';
import {getShortEventId} from 'sentry/utils/events';
import {getShortCommitHash} from 'sentry/utils/git/getShortCommitHash';
import type {ToolCall, ToolLink} from 'sentry/views/seerExplorer/types';
import {buildToolLinkUrl} from 'sentry/views/seerExplorer/utils';

Expand Down Expand Up @@ -252,7 +254,7 @@ function getCodeSearchEvidenceProps({
if (typeof path !== 'string') {
return null;
}
const filename = path.split('/').pop();
const filename = extractFileName(path);
const {code_url} = toolLink?.params ?? {};

if (!defined(filename) || !defined(code_url)) {
Expand All @@ -270,6 +272,48 @@ function getCodeSearchEvidenceProps({
return null;
}

function getGitSearchEvidenceProps({
toolLink,
}: GetEvidencePropsPayload): EvidenceButtonProps | null {
const {repo_name, commit_url, sha, commits_url, start_date, end_date, file_path} =
toolLink?.params ?? {};

if (typeof commit_url === 'string' && typeof sha === 'string') {
return {
href: commit_url,
icon: <IconGithub />, // TODO: support other SCMs
label: t('Commit: %s', truncateText(getShortCommitHash(sha))),
tooltip: sha,
};
}

if (
typeof commits_url === 'string' &&
typeof repo_name === 'string' &&
typeof start_date === 'string' &&
typeof end_date === 'string'
) {
const fileName =
typeof file_path === 'string' ? extractFileName(file_path) : undefined;
return {
href: commits_url,
icon: <IconGithub />, // TODO: support other SCMs
label: t('Commits: %s', fileName ? truncateText(fileName) : repo_name),
tooltip: (
<Fragment>
{typeof file_path === 'string' ? file_path : repo_name}
<br />
{start_date}
{'\u2014'}
{end_date}
</Fragment>
),
};
}

return null;
}

export const AUTOFIX_EVIDENCE_PROPS_RESOLVER: Record<
string,
(payload: GetEvidencePropsPayload) => EvidenceButtonProps | null
Expand All @@ -281,6 +325,7 @@ export const AUTOFIX_EVIDENCE_PROPS_RESOLVER: Record<
get_replay_details: getReplayDetailsEvidenceProps,
get_profile_flamegraph: getProfileFlamegraphEvidenceProps,
code_search: getCodeSearchEvidenceProps,
git_search: getGitSearchEvidenceProps,
};

function parseArgs(toolCall: ToolCall): any {
Expand All @@ -292,6 +337,10 @@ function parseArgs(toolCall: ToolCall): any {
}
}

function extractFileName(filePath: string): string | undefined {
return filePath.split('/').pop();
}

function truncateText(text: string, maxLength = 16): string {
const length = text.length;
if (length <= maxLength) {
Expand Down
Loading