Spaces:
Running
Running
Commit ·
f0cbaa4
1
Parent(s): 21a3ed1
apply new changes
Browse files- src/app/api/contributor/dashboard-summary/route.ts +41 -76
- src/db/index.ts +36 -15
- src/lib/db/queries/issues.ts +91 -67
src/app/api/contributor/dashboard-summary/route.ts
CHANGED
|
@@ -8,102 +8,67 @@
|
|
| 8 |
import { NextRequest, NextResponse } from "next/server";
|
| 9 |
import { getCurrentUser } from "@/lib/auth";
|
| 10 |
import { db } from "@/db";
|
| 11 |
-
import { issues
|
| 12 |
import { eq } from "drizzle-orm";
|
| 13 |
-
import { fetchGitHubContributions } from "@/lib/github-contributions";
|
| 14 |
|
| 15 |
export async function GET(request: NextRequest) {
|
| 16 |
try {
|
| 17 |
const user = await getCurrentUser(request);
|
| 18 |
if (!user) {
|
|
|
|
| 19 |
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
| 20 |
}
|
| 21 |
|
| 22 |
console.log("[Dashboard Summary] User:", user.username);
|
| 23 |
|
| 24 |
-
// Fetch
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
(
|
| 29 |
-
console.log("[Dashboard Summary] Fetching issues...");
|
| 30 |
-
const items = await db.select().from(issues).where(eq(issues.authorName, user.username));
|
| 31 |
-
console.log("[Dashboard Summary] Found %d issues", items.length);
|
| 32 |
-
return items;
|
| 33 |
-
})(),
|
| 34 |
-
// Get tracked repos
|
| 35 |
-
(async () => {
|
| 36 |
-
console.log("[Dashboard Summary] Fetching tracked repos...");
|
| 37 |
-
const repos = await db.select({ repoFullName: userRepositories.repoFullName })
|
| 38 |
-
.from(userRepositories)
|
| 39 |
-
.where(eq(userRepositories.userId, user.id));
|
| 40 |
-
console.log("[Dashboard Summary] Found %d tracked repos", repos.length);
|
| 41 |
-
return repos;
|
| 42 |
-
})(),
|
| 43 |
-
// Get user's GitHub token
|
| 44 |
-
(async () => {
|
| 45 |
-
console.log("[Dashboard Summary] Fetching user token...");
|
| 46 |
-
const userData = await db.select({ githubAccessToken: users.githubAccessToken })
|
| 47 |
-
.from(users)
|
| 48 |
-
.where(eq(users.id, user.id))
|
| 49 |
-
.limit(1);
|
| 50 |
-
console.log("[Dashboard Summary] User token fetch complete");
|
| 51 |
-
return userData;
|
| 52 |
-
})(),
|
| 53 |
-
// Fetch GitHub contributions (will use cache or make API call)
|
| 54 |
-
(async () => {
|
| 55 |
-
console.log("[Dashboard Summary] Fetching GitHub contributions...");
|
| 56 |
-
const ghData = await fetchGitHubContributions(user.username, null).catch(err => {
|
| 57 |
-
console.log("[Dashboard Summary] GitHub fetch failed:", err);
|
| 58 |
-
return null;
|
| 59 |
-
});
|
| 60 |
-
return ghData;
|
| 61 |
-
})()
|
| 62 |
-
]);
|
| 63 |
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
}
|
| 81 |
-
|
| 82 |
-
// Use GitHub API totalContributions if available, otherwise fallback to local count
|
| 83 |
-
const totalContributions = githubData?.totalContributions ?? allItems.length;
|
| 84 |
-
const dataSource = githubData ? 'github' : 'local';
|
| 85 |
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
})
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
} catch (error: any) {
|
| 100 |
-
console.error("
|
| 101 |
-
console.error("Error
|
| 102 |
-
console.error("Error
|
| 103 |
|
| 104 |
return NextResponse.json({
|
| 105 |
-
error: "Failed to fetch dashboard data
|
| 106 |
-
details: error?.message || "
|
| 107 |
}, { status: 500 });
|
| 108 |
}
|
| 109 |
}
|
|
|
|
| 8 |
import { NextRequest, NextResponse } from "next/server";
|
| 9 |
import { getCurrentUser } from "@/lib/auth";
|
| 10 |
import { db } from "@/db";
|
| 11 |
+
import { issues } from "@/db/schema";
|
| 12 |
import { eq } from "drizzle-orm";
|
|
|
|
| 13 |
|
| 14 |
export async function GET(request: NextRequest) {
|
| 15 |
try {
|
| 16 |
const user = await getCurrentUser(request);
|
| 17 |
if (!user) {
|
| 18 |
+
console.log("[Dashboard Summary] No authenticated user");
|
| 19 |
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
| 20 |
}
|
| 21 |
|
| 22 |
console.log("[Dashboard Summary] User:", user.username);
|
| 23 |
|
| 24 |
+
// Fetch all contributor data from the database
|
| 25 |
+
try {
|
| 26 |
+
console.log("[Dashboard Summary] Fetching issues for user:", user.username);
|
| 27 |
+
const allItems = await db.select().from(issues).where(eq(issues.authorName, user.username));
|
| 28 |
+
console.log("[Dashboard Summary] Found %d items", allItems.length);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
+
// Calculate metrics from database records
|
| 31 |
+
const prs = allItems.filter(item => item.isPR);
|
| 32 |
+
const issueItems = allItems.filter(item => !item.isPR);
|
| 33 |
|
| 34 |
+
// PR metrics
|
| 35 |
+
const openPRs = prs.filter(pr => pr.state === 'open').length;
|
| 36 |
+
const mergedPRs = prs.filter(pr => pr.state === 'closed').length;
|
| 37 |
|
| 38 |
+
// Issue metrics
|
| 39 |
+
const openIssues = issueItems.filter(i => i.state === 'open').length;
|
| 40 |
+
const closedIssues = issueItems.filter(i => i.state === 'closed').length;
|
| 41 |
|
| 42 |
+
// Get unique repositories contributed to
|
| 43 |
+
const uniqueRepos = new Set(allItems.map(item => item.repoName).filter(Boolean));
|
| 44 |
+
|
| 45 |
+
console.log("[Dashboard Summary] Calculated: %d PRs, %d Issues, %d repos", prs.length, issueItems.length, uniqueRepos.size);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
+
return NextResponse.json({
|
| 48 |
+
totalContributions: allItems.length,
|
| 49 |
+
totalPRs: prs.length,
|
| 50 |
+
openPRs,
|
| 51 |
+
mergedPRs: prs.length - openPRs,
|
| 52 |
+
totalIssues: issueItems.length,
|
| 53 |
+
openIssues,
|
| 54 |
+
closedIssues: issueItems.length - openIssues,
|
| 55 |
+
repositoriesContributed: uniqueRepos.size,
|
| 56 |
+
repositories: Array.from(uniqueRepos),
|
| 57 |
+
});
|
| 58 |
+
} catch (dbError: any) {
|
| 59 |
+
console.error("[Dashboard Summary] Database error:", dbError);
|
| 60 |
+
console.error("[Dashboard Summary] Error message:", dbError?.message);
|
| 61 |
+
throw dbError;
|
| 62 |
+
}
|
| 63 |
|
| 64 |
} catch (error: any) {
|
| 65 |
+
console.error("[Dashboard Summary] Error:", error);
|
| 66 |
+
console.error("[Dashboard Summary] Error message:", error?.message);
|
| 67 |
+
console.error("[Dashboard Summary] Error stack:", error?.stack);
|
| 68 |
|
| 69 |
return NextResponse.json({
|
| 70 |
+
error: "Failed to fetch dashboard data",
|
| 71 |
+
details: error?.message || "Internal server error"
|
| 72 |
}, { status: 500 });
|
| 73 |
}
|
| 74 |
}
|
src/db/index.ts
CHANGED
|
@@ -32,25 +32,39 @@ let _db: LibSQLDatabase<typeof schema> | null = null;
|
|
| 32 |
|
| 33 |
function getClient(): Client {
|
| 34 |
if (!_client) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
// Determine replica path (works on Hugging Face Spaces and local)
|
| 36 |
const replicaPath = process.env.REPLICA_DB_PATH || path.join(process.cwd(), "replica.db");
|
| 37 |
|
| 38 |
console.log(`[DB] Setting up Turso Embedded Replicas:`);
|
| 39 |
console.log(` • Local replica: ${replicaPath}`);
|
| 40 |
-
console.log(` • Remote sync: ${process.env.TURSO_DATABASE_URL?.slice(0, 30)}...`);
|
| 41 |
console.log(` • Sync interval: ${process.env.SYNC_INTERVAL || "60"}s`);
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
}
|
| 55 |
return _client;
|
| 56 |
}
|
|
@@ -99,9 +113,16 @@ export async function getReplicaStatus(): Promise<{
|
|
| 99 |
|
| 100 |
export const db = new Proxy({} as LibSQLDatabase<typeof schema>, {
|
| 101 |
get(_target, prop, receiver) {
|
| 102 |
-
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
}
|
| 105 |
-
return Reflect.get(_db, prop, receiver);
|
| 106 |
},
|
| 107 |
});
|
|
|
|
| 32 |
|
| 33 |
function getClient(): Client {
|
| 34 |
if (!_client) {
|
| 35 |
+
// Check environment variables
|
| 36 |
+
if (!process.env.TURSO_DATABASE_URL) {
|
| 37 |
+
console.error("[DB] TURSO_DATABASE_URL not set. Using file-only mode.");
|
| 38 |
+
}
|
| 39 |
+
if (!process.env.TURSO_AUTH_TOKEN) {
|
| 40 |
+
console.warn("[DB] TURSO_AUTH_TOKEN not set. Remote sync will not work.");
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
// Determine replica path (works on Hugging Face Spaces and local)
|
| 44 |
const replicaPath = process.env.REPLICA_DB_PATH || path.join(process.cwd(), "replica.db");
|
| 45 |
|
| 46 |
console.log(`[DB] Setting up Turso Embedded Replicas:`);
|
| 47 |
console.log(` • Local replica: ${replicaPath}`);
|
| 48 |
+
console.log(` • Remote sync URL: ${process.env.TURSO_DATABASE_URL?.slice(0, 30)}...`);
|
| 49 |
console.log(` • Sync interval: ${process.env.SYNC_INTERVAL || "60"}s`);
|
| 50 |
|
| 51 |
+
try {
|
| 52 |
+
_client = createClient({
|
| 53 |
+
// Local embedded replica for fast reads
|
| 54 |
+
url: `file:${replicaPath}`,
|
| 55 |
+
// Remote Turso database for syncing (optional)
|
| 56 |
+
syncUrl: process.env.TURSO_DATABASE_URL,
|
| 57 |
+
authToken: process.env.TURSO_AUTH_TOKEN,
|
| 58 |
+
// Sync interval: 60 seconds (adjust as needed)
|
| 59 |
+
syncInterval: parseInt(process.env.SYNC_INTERVAL || "60"),
|
| 60 |
+
// Optional: Encryption key for replica (if you want encrypted local storage)
|
| 61 |
+
encryptionKey: process.env.REPLICA_ENCRYPTION_KEY,
|
| 62 |
+
});
|
| 63 |
+
console.log("[DB] ✅ Client initialized successfully");
|
| 64 |
+
} catch (error: any) {
|
| 65 |
+
console.error("[DB] ❌ Failed to initialize client:", error?.message);
|
| 66 |
+
throw error;
|
| 67 |
+
}
|
| 68 |
}
|
| 69 |
return _client;
|
| 70 |
}
|
|
|
|
| 113 |
|
| 114 |
export const db = new Proxy({} as LibSQLDatabase<typeof schema>, {
|
| 115 |
get(_target, prop, receiver) {
|
| 116 |
+
try {
|
| 117 |
+
if (!_db) {
|
| 118 |
+
const client = getClient();
|
| 119 |
+
_db = drizzle(client, { schema });
|
| 120 |
+
console.log("[DB] ✅ Drizzle instance initialized");
|
| 121 |
+
}
|
| 122 |
+
return Reflect.get(_db, prop, receiver);
|
| 123 |
+
} catch (error: any) {
|
| 124 |
+
console.error("[DB Proxy] Error:", error?.message);
|
| 125 |
+
throw error;
|
| 126 |
}
|
|
|
|
| 127 |
},
|
| 128 |
});
|
src/lib/db/queries/issues.ts
CHANGED
|
@@ -204,78 +204,102 @@ export async function getIssuesWithTriage(filters: IssueFilters, page = 1, limit
|
|
| 204 |
|
| 205 |
console.log("[getIssuesWithTriage] Starting query with filters:", filters, "page:", page, "limit:", limit);
|
| 206 |
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
}
|
| 220 |
-
|
| 221 |
-
if (filters.userId) {
|
| 222 |
-
const userRepos = await db.select({ id: repositories.id })
|
| 223 |
-
.from(repositories)
|
| 224 |
-
.where(and(
|
| 225 |
-
eq(repositories.userId, filters.userId),
|
| 226 |
-
eq(repositories.addedByUser, true)
|
| 227 |
));
|
| 228 |
-
const repoIds = userRepos.map(r => r.id);
|
| 229 |
-
|
| 230 |
-
if (repoIds.length > 0) {
|
| 231 |
-
conditions.push(sql`${issues.repoId} IN (${sql.join(repoIds.map(id => sql`'${id}'`), sql`, `)})`);
|
| 232 |
-
} else {
|
| 233 |
-
return { issues: [], total: 0, page: safePage, limit, totalPages: 0 };
|
| 234 |
}
|
| 235 |
-
}
|
| 236 |
-
|
| 237 |
-
const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
|
| 238 |
-
|
| 239 |
-
console.log("[getIssuesWithTriage] Built where clause, fetching count...");
|
| 240 |
-
|
| 241 |
-
// Get total count
|
| 242 |
-
const countResult = await db.select({ count: count() })
|
| 243 |
-
.from(issues)
|
| 244 |
-
.where(whereClause);
|
| 245 |
-
const total = countResult[0]?.count || 0;
|
| 246 |
-
const totalPages = Math.ceil(total / limit);
|
| 247 |
-
|
| 248 |
-
console.log("[getIssuesWithTriage] Total count:", total, "totalPages:", totalPages);
|
| 249 |
-
|
| 250 |
-
// ✅ Single JOIN query: issues LEFT JOIN triageData
|
| 251 |
-
// Database executes this in one round-trip, no N+1
|
| 252 |
-
console.log("[getIssuesWithTriage] Executing JOIN query...");
|
| 253 |
-
const results = await db.select({
|
| 254 |
-
issue: issues,
|
| 255 |
-
triage: triageData,
|
| 256 |
-
})
|
| 257 |
-
.from(issues)
|
| 258 |
-
.leftJoin(triageData, eq(triageData.issueId, issues.id))
|
| 259 |
-
.where(whereClause)
|
| 260 |
-
.orderBy(desc(issues.createdAt))
|
| 261 |
-
.limit(limit)
|
| 262 |
-
.offset(offset);
|
| 263 |
|
| 264 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
...
|
| 269 |
-
|
| 270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
}
|
| 280 |
|
| 281 |
// =============================================================================
|
|
|
|
| 204 |
|
| 205 |
console.log("[getIssuesWithTriage] Starting query with filters:", filters, "page:", page, "limit:", limit);
|
| 206 |
|
| 207 |
+
try {
|
| 208 |
+
// Build conditions (identical to getIssues)
|
| 209 |
+
const conditions = [];
|
| 210 |
+
if (filters.repoId) conditions.push(eq(issues.repoId, filters.repoId));
|
| 211 |
+
if (filters.repoName) conditions.push(eq(issues.repoName, filters.repoName));
|
| 212 |
+
if (filters.authorName) conditions.push(eq(issues.authorName, filters.authorName));
|
| 213 |
+
if (filters.state) conditions.push(eq(issues.state, filters.state));
|
| 214 |
+
if (filters.isPR !== undefined) conditions.push(eq(issues.isPR, filters.isPR));
|
| 215 |
+
if (filters.search) {
|
| 216 |
+
conditions.push(or(
|
| 217 |
+
like(issues.title, `%${filters.search}%`),
|
| 218 |
+
like(issues.bodySummary, `%${filters.search}%`)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
|
| 222 |
+
if (filters.userId) {
|
| 223 |
+
const userRepos = await db.select({ id: repositories.id })
|
| 224 |
+
.from(repositories)
|
| 225 |
+
.where(and(
|
| 226 |
+
eq(repositories.userId, filters.userId),
|
| 227 |
+
eq(repositories.addedByUser, true)
|
| 228 |
+
));
|
| 229 |
+
const repoIds = userRepos.map(r => r.id);
|
| 230 |
+
|
| 231 |
+
if (repoIds.length > 0) {
|
| 232 |
+
conditions.push(sql`${issues.repoId} IN (${sql.join(repoIds.map(id => sql`'${id}'`), sql`, `)})`);
|
| 233 |
+
} else {
|
| 234 |
+
return { issues: [], total: 0, page: safePage, limit, totalPages: 0 };
|
| 235 |
+
}
|
| 236 |
+
}
|
| 237 |
|
| 238 |
+
const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
|
| 239 |
+
|
| 240 |
+
console.log("[getIssuesWithTriage] Built where clause, fetching count...");
|
| 241 |
+
|
| 242 |
+
// Get total count
|
| 243 |
+
const countResult = await db.select({ count: count() })
|
| 244 |
+
.from(issues)
|
| 245 |
+
.where(whereClause);
|
| 246 |
+
const total = countResult[0]?.count || 0;
|
| 247 |
+
const totalPages = Math.ceil(total / limit);
|
| 248 |
+
|
| 249 |
+
console.log("[getIssuesWithTriage] Total count:", total, "totalPages:", totalPages);
|
| 250 |
+
|
| 251 |
+
// Fetch issues using simple query
|
| 252 |
+
console.log("[getIssuesWithTriage] Executing issues query...");
|
| 253 |
+
const issueResults = await db.select()
|
| 254 |
+
.from(issues)
|
| 255 |
+
.where(whereClause)
|
| 256 |
+
.orderBy(desc(issues.createdAt))
|
| 257 |
+
.limit(limit)
|
| 258 |
+
.offset(offset);
|
| 259 |
+
|
| 260 |
+
console.log("[getIssuesWithTriage] Query returned %d results", issueResults.length);
|
| 261 |
+
|
| 262 |
+
// Fetch triage data for these issues
|
| 263 |
+
if (issueResults.length === 0) {
|
| 264 |
+
return {
|
| 265 |
+
issues: [],
|
| 266 |
+
total,
|
| 267 |
+
page: safePage,
|
| 268 |
+
limit,
|
| 269 |
+
totalPages,
|
| 270 |
+
};
|
| 271 |
+
}
|
| 272 |
|
| 273 |
+
console.log("[getIssuesWithTriage] Fetching triage data for %d issues", issueResults.length);
|
| 274 |
+
const issueIds = issueResults.map(i => i.id);
|
| 275 |
+
const triageDataResults = await db.select()
|
| 276 |
+
.from(triageData)
|
| 277 |
+
.where(sql`${triageData.issueId} IN (${sql.join(issueIds.map(id => sql`'${id}'`), sql`, `)})`);
|
| 278 |
+
|
| 279 |
+
console.log("[getIssuesWithTriage] Found %d triage records", triageDataResults.length);
|
| 280 |
+
|
| 281 |
+
// Create a map of triage data by issueId for quick lookup
|
| 282 |
+
const triageMap = new Map(triageDataResults.map(t => [t.issueId, t]));
|
| 283 |
+
|
| 284 |
+
// Merge issues with their triage data
|
| 285 |
+
const issuesWithTriage = issueResults.map(issue => ({
|
| 286 |
+
...issue,
|
| 287 |
+
triage: triageMap.get(issue.id) || null,
|
| 288 |
+
}));
|
| 289 |
+
|
| 290 |
+
return {
|
| 291 |
+
issues: issuesWithTriage,
|
| 292 |
+
total,
|
| 293 |
+
page: safePage,
|
| 294 |
+
limit,
|
| 295 |
+
totalPages,
|
| 296 |
+
};
|
| 297 |
+
} catch (error: any) {
|
| 298 |
+
console.error("[getIssuesWithTriage] Query failed:", error);
|
| 299 |
+
console.error("[getIssuesWithTriage] Error message:", error?.message);
|
| 300 |
+
console.error("[getIssuesWithTriage] Error stack:", error?.stack);
|
| 301 |
+
throw error;
|
| 302 |
+
}
|
| 303 |
}
|
| 304 |
|
| 305 |
// =============================================================================
|