Primakov Alexandr Alexandrovich 09cdd06307 init
2025-10-12 23:15:09 +03:00

219 lines
6.9 KiB
Python

"""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)
)