Files
Nas-Notification/server/api/notifications/queue/history.get.js

188 lines
5.5 KiB
JavaScript

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 {
}
});