Skip to content

Commit 75c42b2

Browse files
committed
api.views: add pull request description view (bug 2024944)
1 parent 6a26402 commit 75c42b2

7 files changed

Lines changed: 123 additions & 6 deletions

File tree

src/lando/api/views.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@
55
from typing import Callable
66

77
from django import forms
8+
from django.conf import settings
89
from django.core.handlers.wsgi import WSGIRequest
910
from django.http import HttpRequest, JsonResponse
11+
from django.template.loader import render_to_string
12+
from django.urls import reverse
1013
from django.utils.decorators import method_decorator
1114
from django.utils.html import escape
1215
from django.views import View
1316
from django.views.decorators.csrf import csrf_exempt
1417

15-
from lando.api.legacy.commit_message import replace_reviewers
18+
from lando.api.legacy.commit_message import parse_bugs, replace_reviewers
1619
from lando.main.auth import require_authenticated_user
1720
from lando.main.models import (
1821
CommitMap,
@@ -25,7 +28,12 @@
2528
from lando.main.models.landing_job import get_jobs_for_pull
2629
from lando.main.models.revision import DiffWarning, DiffWarningStatus
2730
from lando.main.scm import SCMType
28-
from lando.utils.github import GitHubAPIClient, PullRequest, PullRequestPatchHelper
31+
from lando.utils.github import (
32+
SPECIAL_DELIMITER,
33+
GitHubAPIClient,
34+
PullRequest,
35+
PullRequestPatchHelper,
36+
)
2937
from lando.utils.github_checks import (
3038
ALL_PULL_REQUEST_BLOCKERS,
3139
ALL_PULL_REQUEST_WARNINGS,
@@ -372,3 +380,45 @@ def post(
372380
job.save()
373381

374382
return JsonResponse({"id": job.id}, status=201)
383+
384+
385+
@method_decorator(csrf_exempt, name="dispatch")
386+
class PullRequestEnhancedDescriptionAPIView(PullRequestAPIView):
387+
"""API methods to modify the pull request description box."""
388+
389+
@method_decorator(require_authenticated_user)
390+
def get(
391+
self, request: WSGIRequest, repo_name: str, pull_number: int
392+
) -> JsonResponse:
393+
"""Update PR description box with parsed content."""
394+
395+
if not request.user.has_perm("main.can_change_landing_job"):
396+
raise PermissionError()
397+
398+
context = {}
399+
context.update(
400+
generate_warnings_and_blockers(self.target_repo, self.pull_request, request)
401+
)
402+
403+
path = reverse(
404+
"pull-request",
405+
kwargs={
406+
"repo_name": self.target_repo.name,
407+
"number": self.pull_request.number,
408+
},
409+
)
410+
411+
context["lando_url"] = f"{settings.SITE_URL}{path}"
412+
context["special_delimiter"] = SPECIAL_DELIMITER
413+
bugs = parse_bugs(self.pull_request.title)
414+
context["bugs"] = []
415+
416+
for bug in bugs:
417+
context["bugs"].append((bug, f"{settings.BUGZILLA_URL}/{bug}"))
418+
419+
context["title"] = self.pull_request.title
420+
context["body"] = self.pull_request.parsed_body
421+
422+
rendered = render_to_string("pr_description.md", context)
423+
self.client.update_pull_request_body(pull_number, rendered)
424+
return JsonResponse({"context": context, "md": rendered})

src/lando/jinja.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ def message_type_to_notification_class(flash_message_category: str) -> str:
448448

449449

450450
def environment(**options) -> Environment:
451+
options.update({"trim_blocks": True, "lstrip_blocks": True})
451452
env = Environment(extensions=[CompressorExtension], **options)
452453
env.globals.update(
453454
{
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{# Helpers for generating warnings and blockers #}
2+
{% macro _warning(content) %}:warning: {{ content }}{% endmacro %}
3+
{% macro _blocker(content) %}:no_entry_sign: {{ content }}{% endmacro %}
4+
{% macro _column(content) %}|{{ content }}|{% endmacro %}
5+
{% macro _header() %}|---------|{% endmacro %}
6+
{% macro _table(title, entries, macro) %}
7+
{{ _column(title) }}
8+
{{ _header() }}
9+
{% for entry in entries %}
10+
{{ macro(entry) }}
11+
{% endfor %}
12+
{% endmacro %}
13+
14+
{% block upper %}
15+
Lando: [link]({{ lando_url }})
16+
{% if bugs %}Bugzilla: {% for bug in bugs %}[bug {{ bug.0 }}]({{ bug.1 }}){% endfor %}{% endif %}
17+
18+
{% if not warnings and not blockers %}
19+
:white_check_mark: All Lando checks passed
20+
{% endif %}
21+
22+
{% if warnings %}
23+
{{ _table("Warnings", warnings, _warning) }}
24+
{% endif %}
25+
26+
{% if blockers %}
27+
{{ _table("Blockers", blockers, _blocker) }}
28+
{% endif %}
29+
30+
{% endblock %}
31+
32+
<!--/ DO NOT REMOVE THE LINE BELOW /-->
33+
{{ special_delimiter }}
34+
35+
{% block lower %}
36+
{{ body|e }}
37+
{% endblock %}

src/lando/ui/jinja2/stack/pull_request.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ <h1>
8282
</tr>
8383
<tr>
8484
<th>Commit Body</th>
85-
<td>{{ pull_request.body }}</td>
85+
<td>{{ pull_request.parsed_body }}</td>
8686
</tr>
8787
</table>
8888
</div>

src/lando/ui/pull_requests.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
get_jobs_for_pull,
1414
)
1515
from lando.ui.views import LandoView
16-
from lando.utils.github import GitHubAPIClient
16+
from lando.utils.github import SPECIAL_DELIMITER, GitHubAPIClient
1717

1818
logger = logging.getLogger(__name__)
1919

@@ -70,6 +70,7 @@ def get(
7070
"landing_jobs": landing_jobs,
7171
"last_try_job": last_try_job,
7272
"is_try_compatible": is_try_compatible,
73+
"special_delimiter": SPECIAL_DELIMITER,
7374
}
7475

7576
return TemplateResponse(

src/lando/urls.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
LandingJobPullRequestAPIView,
2626
LegacyDiffWarningView,
2727
PullRequestChecksAPIView,
28+
PullRequestEnhancedDescriptionAPIView,
2829
PullRequestTryPushAPIView,
2930
git2hgCommitMapView,
3031
hg2gitCommitMapView,
@@ -131,6 +132,11 @@
131132
PullRequestChecksAPIView.as_view(),
132133
name="api-pull-request-checks",
133134
),
135+
path(
136+
"api/pulls/<str:repo_name>/<int:pull_number>/description",
137+
PullRequestEnhancedDescriptionAPIView.as_view(),
138+
name="api-pull-request-description",
139+
),
134140
]
135141

136142
# "API" endpoints ported from legacy API app.

src/lando/utils/github.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
logger = logging.getLogger(__name__)
2323

2424

25+
SPECIAL_DELIMITER = "-" * 9
26+
27+
2528
class GitHub:
2629
"""Work with authentication to GitHub repositories."""
2730

@@ -150,6 +153,11 @@ def post(self, path: str, *args, **kwargs) -> requests.Response:
150153
url = f"{self.GITHUB_BASE_URL}/{path}"
151154
return self.session.post(url, *args, **kwargs)
152155

156+
def patch(self, path: str, *args, **kwargs) -> requests.Response:
157+
"""Send a PATCH request to the GitHub API with given args and kwargs."""
158+
url = f"{self.GITHUB_BASE_URL}/{path}"
159+
return self.session.patch(url, *args, **kwargs)
160+
153161

154162
class GitHubAPIClient:
155163
"""A convenience client that provides various methods to interact with the GitHub API."""
@@ -230,6 +238,10 @@ def _post(self, path: str, *args, **kwargs):
230238
result = self._api.post(path, *args, **kwargs)
231239
return result.json()
232240

241+
def _patch(self, path: str, *args, **kwargs):
242+
result = self._api.patch(path, *args, **kwargs)
243+
return result.json()
244+
233245
def build_pull_request(self, pull_number: int) -> "PullRequest":
234246
"""Build a PullRequest object.
235247
@@ -370,6 +382,13 @@ def add_comment_to_pull_request(self, pull_number: int, comment: str) -> dict:
370382
json={"body": comment},
371383
)
372384

385+
def update_pull_request_body(self, pull_number: int, body: str) -> dict:
386+
"""Update the pull request description with provided body."""
387+
return self._patch(
388+
f"{self.repo_base_url}/issues/{pull_number}",
389+
json={"body": body},
390+
)
391+
373392
@classmethod
374393
def convert_timestamp_from_github(cls, timestamp: str) -> str:
375394
timestamp_datetime = datetime.fromisoformat(timestamp)
@@ -451,6 +470,9 @@ def __init__(self, client: GitHubAPIClient, data: dict):
451470
self.diff_url = data["diff_url"]
452471
self.patch_url = data["patch_url"]
453472
self.body = data["body"] # description
473+
self.parsed_body = (
474+
self.body.split(SPECIAL_DELIMITER)[-1].strip() if self.body else ""
475+
)
454476
self.is_draft = data["draft"]
455477
self.comments_url = data["comments_url"]
456478
self.commits_url = data["commits_url"]
@@ -608,8 +630,8 @@ def commit_message(self) -> str:
608630

609631
lines = [self.title, ""]
610632

611-
if self.body:
612-
lines += [self.body, ""]
633+
if self.parsed_body:
634+
lines += [self.parsed_body, ""]
613635

614636
lines.append(f"Pull request: {self.html_url}")
615637

0 commit comments

Comments
 (0)