Update various configuration files, components, and assets; enhance notification system and API endpoints; improve documentation and styles across the application.
This commit is contained in:
187
server/api/notifications/queue/history.get.js
Normal file
187
server/api/notifications/queue/history.get.js
Normal file
@@ -0,0 +1,187 @@
|
||||
import { z } from "zod";
|
||||
import prisma from "~/server/utils/prisma";
|
||||
|
||||
// Query parameter validation schema
|
||||
const querySchema = z.object({
|
||||
period: z.enum(["hour", "day", "week", "month"]).default("day"),
|
||||
metric: z.enum(["throughput", "error_rate", "response_time"]).default("throughput"),
|
||||
});
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
// Parse and validate query parameters
|
||||
const query = getQuery(event);
|
||||
const { period, metric } = querySchema.parse(query);
|
||||
|
||||
// Set time range based on period
|
||||
const now = new Date();
|
||||
let startDate;
|
||||
let intervalMinutes;
|
||||
let numPoints;
|
||||
|
||||
switch (period) {
|
||||
case "hour":
|
||||
startDate = new Date(now.getTime() - 60 * 60 * 1000); // 1 hour ago
|
||||
intervalMinutes = 1; // 1-minute intervals
|
||||
numPoints = 60;
|
||||
break;
|
||||
case "day":
|
||||
startDate = new Date(now.getTime() - 24 * 60 * 60 * 1000); // 24 hours ago
|
||||
intervalMinutes = 60; // 1-hour intervals
|
||||
numPoints = 24;
|
||||
break;
|
||||
case "week":
|
||||
startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); // 7 days ago
|
||||
intervalMinutes = 24 * 60; // 1-day intervals
|
||||
numPoints = 7;
|
||||
break;
|
||||
case "month":
|
||||
startDate = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); // 30 days ago
|
||||
intervalMinutes = 24 * 60; // 1-day intervals
|
||||
numPoints = 30;
|
||||
break;
|
||||
default:
|
||||
startDate = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||
intervalMinutes = 60;
|
||||
numPoints = 24;
|
||||
}
|
||||
|
||||
const dataPoints = [];
|
||||
|
||||
// Generate time buckets
|
||||
for (let i = 0; i < numPoints; i++) {
|
||||
const bucketStart = new Date(startDate.getTime() + i * intervalMinutes * 60 * 1000);
|
||||
const bucketEnd = new Date(bucketStart.getTime() + intervalMinutes * 60 * 1000);
|
||||
|
||||
let value = 0;
|
||||
|
||||
switch (metric) {
|
||||
case "throughput": {
|
||||
// Count completed jobs in this time bucket
|
||||
const count = await prisma.notification_queue.count({
|
||||
where: {
|
||||
status: "completed",
|
||||
updated_at: {
|
||||
gte: bucketStart,
|
||||
lt: bucketEnd,
|
||||
},
|
||||
},
|
||||
});
|
||||
value = count;
|
||||
break;
|
||||
}
|
||||
|
||||
case "error_rate": {
|
||||
// Calculate error rate in this time bucket
|
||||
const [totalJobs, failedJobs] = await Promise.all([
|
||||
prisma.notification_queue.count({
|
||||
where: {
|
||||
OR: [{ status: "completed" }, { status: "failed" }],
|
||||
updated_at: {
|
||||
gte: bucketStart,
|
||||
lt: bucketEnd,
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.notification_queue.count({
|
||||
where: {
|
||||
status: "failed",
|
||||
updated_at: {
|
||||
gte: bucketStart,
|
||||
lt: bucketEnd,
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
value = totalJobs > 0 ? Math.round((failedJobs / totalJobs) * 100) : 0;
|
||||
break;
|
||||
}
|
||||
|
||||
case "response_time": {
|
||||
// Calculate average response time in this time bucket
|
||||
const jobs = await prisma.notification_queue.findMany({
|
||||
where: {
|
||||
status: "completed",
|
||||
updated_at: {
|
||||
gte: bucketStart,
|
||||
lt: bucketEnd,
|
||||
},
|
||||
last_attempt_at: { not: null },
|
||||
},
|
||||
select: {
|
||||
created_at: true,
|
||||
last_attempt_at: true,
|
||||
},
|
||||
take: 50, // Sample size
|
||||
});
|
||||
|
||||
if (jobs.length > 0) {
|
||||
const responseTimes = jobs.map(job => {
|
||||
const diff = new Date(job.last_attempt_at).getTime() - new Date(job.created_at).getTime();
|
||||
return Math.abs(diff);
|
||||
});
|
||||
value = Math.round(responseTimes.reduce((sum, time) => sum + time, 0) / responseTimes.length);
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dataPoints.push({
|
||||
timestamp: bucketStart.toISOString(),
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate summary statistics
|
||||
const values = dataPoints.map(p => p.value);
|
||||
const min = Math.min(...values);
|
||||
const max = Math.max(...values);
|
||||
const avg = Math.round(values.reduce((sum, val) => sum + val, 0) / values.length);
|
||||
|
||||
// Calculate trend (compare first half vs second half)
|
||||
const midpoint = Math.floor(values.length / 2);
|
||||
const firstHalfAvg = values.slice(0, midpoint).reduce((sum, val) => sum + val, 0) / midpoint;
|
||||
const secondHalfAvg = values.slice(midpoint).reduce((sum, val) => sum + val, 0) / (values.length - midpoint);
|
||||
const trend = secondHalfAvg > firstHalfAvg * 1.1 ? "increasing"
|
||||
: secondHalfAvg < firstHalfAvg * 0.9 ? "decreasing"
|
||||
: "stable";
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
metric,
|
||||
period,
|
||||
dataPoints,
|
||||
summary: {
|
||||
min,
|
||||
max,
|
||||
avg,
|
||||
trend,
|
||||
},
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error fetching queue history:", error);
|
||||
|
||||
if (error.name === "ZodError") {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid query parameters",
|
||||
data: {
|
||||
errors: error.errors,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: "Failed to fetch queue history",
|
||||
data: {
|
||||
error: error.message,
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user