Skip to content

Commit c9a398a

Browse files
authored
Build overview: don't create comment for projects with empty diffs (#12291)
1 parent 20e6949 commit c9a398a

File tree

6 files changed

+114
-9
lines changed

6 files changed

+114
-9
lines changed

readthedocs/builds/reporting.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1+
from dataclasses import dataclass
2+
13
from django.conf import settings
24
from django.template.loader import render_to_string
35

46
from readthedocs.builds.models import Build
57
from readthedocs.filetreediff import get_diff
8+
from readthedocs.filetreediff.dataclasses import FileTreeDiff
9+
610

11+
@dataclass
12+
class BuildOverview:
13+
content: str
14+
diff: FileTreeDiff
715

8-
def get_build_overview(build: Build) -> str | None:
16+
17+
def get_build_overview(build: Build) -> BuildOverview | None:
918
"""
1019
Generate a build overview for the given build.
1120
@@ -27,7 +36,7 @@ def get_build_overview(build: Build) -> str | None:
2736
if not diff:
2837
return None
2938

30-
return render_to_string(
39+
content = render_to_string(
3140
"core/build-overview.md",
3241
{
3342
"PRODUCTION_DOMAIN": settings.PRODUCTION_DOMAIN,
@@ -39,3 +48,7 @@ def get_build_overview(build: Build) -> str | None:
3948
"diff": diff,
4049
},
4150
)
51+
return BuildOverview(
52+
content=content,
53+
diff=diff,
54+
)

readthedocs/builds/tasks.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,17 @@ def send_build_status(build_pk, commit, status):
431431

432432
@app.task(max_retries=3, default_retry_delay=60, queue="web")
433433
def post_build_overview(build_pk):
434+
"""
435+
Post an overview about the build to the project's Git service.
436+
437+
The overview contains information about the build,
438+
and the list of files that were changed in the build.
439+
440+
If no files changed in the build,
441+
we only update the build overview if there is an existing comment.
442+
443+
Only GitHub is supported at the moment.
444+
"""
434445
build = Build.objects.filter(pk=build_pk).first()
435446
if not build:
436447
return
@@ -455,15 +466,16 @@ def post_build_overview(build_pk):
455466
log.debug("Git service doesn't support creating comments.")
456467
return
457468

458-
comment = get_build_overview(build)
459-
if not comment:
469+
build_overview = get_build_overview(build)
470+
if not build_overview:
460471
log.debug("No build overview available, skipping posting comment.")
461472
return
462473

463474
for service in service_class.for_project(build.project):
464475
service.post_comment(
465476
build=build,
466-
comment=comment,
477+
comment=build_overview.content,
478+
create_new=bool(build_overview.diff.files),
467479
)
468480
log.debug("PR comment posted successfully.")
469481
return

readthedocs/builds/tests/test_tasks.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ def test_post_build_overview(self, get_diff, post_comment):
228228
post_comment.assert_called_once_with(
229229
build=self.current_version_build,
230230
comment=expected_comment,
231+
create_new=True,
231232
)
232233

233234
@mock.patch.object(GitHubAppService, "post_comment")
@@ -280,6 +281,7 @@ def test_post_build_overview_more_than_5_files(self, get_diff, post_comment):
280281
post_comment.assert_called_once_with(
281282
build=self.current_version_build,
282283
comment=expected_comment,
284+
create_new=True,
283285
)
284286

285287
@mock.patch.object(GitHubAppService, "post_comment")
@@ -312,6 +314,7 @@ def test_post_build_overview_no_files_changed(self, get_diff, post_comment):
312314
post_comment.assert_called_once_with(
313315
build=self.current_version_build,
314316
comment=expected_comment,
317+
create_new=False,
315318
)
316319

317320
@mock.patch.object(GitHubAppService, "post_comment")

readthedocs/oauth/services/base.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,12 @@ def get_clone_token(self, project):
111111
"""Get a token used for cloning the repository."""
112112
raise NotImplementedError
113113

114-
def post_comment(self, build, comment: str):
115-
"""Post a comment on the pull request attached to the build."""
114+
def post_comment(self, build, comment: str, create_new: bool = True):
115+
"""
116+
Post a comment on the pull request attached to the build.
117+
118+
:param create_new: Create a new comment if one doesn't exist.
119+
"""
116120
raise NotImplementedError
117121

118122
@classmethod

readthedocs/oauth/services/githubapp.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ def update_webhook(self, project, integration=None) -> bool:
512512
"""When using a GitHub App, we don't need to set up a webhook."""
513513
return True
514514

