This commit is contained in:
Primakov Alexandr Alexandrovich
2025-10-12 23:15:09 +03:00
commit 09cdd06307
88 changed files with 15007 additions and 0 deletions
+218
View File
@@ -0,0 +1,218 @@
"""Review management endpoints"""
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func
from sqlalchemy.orm import joinedload
from app.database import get_db
from app.models import Review, Comment, PullRequest
from app.schemas.review import ReviewResponse, ReviewList, ReviewStats, PullRequestInfo, CommentResponse
from app.agents import ReviewerAgent
router = APIRouter()
@router.get("", response_model=ReviewList)
async def list_reviews(
skip: int = 0,
limit: int = 100,
repository_id: int = None,
status: str = None,
db: AsyncSession = Depends(get_db)
):
"""List all reviews with filters"""
query = select(Review).options(joinedload(Review.pull_request))
# Apply filters
if repository_id:
query = query.join(PullRequest).where(PullRequest.repository_id == repository_id)
if status:
query = query.where(Review.status == status)
# Get total count
count_query = select(func.count(Review.id))
if repository_id:
count_query = count_query.join(PullRequest).where(PullRequest.repository_id == repository_id)
if status:
count_query = count_query.where(Review.status == status)
count_result = await db.execute(count_query)
total = count_result.scalar()
# Get reviews
query = query.offset(skip).limit(limit).order_by(Review.started_at.desc())
result = await db.execute(query)
reviews = result.scalars().all()
# Convert to response models
items = []
for review in reviews:
pr_info = PullRequestInfo(
id=review.pull_request.id,
pr_number=review.pull_request.pr_number,
title=review.pull_request.title,
author=review.pull_request.author,
source_branch=review.pull_request.source_branch,
target_branch=review.pull_request.target_branch,
url=review.pull_request.url
)
items.append(ReviewResponse(
id=review.id,
pull_request_id=review.pull_request_id,
pull_request=pr_info,
status=review.status,
started_at=review.started_at,
completed_at=review.completed_at,
files_analyzed=review.files_analyzed,
comments_generated=review.comments_generated,
error_message=review.error_message
))
return ReviewList(items=items, total=total)
@router.get("/{review_id}", response_model=ReviewResponse)
async def get_review(
review_id: int,
db: AsyncSession = Depends(get_db)
):
"""Get review by ID with comments"""
result = await db.execute(
select(Review)
.options(joinedload(Review.pull_request), joinedload(Review.comments))
.where(Review.id == review_id)
)
review = result.unique().scalar_one_or_none()
if not review:
raise HTTPException(status_code=404, detail="Review not found")
pr_info = PullRequestInfo(
id=review.pull_request.id,
pr_number=review.pull_request.pr_number,
title=review.pull_request.title,
author=review.pull_request.author,
source_branch=review.pull_request.source_branch,
target_branch=review.pull_request.target_branch,
url=review.pull_request.url
)
comments = [
CommentResponse(
id=comment.id,
file_path=comment.file_path,
line_number=comment.line_number,
content=comment.content,
severity=comment.severity,
posted=comment.posted,
posted_at=comment.posted_at,
created_at=comment.created_at
)
for comment in review.comments
]
return ReviewResponse(
id=review.id,
pull_request_id=review.pull_request_id,
pull_request=pr_info,
status=review.status,
started_at=review.started_at,
completed_at=review.completed_at,
files_analyzed=review.files_analyzed,
comments_generated=review.comments_generated,
error_message=review.error_message,
comments=comments
)
async def run_review_task(review_id: int, pr_number: int, repository_id: int, db: AsyncSession):
"""Background task to run review"""
agent = ReviewerAgent(db)
await agent.run_review(review_id, pr_number, repository_id)
@router.post("/{review_id}/retry")
async def retry_review(
review_id: int,
background_tasks: BackgroundTasks,
db: AsyncSession = Depends(get_db)
):
"""Retry a failed review"""
result = await db.execute(
select(Review).options(joinedload(Review.pull_request)).where(Review.id == review_id)
)
review = result.scalar_one_or_none()
if not review:
raise HTTPException(status_code=404, detail="Review not found")
# Reset review status
from app.models.review import ReviewStatusEnum
review.status = ReviewStatusEnum.PENDING
review.error_message = None
await db.commit()
# Run review in background
background_tasks.add_task(
run_review_task,
review.id,
review.pull_request.pr_number,
review.pull_request.repository_id,
db
)
return {"message": "Review queued"}
@router.get("/stats/dashboard", response_model=ReviewStats)
async def get_review_stats(db: AsyncSession = Depends(get_db)):
"""Get review statistics for dashboard"""
# Total reviews
total_result = await db.execute(select(func.count(Review.id)))
total_reviews = total_result.scalar()
# Active reviews
from app.models.review import ReviewStatusEnum
active_result = await db.execute(
select(func.count(Review.id)).where(
Review.status.in_([
ReviewStatusEnum.PENDING,
ReviewStatusEnum.FETCHING,
ReviewStatusEnum.ANALYZING,
ReviewStatusEnum.COMMENTING
])
)
)
active_reviews = active_result.scalar()
# Completed reviews
completed_result = await db.execute(
select(func.count(Review.id)).where(Review.status == ReviewStatusEnum.COMPLETED)
)
completed_reviews = completed_result.scalar()
# Failed reviews
failed_result = await db.execute(
select(func.count(Review.id)).where(Review.status == ReviewStatusEnum.FAILED)
)
failed_reviews = failed_result.scalar()
# Total comments
comments_result = await db.execute(select(func.count(Comment.id)))
total_comments = comments_result.scalar()
# Average comments per review
avg_comments = total_comments / total_reviews if total_reviews > 0 else 0
return ReviewStats(
total_reviews=total_reviews,
active_reviews=active_reviews,
completed_reviews=completed_reviews,
failed_reviews=failed_reviews,
total_comments=total_comments,
avg_comments_per_review=round(avg_comments, 2)
)