Skip to content

Commit 19431d0

Browse files
committed
ref: make pr_comment_workflow task generic
1 parent 08c1d6d commit 19431d0

File tree

5 files changed

+184
-150
lines changed

5 files changed

+184
-150
lines changed

src/sentry/conf/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
from sentry.conf.types.service_options import ServiceOptions
3232
from sentry.conf.types.taskworker import ScheduleConfigMap
3333
from sentry.conf.types.uptime import UptimeRegionConfig
34-
from sentry.utils import json # NOQA (used in getsentry config)
3534
from sentry.utils.celery import make_split_task_queues
3635

3736

@@ -770,6 +769,7 @@ def SOCIAL_AUTH_DEFAULT_USERNAME() -> str:
770769
"sentry.hybridcloud.tasks.backfill_outboxes",
771770
"sentry.hybridcloud.tasks.deliver_from_outbox",
772771
"sentry.incidents.tasks",
772+
"sentry.integrations.source_code_management.tasks",
773773
"sentry.integrations.github.tasks",
774774
"sentry.integrations.github.tasks.pr_comment",
775775
"sentry.integrations.jira.tasks",

src/sentry/integrations/github/integration.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -336,11 +336,6 @@ class GitHubPRCommentWorkflow(PRCommentWorkflow):
336336
referrer = Referrer.GITHUB_PR_COMMENT_BOT
337337
referrer_id = GITHUB_PR_BOT_REFERRER
338338

339-
def queue_task(self, pr: PullRequest, project_id: int) -> None:
340-
from sentry.integrations.github.tasks.pr_comment import github_comment_workflow
341-
342-
github_comment_workflow.delay(pullrequest_id=pr.id, project_id=project_id)
343-
344339
@staticmethod
345340
def format_comment_subtitle(subtitle: str) -> str:
346341
return subtitle[:47] + "..." if len(subtitle) > 50 else subtitle

src/sentry/integrations/github/tasks/pr_comment.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from sentry.constants import ObjectStatus
99
from sentry.integrations.github.constants import RATE_LIMITED_MESSAGE
1010
from sentry.integrations.services.integration import integration_service
11-
from sentry.integrations.source_code_management.commit_context import run_pr_comment_workflow
11+
from sentry.integrations.source_code_management.tasks import pr_comment_workflow
1212
from sentry.models.pullrequest import PullRequestComment
1313
from sentry.models.repository import Repository
1414
from sentry.shared_integrations.exceptions import ApiError
@@ -30,7 +30,9 @@
3030
),
3131
)
3232
def github_comment_workflow(pullrequest_id: int, project_id: int):
33-
run_pr_comment_workflow(integration_name="github", pr_id=pullrequest_id, project_id=project_id)
33+
# TODO(jianyuan): Using `sentry.integrations.source_code_management.tasks.pr_comment_workflow` now.
34+
# Keep this task temporarily to avoid breaking changes.
35+
pr_comment_workflow(pr_id=pullrequest_id, project_id=project_id)
3436

3537

