Skip to content

Commit eab6891

Browse files
jianyuanandrewshie-sentry
authored andcommitted
ref(source_code_management): Make Open PR Comment Workflow generic (#90762)
Make Open PR Comment Workflow generic. Moved github task to `sentry.integrations.source_code_management.tasks.open_pr_comment_workflow` which can be used by other integrations.
1 parent b0c6cd2 commit eab6891

File tree

4 files changed

+284
-271
lines changed

4 files changed

+284
-271
lines changed
Lines changed: 5 additions & 258 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,14 @@
11
from __future__ import annotations
22

3-
import itertools
43
import logging
5-
from typing import Any
64

7-
from sentry.constants import EXTENSION_LANGUAGE_MAP, ObjectStatus
8-
from sentry.integrations.services.integration import integration_service
9-
from sentry.integrations.source_code_management.commit_context import (
10-
OPEN_PR_METRICS_BASE,
11-
CommitContextIntegration,
12-
_open_pr_comment_log,
5+
from sentry.integrations.source_code_management.tasks import (
6+
open_pr_comment_workflow as real_open_pr_comment_workflow,
137
)
14-
from sentry.integrations.source_code_management.language_parsers import PATCH_PARSERS
15-
from sentry.models.organization import Organization
16-
from sentry.models.pullrequest import CommentType, PullRequest
17-
from sentry.models.repository import Repository
18-
from sentry.shared_integrations.exceptions import ApiError
198
from sentry.silo.base import SiloMode
209
from sentry.tasks.base import instrumented_task
2110
from sentry.taskworker.config import TaskworkerConfig
2211
from sentry.taskworker.namespaces import integrations_tasks
23-
from sentry.utils import metrics
2412

2513
logger = logging.getLogger(__name__)
2614

@@ -33,247 +21,6 @@
3321
),
3422
)
3523
def open_pr_comment_workflow(pr_id: int) -> None:
36-
# TODO(jianyuan): Move this to source code management tasks
37-
integration_name = "github"
38-
39-
logger.info(_open_pr_comment_log(integration_name=integration_name, suffix="start_workflow"))
40-
41-
# CHECKS
42-
# check PR exists to get PR key
43-
try:
44-
pull_request = PullRequest.objects.get(id=pr_id)
45-
except PullRequest.DoesNotExist:
46-
logger.info(_open_pr_comment_log(integration_name=integration_name, suffix="pr_missing"))
47-
metrics.incr(
48-
OPEN_PR_METRICS_BASE.format(integration=integration_name, key="error"),
49-
tags={"type": "missing_pr"},
50-
)
51-
return
52-
53-
# check org option
54-
org_id = pull_request.organization_id
55-
try:
56-
organization = Organization.objects.get_from_cache(id=org_id)
57-
assert isinstance(organization, Organization)
58-
except Organization.DoesNotExist:
59-
logger.exception(
60-
_open_pr_comment_log(integration_name=integration_name, suffix="org_missing")
61-
)
62-
metrics.incr(
63-
OPEN_PR_METRICS_BASE.format(integration=integration_name, key="error"),
64-
tags={"type": "missing_org"},
65-
)
66-
return
67-
68-
# check PR repo exists to get repo name
69-
try:
70-
repo = Repository.objects.get(id=pull_request.repository_id)
71-
except Repository.DoesNotExist:
72-
logger.info(
73-
_open_pr_comment_log(integration_name=integration_name, suffix="repo_missing"),
74-
extra={"organization_id": organization.id},
75-
)
76-
metrics.incr(
77-
OPEN_PR_METRICS_BASE.format(integration=integration_name, key="error"),
78-
tags={"type": "missing_repo"},
79-
)
80-
return
81-
82-
# check integration exists to hit Github API with client
83-
integration = integration_service.get_integration(
84-
integration_id=repo.integration_id, status=ObjectStatus.ACTIVE
85-
)
86-
if not integration:
87-
logger.info(
88-
_open_pr_comment_log(integration_name=integration_name, suffix="integration_missing"),
89-
extra={"organization_id": organization.id},
90-
)
91-
metrics.incr(
92-
OPEN_PR_METRICS_BASE.format(integration=integration_name, key="error"),
93-
tags={"type": "missing_integration"},
94-
)
95-
return
96-
97-
installation = integration.get_installation(organization_id=organization.id)
98-
assert isinstance(installation, CommitContextIntegration)
99-
100-
# TODO(jianyuan): Remove this once we have implemented the new open_pr_comment_workflow for GH Enterprise
101-
try:
102-
open_pr_comment_workflow = installation.get_open_pr_comment_workflow()
103-
except NotImplementedError:
104-
logger.info(
105-
_open_pr_comment_log(integration_name=integration_name, suffix="not_implemented")
106-
)
107-
return
108-
109-
# CREATING THE COMMENT
110-
111-
# fetch the files in the PR and determine if it is safe to comment
112-
pullrequest_files = open_pr_comment_workflow.get_pr_files_safe_for_comment(
113-
repo=repo, pr=pull_request
114-
)
115-
116-
issue_table_contents = {}
117-
top_issues_per_file = []
118-
119-
patch_parsers = PATCH_PARSERS
120-
# NOTE: if we are testing beta patch parsers, add check here
121-
122-
file_extensions = set()
123-
# fetch issues related to the files
124-
for file in pullrequest_files:
125-
projects, sentry_filenames = (
126-
open_pr_comment_workflow.get_projects_and_filenames_from_source_file(
127-
organization=organization, repo=repo, pr_filename=file.filename
128-
)
129-
)
130-
if not len(projects) or not len(sentry_filenames):
131-
continue
132-
133-
file_extension = file.filename.split(".")[-1]
134-
logger.info(
135-
_open_pr_comment_log(integration_name=integration_name, suffix="file_extension"),
136-
extra={
137-
"organization_id": org_id,
138-
"repository_id": repo.id,
139-
"extension": file_extension,
140-
},
141-
)
142-
143-
language_parser = patch_parsers.get(file.filename.split(".")[-1], None)
144-
if not language_parser:
145-
logger.info(
146-
_open_pr_comment_log(integration_name=integration_name, suffix="missing_parser"),
147-
extra={"extension": file_extension},
148-
)
149-
metrics.incr(
150-
OPEN_PR_METRICS_BASE.format(integration=integration_name, key="missing_parser"),
151-
tags={"extension": file_extension},
152-
)
153-
continue
154-
155-
function_names = language_parser.extract_functions_from_patch(file.patch)
156-
157-
if file_extension in ["js", "jsx"]:
158-
logger.info(
159-
_open_pr_comment_log(integration_name=integration_name, suffix="javascript"),
160-
extra={
161-
"organization_id": org_id,
162-
"repository_id": repo.id,
163-
"extension": file_extension,
164-
"has_function_names": bool(function_names),
165-
},
166-
)
167-
168-
if file_extension == ["php"]:
169-
logger.info(
170-
_open_pr_comment_log(integration_name=integration_name, suffix="php"),
171-
extra={
172-
"organization_id": org_id,
173-
"repository_id": repo.id,
174-
"extension": file_extension,
175-
"has_function_names": bool(function_names),
176-
},
177-
)
178-
179-
if file_extension == ["rb"]:
180-
logger.info(
181-
_open_pr_comment_log(integration_name=integration_name, suffix="ruby"),
182-
extra={
183-
"organization_id": org_id,
184-
"repository_id": repo.id,
185-
"extension": file_extension,
186-
"has_function_names": bool(function_names),
187-
},
188-
)
189-
190-
if not len(function_names):
191-
continue
192-
193-
top_issues = open_pr_comment_workflow.get_top_5_issues_by_count_for_file(
194-
projects=list(projects),
195-
sentry_filenames=list(sentry_filenames),
196-
function_names=list(function_names),
197-
)
198-
if not len(top_issues):
199-
continue
200-
201-
top_issues_per_file.append(top_issues)
202-
file_extensions.add(file_extension)
203-
204-
issue_table_contents[file.filename] = open_pr_comment_workflow.get_issue_table_contents(
205-
top_issues
206-
)
207-
208-
if not len(issue_table_contents):
209-
logger.info(_open_pr_comment_log(integration_name=integration_name, suffix="no_issues"))
210-
# don't leave a comment if no issues for files in PR
211-
metrics.incr(OPEN_PR_METRICS_BASE.format(integration=integration_name, key="no_issues"))
212-
return
213-
214-
# format issues per file into comment
215-
issue_tables = []
216-
first_table = True
217-
for file in pullrequest_files:
218-
pr_filename = file.filename
219-
issue_table_content = issue_table_contents.get(pr_filename, None)
220-
221-
if issue_table_content is None:
222-
continue
223-
224-
if first_table:
225-
issue_table = open_pr_comment_workflow.format_issue_table(
226-
diff_filename=pr_filename,
227-
issues=issue_table_content,
228-
patch_parsers=patch_parsers,
229-
toggle=False,
230-
)
231-
first_table = False
232-
else:
233-
# toggle all tables but the first one
234-
issue_table = open_pr_comment_workflow.format_issue_table(
235-
diff_filename=pr_filename,
236-
issues=issue_table_content,
237-
patch_parsers=patch_parsers,
238-
toggle=True,
239-
)
240-
241-
issue_tables.append(issue_table)
242-
243-
comment_body = open_pr_comment_workflow.format_open_pr_comment(issue_tables)
244-
245-
# list all issues in the comment
246-
issue_list: list[dict[str, Any]] = list(itertools.chain.from_iterable(top_issues_per_file))
247-
issue_id_list: list[int] = [issue["group_id"] for issue in issue_list]
248-
249-
# pick one language from the list of languages in the PR for analytics
250-
languages = [
251-
EXTENSION_LANGUAGE_MAP[extension]
252-
for extension in file_extensions
253-
if extension in EXTENSION_LANGUAGE_MAP
254-
]
255-
language = languages[0] if len(languages) else "not found"
256-
257-
comment_data = open_pr_comment_workflow.get_comment_data(comment_body=comment_body)
258-
259-
try:
260-
installation.create_or_update_comment(
261-
repo=repo,
262-
pr=pull_request,
263-
comment_data=comment_data,
264-
issue_list=issue_id_list,
265-
comment_type=CommentType.OPEN_PR,
266-
metrics_base=OPEN_PR_METRICS_BASE,
267-
language=language,
268-
)
269-
except ApiError as e:
270-
if installation.on_create_or_update_comment_error(
271-
api_error=e, metrics_base=OPEN_PR_METRICS_BASE
272-
):
273-
return
274-
275-
metrics.incr(
276-
OPEN_PR_METRICS_BASE.format(integration=integration_name, key="error"),
277-
tags={"type": "api_error"},
278-
)
279-
raise
24+
# TODO(jianyuan): Using `sentry.integrations.source_code_management.tasks.open_pr_comment_workflow` now.
25+
# Keep this task temporarily to avoid breaking changes.
26+
real_open_pr_comment_workflow(pr_id)

src/sentry/integrations/github/webhook.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@
2424
from sentry.constants import EXTENSION_LANGUAGE_MAP, ObjectStatus
2525
from sentry.identity.services.identity.service import identity_service
2626
from sentry.integrations.base import IntegrationDomain
27-
from sentry.integrations.github.tasks.open_pr_comment import open_pr_comment_workflow
2827
from sentry.integrations.pipeline import ensure_integration
2928
from sentry.integrations.services.integration.model import RpcIntegration
3029
from sentry.integrations.services.integration.service import integration_service
3130
from sentry.integrations.services.repository.service import repository_service
31+
from sentry.integrations.source_code_management.tasks import open_pr_comment_workflow
3232
from sentry.integrations.source_code_management.webhook import SCMWebhook
3333
from sentry.integrations.utils.metrics import IntegrationWebhookEvent, IntegrationWebhookEventType
3434
from sentry.integrations.utils.scope import clear_tags_and_context

0 commit comments

Comments
 (0)