feat: add inline mobile review comments (#165)
This commit is contained in:
1 parent
4b7405aa65
commit
58376f6d45
7 files changed
+436
-16
No files matched your search
@@ -18,6 +18,10 @@ class StaleReviewError(ValueError):
|
||||
"""Raised before mutation when a pull request head changed during review."""
|
||||
|
||||
|
||||
class InvalidReviewCommentError(ValueError):
|
||||
"""Raised before mutation when an inline comment cannot target this change."""
|
||||
|
||||
|
||||
class StalePullError(ValueError):
|
||||
"""Raised before merge when an assigned pull request head changed."""
|
||||
|
||||
@@ -694,6 +698,7 @@ async def submit_pull_review(
|
||||
expected_head_sha: str,
|
||||
decision: str,
|
||||
body: str,
|
||||
comments: list[dict] | None = None,
|
||||
) -> dict:
|
||||
base = f"repos/{repository}/pulls/{number}"
|
||||
pull = await fetch(base)
|
||||
@@ -701,6 +706,15 @@ async def submit_pull_review(
|
||||
current_sha = head.get("sha") if isinstance(head, dict) else None
|
||||
if current_sha != expected_head_sha:
|
||||
raise StaleReviewError("Pull request changed while it was being reviewed")
|
||||
if comments:
|
||||
files = await fetch(f"{base}/files")
|
||||
changed_paths = {
|
||||
item.get("filename")
|
||||
for item in (files if isinstance(files, list) else [])
|
||||
if isinstance(item, dict) and isinstance(item.get("filename"), str)
|
||||
}
|
||||
if any(comment.get("path") not in changed_paths for comment in comments):
|
||||
raise InvalidReviewCommentError("Inline comment path is not in this pull request")
|
||||
response = await _get_client().post(
|
||||
f"/api/v1/{base}/reviews",
|
||||
headers=_auth(),
|
||||
@@ -712,6 +726,7 @@ async def submit_pull_review(
|
||||
"request_changes": "REQUEST_CHANGES",
|
||||
}[decision],
|
||||
"commit_id": current_sha,
|
||||
**({"comments": comments} if comments else {}),
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
+35
-2
@@ -7,7 +7,7 @@ from pathlib import Path
|
||||
from fastapi import FastAPI, HTTPException, Path as PathParam, Query
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from pydantic import BaseModel, Field, PositiveInt, field_validator
|
||||
from pydantic import BaseModel, Field, PositiveInt, field_validator, model_validator
|
||||
|
||||
from src import gitea_proxy
|
||||
from src.gitea_proxy import (
|
||||
@@ -125,10 +125,32 @@ class IssueCreation(BaseModel):
|
||||
return value.strip()
|
||||
|
||||
|
||||
class PullReviewComment(BaseModel):
|
||||
path: str = Field(min_length=1, max_length=1_000)
|
||||
body: str = Field(min_length=1, max_length=10_000)
|
||||
new_position: PositiveInt | None = None
|
||||
old_position: PositiveInt | None = None
|
||||
|
||||
@field_validator("path", "body")
|
||||
@classmethod
|
||||
def strip_comment_text(cls, value: str) -> str:
|
||||
value = value.strip()
|
||||
if not value:
|
||||
raise ValueError("inline comment values must not be blank")
|
||||
return value
|
||||
|
||||
@model_validator(mode="after")
|
||||
def require_one_position(self):
|
||||
if (self.new_position is None) == (self.old_position is None):
|
||||
raise ValueError("inline comments require exactly one old or new position")
|
||||
return self
|
||||
|
||||
|
||||
class PullReviewSubmission(BaseModel):
|
||||
decision: str
|
||||
body: str = Field(max_length=10_000)
|
||||
expected_head_sha: str
|
||||
comments: list[PullReviewComment] = Field(default_factory=list, max_length=50)
|
||||
|
||||
@field_validator("decision")
|
||||
@classmethod
|
||||
@@ -987,13 +1009,19 @@ async def submit_review(
|
||||
async def submit_requested_review():
|
||||
if not await is_requested_review(repository, number):
|
||||
raise HTTPException(status_code=404, detail="Review request not found")
|
||||
return await gitea_proxy.submit_pull_review(
|
||||
args = (
|
||||
repository,
|
||||
number,
|
||||
submission.expected_head_sha,
|
||||
submission.decision,
|
||||
submission.body,
|
||||
)
|
||||
if submission.comments:
|
||||
return await gitea_proxy.submit_pull_review(
|
||||
*args,
|
||||
[comment.model_dump() for comment in submission.comments],
|
||||
)
|
||||
return await gitea_proxy.submit_pull_review(*args)
|
||||
|
||||
try:
|
||||
result = await asyncio.wait_for(
|
||||
@@ -1006,6 +1034,11 @@ async def submit_review(
|
||||
{"error": "New commits were pushed. Refresh the review before submitting."},
|
||||
status_code=409,
|
||||
)
|
||||
except gitea_proxy.InvalidReviewCommentError:
|
||||
return JSONResponse(
|
||||
{"error": "An inline comment no longer matches this pull request. Refresh the review."},
|
||||
status_code=422,
|
||||
)
|
||||
except Exception:
|
||||
return JSONResponse(
|
||||
{"error": "The review could not be submitted. Your draft is safe; please retry."},
|
||||
|
||||
Reference in new issue
Block a user