3638
@instrumented_task(

src/sentry/integrations/source_code_management/commit_context.py

Lines changed: 3 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@
1515

1616
from sentry import analytics
1717
from sentry.auth.exceptions import IdentityNotValid
18-
from sentry.constants import ObjectStatus
1918
from sentry.integrations.gitlab.constants import GITLAB_CLOUD_BASE_URL
2019
from sentry.integrations.models.repository_project_path_config import RepositoryProjectPathConfig
21-
from sentry.integrations.services.integration import integration_service
2220
from sentry.integrations.source_code_management.metrics import (
2321
CommitContextHaltReason,
2422
CommitContextIntegrationInteractionEvent,
@@ -443,9 +441,10 @@ def referrer(self) -> Referrer:
443441
def referrer_id(self) -> str:
444442
raise NotImplementedError
445443

446-
@abstractmethod
447444
def queue_task(self, pr: PullRequest, project_id: int) -> None:
448-
raise NotImplementedError
445+
from sentry.integrations.source_code_management.tasks import pr_comment_workflow
446+
447+
pr_comment_workflow.delay(pr_id=pr.id, project_id=project_id)
449448

450449
@abstractmethod
451450
def get_comment_body(self, issue_ids: list[int]) -> str:
@@ -505,141 +504,3 @@ def get_top_5_issues_by_count(
505504
),
506505
)
507506
return raw_snql_query(request, referrer=self.referrer.value)["data"]
508-
509-
510-
def run_pr_comment_workflow(integration_name: str, pr_id: int, project_id: int) -> None:
511-
cache_key = _debounce_pr_comment_cache_key(pullrequest_id=pr_id)
512-
513-
try:
514-
pr = PullRequest.objects.get(id=pr_id)
515-
assert isinstance(pr, PullRequest)
516-
except PullRequest.DoesNotExist:
517-
cache.delete(cache_key)
518-
logger.info(_pr_comment_log(integration_name=integration_name, suffix="pr_missing"))
519-
return
520-
521-
try:
522-
organization = Organization.objects.get_from_cache(id=pr.organization_id)
523-
assert isinstance(organization, Organization)
524-
except Organization.DoesNotExist:
525-
cache.delete(cache_key)
526-
logger.info(_pr_comment_log(integration_name=integration_name, suffix="org_missing"))
527-
metrics.incr(
528-
MERGED_PR_METRICS_BASE.format(integration=integration_name, key="error"),
529-
tags={"type": "missing_org"},
530-
)
531-
return
532-
533-
try:
534-
repo = Repository.objects.get(id=pr.repository_id)
535-
assert isinstance(repo, Repository)
536-
except Repository.DoesNotExist:
537-
cache.delete(cache_key)
538-
logger.info(
539-
_pr_comment_log(integration_name=integration_name, suffix="repo_missing"),
540-
extra={"organization_id": organization.id},
541-
)
542-
metrics.incr(
543-
MERGED_PR_METRICS_BASE.format(integration=integration_name, key="error"),
544-
tags={"type": "missing_repo"},
545-
)
546-
return
547-
548-
integration = integration_service.get_integration(
549-
integration_id=repo.integration_id, status=ObjectStatus.ACTIVE
550-
)
551-
if not integration:
552-
cache.delete(cache_key)
553-
logger.info(
554-
_pr_comment_log(integration_name=integration_name, suffix="integration_missing"),
555-
extra={"organization_id": organization.id},
556-
)
557-
metrics.incr(
558-
MERGED_PR_METRICS_BASE.format(integration=integration_name, key="error"),
559-
tags={"type": "missing_integration"},
560-
)
561-
return
562-
563-
installation = integration.get_installation(organization_id=organization.id)
564-
assert isinstance(installation, CommitContextIntegration)
565-
566-
pr_comment_workflow = installation.get_pr_comment_workflow()
567-
568-
# cap to 1000 issues in which the merge commit is the suspect commit
569-
issue_ids = pr_comment_workflow.get_issue_ids_from_pr(pr=pr, limit=MAX_SUSPECT_COMMITS)
570-
571-
if not OrganizationOption.objects.get_value(
572-
organization=organization,
573-
key=pr_comment_workflow.organization_option_key,
574-
default=True,
575-
):
576-
logger.info(
577-
_pr_comment_log(integration_name=integration_name, suffix="option_missing"),
578-
extra={"organization_id": organization.id},
579-
)
580-
return
581-
582-
try:
583-
project = Project.objects.get_from_cache(id=project_id)
584-
except Project.DoesNotExist:
585-
cache.delete(cache_key)
586-
logger.info(
587-
_pr_comment_log(integration_name=integration_name, suffix="project_missing"),
588-
extra={"organization_id": organization.id},
589-
)
590-
metrics.incr(
591-
MERGED_PR_METRICS_BASE.format(integration=integration_name, key="error"),
592-
tags={"type": "missing_project"},
593-
)
594-
return
595-
596-
top_5_issues = pr_comment_workflow.get_top_5_issues_by_count(
597-
issue_ids=issue_ids, project=project
598-
)
599-
if not top_5_issues:
600-
logger.info(
601-
_pr_comment_log(integration_name=integration_name, suffix="no_issues"),
602-
extra={"organization_id": organization.id, "pr_id": pr.id},
603-
)
604-
cache.delete(cache_key)
605-
return
606-
607-
top_5_issue_ids = [issue["group_id"] for issue in top_5_issues]
608-
609-
comment_body = pr_comment_workflow.get_comment_body(issue_ids=top_5_issue_ids)
610-
logger.info(
611-
_pr_comment_log(integration_name=integration_name, suffix="comment_body"),
612-
extra={"body": comment_body},
613-
)
614-
615-
top_24_issue_ids = issue_ids[:24] # 24 is the P99 for issues-per-PR
616-
617-
comment_data = pr_comment_workflow.get_comment_data(
618-
organization=organization,
619-
repo=repo,
620-
pr=pr,
621-
comment_body=comment_body,
622-
issue_ids=top_24_issue_ids,
623-
)
624-
625-
try:
626-
installation.create_or_update_comment(
627-
repo=repo,
628-
pr=pr,
629-
comment_data=comment_data,
630-
issue_list=top_24_issue_ids,
631-
metrics_base=MERGED_PR_METRICS_BASE,
632-
)
633-
except ApiError as e:
634-
cache.delete(cache_key)
635-
636-
if installation.on_create_or_update_comment_error(
637-
api_error=e, metrics_base=MERGED_PR_METRICS_BASE
638-
):
639-
return
640-
641-
metrics.incr(
642-
MERGED_PR_METRICS_BASE.format(integration=integration_name, key="error"),
643-
tags={"type": "api_error"},
644-
)
645-
raise
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import logging
2+
3+
from sentry.constants import ObjectStatus
4+
from sentry.integrations.services.integration import integration_service
5+
from sentry.integrations.source_code_management.commit_context import (
6+
MAX_SUSPECT_COMMITS,
7+
MERGED_PR_METRICS_BASE,
8+
CommitContextIntegration,
9+
_debounce_pr_comment_cache_key,
10+
_pr_comment_log,
11+
)
12+
from sentry.models.options.organization_option import OrganizationOption
13+
from sentry.models.organization import Organization
14+
from sentry.models.project import Project
15+
from sentry.models.pullrequest import PullRequest
16+
from sentry.models.repository import Repository
17+
from sentry.shared_integrations.exceptions import ApiError
18+
from sentry.silo.base import SiloMode
19+
from sentry.tasks.base import instrumented_task
20+
from sentry.taskworker.config import TaskworkerConfig
21+
from sentry.taskworker.namespaces import integrations_tasks
22+
from sentry.utils import metrics
23+
from sentry.utils.cache import cache
24+
25+
logger = logging.getLogger(__name__)
26+
27+
28+
@instrumented_task(
29+
name="sentry.integrations.source_code_management.tasks.pr_comment_workflow",
30+
silo_mode=SiloMode.REGION,
31+
taskworker_config=TaskworkerConfig(
32+
namespace=integrations_tasks,
33+
),
34+
)
35+
def pr_comment_workflow(pr_id: int, project_id: int):
36+
cache_key = _debounce_pr_comment_cache_key(pullrequest_id=pr_id)
37+
38+
try:
39+
pr = PullRequest.objects.get(id=pr_id)
40+
assert isinstance(pr, PullRequest)
41+
except PullRequest.DoesNotExist:
42+
cache.delete(cache_key)
43+
logger.info(_pr_comment_log(integration_name="source_code_management", suffix="pr_missing"))
44+
return
45+
46+
try:
47+
organization = Organization.objects.get_from_cache(id=pr.organization_id)
48+
assert isinstance(organization, Organization)
49+
except Organization.DoesNotExist:
50+
cache.delete(cache_key)
51+
logger.info(
52+
_pr_comment_log(integration_name="source_code_management", suffix="org_missing")
53+
)
54+
metrics.incr(
55+
MERGED_PR_METRICS_BASE.format(integration="source_code_management", key="error"),
56+
tags={"type": "missing_org"},
57+
)
58+
return
59+
60+
try:
61+
repo = Repository.objects.get(id=pr.repository_id)
62+
assert isinstance(repo, Repository)
63+
except Repository.DoesNotExist:
64+
cache.delete(cache_key)
65+
logger.info(
66+
_pr_comment_log(integration_name="source_code_management", suffix="repo_missing"),
67+
extra={"organization_id": organization.id},
68+
)
69+
metrics.incr(
70+
MERGED_PR_METRICS_BASE.format(integration="source_code_management", key="error"),
71+
tags={"type": "missing_repo"},
72+
)
73+
return
74+
75+
integration = integration_service.get_integration(
76+
integration_id=repo.integration_id, status=ObjectStatus.ACTIVE
77+
)
78+
if not integration:
79+
cache.delete(cache_key)
80+
logger.info(
81+
_pr_comment_log(
82+
integration_name="source_code_management", suffix="integration_missing"
83+
),
84+
extra={"organization_id": organization.id},
85+
)
86+
metrics.incr(
87+
MERGED_PR_METRICS_BASE.format(integration="source_code_management", key="error"),
88+
tags={"type": "missing_integration"},
89+
)
90+
return
91+
92+
installation = integration.get_installation(organization_id=organization.id)
93+
assert isinstance(installation, CommitContextIntegration)
94+
95+
integration_name = installation.integration_name
96+
pr_comment_workflow = installation.get_pr_comment_workflow()
97+
98+
# cap to 1000 issues in which the merge commit is the suspect commit
99+
issue_ids = pr_comment_workflow.get_issue_ids_from_pr(pr=pr, limit=MAX_SUSPECT_COMMITS)
100+
101+
if not OrganizationOption.objects.get_value(
102+
organization=organization,
103+
key=pr_comment_workflow.organization_option_key,
104+
default=True,
105+
):
106+
logger.info(
107+
_pr_comment_log(integration_name=integration_name, suffix="option_missing"),
108+
extra={"organization_id": organization.id},
109+
)
110+
return
111+
112+
try:
113+
project = Project.objects.get_from_cache(id=project_id)
114+
assert isinstance(project, Project)
115+
except Project.DoesNotExist:
116+
cache.delete(cache_key)
117+
logger.info(
118+
_pr_comment_log(integration_name=integration_name, suffix="project_missing"),
119+
extra={"organization_id": organization.id},
120+
)
121+
metrics.incr(
122+
MERGED_PR_METRICS_BASE.format(integration=integration_name, key="error"),
123+
tags={"type": "missing_project"},
124+
)
125+
return
126+
127+
top_5_issues = pr_comment_workflow.get_top_5_issues_by_count(
128+
issue_ids=issue_ids, project=project
129+
)
130+
if not top_5_issues:
131+
logger.info(
132+
_pr_comment_log(integration_name=integration_name, suffix="no_issues"),
133+
extra={"organization_id": organization.id, "pr_id": pr.id},
134+
)
135+
cache.delete(cache_key)
136+
return
137+
138+
top_5_issue_ids = [issue["group_id"] for issue in top_5_issues]
139+
140+
comment_body = pr_comment_workflow.get_comment_body(issue_ids=top_5_issue_ids)
141+
logger.info(
142+
_pr_comment_log(integration_name=integration_name, suffix="comment_body"),
143+
extra={"body": comment_body},
144+
)
145+
146+
top_24_issue_ids = issue_ids[:24] # 24 is the P99 for issues-per-PR
147+
148+
comment_data = pr_comment_workflow.get_comment_data(
149+
organization=organization,
150+
repo=repo,
151+
pr=pr,
152+
comment_body=comment_body,
153+
issue_ids=top_24_issue_ids,
154+
)
155+
156+
try:
157+
installation.create_or_update_comment(
158+
repo=repo,
159+
pr=pr,
160+
comment_data=comment_data,
161+
issue_list=top_24_issue_ids,
162+
metrics_base=MERGED_PR_METRICS_BASE,
163+
)
164+
except ApiError as e:
165+
cache.delete(cache_key)
166+
167+
if installation.on_create_or_update_comment_error(
168+
api_error=e, metrics_base=MERGED_PR_METRICS_BASE
169+
):
170+
return
171+
172+
metrics.incr(
173+
MERGED_PR_METRICS_BASE.format(integration=integration_name, key="error"),
174+
tags={"type": "api_error"},
175+
)
176+
raise

0 commit comments

Comments
 (0)