|
28 | 28 | )
|
29 | 29 | from sentry.integrations.github.constants import ISSUE_LOCKED_ERROR_MESSAGE, RATE_LIMITED_MESSAGE
|
30 | 30 | from sentry.integrations.github.tasks.link_all_repos import link_all_repos
|
| 31 | +from sentry.integrations.github.tasks.utils import GithubAPIErrorType |
31 | 32 | from sentry.integrations.models.integration import Integration
|
32 | 33 | from sentry.integrations.models.organization_integration import OrganizationIntegration
|
33 | 34 | from sentry.integrations.services.repository import RpcRepository, repository_service
|
34 | 35 | from sentry.integrations.source_code_management.commit_context import (
|
| 36 | + OPEN_PR_MAX_FILES_CHANGED, |
| 37 | + OPEN_PR_MAX_LINES_CHANGED, |
| 38 | + OPEN_PR_METRICS_BASE, |
35 | 39 | CommitContextIntegration,
|
36 | 40 | CommitContextOrganizationOptionKeys,
|
37 | 41 | CommitContextReferrerIds,
|
38 | 42 | CommitContextReferrers,
|
| 43 | + PullRequestFile, |
39 | 44 | PullRequestIssue,
|
40 | 45 | )
|
| 46 | +from sentry.integrations.source_code_management.language_parsers import PATCH_PARSERS |
41 | 47 | from sentry.integrations.source_code_management.repo_trees import RepoTreesIntegration
|
42 | 48 | from sentry.integrations.source_code_management.repository import RepositoryIntegration
|
43 | 49 | from sentry.integrations.tasks.migrate_repo import migrate_repo
|
|
47 | 53 | )
|
48 | 54 | from sentry.models.group import Group
|
49 | 55 | from sentry.models.organization import Organization
|
| 56 | +from sentry.models.pullrequest import PullRequest |
50 | 57 | from sentry.models.repository import Repository
|
51 | 58 | from sentry.organizations.absolute_url import generate_organization_url
|
52 | 59 | from sentry.organizations.services.organization.model import RpcOrganization
|
@@ -391,6 +398,82 @@ def on_create_or_update_comment_error(self, api_error: ApiError, metrics_base: s
|
391 | 398 |
|
392 | 399 | return False
|
393 | 400 |
|
| 401 | + def get_pr_files_safe_for_comment( |
| 402 | + self, repo: Repository, pr: PullRequest |
| 403 | + ) -> list[dict[str, str]]: |
| 404 | + client = self.get_client() |
| 405 | + |
| 406 | + logger.info("github.open_pr_comment.check_safe_for_comment") |
| 407 | + try: |
| 408 | + pr_files = client.get_pullrequest_files(repo=repo, pr=pr) |
| 409 | + except ApiError as e: |
| 410 | + logger.info("github.open_pr_comment.api_error") |
| 411 | + if e.json and RATE_LIMITED_MESSAGE in e.json.get("message", ""): |
| 412 | + metrics.incr( |
| 413 | + OPEN_PR_METRICS_BASE.format(integration="github", key="api_error"), |
| 414 | + tags={"type": GithubAPIErrorType.RATE_LIMITED.value, "code": e.code}, |
| 415 | + ) |
| 416 | + elif e.code == 404: |
| 417 | + metrics.incr( |
| 418 | + OPEN_PR_METRICS_BASE.format(integration="github", key="api_error"), |
| 419 | + tags={"type": GithubAPIErrorType.MISSING_PULL_REQUEST.value, "code": e.code}, |
| 420 | + ) |
| 421 | + else: |
| 422 | + metrics.incr( |
| 423 | + OPEN_PR_METRICS_BASE.format(integration="github", key="api_error"), |
| 424 | + tags={"type": GithubAPIErrorType.UNKNOWN.value, "code": e.code}, |
| 425 | + ) |
| 426 | + logger.exception( |
| 427 | + "github.open_pr_comment.unknown_api_error", extra={"error": str(e)} |
| 428 | + ) |
| 429 | + return [] |
| 430 | + |
| 431 | + changed_file_count = 0 |
| 432 | + changed_lines_count = 0 |
| 433 | + filtered_pr_files = [] |
| 434 | + |
| 435 | + patch_parsers = PATCH_PARSERS |
| 436 | + # NOTE: if we are testing beta patch parsers, add check here |
| 437 | + |
| 438 | + for file in pr_files: |
| 439 | + filename = file["filename"] |
| 440 | + # we only count the file if it's modified and if the file extension is in the list of supported file extensions |
| 441 | + # we cannot look at deleted or newly added files because we cannot extract functions from the diffs |
| 442 | + if file["status"] != "modified" or filename.split(".")[-1] not in patch_parsers: |
| 443 | + continue |
| 444 | + |
| 445 | + changed_file_count += 1 |
| 446 | + changed_lines_count += file["changes"] |
| 447 | + filtered_pr_files.append(file) |
| 448 | + |
| 449 | + if changed_file_count > OPEN_PR_MAX_FILES_CHANGED: |
| 450 | + metrics.incr( |
| 451 | + OPEN_PR_METRICS_BASE.format(integration="github", key="rejected_comment"), |
| 452 | + tags={"reason": "too_many_files"}, |
| 453 | + ) |
| 454 | + return [] |
| 455 | + if changed_lines_count > OPEN_PR_MAX_LINES_CHANGED: |
| 456 | + metrics.incr( |
| 457 | + OPEN_PR_METRICS_BASE.format(integration="github", key="rejected_comment"), |
| 458 | + tags={"reason": "too_many_lines"}, |
| 459 | + ) |
| 460 | + return [] |
| 461 | + |
| 462 | + return filtered_pr_files |
| 463 | + |
| 464 | + def get_pr_files(self, pr_files: list[dict[str, str]]) -> list[PullRequestFile]: |
| 465 | + # new files will not have sentry issues associated with them |
| 466 | + # only fetch Python files |
| 467 | + pullrequest_files = [ |
| 468 | + PullRequestFile(filename=file["filename"], patch=file["patch"]) |
| 469 | + for file in pr_files |
| 470 | + if "patch" in file |
| 471 | + ] |
| 472 | + |
| 473 | + logger.info("github.open_pr_comment.pr_filenames", extra={"count": len(pullrequest_files)}) |
| 474 | + |
| 475 | + return pullrequest_files |
| 476 | + |
394 | 477 | def format_open_pr_comment(self, issue_tables: list[str]) -> str:
|
395 | 478 | comment_body_template = """\
|
396 | 479 | ## 🔍 Existing Issues For Review
|
|
0 commit comments