515-
def post_comment(self, build, comment: str):
515+
def post_comment(self, build, comment: str, create_new: bool = True):
516516
"""
517517
Post a comment on the pull request attached to the build.
518518
@@ -545,5 +545,11 @@ def post_comment(self, build, comment: str):
545545
comment = f"{comment_marker}\n{comment}"
546546
if existing_gh_comment:
547547
existing_gh_comment.edit(body=comment)
548-
else:
548+
elif create_new:
549549
gh_issue.create_comment(body=comment)
550+
else:
551+
log.debug(
552+
"No comment to update, skipping commenting",
553+
project=project.slug,
554+
build=build.pk,
555+
)

readthedocs/rtd_tests/tests/test_oauth.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1143,6 +1143,73 @@ def test_post_comment(self, request):
11431143
"body": f"<!-- readthedocs-{another_project.id} -->\nComment from another project.",
11441144
}
11451145

1146+
@requests_mock.Mocker(kw="request")
1147+
def test_post_comment_update_only(self, request):
1148+
version = get(
1149+
Version,
1150+
verbose_name="1234",
1151+
project=self.project,
1152+
type=EXTERNAL,
1153+
)
1154+
build = get(
1155+
Build,
1156+
project=self.project,
1157+
version=version,
1158+
)
1159+
1160+
request.post(
1161+
f"{self.api_url}/app/installations/1111/access_tokens",
1162+
json=self._get_access_token_json(),
1163+
)
1164+
request.get(
1165+
f"{self.api_url}/repositories/{self.remote_repository.remote_id}/issues/{version.verbose_name}",
1166+
json=self._get_pull_request_json(
1167+
number=int(version.verbose_name),
1168+
repo_full_name=self.remote_repository.full_name,
1169+
),
1170+
)
1171+
request.get(
1172+
f"{self.api_url}/repos/{self.remote_repository.full_name}/issues/{version.verbose_name}/comments",
1173+
json=[],
1174+
)
1175+
request_post_comment = request.post(
1176+
f"{self.api_url}/repos/{self.remote_repository.full_name}/issues/{version.verbose_name}/comments",
1177+
)
1178+
1179+
service = self.installation.service
1180+
1181+
# No comments exist, so it will not create a new one.
1182+
service.post_comment(build, "Comment!", create_new=False)
1183+
assert not request_post_comment.called
1184+
1185+
request.get(
1186+
f"{self.api_url}/repos/{self.remote_repository.full_name}/issues/{version.verbose_name}/comments",
1187+
json=[
1188+
self._get_comment_json(
1189+
id=1,
1190+
issue_number=int(version.verbose_name),
1191+
repo_full_name=self.remote_repository.full_name,
1192+
user={"login": f"{settings.GITHUB_APP_NAME}[bot]"},
1193+
body=f"<!-- readthedocs-{self.project.id} -->\nComment!",
1194+
),
1195+
],
1196+
)
1197+
request_patch_comment = request.patch(
1198+
f"{self.api_url}/repos/{self.remote_repository.full_name}/issues/comments/1",
1199+
json={},
1200+
)
1201+
1202+
request_post_comment.reset()
1203+
1204+
# A comment exists from the bot, so it will update it.
1205+
service.post_comment(build, "Comment!", create_new=False)
1206+
assert not request_post_comment.called
1207+
1208+
assert request_patch_comment.called
1209+
assert request_patch_comment.last_request.json() == {
1210+
"body": f"<!-- readthedocs-{self.project.id} -->\nComment!",
1211+
}
1212+
11461213
def test_integration_attributes(self):
11471214
assert self.integration.is_active
11481215
assert self.integration.get_absolute_url() == "https://github.com/apps/readthedocs/installations/1111"

0 commit comments

Comments
 (0)