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