[Fix] Filter Query and Has Repository Query (#1951)

https://github.com/user-attachments/assets/bff0d449-0fbf-424e-9f5e-93054d8f7da6

### The problem was that there was no AND between Has Repo and the rest
of the query, and for Quality, the issue was that we didn’t check for
repository IDs or user IDs.



### How to test? 
Make the CF-WebApp connect to the production database, and then try
filtering and searching in staging. and make sure you are accessing the
correct data

---------

Co-authored-by: mashraf-222 <ashraf@codeflash.ai>
Co-authored-by: Sarthak Agarwal <sarthak.saga@gmail.com>
This commit is contained in:
HeshamHM28 2025-11-10 10:04:19 -08:00 committed by GitHub
parent f37308fa79
commit ac99349a92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 368 additions and 353 deletions

View file

@ -45,139 +45,132 @@ export async function getAllOptimizationEvents({
}
if (filter) {
// Merge filter into where, excluding review_quality since it's in a different table
Object.keys(filter).forEach(key => {
// COMMENTED OUT: Repository and quality filtering
// if (key === "repository_id") {
// where[key] = filter[key]
// } else if (key !== "review_quality") {
// where[key] = filter[key]
// }
// Only apply non-repository and non-quality filters
if (key !== "repository_id" && key !== "review_quality") {
if (key === "repository_id") {
where.AND = where.AND || []
where.AND.push({ [key]: filter[key] })
} else if (key !== "review_quality") {
where[key] = filter[key]
}
})
}
// COMMENTED OUT: Check if we need to join with optimization_features for quality sorting/filtering
// const needsOptimizationFeaturesJoin =
// (sort && Object.keys(sort).some(k => k.toLowerCase() === "review_quality")) ||
// (filter && Object.keys(filter).some(k => k.toLowerCase() === "review_quality"))
// Always use standard Prisma query (no quality joins)
const needsOptimizationFeaturesJoin = false
const needsOptimizationFeaturesJoin =
(sort && Object.keys(sort).some(k => k.toLowerCase() === "review_quality")) ||
(filter && Object.keys(filter).some(k => k.toLowerCase() === "review_quality"))
if (needsOptimizationFeaturesJoin) {
// ENTIRE BLOCK COMMENTED OUT - This was for quality filtering/sorting
// Build the WHERE clause for raw SQL
// const whereConditions = []
// const params: any[] = []
// let paramIndex = 1
// // Add is_staging condition
// whereConditions.push(`oe.is_staging = true`)
// // Add search conditions
// if (search) {
// whereConditions.push(
// `(oe.function_name ILIKE $${paramIndex} OR oe.file_path ILIKE $${paramIndex})`,
// )
// params.push(`%${search}%`)
// paramIndex += 1
// }
// // Add filter conditions
// if (filter) {
// if (filter.status) {
// whereConditions.push(`oe.status = $${paramIndex}`)
// params.push(filter.status)
// paramIndex += 1
// }
// if (filter.event_type) {
// whereConditions.push(`oe.event_type = $${paramIndex}`)
// params.push(filter.event_type)
// paramIndex += 1
// }
// if (filter.review_quality) {
// whereConditions.push(`of.review_quality = $${paramIndex}`)
// params.push(filter.review_quality)
// paramIndex += 1
// }
// if (filter.repository_id !== undefined) {
// if (filter.repository_id === null) {
// whereConditions.push(`oe.repository_id IS NULL`)
// } else if (filter.repository_id.not !== undefined && filter.repository_id.not === null) {
// whereConditions.push(`oe.repository_id IS NOT NULL`)
// }
// }
// }
// const whereClause = whereConditions.join(" AND ")
// // Build ORDER BY clause - handle all sort fields
// const orderByClauses: string[] = []
// if (sort && Object.keys(sort).length > 0) {
// Object.entries(sort).forEach(([key, direction]) => {
// const dir = direction.toUpperCase()
// if (key.toLowerCase() === "review_quality") {
// // Special handling for review_quality (case-insensitive)
// orderByClauses.push(`
// CASE
// WHEN LOWER(of.review_quality) = 'high' THEN 3
// WHEN LOWER(of.review_quality) = 'medium' THEN 2
// WHEN LOWER(of.review_quality) = 'low' THEN 1
// ELSE 0
// END ${dir}
// `)
// } else {
// // Regular field sorting
// orderByClauses.push(`oe.${key} ${dir}`)
// }
// })
// }
// // Add default sort by created_at only if sort is falsy
// if (!sort) {
// orderByClauses.push("oe.created_at DESC")
// }
// const orderByClause = orderByClauses.join(", ")
// // Get events with custom ordering - join with optimization_features table
// const events = await prisma.$queryRawUnsafe<any[]>(
// `
// SELECT
// oe.*,
// of.review_quality,
// of.review_explanation
// FROM optimization_events oe
// LEFT JOIN optimization_features of ON oe.trace_id = of.trace_id
// WHERE ${whereClause}
// ORDER BY ${orderByClause}
// LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
// `,
// ...params,
// pageSize,
// (page - 1) * pageSize,
// )
// // Get total count
// const countResult = await prisma.$queryRawUnsafe<[{ count: bigint }]>(
// `
// SELECT COUNT(*) as count
// FROM optimization_events oe
// LEFT JOIN optimization_features of ON oe.trace_id = of.trace_id
// WHERE ${whereClause}
// `,
// ...params,
// )
// const totalCount = Number(countResult[0].count)
// // Fetch repository data for the events
// const eventsWithRepo = await Promise.all(
// events.map(async event => {
// if (event.repository_id) {
// const repository = await prisma.repositories.findUnique({
// where: { id: event.repository_id },
// })
// return { ...event, repository }
// }
// return { ...event, repository: null }
// }),
// )
// return { events: eventsWithRepo, totalCount }
const whereConditions = []
const params: any[] = []
let paramIndex = 1
whereConditions.push(`oe.is_staging = true`)
if ("orgId" in payload) {
whereConditions.push(`oe.repository_id IN (${repoIds.map(id => `'${id}'`).join(",")})`)
} else {
whereConditions.push(
`(
oe.repository_id IN (${repoIds.map(id => `'${id}'`).join(",")})
OR oe.user_id = '${payload.userId}'
OR oe.current_username = '${payload.username}'
)`,
)
}
// Add search conditions
if (search) {
whereConditions.push(
`(oe.function_name ILIKE $${paramIndex} OR oe.file_path ILIKE $${paramIndex})`,
)
params.push(`%${search}%`)
paramIndex += 1
}
// Add filter conditions
if (filter) {
if (filter.status) {
whereConditions.push(`oe.status = $${paramIndex}`)
params.push(filter.status)
paramIndex += 1
}
if (filter.event_type) {
whereConditions.push(`oe.event_type = $${paramIndex}`)
params.push(filter.event_type)
paramIndex += 1
}
if (filter.review_quality) {
whereConditions.push(`of.review_quality = $${paramIndex}`)
params.push(filter.review_quality)
paramIndex += 1
}
if (filter.repository_id !== undefined) {
if (filter.repository_id === null) {
whereConditions.push(`oe.repository_id IS NULL`)
} else if (filter.repository_id.not !== undefined && filter.repository_id.not === null) {
whereConditions.push(`oe.repository_id IS NOT NULL`)
}
}
}
const whereClause = whereConditions.join(" AND ")
const orderByClauses: string[] = []
if (sort && Object.keys(sort).length > 0) {
Object.entries(sort).forEach(([key, direction]) => {
const dir = direction.toUpperCase()
if (key.toLowerCase() === "review_quality") {
orderByClauses.push(`
CASE
WHEN LOWER(of.review_quality) = 'high' THEN 3
WHEN LOWER(of.review_quality) = 'medium' THEN 2
WHEN LOWER(of.review_quality) = 'low' THEN 1
ELSE 0
END ${dir}
`)
} else {
orderByClauses.push(`oe.${key} ${dir}`)
}
})
}
if (!sort) {
orderByClauses.push("oe.created_at DESC")
}
const orderByClause = orderByClauses.join(", ")
const events = await prisma.$queryRawUnsafe<any[]>(
`
SELECT
oe.*,
of.review_quality,
of.review_explanation
FROM optimization_events oe
LEFT JOIN optimization_features of ON oe.trace_id = of.trace_id
WHERE ${whereClause}
ORDER BY ${orderByClause}
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
`,
...params,
pageSize,
(page - 1) * pageSize,
)
// Get total count
const countResult = await prisma.$queryRawUnsafe<[{ count: bigint }]>(
`
SELECT COUNT(*) as count
FROM optimization_events oe
LEFT JOIN optimization_features of ON oe.trace_id = of.trace_id
WHERE ${whereClause}
`,
...params,
)
const totalCount = Number(countResult[0].count)
// Fetch repository data for the events
const eventsWithRepo = await Promise.all(
events.map(async event => {
if (event.repository_id) {
const repository = await prisma.repositories.findUnique({
where: { id: event.repository_id },
})
return { ...event, repository }
}
return { ...event, repository: null }
}),
)
return { events: eventsWithRepo, totalCount }
} else {
// Standard Prisma query with native orderBy
const orderBy = sort || { created_at: "desc" }

View file

@ -1,5 +1,5 @@
"use client"
import { useState, useEffect, useCallback } from "react"
import { useState, useEffect, useCallback, useRef } from "react"
import { Input } from "@/components/ui/input"
import { Badge } from "@/components/ui/badge"
import {
@ -30,13 +30,12 @@ import {
ArrowDown,
} from "lucide-react"
import { formatDistanceToNow } from "date-fns"
import { useRouter, useSearchParams } from "next/navigation"
import { useRouter } from "next/navigation"
import { getUserId, getUserIdAndUsername } from "@/app/utils/auth"
import { Button } from "@/components/ui/button"
import { getAllOptimizationEvents } from "./action"
import Image from "next/image"
import { useViewMode } from "@/app/app/ViewModeContext"
// COMMENTED OUT: Quality badge import
import { ReviewQualityBadge } from "@/components/ui/quality_badge"
// Type definitions
@ -70,6 +69,66 @@ interface OptimizationEvent {
review_quality: string
}
interface FilterState {
search: string
hasRepo: string
status: string
eventType: string
reviewQuality: string
sortBy: string
page: number
}
function TableSkeleton() {
return (
<>
{Array.from({ length: 5 }).map((_, index) => (
<TableRow key={index}>
<TableCell>
<div className="flex items-start gap-3">
<div className="h-4 w-4 bg-muted animate-pulse rounded mt-1 flex-shrink-0" />
<div className="flex-1 min-w-0 space-y-2">
<div className="h-4 bg-muted animate-pulse rounded w-3/4" />
<div className="h-3 bg-muted animate-pulse rounded w-1/2" />
</div>
</div>
</TableCell>
<TableCell>
<div className="flex items-center gap-3">
<div className="h-8 w-8 bg-muted animate-pulse rounded-full flex-shrink-0" />
<div className="h-4 bg-muted animate-pulse rounded w-32" />
</div>
</TableCell>
<TableCell className="text-center">
<div className="h-6 bg-muted animate-pulse rounded-full w-20 mx-auto" />
</TableCell>
<TableCell className="text-center">
<div className="h-6 bg-muted animate-pulse rounded-full w-24 mx-auto" />
</TableCell>
<TableCell className="text-center">
<div className="h-6 bg-muted animate-pulse rounded-full w-16 mx-auto" />
</TableCell>
<TableCell className="text-center">
<div className="h-6 bg-muted animate-pulse rounded-full w-24 mx-auto" />
</TableCell>
<TableCell className="text-center">
<div className="flex items-center justify-center gap-2">
<div className="h-6 bg-muted animate-pulse rounded-full w-12" />
<div className="h-6 bg-muted animate-pulse rounded-full w-12" />
</div>
</TableCell>
<TableCell className="text-right">
<div className="flex items-center justify-end gap-2">
<div className="h-3 w-3 bg-muted animate-pulse rounded flex-shrink-0" />
<div className="h-4 bg-muted animate-pulse rounded w-24" />
</div>
</TableCell>
</TableRow>
))}
</>
)
}
// Helper function to calculate diff stats
function calculateDiffStats(
diffContents: Record<string, { oldContent: string; newContent: string }>,
@ -126,22 +185,6 @@ function calculateDiffStats(
}
}
// Helper function to calculate coverage percentage
function getCoverageInfo(diffContents: Record<string, { oldContent: string; newContent: string }>) {
// This is a simplified coverage calculation
// In practice, you'd want to integrate with actual coverage data
const { totalAdditions, totalDeletions } = calculateDiffStats(diffContents)
const totalLines = Object.values(diffContents).reduce((acc, { newContent }) => {
return acc + newContent.split("\n").filter(line => line.trim() !== "").length
}, 0)
// Simple heuristic: assume coverage decreases with more changes
const changeRatio = (totalAdditions + totalDeletions) / Math.max(totalLines, 1)
const coverage = Math.max(0.5, 1 - changeRatio * 0.3) // Keep between 50-100%
return Math.round(coverage * 100)
}
// Client component for handling row clicks
function ClickableTableRow({
event,
@ -152,13 +195,15 @@ function ClickableTableRow({
children: React.ReactNode
onRowClick: (eventId: string) => void
}) {
const handleRowClick = (e: React.MouseEvent) => {
// Don't navigate if clicking on external link
if ((e.target as HTMLElement).closest('a[href^="http"]')) {
return
}
onRowClick(event.id)
}
const handleRowClick = useCallback(
(e: React.MouseEvent) => {
if ((e.target as HTMLElement).closest('a[href^="http"]')) {
return
}
onRowClick(event.trace_id)
},
[event.trace_id, onRowClick],
)
return (
<TableRow
@ -175,37 +220,32 @@ export default function StagingPage() {
const [userId, setUserId] = useState<string | null>(null)
const [isLoadingUser, setIsLoadingUser] = useState(true)
const router = useRouter()
const searchParams = useSearchParams()
const [events, setEvents] = useState<OptimizationEvent[]>([])
const [totalCount, setTotalCount] = useState(0)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
// Local page state instead of URL-based
const [page, setPage] = useState(1)
const search = searchParams.get("search") || ""
// COMMENTED OUT: Repository filter from URL
// const hasRepo = searchParams.get("hasRepo") || "all"
const status = searchParams.get("status") || "all"
const eventType = searchParams.get("eventType") || "all"
// COMMENTED OUT: Quality filter from URL
// const reviewQuality = searchParams.get("reviewQuality") || "all"
const sortBy = searchParams.get("sortBy") || "created_at_desc"
const pageSize = 10
// Local state for filter inputs
const [searchInput, setSearchInput] = useState(search)
// COMMENTED OUT: Repository filter state
// const [hasRepoFilter, setHasRepoFilter] = useState(hasRepo)
const [statusFilter, setStatusFilter] = useState(status)
const [eventTypeFilter, setEventTypeFilter] = useState(eventType)
// COMMENTED OUT: Quality filter state
// const [reviewQualityFilter, setReviewQualityFilter] = useState(reviewQuality)
const [sortByFilter, setSortByFilter] = useState(sortBy)
const { currentOrg } = useViewMode()
// Load user ID on mount
// Combined filter state
const [filters, setFilters] = useState<FilterState>({
search: "",
hasRepo: "all",
status: "all",
eventType: "all",
reviewQuality: "all",
sortBy: "created_at_desc",
page: 1,
})
const pageSize = 10
// Refs to track if initial load is done
const isInitialMount = useRef(true)
const debounceTimer = useRef<NodeJS.Timeout>()
// Load user ID on mount - only once
useEffect(() => {
const loadUserId = async () => {
try {
@ -221,6 +261,7 @@ export default function StagingPage() {
loadUserId()
}, [])
// Memoized load events function
const loadEvents = useCallback(async () => {
if (!userId) return
@ -230,28 +271,26 @@ export default function StagingPage() {
try {
const filter: Record<string, any> = {}
// COMMENTED OUT: Repository filter logic
// if (hasRepo === "yes") {
// filter.repository_id = { not: null }
// } else if (hasRepo === "no") {
// filter.repository_id = null
// }
if (status !== "all") {
filter.status = status
if (filters.hasRepo === "yes") {
filter.repository_id = { not: null }
} else if (filters.hasRepo === "no") {
filter.repository_id = null
}
if (eventType !== "all") {
filter.event_type = eventType
if (filters.status !== "all") {
filter.status = filters.status
}
// COMMENTED OUT: Quality filter logic
// if (reviewQuality !== "all") {
// filter.review_quality = reviewQuality
// }
if (filters.eventType !== "all") {
filter.event_type = filters.eventType
}
if (filters.reviewQuality !== "all") {
filter.review_quality = filters.reviewQuality
}
// Parse sort parameter
const [sortField, sortDirection] = sortBy.split("_").reduce(
const [sortField, sortDirection] = filters.sortBy.split("_").reduce(
(acc, part, index, arr) => {
if (index === arr.length - 1 && (part === "asc" || part === "desc")) {
return [acc[0], part]
@ -264,15 +303,16 @@ export default function StagingPage() {
const sort: Record<string, "asc" | "desc"> = {
[sortField]: sortDirection as "asc" | "desc",
}
const userSession = await getUserIdAndUsername()
const data = await getAllOptimizationEvents({
payload: currentOrg
? { orgId: currentOrg.id }
: { userId: userSession.userId, username: userSession.username },
search,
search: filters.search,
filter,
sort,
page,
page: filters.page,
pageSize,
})
@ -295,105 +335,116 @@ export default function StagingPage() {
} finally {
setIsLoading(false)
}
}, [
userId,
search,
// COMMENTED OUT: hasRepo dependency
// hasRepo,
status,
eventType,
// COMMENTED OUT: reviewQuality dependency
// reviewQuality,
sortBy,
page,
pageSize,
currentOrg,
])
}, [userId, filters, currentOrg, pageSize])
// Load events when filters change - with debounce for search
useEffect(() => {
if (!isLoadingUser && userId) {
loadEvents()
// Skip initial mount
if (isInitialMount.current) {
isInitialMount.current = false
loadEvents()
return
}
// Clear existing timer
if (debounceTimer.current) {
clearTimeout(debounceTimer.current)
}
// Debounce only for search changes
const hasSearchChanged = filters.search !== ""
if (hasSearchChanged) {
debounceTimer.current = setTimeout(() => {
loadEvents()
}, 300)
} else {
loadEvents()
}
}
}, [userId, isLoadingUser, loadEvents])
const handleRowClick = (traceId: string) => {
router.push(`/review-optimizations/${traceId}`)
}
return () => {
if (debounceTimer.current) {
clearTimeout(debounceTimer.current)
}
}
}, [userId, isLoadingUser, filters, loadEvents])
// Update URL when filters change
useEffect(() => {
const timer = setTimeout(() => {
const params = new URLSearchParams()
if (searchInput) params.set("search", searchInput)
// COMMENTED OUT: Repository filter param
// if (hasRepoFilter !== "all") params.set("hasRepo", hasRepoFilter)
if (statusFilter !== "all") params.set("status", statusFilter)
if (eventTypeFilter !== "all") params.set("eventType", eventTypeFilter)
// COMMENTED OUT: Quality filter param
// if (reviewQualityFilter !== "all") params.set("reviewQuality", reviewQualityFilter)
if (sortByFilter !== "created_at_desc") params.set("sortBy", sortByFilter)
const handleRowClick = useCallback(
(traceId: string) => {
router.push(`/review-optimizations/${traceId}`)
},
[router],
)
// Reset to page 1 when filters change
setPage(1)
router.push(`?${params.toString()}`)
}, 300) // 300ms debounce
// Update filter functions
const updateFilter = useCallback((key: keyof FilterState, value: string | number) => {
setFilters(prev => ({
...prev,
[key]: value,
// Reset page when filters change (except page itself)
...(key !== "page" && { page: 1 }),
}))
}, [])
return () => clearTimeout(timer)
}, [
searchInput,
// COMMENTED OUT: hasRepoFilter dependency
// hasRepoFilter,
statusFilter,
eventTypeFilter,
// COMMENTED OUT: reviewQualityFilter dependency
// reviewQualityFilter,
sortByFilter,
router,
])
const clearFilters = () => {
setSearchInput("")
// COMMENTED OUT: Reset repository filter
// setHasRepoFilter("all")
setStatusFilter("all")
setEventTypeFilter("all")
// COMMENTED OUT: Reset quality filter
// setReviewQualityFilter("all")
setSortByFilter("created_at_desc")
setPage(1)
router.push("/review-optimizations")
}
const clearFilters = useCallback(() => {
setFilters({
search: "",
hasRepo: "all",
status: "all",
eventType: "all",
reviewQuality: "all",
sortBy: "created_at_desc",
page: 1,
})
}, [])
const hasActiveFilters =
searchInput ||
// COMMENTED OUT: hasRepoFilter check
// hasRepoFilter !== "all" ||
statusFilter !== "all" ||
eventTypeFilter !== "all" ||
// COMMENTED OUT: reviewQualityFilter check
// reviewQualityFilter !== "all" ||
sortByFilter !== "created_at_desc"
filters.search ||
filters.hasRepo !== "all" ||
filters.status !== "all" ||
filters.eventType !== "all" ||
filters.reviewQuality !== "all" ||
filters.sortBy !== "created_at_desc"
const totalPages = Math.ceil(totalCount / pageSize)
const handlePageChange = (newPage: number) => {
if (newPage >= 1 && newPage <= totalPages) {
setPage(newPage)
}
}
const handlePageChange = useCallback(
(newPage: number) => {
if (newPage >= 1 && newPage <= totalPages) {
updateFilter("page", newPage)
}
},
[totalPages, updateFilter],
)
const getSortIcon = (field: string) => {
if (sortByFilter.startsWith(field)) {
return sortByFilter.endsWith("_asc") ? (
<ArrowUp className="h-4 w-4" />
) : (
<ArrowDown className="h-4 w-4" />
)
}
return <ArrowUpDown className="h-4 w-4 opacity-50" />
}
const getSortIcon = useCallback(
(field: string) => {
if (filters.sortBy.startsWith(field)) {
return filters.sortBy.endsWith("_asc") ? (
<ArrowUp className="h-4 w-4" />
) : (
<ArrowDown className="h-4 w-4" />
)
}
return <ArrowUpDown className="h-4 w-4 opacity-50" />
},
[filters.sortBy],
)
const getSpeedupBadge = (speedup?: number, speedupPct?: number) => {
const toggleSort = useCallback(
(field: string) => {
const newSort = filters.sortBy.startsWith(field)
? filters.sortBy === `${field}_desc`
? `${field}_asc`
: `${field}_desc`
: `${field}_desc`
updateFilter("sortBy", newSort)
},
[filters.sortBy, updateFilter],
)
const getSpeedupBadge = useCallback((speedup?: number, speedupPct?: number) => {
if (typeof speedup !== "number" || typeof speedupPct !== "number") return null
const clamp = (v: number, min: number, max: number) => Math.min(Math.max(v, min), max)
@ -423,9 +474,9 @@ export default function StagingPage() {
{speedup.toFixed(2)}x ({speedupPct.toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, ",")}%)
</Badge>
)
}
}, [])
const getStatusBadge = (status?: string) => {
const getStatusBadge = useCallback((status?: string) => {
if (!status) return null
const variants: Record<string, { className: string; label: string }> = {
@ -451,9 +502,9 @@ export default function StagingPage() {
{variant.label}
</Badge>
)
}
}, [])
const getEventTypeBadge = (eventType?: string) => {
const getEventTypeBadge = useCallback((eventType?: string) => {
if (!eventType) return null
const variants: Record<string, { className: string; label: string }> = {
@ -489,7 +540,7 @@ export default function StagingPage() {
{variant.label}
</Badge>
)
}
}, [])
if (isLoadingUser) {
return (
@ -523,8 +574,8 @@ export default function StagingPage() {
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4" />
<Input
placeholder="Search by function name or file path..."
value={searchInput}
onChange={e => setSearchInput(e.target.value)}
value={filters.search}
onChange={e => updateFilter("search", e.target.value)}
className="pl-10 w-full"
/>
</div>
@ -536,19 +587,18 @@ export default function StagingPage() {
</div>
{/* Filter Controls */}
{/* <Select value={hasRepoFilter} onValueChange={setHasRepoFilter}>
<Select value={filters.hasRepo} onValueChange={value => updateFilter("hasRepo", value)}>
<SelectTrigger className="w-[140px] sm:w-[180px]">
<SelectValue placeholder="Repository" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Repositories</SelectItem>
<SelectItem value="yes">Has Repository</SelectItem>
<SelectItem value="no">No Repository</SelectItem>
<SelectItem value="yes">With Repository</SelectItem>
<SelectItem value="no">Without Repository</SelectItem>
</SelectContent>
</Select> */}
</Select>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<Select value={filters.status} onValueChange={value => updateFilter("status", value)}>
<SelectTrigger className="w-[120px] sm:w-[150px]">
<SelectValue placeholder="Status" />
</SelectTrigger>
@ -559,7 +609,10 @@ export default function StagingPage() {
</SelectContent>
</Select>
<Select value={eventTypeFilter} onValueChange={setEventTypeFilter}>
<Select
value={filters.eventType}
onValueChange={value => updateFilter("eventType", value)}
>
<SelectTrigger className="w-[120px] sm:w-[150px]">
<SelectValue placeholder="Event Type" />
</SelectTrigger>
@ -572,20 +625,22 @@ export default function StagingPage() {
</SelectContent>
</Select>
{/* COMMENTED OUT: Quality Filter */}
{/* <Select value={reviewQualityFilter} onValueChange={setReviewQualityFilter}>
<Select
value={filters.reviewQuality}
onValueChange={value => updateFilter("reviewQuality", value)}
>
<SelectTrigger className="w-[120px] sm:w-[150px]">
<SelectValue placeholder="Quality" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All qualities</SelectItem>
<SelectItem value="all">All Quality</SelectItem>
<SelectItem value="high">High</SelectItem>
<SelectItem value="medium">Medium</SelectItem>
<SelectItem value="low">Low</SelectItem>
</SelectContent>
</Select> */}
</Select>
<Select value={sortByFilter} onValueChange={setSortByFilter}>
<Select value={filters.sortBy} onValueChange={value => updateFilter("sortBy", value)}>
<SelectTrigger className="w-[140px] sm:w-[200px]">
<SelectValue placeholder="Sort by" />
</SelectTrigger>
@ -594,9 +649,8 @@ export default function StagingPage() {
<SelectItem value="created_at_asc">Oldest</SelectItem>
<SelectItem value="speedup_x_desc">Speedup (Highest)</SelectItem>
<SelectItem value="speedup_x_asc">Speedup (Lowest)</SelectItem>
{/* COMMENTED OUT: Quality sort options */}
{/* <SelectItem value="review_quality_desc">Quality (High to Low)</SelectItem>
<SelectItem value="review_quality_asc">Quality (Low to High)</SelectItem> */}
<SelectItem value="review_quality_desc">Quality (High to Low)</SelectItem>
<SelectItem value="review_quality_asc">Quality (Low to High)</SelectItem>
</SelectContent>
</Select>
@ -638,33 +692,18 @@ export default function StagingPage() {
<TableHead className="w-[18%]">REPOSITORY</TableHead>
<TableHead className="text-center">REVIEW</TableHead>
<TableHead className="text-center">STATUS</TableHead>
{/* COMMENTED OUT: Quality column header with sorting */}
<TableHead
className="text-center cursor-pointer hover:bg-muted/50"
// onClick={() => {
// const newSort = sortByFilter.startsWith("review_quality")
// ? sortByFilter === "review_quality_desc"
// ? "review_quality_asc"
// : "review_quality_desc"
// : "review_quality_desc"
// setSortByFilter(newSort)
// }}
onClick={() => toggleSort("review_quality")}
>
<div className="flex items-center justify-center gap-1">
<span>QUALITY</span>
{/* {getSortIcon("review_quality")} */}
{getSortIcon("review_quality")}
</div>
</TableHead>
<TableHead
className="text-center cursor-pointer hover:bg-muted/50"
onClick={() => {
const newSort = sortByFilter.startsWith("speedup_x")
? sortByFilter === "speedup_x_desc"
? "speedup_x_asc"
: "speedup_x_desc"
: "speedup_x_desc"
setSortByFilter(newSort)
}}
onClick={() => toggleSort("speedup_x")}
>
<div className="flex items-center justify-center gap-1">
<span>SPEEDUP</span>
@ -674,14 +713,7 @@ export default function StagingPage() {
<TableHead className="text-center">CHANGES</TableHead>
<TableHead
className="text-right cursor-pointer hover:bg-muted/50"
onClick={() => {
const newSort = sortByFilter.startsWith("created_at")
? sortByFilter === "created_at_desc"
? "created_at_asc"
: "created_at_desc"
: "created_at_desc"
setSortByFilter(newSort)
}}
onClick={() => toggleSort("created_at")}
>
<div className="flex items-center justify-end gap-1">
<span>CREATED AT</span>
@ -691,7 +723,9 @@ export default function StagingPage() {
</TableRow>
</TableHeader>
<TableBody>
{events.length === 0 ? (
{isLoading ? (
<TableSkeleton />
) : events.length === 0 ? (
<TableRow>
<TableCell colSpan={8} className="text-center py-12">
<div className="text-muted-foreground">
@ -708,7 +742,7 @@ export default function StagingPage() {
totalAdditions: 0,
totalDeletions: 0,
}
let coverage: number | null = null
if (
event.metadata &&
typeof event.metadata === "object" &&
@ -717,7 +751,6 @@ export default function StagingPage() {
event.metadata.diffContents !== null
) {
const diffContentsRaw = event.metadata.diffContents
// Check that every value is an object with oldContent and newContent as strings
if (diffContentsRaw && typeof diffContentsRaw === "object") {
let valid = true
for (const value of Object.values(diffContentsRaw as object)) {
@ -737,19 +770,12 @@ export default function StagingPage() {
{ oldContent: string; newContent: string }
>
diffStats = calculateDiffStats(diffContents)
coverage = getCoverageInfo(diffContents)
}
}
}
// fallback for coverage if not available
if (coverage === null) coverage = 100
return (
<ClickableTableRow
key={event.id}
event={event}
onRowClick={() => handleRowClick(event.trace_id)}
>
<ClickableTableRow key={event.id} event={event} onRowClick={handleRowClick}>
<TableCell className="w-auto min-w-0">
<div className="flex items-start gap-3">
<FileCode2 className="h-4 w-4 text-muted-foreground mt-1 flex-shrink-0" />
@ -767,7 +793,6 @@ export default function StagingPage() {
<div className="flex items-center gap-3">
{event.repository ? (
<>
{/* Repository image */}
<div className="relative h-8 w-8 flex-shrink-0">
{event.repository.full_name && (
<Image
@ -777,13 +802,10 @@ export default function StagingPage() {
className="rounded-full object-cover"
onError={e => {
e.currentTarget.style.display = "none"
e.currentTarget.nextElementSibling?.classList.remove("hidden")
}}
/>
)}
</div>
{/* Repository name */}
<div className="flex-1 min-w-0 overflow-hidden">
<div className="flex items-center gap-1">
<span className="text-sm font-medium truncate">
@ -794,7 +816,6 @@ export default function StagingPage() {
</>
) : (
<>
{/* Placeholder for untracked optimization */}
<div className="h-8 w-8 rounded-full bg-muted flex items-center justify-center flex-shrink-0">
<Zap className="h-4 w-4 text-muted-foreground" />
</div>
@ -856,18 +877,19 @@ export default function StagingPage() {
</Table>
</div>
{totalPages > 1 && (
{/* Pagination */}
{!isLoading && totalPages > 1 && (
<div className="flex items-center justify-between mt-6">
<p className="text-sm text-muted-foreground">
Showing {(page - 1) * pageSize + 1} to {Math.min(page * pageSize, totalCount)} of{" "}
{totalCount} events
Showing {(filters.page - 1) * pageSize + 1} to{" "}
{Math.min(filters.page * pageSize, totalCount)} of {totalCount} events
</p>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
disabled={page === 1}
onClick={() => handlePageChange(page - 1)}
disabled={filters.page === 1}
onClick={() => handlePageChange(filters.page - 1)}
>
<ChevronLeft className="h-4 w-4" />
Previous
@ -879,18 +901,18 @@ export default function StagingPage() {
if (totalPages <= 5) {
pageNum = i + 1
} else if (page <= 3) {
} else if (filters.page <= 3) {
pageNum = i + 1
} else if (page >= totalPages - 2) {
} else if (filters.page >= totalPages - 2) {
pageNum = totalPages - 4 + i
} else {
pageNum = page - 2 + i
pageNum = filters.page - 2 + i
}
return (
<Button
key={i}
variant={page === pageNum ? "default" : "outline"}
variant={filters.page === pageNum ? "default" : "outline"}
size="sm"
className="w-8 h-8 p-0"
onClick={() => handlePageChange(pageNum)}
@ -899,8 +921,8 @@ export default function StagingPage() {
</Button>
)
})}
{totalPages > 5 && page < totalPages - 2 && <span className="px-2">...</span>}
{totalPages > 5 && page < totalPages - 2 && (
{totalPages > 5 && filters.page < totalPages - 2 && <span className="px-2">...</span>}
{totalPages > 5 && filters.page < totalPages - 2 && (
<Button
variant="outline"
size="sm"
@ -915,8 +937,8 @@ export default function StagingPage() {
<Button
variant="outline"
size="sm"
disabled={page === totalPages}
onClick={() => handlePageChange(page + 1)}
disabled={filters.page === totalPages}
onClick={() => handlePageChange(filters.page + 1)}
>
Next
<ChevronRight className="h-4 w-4" />