perf: optimize Prisma queries — indexed lookups, parallel execution, select narrowing
Replace findFirst with findUnique on composite-indexed columns (organization_members, repository_members) to use index seeks instead of scans. Parallelize independent queries with Promise.all. Add select clauses to eliminate overfetch of unused columns. Replace Array.some/find permission checks with direct indexed lookups, removing the need to load entire member arrays. Use Map/Set for O(1) lookups replacing O(n) array iteration. Affected server actions: getOrganizationMembers, addOrganizationMember, updateOrganizationMemberRole, removeOrganizationMember, getCurrentUserRole, getRepositoryById, addRepositoryMemberById, getRepositoryMembers, updateRepositoryMemberRole, removeRepositoryMember, updateOrgPrivacyMode, getRepositoriesWithStagingEvents, getRecentTraces, and observability pages.
This commit is contained in:
parent
46297a23a5
commit
13b302a8f0
11 changed files with 458 additions and 414 deletions
|
|
@ -47,7 +47,8 @@ describe("getOrganizationMembers", () => {
|
|||
|
||||
describe("successful retrieval", () => {
|
||||
it("returns members when user has access", async () => {
|
||||
vi.mocked(prisma.organizations.findFirst).mockResolvedValue(mockOrg as any)
|
||||
vi.mocked(prisma.organizations.findUnique).mockResolvedValue(mockOrg as any)
|
||||
vi.mocked(prisma.organization_members.findUnique).mockResolvedValue({ id: "member-1" } as any)
|
||||
|
||||
const result = await getOrganizationMembers("user-1", "org-1")
|
||||
|
||||
|
|
@ -56,7 +57,8 @@ describe("getOrganizationMembers", () => {
|
|||
})
|
||||
|
||||
it("maps nested organization_members to flat Member structure", async () => {
|
||||
vi.mocked(prisma.organizations.findFirst).mockResolvedValue(mockOrg as any)
|
||||
vi.mocked(prisma.organizations.findUnique).mockResolvedValue(mockOrg as any)
|
||||
vi.mocked(prisma.organization_members.findUnique).mockResolvedValue({ id: "member-1" } as any)
|
||||
|
||||
const result = await getOrganizationMembers("user-1", "org-1")
|
||||
const member = result.data![0]
|
||||
|
|
@ -76,7 +78,8 @@ describe("getOrganizationMembers", () => {
|
|||
|
||||
describe("access control", () => {
|
||||
it("returns error when organization not found", async () => {
|
||||
vi.mocked(prisma.organizations.findFirst).mockResolvedValue(null)
|
||||
vi.mocked(prisma.organizations.findUnique).mockResolvedValue(null)
|
||||
vi.mocked(prisma.organization_members.findUnique).mockResolvedValue(null)
|
||||
|
||||
const result = await getOrganizationMembers("user-1", "org-1")
|
||||
|
||||
|
|
@ -85,7 +88,8 @@ describe("getOrganizationMembers", () => {
|
|||
})
|
||||
|
||||
it("returns error when user is not in organization members", async () => {
|
||||
vi.mocked(prisma.organizations.findFirst).mockResolvedValue(mockOrg as any)
|
||||
vi.mocked(prisma.organizations.findUnique).mockResolvedValue(mockOrg as any)
|
||||
vi.mocked(prisma.organization_members.findUnique).mockResolvedValue(null)
|
||||
|
||||
const result = await getOrganizationMembers("unknown-user", "org-1")
|
||||
|
||||
|
|
@ -96,9 +100,7 @@ describe("getOrganizationMembers", () => {
|
|||
|
||||
describe("error handling", () => {
|
||||
it("returns error response when Prisma throws", async () => {
|
||||
vi.mocked(prisma.organizations.findFirst).mockRejectedValue(
|
||||
new Error("Connection failed"),
|
||||
)
|
||||
vi.mocked(prisma.organizations.findUnique).mockRejectedValue(new Error("Connection failed"))
|
||||
|
||||
const result = await getOrganizationMembers("user-1", "org-1")
|
||||
|
||||
|
|
@ -107,7 +109,7 @@ describe("getOrganizationMembers", () => {
|
|||
})
|
||||
|
||||
it("uses fallback message for non-Error exceptions", async () => {
|
||||
vi.mocked(prisma.organizations.findFirst).mockRejectedValue("string error")
|
||||
vi.mocked(prisma.organizations.findUnique).mockRejectedValue("string error")
|
||||
|
||||
const result = await getOrganizationMembers("user-1", "org-1")
|
||||
|
||||
|
|
|
|||
|
|
@ -18,28 +18,34 @@ export const getOrganizationMembers = withTiming(
|
|||
"getOrganizationMembers",
|
||||
async (currentUserId: string, organizationId: string): Promise<ActionResponse<Member[]>> => {
|
||||
try {
|
||||
const org = await prisma.organizations.findFirst({
|
||||
where: { id: organizationId },
|
||||
include: {
|
||||
organization_members: {
|
||||
include: {
|
||||
user: { select: { user_id: true, github_username: true, name: true, email: true } },
|
||||
},
|
||||
orderBy: {
|
||||
added_at: "asc",
|
||||
// Check access via indexed composite key in parallel with member fetch
|
||||
const [org, accessCheck] = await Promise.all([
|
||||
prisma.organizations.findUnique({
|
||||
where: { id: organizationId },
|
||||
include: {
|
||||
organization_members: {
|
||||
include: {
|
||||
user: { select: { user_id: true, github_username: true, name: true, email: true } },
|
||||
},
|
||||
orderBy: {
|
||||
added_at: "asc",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}),
|
||||
prisma.organization_members.findUnique({
|
||||
where: {
|
||||
organization_id_user_id: { organization_id: organizationId, user_id: currentUserId },
|
||||
},
|
||||
select: { id: true },
|
||||
}),
|
||||
])
|
||||
|
||||
if (!org) {
|
||||
return createErrorResponse("Organization not found")
|
||||
}
|
||||
|
||||
// Check if user has access
|
||||
const hasAccess = org.organization_members.some(m => m.user_id === currentUserId)
|
||||
|
||||
if (!hasAccess) {
|
||||
if (!accessCheck) {
|
||||
return createErrorResponse("You don't have access to this organization")
|
||||
}
|
||||
|
||||
|
|
@ -72,20 +78,33 @@ export async function addOrganizationMember(
|
|||
organizationId: string,
|
||||
): Promise<ActionResponse<Member>> {
|
||||
try {
|
||||
const org = await prisma.organizations.findFirst({
|
||||
where: { id: organizationId },
|
||||
include: {
|
||||
organization_members: true,
|
||||
},
|
||||
})
|
||||
const invitedUserId = `github|${invitedUser.githubUserId.toString()}`
|
||||
|
||||
if (!org) {
|
||||
// Verify org exists and check permissions + duplicate in parallel using indexed lookups
|
||||
// instead of loading the entire organization_members array
|
||||
const [orgExists, currentUserMember, existingMember] = await Promise.all([
|
||||
prisma.organizations.findUnique({
|
||||
where: { id: organizationId },
|
||||
select: { id: true },
|
||||
}),
|
||||
prisma.organization_members.findUnique({
|
||||
where: {
|
||||
organization_id_user_id: { organization_id: organizationId, user_id: currentUserId },
|
||||
},
|
||||
select: { role: true },
|
||||
}),
|
||||
prisma.organization_members.findUnique({
|
||||
where: {
|
||||
organization_id_user_id: { organization_id: organizationId, user_id: invitedUserId },
|
||||
},
|
||||
select: { id: true },
|
||||
}),
|
||||
])
|
||||
|
||||
if (!orgExists) {
|
||||
return createErrorResponse("Organization not found")
|
||||
}
|
||||
|
||||
const currentUserMember = org.organization_members.find(m => m.user_id === currentUserId)
|
||||
|
||||
// Check if user has permission to add members
|
||||
const isAdmin = currentUserMember?.role === "admin" || currentUserMember?.role === "owner"
|
||||
|
||||
|
|
@ -93,9 +112,7 @@ export async function addOrganizationMember(
|
|||
return createErrorResponse("You don't have permission to add members")
|
||||
}
|
||||
|
||||
// Check if member already exists by username
|
||||
const existingMember = org.organization_members.find(m => m.user_id === invitedUserId)
|
||||
|
||||
// Check if member already exists
|
||||
if (existingMember) {
|
||||
return createErrorResponse("User is already a member of this organization")
|
||||
}
|
||||
|
|
@ -157,35 +174,34 @@ export async function updateOrganizationMemberRole(
|
|||
newRole: "admin" | "member" | "owner",
|
||||
): Promise<ActionResponse<Boolean>> {
|
||||
try {
|
||||
const org = await prisma.organizations.findFirst({
|
||||
where: { id: organizationId },
|
||||
include: {
|
||||
organization_members: true,
|
||||
},
|
||||
})
|
||||
// Fetch only the two specific members we need instead of loading ALL members
|
||||
const [currentUserMember, targetMember] = await Promise.all([
|
||||
prisma.organization_members.findUnique({
|
||||
where: {
|
||||
organization_id_user_id: { organization_id: organizationId, user_id: currentUserId },
|
||||
},
|
||||
select: { role: true },
|
||||
}),
|
||||
prisma.organization_members.findUnique({
|
||||
where: { id: memberId },
|
||||
select: { id: true, role: true, user_id: true },
|
||||
}),
|
||||
])
|
||||
|
||||
if (!org) {
|
||||
if (!currentUserMember) {
|
||||
return createErrorResponse("Organization not found")
|
||||
}
|
||||
|
||||
const currentUserMember = org.organization_members.find(m => m.user_id === currentUserId)
|
||||
|
||||
// Only admins and owners can change roles
|
||||
if (currentUserMember?.role !== "admin" && currentUserMember?.role !== "owner") {
|
||||
if (currentUserMember.role !== "admin" && currentUserMember.role !== "owner") {
|
||||
return createErrorResponse("Only admins can change member roles")
|
||||
}
|
||||
|
||||
// Don't allow changing owner role
|
||||
const targetMember = org.organization_members.find(m => m.id === memberId)
|
||||
if (targetMember?.role === "owner") {
|
||||
return createErrorResponse("Cannot change owner role")
|
||||
}
|
||||
|
||||
// Don't allow changing own role if you're the only admin
|
||||
const adminCount = org.organization_members.filter(
|
||||
m => m.role === "admin" || m.role === "owner",
|
||||
).length
|
||||
|
||||
if (targetMember?.user_id === currentUserId) {
|
||||
return createErrorResponse("Cannot change your own role as the only admin")
|
||||
}
|
||||
|
|
@ -211,19 +227,19 @@ export async function removeOrganizationMember(
|
|||
memberId: string,
|
||||
): Promise<ActionResponse<Boolean>> {
|
||||
try {
|
||||
const org = await prisma.organizations.findFirst({
|
||||
where: { id: organizationId },
|
||||
include: {
|
||||
organization_members: true,
|
||||
},
|
||||
})
|
||||
|
||||
if (!org) {
|
||||
return createErrorResponse("Organization not found")
|
||||
}
|
||||
|
||||
const currentUserMember = org.organization_members.find(m => m.user_id === currentUserId)
|
||||
const targetMember = org.organization_members.find(m => m.id === memberId)
|
||||
// Fetch only the two specific members we need instead of loading ALL members
|
||||
const [currentUserMember, targetMember] = await Promise.all([
|
||||
prisma.organization_members.findUnique({
|
||||
where: {
|
||||
organization_id_user_id: { organization_id: organizationId, user_id: currentUserId },
|
||||
},
|
||||
select: { role: true },
|
||||
}),
|
||||
prisma.organization_members.findUnique({
|
||||
where: { id: memberId },
|
||||
select: { id: true, role: true, user_id: true },
|
||||
}),
|
||||
])
|
||||
|
||||
if (!targetMember) {
|
||||
return createErrorResponse("Member not found")
|
||||
|
|
@ -266,11 +282,11 @@ export async function getCurrentUserRole(
|
|||
organizationId: string,
|
||||
): Promise<ActionResponse<{ role: UserRole }>> {
|
||||
try {
|
||||
const member = await prisma.organization_members.findFirst({
|
||||
const member = await prisma.organization_members.findUnique({
|
||||
where: {
|
||||
organization_id: organizationId,
|
||||
user_id: userId,
|
||||
organization_id_user_id: { organization_id: organizationId, user_id: userId },
|
||||
},
|
||||
select: { role: true },
|
||||
})
|
||||
|
||||
if (!member) {
|
||||
|
|
|
|||
|
|
@ -26,17 +26,22 @@ export async function getMembersPageInitData() {
|
|||
|
||||
// Fetch org with members + current user's role in parallel
|
||||
const [org, roleMember] = await Promise.all([
|
||||
prisma.organizations.findFirst({
|
||||
prisma.organizations.findUnique({
|
||||
where: { id: orgId },
|
||||
include: {
|
||||
organization_members: {
|
||||
include: { user: true },
|
||||
include: {
|
||||
user: {
|
||||
select: { user_id: true, github_username: true, name: true, email: true },
|
||||
},
|
||||
},
|
||||
orderBy: { added_at: "asc" },
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.organization_members.findFirst({
|
||||
where: { organization_id: orgId, user_id: userId },
|
||||
prisma.organization_members.findUnique({
|
||||
where: { organization_id_user_id: { organization_id: orgId, user_id: userId } },
|
||||
select: { role: true },
|
||||
}),
|
||||
])
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ describe("getRepositoryById", () => {
|
|||
|
||||
describe("parallel fetch", () => {
|
||||
it("fetches repo and authorized repoIds concurrently", async () => {
|
||||
vi.mocked(prisma.repositories.findFirst).mockResolvedValue(mockRepo as any)
|
||||
vi.mocked(prisma.repositories.findUnique).mockResolvedValue(mockRepo as any)
|
||||
vi.mocked(getRepositoriesForAccountCached).mockResolvedValue({
|
||||
repoIds: ["repo-1"],
|
||||
repos: [],
|
||||
|
|
@ -51,12 +51,12 @@ describe("getRepositoryById", () => {
|
|||
|
||||
await getRepositoryById(mockPayload as any, "repo-1")
|
||||
|
||||
expect(prisma.repositories.findFirst).toHaveBeenCalledTimes(1)
|
||||
expect(prisma.repositories.findUnique).toHaveBeenCalledTimes(1)
|
||||
expect(getRepositoriesForAccountCached).toHaveBeenCalledWith(mockPayload)
|
||||
})
|
||||
|
||||
it("returns null when repo is not found", async () => {
|
||||
vi.mocked(prisma.repositories.findFirst).mockResolvedValue(null)
|
||||
vi.mocked(prisma.repositories.findUnique).mockResolvedValue(null)
|
||||
vi.mocked(getRepositoriesForAccountCached).mockResolvedValue({
|
||||
repoIds: ["repo-1"],
|
||||
repos: [],
|
||||
|
|
@ -67,7 +67,7 @@ describe("getRepositoryById", () => {
|
|||
})
|
||||
|
||||
it("returns null when repo is not in authorized list", async () => {
|
||||
vi.mocked(prisma.repositories.findFirst).mockResolvedValue(mockRepo as any)
|
||||
vi.mocked(prisma.repositories.findUnique).mockResolvedValue(mockRepo as any)
|
||||
vi.mocked(getRepositoriesForAccountCached).mockResolvedValue({
|
||||
repoIds: ["other-repo"],
|
||||
repos: [],
|
||||
|
|
@ -80,7 +80,7 @@ describe("getRepositoryById", () => {
|
|||
|
||||
describe("successful retrieval", () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(prisma.repositories.findFirst).mockResolvedValue(mockRepo as any)
|
||||
vi.mocked(prisma.repositories.findUnique).mockResolvedValue(mockRepo as any)
|
||||
vi.mocked(getRepositoriesForAccountCached).mockResolvedValue({
|
||||
repoIds: ["repo-1"],
|
||||
repos: [],
|
||||
|
|
@ -127,7 +127,7 @@ describe("getRepositoryById", () => {
|
|||
|
||||
describe("analytics tracking", () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(prisma.repositories.findFirst).mockResolvedValue(mockRepo as any)
|
||||
vi.mocked(prisma.repositories.findUnique).mockResolvedValue(mockRepo as any)
|
||||
vi.mocked(getRepositoriesForAccountCached).mockResolvedValue({
|
||||
repoIds: ["repo-1"],
|
||||
repos: [],
|
||||
|
|
@ -148,9 +148,7 @@ describe("getRepositoryById", () => {
|
|||
describe("error handling", () => {
|
||||
it("returns null and logs when Prisma throws", async () => {
|
||||
vi.spyOn(console, "error").mockImplementation(() => {})
|
||||
vi.mocked(prisma.repositories.findFirst).mockRejectedValue(
|
||||
new Error("timeout"),
|
||||
)
|
||||
vi.mocked(prisma.repositories.findUnique).mockRejectedValue(new Error("timeout"))
|
||||
vi.mocked(getRepositoriesForAccountCached).mockResolvedValue({
|
||||
repoIds: ["repo-1"],
|
||||
repos: [],
|
||||
|
|
|
|||
|
|
@ -12,43 +12,34 @@ import { trackMemberInvited, trackRepositoryConnected } from "@/lib/analytics/tr
|
|||
|
||||
export async function getOptimizationsTimeSeriesData(repoId: string, onlySuccessful?: boolean) {
|
||||
try {
|
||||
const data = await prisma.optimization_events.findMany({
|
||||
where: {
|
||||
...(onlySuccessful === true ? { is_optimization_found: true } : {}),
|
||||
repository_id: repoId,
|
||||
},
|
||||
select: {
|
||||
created_at: true,
|
||||
},
|
||||
})
|
||||
// Use SQL GROUP BY to aggregate on the database side instead of fetching every row
|
||||
const successFilter = onlySuccessful === true ? "AND is_optimization_found = true" : ""
|
||||
const dailyCounts = await prisma.$queryRawUnsafe<Array<{ day: string; cnt: bigint }>>(
|
||||
`SELECT DATE(created_at) AS day, COUNT(*)::bigint AS cnt
|
||||
FROM optimization_events
|
||||
WHERE repository_id = $1 ${successFilter}
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY day`,
|
||||
repoId,
|
||||
)
|
||||
|
||||
if (dailyCounts.length === 0) return []
|
||||
|
||||
const groupedByDay: Record<string, number> = {}
|
||||
for (const row of dailyCounts) {
|
||||
// DATE columns come back as Date objects from Prisma; format to YYYY-MM-DD
|
||||
const dayStr =
|
||||
typeof row.day === "string"
|
||||
? row.day
|
||||
: (row.day as unknown as Date).toISOString().slice(0, 10)
|
||||
groupedByDay[dayStr] = Number(row.cnt)
|
||||
}
|
||||
|
||||
data.forEach(item => {
|
||||
const day = item.created_at
|
||||
.toLocaleDateString(undefined, {
|
||||
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
})
|
||||
.replace(/(\d{2})\/(\d{2})\/(\d{4})/, "$3-$1-$2")
|
||||
groupedByDay[day] = (groupedByDay[day] || 0) + 1
|
||||
})
|
||||
|
||||
const sortedDays = Object.keys(groupedByDay).sort()
|
||||
const allDates = eachDayOfInterval({
|
||||
start: new Date(Object.keys(groupedByDay).sort()[0]),
|
||||
start: new Date(sortedDays[0] + "T00:00:00"),
|
||||
end: startOfDay(new Date()),
|
||||
}).map(d =>
|
||||
d
|
||||
.toLocaleDateString(undefined, {
|
||||
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
})
|
||||
.replace(/(\d{2})\/(\d{2})\/(\d{4})/, "$3-$1-$2"),
|
||||
)
|
||||
}).map(d => d.toISOString().slice(0, 10))
|
||||
|
||||
let cumulativeCount = 0
|
||||
const completeData = allDates.map(date => {
|
||||
|
|
@ -65,45 +56,43 @@ export async function getOptimizationsTimeSeriesData(repoId: string, onlySuccess
|
|||
|
||||
export async function getPullRequestEventTimeSeriesData(year: number, repoId: string) {
|
||||
try {
|
||||
const eventTypes = ["pr_created", "pr_merged", "pr_closed"]
|
||||
const data = await prisma.optimization_events.findMany({
|
||||
where: {
|
||||
event_type: { in: eventTypes },
|
||||
created_at: {
|
||||
gte: new Date(`${year}-01-01T00:00:00.000Z`),
|
||||
lt: new Date(`${year + 1}-01-01T00:00:00.000Z`),
|
||||
},
|
||||
repository_id: repoId,
|
||||
},
|
||||
select: {
|
||||
event_type: true,
|
||||
created_at: true,
|
||||
},
|
||||
})
|
||||
// Use SQL GROUP BY to aggregate on the database side instead of fetching every row
|
||||
const monthlyStats = await prisma.$queryRawUnsafe<
|
||||
Array<{
|
||||
month: number
|
||||
pr_created: bigint
|
||||
pr_merged: bigint
|
||||
pr_closed: bigint
|
||||
}>
|
||||
>(
|
||||
`SELECT
|
||||
EXTRACT(MONTH FROM created_at)::int AS month,
|
||||
SUM(CASE WHEN event_type = 'pr_created' THEN 1 ELSE 0 END)::bigint AS pr_created,
|
||||
SUM(CASE WHEN event_type = 'pr_merged' THEN 1 ELSE 0 END)::bigint AS pr_merged,
|
||||
SUM(CASE WHEN event_type = 'pr_closed' THEN 1 ELSE 0 END)::bigint AS pr_closed
|
||||
FROM optimization_events
|
||||
WHERE event_type IN ('pr_created', 'pr_merged', 'pr_closed')
|
||||
AND created_at >= $1
|
||||
AND created_at < $2
|
||||
AND repository_id = $3
|
||||
GROUP BY EXTRACT(MONTH FROM created_at)`,
|
||||
new Date(`${year}-01-01T00:00:00.000Z`),
|
||||
new Date(`${year + 1}-01-01T00:00:00.000Z`),
|
||||
repoId,
|
||||
)
|
||||
|
||||
const groupedByMonth: Record<string, Record<string, number>> = {}
|
||||
const statsMap = new Map(monthlyStats.map(r => [r.month, r]))
|
||||
|
||||
for (let month = 1; month <= 12; month++) {
|
||||
const monthKey = `${year}-${month.toString().padStart(2, "0")}`
|
||||
groupedByMonth[monthKey] = { pr_created: 0, pr_merged: 0, pr_closed: 0 }
|
||||
}
|
||||
|
||||
data.forEach(item => {
|
||||
const month = item.created_at.getMonth() + 1
|
||||
const monthKey = `${year}-${month.toString().padStart(2, "0")}`
|
||||
if (groupedByMonth[monthKey]) {
|
||||
groupedByMonth[monthKey][item.event_type] += 1
|
||||
return Array.from({ length: 12 }, (_, i) => {
|
||||
const month = i + 1
|
||||
const stats = statsMap.get(month)
|
||||
return {
|
||||
month: `${year}-${month.toString().padStart(2, "0")}`,
|
||||
pr_created: Number(stats?.pr_created ?? 0),
|
||||
pr_merged: Number(stats?.pr_merged ?? 0),
|
||||
pr_closed: Number(stats?.pr_closed ?? 0),
|
||||
}
|
||||
})
|
||||
|
||||
const completeData = Object.keys(groupedByMonth).map(monthKey => ({
|
||||
month: monthKey,
|
||||
pr_created: groupedByMonth[monthKey].pr_created,
|
||||
pr_merged: groupedByMonth[monthKey].pr_merged,
|
||||
pr_closed: groupedByMonth[monthKey].pr_closed,
|
||||
}))
|
||||
|
||||
return completeData
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch pull request event time series data:", error)
|
||||
return []
|
||||
|
|
@ -127,6 +116,27 @@ export async function getUserOptimizationSuccessfulCountByRepo(repoId: string) {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get both total and successful optimization counts in a single query.
|
||||
* Callers that need both counts should prefer this over two separate calls.
|
||||
*/
|
||||
export async function getOptimizationCountsByRepo(
|
||||
repoId: string,
|
||||
): Promise<{ total: number; successful: number }> {
|
||||
const result = await prisma.$queryRawUnsafe<[{ total: bigint; successful: bigint }]>(
|
||||
`SELECT
|
||||
COUNT(*)::bigint AS total,
|
||||
SUM(CASE WHEN is_optimization_found THEN 1 ELSE 0 END)::bigint AS successful
|
||||
FROM optimization_events
|
||||
WHERE repository_id = $1`,
|
||||
repoId,
|
||||
)
|
||||
return {
|
||||
total: Number(result[0].total),
|
||||
successful: Number(result[0].successful),
|
||||
}
|
||||
}
|
||||
|
||||
export async function getActiveUserLeaderboardLast30DaysForRepo(
|
||||
repoId: string,
|
||||
): Promise<{ username: string; eventCount: number; avatarUrl: string }[]> {
|
||||
|
|
@ -166,7 +176,7 @@ export const getRepositoryById = withTiming(
|
|||
try {
|
||||
// Fetch repo, authorized repoIds, and recent activity count in parallel
|
||||
const [repo, { repoIds }, recentEventCount] = await Promise.all([
|
||||
prisma.repositories.findFirst({
|
||||
prisma.repositories.findUnique({
|
||||
where: { id: repoId },
|
||||
include: { _count: { select: { repository_members: true } } },
|
||||
}),
|
||||
|
|
@ -223,35 +233,40 @@ export async function addRepositoryMemberById(
|
|||
): Promise<ActionResponse> {
|
||||
try {
|
||||
const invitedUserId = `github|${invitedUser.githubUserId.toString()}`
|
||||
// Check if current user is admin or the only member
|
||||
const repo = await prisma.repositories.findFirst({
|
||||
where: { id: repoId },
|
||||
include: {
|
||||
repository_members: {
|
||||
include: {
|
||||
user: { select: { user_id: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if (!repo) {
|
||||
// Verify repo exists, check permissions, and check for duplicate in parallel
|
||||
// using indexed lookups instead of loading ALL members
|
||||
const [repoExists, currentUserMember, existingMember, memberCount] = await Promise.all([
|
||||
prisma.repositories.findUnique({
|
||||
where: { id: repoId },
|
||||
select: { id: true },
|
||||
}),
|
||||
prisma.repository_members.findUnique({
|
||||
where: { repository_id_user_id: { repository_id: repoId, user_id: currentUserId } },
|
||||
select: { role: true },
|
||||
}),
|
||||
prisma.repository_members.findUnique({
|
||||
where: { repository_id_user_id: { repository_id: repoId, user_id: invitedUserId } },
|
||||
select: { id: true },
|
||||
}),
|
||||
prisma.repository_members.count({
|
||||
where: { repository_id: repoId },
|
||||
}),
|
||||
])
|
||||
|
||||
if (!repoExists) {
|
||||
return createErrorResponse("Repository not found")
|
||||
}
|
||||
|
||||
const currentUserMember = repo.repository_members.find(m => m.user_id === currentUserId)
|
||||
|
||||
// Check if user has permission to add members
|
||||
const isAdmin = currentUserMember?.role === "admin" || currentUserMember?.role === "owner"
|
||||
const isOnlyMember = repo.repository_members.length === 1 && currentUserMember // if only member we need to let him add because we are was not manage well the member role
|
||||
const isOnlyMember = memberCount === 1 && currentUserMember // if only member we need to let him add because we are was not manage well the member role
|
||||
|
||||
if (!isAdmin && !isOnlyMember) {
|
||||
return createErrorResponse("You don't have permission to add members")
|
||||
}
|
||||
|
||||
// Check if member already exists by username
|
||||
const existingMember = repo.repository_members.find(m => m.user.user_id === invitedUserId)
|
||||
|
||||
// Check if member already exists
|
||||
if (existingMember) {
|
||||
return createErrorResponse("User is already a member of this repository")
|
||||
}
|
||||
|
|
@ -305,29 +320,29 @@ export async function getRepositoryMembers(
|
|||
repoId: string,
|
||||
): Promise<ActionResponse<Member[]>> {
|
||||
try {
|
||||
const repo = await prisma.repositories.findFirst({
|
||||
where: { id: repoId },
|
||||
include: {
|
||||
repository_members: {
|
||||
include: {
|
||||
user: { select: { user_id: true, github_username: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
// Check access with a single indexed lookup, then fetch members only if authorized
|
||||
const hasAccess = await prisma.repository_members.findUnique({
|
||||
where: { repository_id_user_id: { repository_id: repoId, user_id: currentUserId } },
|
||||
select: { id: true },
|
||||
})
|
||||
|
||||
if (!repo) {
|
||||
return createErrorResponse("Repository not found")
|
||||
}
|
||||
|
||||
// Check if user has access
|
||||
const hasAccess = repo.repository_members.some(m => m.user_id === currentUserId)
|
||||
|
||||
if (!hasAccess) {
|
||||
return createErrorResponse("You don't have access to this repository")
|
||||
}
|
||||
|
||||
const members: Member[] = repo.repository_members.map(member => ({
|
||||
// Now fetch all members (only needed fields)
|
||||
const repoMembers = await prisma.repository_members.findMany({
|
||||
where: { repository_id: repoId },
|
||||
select: {
|
||||
id: true,
|
||||
user_id: true,
|
||||
role: true,
|
||||
added_at: true,
|
||||
user: { select: { github_username: true } },
|
||||
},
|
||||
})
|
||||
|
||||
const members: Member[] = repoMembers.map(member => ({
|
||||
id: member.id,
|
||||
user_id: member.user_id,
|
||||
username: member.user.github_username,
|
||||
|
|
@ -354,26 +369,28 @@ export async function updateRepositoryMemberRole(
|
|||
newRole: UserRole,
|
||||
): Promise<ActionResponse<Boolean>> {
|
||||
try {
|
||||
const repo = await prisma.repositories.findFirst({
|
||||
where: { id: repoId },
|
||||
include: {
|
||||
repository_members: true,
|
||||
},
|
||||
})
|
||||
// Fetch only the two specific members we need instead of loading ALL repository members
|
||||
const [currentUserMember, targetMember] = await Promise.all([
|
||||
prisma.repository_members.findUnique({
|
||||
where: { repository_id_user_id: { repository_id: repoId, user_id: currentUserId } },
|
||||
select: { role: true },
|
||||
}),
|
||||
prisma.repository_members.findUnique({
|
||||
where: { id: memberId },
|
||||
select: { id: true, role: true, user_id: true },
|
||||
}),
|
||||
])
|
||||
|
||||
if (!repo) {
|
||||
if (!currentUserMember) {
|
||||
return createErrorResponse("Repository not found")
|
||||
}
|
||||
|
||||
const currentUserMember = repo.repository_members.find(m => m.user_id === currentUserId)
|
||||
|
||||
// Only admins and owners can change roles
|
||||
if (currentUserMember?.role !== "admin" && currentUserMember?.role !== "owner") {
|
||||
if (currentUserMember.role !== "admin" && currentUserMember.role !== "owner") {
|
||||
return createErrorResponse("Only admins can change member roles")
|
||||
}
|
||||
|
||||
// Don't allow changing owner role
|
||||
const targetMember = repo.repository_members.find(m => m.id === memberId)
|
||||
if (targetMember?.role === "owner") {
|
||||
return createErrorResponse("Cannot change owner role")
|
||||
}
|
||||
|
|
@ -404,19 +421,17 @@ export async function removeRepositoryMember(
|
|||
memberId: string,
|
||||
): Promise<ActionResponse<Boolean>> {
|
||||
try {
|
||||
const repo = await prisma.repositories.findFirst({
|
||||
where: { id: repoId },
|
||||
include: {
|
||||
repository_members: true,
|
||||
},
|
||||
})
|
||||
|
||||
if (!repo) {
|
||||
return createErrorResponse("Repository not found")
|
||||
}
|
||||
|
||||
const currentUserMember = repo.repository_members.find(m => m.user_id === currentUserId)
|
||||
const targetMember = repo.repository_members.find(m => m.id === memberId)
|
||||
// Fetch only the two specific members we need instead of loading ALL repository members
|
||||
const [currentUserMember, targetMember] = await Promise.all([
|
||||
prisma.repository_members.findUnique({
|
||||
where: { repository_id_user_id: { repository_id: repoId, user_id: currentUserId } },
|
||||
select: { role: true },
|
||||
}),
|
||||
prisma.repository_members.findUnique({
|
||||
where: { id: memberId },
|
||||
select: { id: true, role: true, user_id: true },
|
||||
}),
|
||||
])
|
||||
|
||||
if (!targetMember) {
|
||||
return createErrorResponse("Member not found")
|
||||
|
|
|
|||
|
|
@ -8,28 +8,29 @@ export const getRepositoriesWithStagingEvents = withTiming(
|
|||
async (payload: AccountPayload): Promise<Array<{ id: string; full_name: string }>> => {
|
||||
const { repoIds, repos: allRepos } = await getRepositoriesForAccountCached(payload)
|
||||
|
||||
if (repoIds.length === 0) {
|
||||
return []
|
||||
}
|
||||
if (repoIds.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Get distinct repository IDs that have staging events using groupBy (more efficient than findMany with distinct)
|
||||
const repoIdsWithStagingEvents = await prisma.optimization_events.groupBy({
|
||||
by: ["repository_id"],
|
||||
where: {
|
||||
is_staging: true,
|
||||
...buildOptimizationOrCondition(payload, repoIds),
|
||||
repository_id: { not: null },
|
||||
},
|
||||
})
|
||||
// Get distinct repository IDs that have staging events using groupBy (more efficient than findMany with distinct)
|
||||
const repoIdsWithStagingEvents = await prisma.optimization_events.groupBy({
|
||||
by: ["repository_id"],
|
||||
where: {
|
||||
is_staging: true,
|
||||
...buildOptimizationOrCondition(payload, repoIds),
|
||||
repository_id: { not: null },
|
||||
},
|
||||
})
|
||||
|
||||
// Filter and map repos that have staging events
|
||||
return allRepos
|
||||
.filter(repo => repoIdsWithStagingEvents.some(group => group.repository_id === repo.id))
|
||||
.map(repo => ({
|
||||
id: repo.id,
|
||||
full_name: repo.full_name,
|
||||
}))
|
||||
.sort((a, b) => a.full_name.localeCompare(b.full_name))
|
||||
// Filter and map repos that have staging events (O(1) Set lookup instead of O(n) .some)
|
||||
const stagingRepoSet = new Set(repoIdsWithStagingEvents.map(g => g.repository_id))
|
||||
return allRepos
|
||||
.filter(repo => stagingRepoSet.has(repo.id))
|
||||
.map(repo => ({
|
||||
id: repo.id,
|
||||
full_name: repo.full_name,
|
||||
}))
|
||||
.sort((a, b) => a.full_name.localeCompare(b.full_name))
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -50,112 +51,112 @@ export const getAllOptimizationEvents = withTiming(
|
|||
page?: number
|
||||
pageSize?: number
|
||||
}) => {
|
||||
const repoIds = (await getRepositoriesForAccountCached(payload)).repoIds
|
||||
const repoIds = (await getRepositoriesForAccountCached(payload)).repoIds
|
||||
|
||||
const where: any = {
|
||||
is_staging: true,
|
||||
...buildOptimizationOrCondition(payload, repoIds),
|
||||
}
|
||||
const where: any = {
|
||||
is_staging: true,
|
||||
...buildOptimizationOrCondition(payload, repoIds),
|
||||
}
|
||||
|
||||
if (search) {
|
||||
where.AND = where.AND || []
|
||||
where.AND.push({
|
||||
OR: [
|
||||
{
|
||||
function_name: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
{
|
||||
file_path: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
{
|
||||
repository: {
|
||||
full_name: {
|
||||
if (search) {
|
||||
where.AND = where.AND || []
|
||||
where.AND.push({
|
||||
OR: [
|
||||
{
|
||||
function_name: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
{
|
||||
file_path: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
{
|
||||
repository: {
|
||||
full_name: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
if (filter) {
|
||||
Object.keys(filter).forEach(key => {
|
||||
if (key === "repository_id") {
|
||||
where.AND = where.AND || []
|
||||
where.AND.push({ [key]: filter[key] })
|
||||
} else if (key !== "review_quality") {
|
||||
where[key] = filter[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
if (filter) {
|
||||
Object.keys(filter).forEach(key => {
|
||||
if (key === "repository_id") {
|
||||
where.AND = where.AND || []
|
||||
where.AND.push({ [key]: filter[key] })
|
||||
} else if (key !== "review_quality") {
|
||||
where[key] = filter[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const needsOptimizationFeaturesJoin =
|
||||
(sort && Object.keys(sort).some(k => k.toLowerCase() === "review_quality")) ||
|
||||
(filter && Object.keys(filter).some(k => k.toLowerCase() === "review_quality"))
|
||||
const needsOptimizationFeaturesJoin =
|
||||
(sort && Object.keys(sort).some(k => k.toLowerCase() === "review_quality")) ||
|
||||
(filter && Object.keys(filter).some(k => k.toLowerCase() === "review_quality"))
|
||||
|
||||
if (needsOptimizationFeaturesJoin) {
|
||||
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(
|
||||
`(
|
||||
if (needsOptimizationFeaturesJoin) {
|
||||
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} OR r.full_name ILIKE $${paramIndex})`,
|
||||
)
|
||||
params.push(`%${search}%`)
|
||||
paramIndex += 1
|
||||
}
|
||||
// Add filter conditions
|
||||
if (filter) {
|
||||
if (filter.status) {
|
||||
whereConditions.push(`oe.status = $${paramIndex}`)
|
||||
params.push(filter.status)
|
||||
)
|
||||
}
|
||||
// Add search conditions
|
||||
if (search) {
|
||||
whereConditions.push(
|
||||
`(oe.function_name ILIKE $${paramIndex} OR oe.file_path ILIKE $${paramIndex} OR r.full_name ILIKE $${paramIndex})`,
|
||||
)
|
||||
params.push(`%${search}%`)
|
||||
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`)
|
||||
// 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(`
|
||||
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
|
||||
|
|
@ -163,18 +164,18 @@ export const getAllOptimizationEvents = withTiming(
|
|||
ELSE 0
|
||||
END ${dir}
|
||||
`)
|
||||
} else {
|
||||
orderByClauses.push(`oe.${key} ${dir}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
if (!sort) {
|
||||
orderByClauses.push("oe.created_at DESC")
|
||||
}
|
||||
const orderByClause = orderByClauses.join(", ")
|
||||
const [events, countResult] = await Promise.all([
|
||||
prisma.$queryRawUnsafe<any[]>(
|
||||
`
|
||||
} else {
|
||||
orderByClauses.push(`oe.${key} ${dir}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
if (!sort) {
|
||||
orderByClauses.push("oe.created_at DESC")
|
||||
}
|
||||
const orderByClause = orderByClauses.join(", ")
|
||||
const [events, countResult] = await Promise.all([
|
||||
prisma.$queryRawUnsafe<any[]>(
|
||||
`
|
||||
SELECT
|
||||
oe.*,
|
||||
of.review_quality,
|
||||
|
|
@ -189,67 +190,69 @@ export const getAllOptimizationEvents = withTiming(
|
|||
ORDER BY ${orderByClause}
|
||||
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
|
||||
`,
|
||||
...params,
|
||||
pageSize,
|
||||
(page - 1) * pageSize,
|
||||
),
|
||||
prisma.$queryRawUnsafe<[{ count: bigint }]>(
|
||||
`
|
||||
...params,
|
||||
pageSize,
|
||||
(page - 1) * pageSize,
|
||||
),
|
||||
prisma.$queryRawUnsafe<[{ count: bigint }]>(
|
||||
`
|
||||
SELECT COUNT(*) as count
|
||||
FROM optimization_events oe
|
||||
LEFT JOIN optimization_features of ON oe.trace_id = of.trace_id
|
||||
LEFT JOIN repositories r ON oe.repository_id = r.id
|
||||
WHERE ${whereClause}
|
||||
`,
|
||||
...params,
|
||||
),
|
||||
])
|
||||
const totalCount = Number(countResult[0].count)
|
||||
// Repository data is already included from the JOIN
|
||||
const eventsWithRepo = events.map(event => ({
|
||||
...event,
|
||||
repository: event.repo_id ? { id: event.repo_id, full_name: event.repo_full_name, name: event.repo_name } : null,
|
||||
}))
|
||||
return { events: eventsWithRepo, totalCount }
|
||||
} else {
|
||||
// Standard Prisma query with native orderBy
|
||||
const orderBy = sort || { created_at: "desc" }
|
||||
|
||||
const [events, totalCount] = await Promise.all([
|
||||
prisma.optimization_events.findMany({
|
||||
where,
|
||||
orderBy,
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
include: {
|
||||
repository: true,
|
||||
},
|
||||
}),
|
||||
prisma.optimization_events.count({ where }),
|
||||
])
|
||||
|
||||
// Batch-fetch review data for all events in a single query
|
||||
const traceIds = events.map(e => e.trace_id)
|
||||
const features = await prisma.optimization_features.findMany({
|
||||
where: { trace_id: { in: traceIds } },
|
||||
select: {
|
||||
trace_id: true,
|
||||
review_quality: true,
|
||||
review_explanation: true,
|
||||
},
|
||||
})
|
||||
const featuresMap = new Map(features.map(f => [f.trace_id, f]))
|
||||
|
||||
const eventsWithReviewData = events.map(event => {
|
||||
const f = featuresMap.get(event.trace_id)
|
||||
return {
|
||||
...params,
|
||||
),
|
||||
])
|
||||
const totalCount = Number(countResult[0].count)
|
||||
// Repository data is already included from the JOIN
|
||||
const eventsWithRepo = events.map(event => ({
|
||||
...event,
|
||||
review_quality: f?.review_quality || null,
|
||||
review_explanation: f?.review_explanation || null,
|
||||
}
|
||||
})
|
||||
repository: event.repo_id
|
||||
? { id: event.repo_id, full_name: event.repo_full_name, name: event.repo_name }
|
||||
: null,
|
||||
}))
|
||||
return { events: eventsWithRepo, totalCount }
|
||||
} else {
|
||||
// Standard Prisma query with native orderBy
|
||||
const orderBy = sort || { created_at: "desc" }
|
||||
|
||||
return { events: eventsWithReviewData, totalCount }
|
||||
}
|
||||
const [events, totalCount] = await Promise.all([
|
||||
prisma.optimization_events.findMany({
|
||||
where,
|
||||
orderBy,
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
include: {
|
||||
repository: true,
|
||||
},
|
||||
}),
|
||||
prisma.optimization_events.count({ where }),
|
||||
])
|
||||
|
||||
// Batch-fetch review data for all events in a single query
|
||||
const traceIds = events.map(e => e.trace_id)
|
||||
const features = await prisma.optimization_features.findMany({
|
||||
where: { trace_id: { in: traceIds } },
|
||||
select: {
|
||||
trace_id: true,
|
||||
review_quality: true,
|
||||
review_explanation: true,
|
||||
},
|
||||
})
|
||||
const featuresMap = new Map(features.map(f => [f.trace_id, f]))
|
||||
|
||||
const eventsWithReviewData = events.map(event => {
|
||||
const f = featuresMap.get(event.trace_id)
|
||||
return {
|
||||
...event,
|
||||
review_quality: f?.review_quality || null,
|
||||
review_explanation: f?.review_explanation || null,
|
||||
}
|
||||
})
|
||||
|
||||
return { events: eventsWithReviewData, totalCount }
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -61,12 +61,12 @@ export async function updateOrgPrivacyMode(
|
|||
privacyMode: boolean,
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
try {
|
||||
// Check if user is admin or owner
|
||||
const member = await prisma.organization_members.findFirst({
|
||||
// Check if user is admin or owner (uses unique composite index)
|
||||
const member = await prisma.organization_members.findUnique({
|
||||
where: {
|
||||
organization_id: organizationId,
|
||||
user_id: userId,
|
||||
organization_id_user_id: { organization_id: organizationId, user_id: userId },
|
||||
},
|
||||
select: { role: true },
|
||||
})
|
||||
|
||||
if (!member || (member.role !== "admin" && member.role !== "owner")) {
|
||||
|
|
|
|||
|
|
@ -35,8 +35,10 @@ export async function getRecentTraces(): Promise<RecentTrace[]> {
|
|||
events.map(e => [e.trace_id, { functionName: e.function_name, filePath: e.file_path }]),
|
||||
)
|
||||
|
||||
const traceMap = new Map(distinctTraces.map(t => [t.trace_id, t]))
|
||||
|
||||
return traceIds.map(id => {
|
||||
const trace = distinctTraces.find(t => t.trace_id === id)!
|
||||
const trace = traceMap.get(id)!
|
||||
return {
|
||||
traceId: id,
|
||||
lastSeenAt: trace._max.created_at ?? new Date(),
|
||||
|
|
|
|||
|
|
@ -38,6 +38,18 @@ async function getUniqueOrganizations() {
|
|||
.sort() as string[]
|
||||
}
|
||||
|
||||
async function getOrgTraceIds(organization: string) {
|
||||
"use cache"
|
||||
cacheLife("frequent")
|
||||
|
||||
const orgFeatures = await prisma.optimization_features.findMany({
|
||||
where: { organization },
|
||||
select: { trace_id: true },
|
||||
distinct: ["trace_id"],
|
||||
})
|
||||
return orgFeatures.map(f => f.trace_id).filter(Boolean) as string[]
|
||||
}
|
||||
|
||||
async function getCallTypes() {
|
||||
"use cache"
|
||||
cacheLife({ stale: 60, revalidate: 300, expire: 1800 })
|
||||
|
|
@ -95,15 +107,10 @@ export default async function LLMCallsPage(props: { searchParams: Promise<Search
|
|||
// Get unique organizations for filter dropdown (cached)
|
||||
const uniqueOrganizations = await getUniqueOrganizations()
|
||||
|
||||
// If organization filter is specified, get matching trace_ids
|
||||
// If organization filter is specified, get matching trace_ids (cached)
|
||||
let filteredTraceIds: string[] = []
|
||||
if (searchParams.organization) {
|
||||
const orgFeatures = await prisma.optimization_features.findMany({
|
||||
where: { organization: searchParams.organization },
|
||||
select: { trace_id: true },
|
||||
distinct: ["trace_id"],
|
||||
})
|
||||
filteredTraceIds = orgFeatures.map(f => f.trace_id).filter(Boolean) as string[]
|
||||
filteredTraceIds = await getOrgTraceIds(searchParams.organization)
|
||||
|
||||
// If organization filter is applied but no traces found, return empty result early
|
||||
if (filteredTraceIds.length === 0) {
|
||||
|
|
@ -254,9 +261,9 @@ export default async function LLMCallsPage(props: { searchParams: Promise<Search
|
|||
where.trace_id = { in: filteredTraceIds }
|
||||
}
|
||||
|
||||
// Fetch LLM calls with pagination and count
|
||||
// Batched into groups of 2 to avoid exhausting the connection pool
|
||||
const [llmCalls, totalCount] = await Promise.all([
|
||||
// Fetch LLM calls, count, aggregate stats, and success count — all in parallel
|
||||
// (none depends on another's result)
|
||||
const [llmCalls, totalCount, aggregateStats, successCount] = await Promise.all([
|
||||
prisma.llm_calls.findMany({
|
||||
where,
|
||||
orderBy: { created_at: "desc" },
|
||||
|
|
@ -281,10 +288,6 @@ export default async function LLMCallsPage(props: { searchParams: Promise<Search
|
|||
},
|
||||
}),
|
||||
prisma.llm_calls.count({ where }),
|
||||
])
|
||||
|
||||
// Aggregate stats and success count (second batch)
|
||||
const [aggregateStats, successCount] = await Promise.all([
|
||||
prisma.llm_calls.aggregate({
|
||||
where,
|
||||
_sum: {
|
||||
|
|
|
|||
|
|
@ -316,10 +316,11 @@ async function TracesPageContent({
|
|||
// Extract trace_ids from the paginated results
|
||||
const paginatedTraceIds = distinctTraces.map(t => t.trace_id).filter(Boolean) as string[]
|
||||
|
||||
// STEP 2: Fetch all LLM calls ONLY for the paginated trace_ids
|
||||
const llmCallsRaw =
|
||||
// STEP 2: Fetch LLM calls and optimization features in parallel
|
||||
// (both depend only on paginatedTraceIds, not each other)
|
||||
const [llmCallsRaw, allOptimizationFeatures] = await Promise.all([
|
||||
paginatedTraceIds.length > 0
|
||||
? await prisma.llm_calls.findMany({
|
||||
? prisma.llm_calls.findMany({
|
||||
where: { trace_id: { in: paginatedTraceIds } },
|
||||
orderBy: { created_at: "desc" },
|
||||
select: {
|
||||
|
|
@ -331,13 +332,7 @@ async function TracesPageContent({
|
|||
call_type: true,
|
||||
},
|
||||
})
|
||||
: []
|
||||
|
||||
// Filter out null trace_ids
|
||||
const llmCalls = llmCallsRaw.filter(call => call.trace_id !== null)
|
||||
|
||||
// Fetch organizations ONLY for the paginated trace_ids
|
||||
const [allOptimizationFeatures] = await Promise.all([
|
||||
: [],
|
||||
paginatedTraceIds.length > 0
|
||||
? prisma.optimization_features.findMany({
|
||||
where: { trace_id: { in: paginatedTraceIds } },
|
||||
|
|
@ -349,6 +344,9 @@ async function TracesPageContent({
|
|||
: [],
|
||||
])
|
||||
|
||||
// Filter out null trace_ids
|
||||
const llmCalls = llmCallsRaw.filter(call => call.trace_id !== null)
|
||||
|
||||
// Create a map of trace_id to organization
|
||||
const traceIdToOrganization = new Map<string, string>()
|
||||
allOptimizationFeatures.forEach(feature => {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ vi.mock("@codeflash-ai/common", () => {
|
|||
const mockPrisma = {
|
||||
organizations: {
|
||||
findFirst: vi.fn(),
|
||||
findUnique: vi.fn(),
|
||||
},
|
||||
optimization_events: {
|
||||
findMany: vi.fn(),
|
||||
|
|
@ -20,13 +21,16 @@ vi.mock("@codeflash-ai/common", () => {
|
|||
},
|
||||
repositories: {
|
||||
findFirst: vi.fn(),
|
||||
findUnique: vi.fn(),
|
||||
},
|
||||
organization_members: {
|
||||
findFirst: vi.fn(),
|
||||
findUnique: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
},
|
||||
repository_members: {
|
||||
findUnique: vi.fn(),
|
||||
create: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
},
|
||||
|
|
@ -53,9 +57,7 @@ vi.mock("@codeflash-ai/common", () => {
|
|||
// Mock: @sentry/nextjs
|
||||
// ---------------------------------------------------------------------------
|
||||
vi.mock("@sentry/nextjs", () => ({
|
||||
startSpan: vi.fn((_opts: any, callback: any) =>
|
||||
callback({ setAttribute: vi.fn() }),
|
||||
),
|
||||
startSpan: vi.fn((_opts: any, callback: any) => callback({ setAttribute: vi.fn() })),
|
||||
captureException: vi.fn(),
|
||||
}))
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue