add range buckets for status pages
This commit is contained in:
1 parent
697574a2a9
commit
3a22d3e819
5 files changed
+64
-4
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod";
|
||||
import { booleanCoercion, dnsHostnameRegex } from "./shared.js";
|
||||
import { StatusPageTypes, StatusPageThemes, StatusPageThemeModes } from "@/domain/status-pages/status-page.type.js";
|
||||
import { StatusPageTypes, StatusPageThemes, StatusPageThemeModes, StatusPageRanges } from "@/domain/status-pages/status-page.type.js";
|
||||
import { normalizeStatusPageDomain } from "@/utils/statusPageDomain.js";
|
||||
import { cssReferencesExternalResource } from "@/utils/customCss.js";
|
||||
import { ImageMimeTypes } from "@/types/upload.js";
|
||||
@@ -15,7 +15,7 @@ export const getStatusPageParamValidation = z.object({
|
||||
|
||||
export const getStatusPageQueryValidation = z.object({
|
||||
type: z.union([z.enum(StatusPageTypes), z.array(z.enum(StatusPageTypes))]).transform((val) => (Array.isArray(val) ? val : [val])),
|
||||
timeFrame: z.coerce.number().optional(),
|
||||
range: z.enum(StatusPageRanges).optional().default("latest"),
|
||||
});
|
||||
|
||||
export const resolveStatusPageQueryValidation = getStatusPageQueryValidation.extend({
|
||||
|
||||
@@ -243,3 +243,12 @@ export interface HardwareStats {
|
||||
export interface HardwareChecksResult extends HardwareStats {
|
||||
monitorType: "hardware";
|
||||
}
|
||||
|
||||
export type DailyCheckBucket = {
|
||||
monitorId: string;
|
||||
date: string;
|
||||
totalChecks: number;
|
||||
upChecks: number;
|
||||
downChecks: number;
|
||||
avgResponseTime: number;
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DailyCheckBucket } from "@/domain/checks/check.type.js";
|
||||
import type { StatusPage } from "@/domain/status-pages/status-page.type.js";
|
||||
|
||||
export interface IStatusPagesRepository {
|
||||
@@ -8,6 +9,7 @@ export interface IStatusPagesRepository {
|
||||
findByCustomDomain(customDomain: string): Promise<StatusPage>;
|
||||
findByTeamId(teamId: string): Promise<StatusPage[]>;
|
||||
// collection fetch
|
||||
getDailyStatusBuckets(monitorIds: string[], days: number, timezone: string | undefined): Promise<DailyCheckBucket[]>;
|
||||
// update
|
||||
updateById(id: string, teamId: string, image: Express.Multer.File | undefined, data: Partial<StatusPage>): Promise<StatusPage>;
|
||||
// delete
|
||||
|
||||
@@ -5,6 +5,7 @@ import mongoose from "mongoose";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { normalizeStatusPageDomain } from "@/utils/statusPageDomain.js";
|
||||
import { toStringId, toDateString } from "@/utils/mongoMappers.js";
|
||||
import CheckModel from "@/domain/checks/check.model.js";
|
||||
// Type for update data that can include document-level fields (Buffer for logo)
|
||||
type StatusPageUpdateData = Partial<Omit<StatusPage, "id" | "userId" | "teamId" | "logo" | "createdAt" | "updatedAt">> & {
|
||||
logo?: StatusPageLogoDocument | null;
|
||||
@@ -113,6 +114,43 @@ class MongoStatusPagesRepository implements IStatusPagesRepository {
|
||||
return this.mapDocuments(statusPages);
|
||||
};
|
||||
|
||||
getDailyStatusBuckets = async (monitorIds: string[], days: number, timezone: string | undefined) => {
|
||||
const objectIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id));
|
||||
const windowStart = new Date(Date.now() - (days - 1) * 24 * 60 * 60 * 1000);
|
||||
const results = await CheckModel.aggregate([
|
||||
{
|
||||
$match: {
|
||||
"metadata.monitorId": { $in: objectIds },
|
||||
createdAt: { $gte: windowStart },
|
||||
},
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: {
|
||||
monitorId: "$metadata.monitorId",
|
||||
day: { $dateTrunc: { date: "$createdAt", unit: "day", timezone } },
|
||||
},
|
||||
totalChecks: { $sum: 1 },
|
||||
upChecks: { $sum: { $cond: [{ $eq: ["$status", true] }, 1, 0] } },
|
||||
avgResponseTime: { $avg: { $ifNull: ["$responseTime", 0] } },
|
||||
},
|
||||
},
|
||||
{ $sort: { "_id.day": 1 } },
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
monitorId: { $toString: "$_id.monitorId" },
|
||||
date: { $dateToString: { date: "$_id.day", format: "%Y-%m-%d", timezone } },
|
||||
totalChecks: 1,
|
||||
upChecks: 1,
|
||||
downChecks: { $subtract: ["$totalChecks", "$upChecks"] },
|
||||
avgResponseTime: { $round: ["$avgResponseTime", 0] },
|
||||
},
|
||||
},
|
||||
]);
|
||||
return results;
|
||||
};
|
||||
|
||||
updateById = async (
|
||||
id: string,
|
||||
teamId: string,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DailyCheckBucket } from "@/domain/checks/check.type.js";
|
||||
import type { Monitor } from "@/domain/monitors/monitor.type.js";
|
||||
export const StatusPageTypes = ["uptime", "infrastructure"] as const;
|
||||
export type StatusPageType = (typeof StatusPageTypes)[number];
|
||||
@@ -10,6 +11,13 @@ export const StatusPageThemeModes = ["auto", "light", "dark"] as const;
|
||||
export type StatusPageThemeMode = (typeof StatusPageThemeModes)[number];
|
||||
export const DEFAULT_STATUS_PAGE_THEME_MODE: StatusPageThemeMode = "auto";
|
||||
|
||||
export const StatusPageRanges = ["latest", "30d", "60d", "90d"] as const;
|
||||
export type StatusPageRange = (typeof StatusPageRanges)[number];
|
||||
export const STATUS_PAGE_RANGE_DAYS: Record<Exclude<StatusPageRange, "latest">, number> = {
|
||||
"30d": 30,
|
||||
"60d": 60,
|
||||
"90d": 90,
|
||||
};
|
||||
export interface StatusPageLogo {
|
||||
data: string;
|
||||
contentType: string;
|
||||
@@ -46,8 +54,11 @@ export interface StatusPage {
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export type PublicStatusPageMonitor = Pick<Monitor, "id" | "name" | "type" | "status" | "uptimePercentage" | "recentChecks"> &
|
||||
Partial<Pick<Monitor, "url" | "port">>;
|
||||
export type PublicStatusPageMonitor = Pick<Monitor, "id" | "name" | "type" | "status" | "uptimePercentage" | "recentChecks"> & {
|
||||
url?: string;
|
||||
port?: number;
|
||||
dailyChecks?: DailyCheckBucket[]; // Only present when range !== "latest"
|
||||
};
|
||||
|
||||
export interface PublicStatusPagePayload {
|
||||
statusPage: StatusPage;
|
||||
|
||||
Reference in new issue
Block a user