incidents feature fron-end

This commit is contained in:
karenvicent committed 2025-12-17 18:51:34 -05:00
1 parent fec32380ae
commit fd12ca6e21
19 files changed
+1774 -42

No files matched your search

@@ -77,13 +77,20 @@ class IncidentController extends BaseController {
* @function getIncidentSummary
* @param {Object} req - Express request object
* @param {Object} req.query - Query parameters
* @param {string} [req.query.dateRange] - Date range filter
* @param {number} [req.query.limit=10] - Number of latest incidents to return
* @param {Object} req.user - Current authenticated user (from JWT)
* @param {string} req.user.teamId - User's team ID
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response with incidents summary
* @returns {Promise<Object>} Success response with incidents summary containing:
* - totalActive: Number of active incidents
* - avgResolutionTimeHours: Average time to resolve incidents in hours
* - topMonitor: Monitor with most incidents (monitorId, monitorName, incidentCount)
* - total: Total number of incidents
* - totalManualResolutions: Total incidents resolved manually
* - totalAutomaticResolutions: Total incidents resolved automatically
* - latestIncidents: Array of latest incidents created
* @example
* GET /incidents/summary?dateRange=week
* GET /incidents/team/summary?limit=5
* // Requires JWT authentication
*/
getIncidentSummary = this.asyncHandler(
+172 -26
View File
@@ -185,13 +185,29 @@ class IncidentModule {
...(statusBoolean !== undefined && { status: statusBoolean }),
...(monitorId && { monitorId: new ObjectId(monitorId) }),
...(resolutionType && { resolutionType }),
...(dateRangeLookup[dateRange] && {
startTime: {
$gte: dateRangeLookup[dateRange],
},
}),
};
// Date range filter:
// - Active incidents: always show (no date filter) - they're currently happening
// - Resolved incidents: filter by endTime (when they were resolved)
// - All incidents: show all active + resolved in the range
if (dateRangeLookup[dateRange]) {
const dateThreshold = dateRangeLookup[dateRange];
if (statusBoolean === true) {
// Active incidents: show all active incidents regardless of when they started
// No date filter applied
} else if (statusBoolean === false) {
// Resolved incidents: only show if resolved in the range
matchStage.endTime = { $gte: dateThreshold };
} else {
// All incidents: show all active + resolved incidents in the range
matchStage.$or = [
{ status: true }, // All active incidents
{ status: false, endTime: { $gte: dateThreshold } }, // Resolved in range
];
}
}
sortOrder = sortOrder === "asc" ? 1 : -1;
const skip = page * rowsPerPage;
@@ -199,6 +215,14 @@ class IncidentModule {
const incidents = await this.Incident.aggregate([
{ $match: matchStage },
{ $sort: { startTime: sortOrder } },
{
$lookup: {
from: "monitors",
localField: "monitorId",
foreignField: "_id",
as: "monitor",
},
},
{
$facet: {
summary: [{ $count: "incidentsCount" }],
@@ -211,7 +235,27 @@ class IncidentModule {
$ifNull: [{ $arrayElemAt: ["$summary.incidentsCount", 0] }, 0],
},
incidents: {
$ifNull: ["$incidents", []],
$map: {
input: { $ifNull: ["$incidents", []] },
as: "incident",
in: {
$mergeObjects: [
"$$incident",
{
monitorName: {
$let: {
vars: {
monitor: { $arrayElemAt: ["$$incident.monitor", 0] },
},
in: {
$ifNull: ["$$monitor.name", null],
},
},
},
},
],
},
},
},
},
},
@@ -225,18 +269,14 @@ class IncidentModule {
}
};
getIncidentSummary = async ({ teamId, dateRange }) => {
getIncidentSummary = async ({ teamId, limit = 10 }) => {
try {
const matchStage = {
teamId: new ObjectId(teamId),
...(dateRangeLookup[dateRange] && {
startTime: {
$gte: dateRangeLookup[dateRange],
},
}),
};
const summary = await this.Incident.aggregate([
// Get basic counts and resolution types
const countsPipeline = [
{ $match: matchStage },
{
$group: {
@@ -247,29 +287,135 @@ class IncidentModule {
$cond: [{ $eq: ["$resolutionType", "manual"] }, 1, 0],
},
},
automaticResolutions: {
$sum: {
$cond: [{ $eq: ["$resolutionType", "automatic"] }, 1, 0],
},
},
},
},
]);
];
const result = {
total: 0,
active: 0,
resolved: 0,
manual: 0,
};
const counts = await this.Incident.aggregate(countsPipeline);
summary.forEach((item) => {
result.total += item.count;
// Calculate totals
let total = 0;
let active = 0;
let resolved = 0;
let manual = 0;
let automatic = 0;
counts.forEach((item) => {
total += item.count;
if (item._id === true) {
result.active = item.count;
active = item.count;
}
if (item._id === false) {
result.resolved = item.count;
resolved = item.count;
}
result.manual += item.manualResolutions;
manual += item.manualResolutions;
automatic += item.automaticResolutions;
});
return result;
// Calculate average resolution time (in milliseconds)
const resolutionTimePipeline = [
{ $match: { ...matchStage, status: false, endTime: { $exists: true, $ne: null } } },
{
$project: {
resolutionTime: {
$subtract: ["$endTime", "$startTime"],
},
},
},
{
$group: {
_id: null,
avgResolutionTime: { $avg: "$resolutionTime" },
},
},
];
const resolutionTimeResult = await this.Incident.aggregate(resolutionTimePipeline);
const avgResolutionTimeMs = resolutionTimeResult[0]?.avgResolutionTime || 0;
const avgResolutionTimeHours = avgResolutionTimeMs / (1000 * 60 * 60); // Convert to hours
// Get monitor with most incidents
const monitorPipeline = [
{ $match: matchStage },
{
$group: {
_id: "$monitorId",
count: { $sum: 1 },
},
},
{ $sort: { count: -1 } },
{ $limit: 1 },
{
$lookup: {
from: "monitors",
localField: "_id",
foreignField: "_id",
as: "monitor",
},
},
{
$project: {
monitorId: "$_id",
count: 1,
monitorName: { $arrayElemAt: ["$monitor.name", 0] },
},
},
];
const monitorResult = await this.Incident.aggregate(monitorPipeline);
const topMonitor = monitorResult[0] || null;
// Get latest incidents
const latestIncidentsPipeline = [
{ $match: matchStage },
{ $sort: { createdAt: -1 } },
{ $limit: parseInt(limit) },
{
$lookup: {
from: "monitors",
localField: "monitorId",
foreignField: "_id",
as: "monitor",
},
},
{
$project: {
_id: 1,
monitorId: 1,
monitorName: { $arrayElemAt: ["$monitor.name", 0] },
status: 1,
startTime: 1,
endTime: 1,
resolutionType: 1,
message: 1,
statusCode: 1,
createdAt: 1,
},
},
];
const latestIncidents = await this.Incident.aggregate(latestIncidentsPipeline);
return {
totalActive: active,
avgResolutionTimeHours: Math.round(avgResolutionTimeHours * 100) / 100, // Round to 2 decimal places
topMonitor: topMonitor
? {
monitorId: topMonitor.monitorId,
monitorName: topMonitor.monitorName,
incidentCount: topMonitor.count,
}
: null,
total: total,
totalManualResolutions: manual,
totalAutomaticResolutions: automatic,
latestIncidents: latestIncidents,
};
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getIncidentSummary";
@@ -215,11 +215,11 @@ class IncidentService {
throw this.errorService.createBadRequestError("No team ID in request");
}
const { dateRange } = query || {};
const { limit } = query || {};
const summary = await this.db.incidentModule.getIncidentSummary({
teamId,
dateRange,
limit,
});
return summary;