extract date range calculation for conisistency

This commit is contained in:
Alex Holliday committed 2026-07-23 10:25:24 -07:00
1 parent 4a90ab8e78
commit 3af00281c4
9 files changed
+78 -119

No files matched your search

@@ -8,6 +8,7 @@ import type {
} from "@/domain/checks/check.type.js";
import type { MonitorType } from "@/domain/monitors/monitor.types.js";
import type { LatestChecksMap } from "@/domain/checks/check.repository.mongo.js";
import { DateRange } from "@/types/query.js";
export interface IChecksRepository {
// create
@@ -19,7 +20,7 @@ export interface IChecksRepository {
findByMonitorId(
monitorId: string,
sortOrder: string,
dateRange: string,
dateRange: DateRange,
filter: string | undefined,
page: number,
rowsPerPage: number,
@@ -27,7 +28,7 @@ export interface IChecksRepository {
): Promise<ChecksQueryResult>;
findByTeamId(
sortOrder: string,
dateRange: string,
dateRange: DateRange,
filter: string | undefined,
page: number,
rowsPerPage: number,
@@ -36,12 +37,10 @@ export interface IChecksRepository {
findLatestByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise<LatestChecksMap>;
findByDateRangeAndMonitorId(
monitorId: string,
startDate: Date,
endDate: Date,
dateString: string,
dateRange: DateRange,
options?: { type?: MonitorType }
): Promise<UptimeChecksResult | HardwareChecksResult | PageSpeedChecksResult>;
findSummaryByTeamId(teamId: string, dateRange: string): Promise<ChecksSummary>;
findSummaryByTeamId(teamId: string, dateRange: DateRange): Promise<ChecksSummary>;
findUnevaluatedByMonitorId(monitorId: string, since: number): Promise<Check[]>;
// update
//delete
@@ -15,11 +15,12 @@ import type {
import type { MonitorType } from "@/domain/monitors/monitor.types.js";
import { CheckModel, type CheckDocument } from "@/domain/checks/check.model.js";
import mongoose from "mongoose";
import { getDateForRange } from "@/utils/dataUtils.js";
import { getDateFormat, getDateForRange } from "@/utils/dataUtils.js";
import { ILogger } from "@/utils/logger.js";
import { toStringId, toDateString } from "@/utils/mongoMappers.js";
import { getHardwareUpChecks, getHardwareStats, getHardwareTotalChecks } from "@/domain/checks/check.hardware.aggregations.js";
import { DateRange } from "@/types/query.js";
const SERVICE_NAME = "StatusService";
@@ -207,7 +208,7 @@ class MongoChecksRepository implements IChecksRepository {
findByMonitorId = async (
monitorId: string,
sortOrder: string,
dateRange: string,
dateRange: DateRange,
filter: string | undefined,
page: number,
rowsPerPage: number,
@@ -217,11 +218,7 @@ class MongoChecksRepository implements IChecksRepository {
const matchStage: Record<string, unknown> = {
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
...(typeof status !== "undefined" && { status }),
...(getDateForRange(dateRange) && {
createdAt: {
$gte: getDateForRange(dateRange),
},
}),
createdAt: { $gte: getDateForRange(dateRange) },
};
if (filter !== undefined) {
@@ -265,14 +262,10 @@ class MongoChecksRepository implements IChecksRepository {
return { checksCount, checks: this.mapDocuments(checks) };
};
findByTeamId = async (sortOrder: string, dateRange: string, filter: string, page: number, rowsPerPage: number, teamId: string) => {
findByTeamId = async (sortOrder: string, dateRange: DateRange, filter: string, page: number, rowsPerPage: number, teamId: string) => {
const matchStage: Record<string, unknown> = {
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
...(getDateForRange(dateRange) && {
createdAt: {
$gte: getDateForRange(dateRange),
},
}),
createdAt: { $gte: getDateForRange(dateRange) },
};
// Add filter to match stage
if (filter !== undefined) {
@@ -341,25 +334,25 @@ class MongoChecksRepository implements IChecksRepository {
return mapped;
};
findByDateRangeAndMonitorId = async (monitorId: string, startDate: Date, endDate: Date, dateString: string, options?: { type?: MonitorType }) => {
findByDateRangeAndMonitorId = async (monitorId: string, dateRange: DateRange, options?: { type?: MonitorType }) => {
const monitorObjectId = new mongoose.Types.ObjectId(monitorId);
const start = getDateForRange(dateRange);
const dateString = getDateFormat(dateRange);
const end = new Date();
if (options?.type === "hardware") {
return this.findHardwareDateRangeChecks(monitorObjectId, startDate, endDate, dateString);
return this.findHardwareDateRangeChecks(monitorObjectId, start, end, dateString);
}
if (options?.type === "pagespeed") {
return this.findPageSpeedDateRangeChecks(monitorObjectId, startDate, endDate, dateString);
return this.findPageSpeedDateRangeChecks(monitorObjectId, start, end, dateString);
}
return this.findUptimeDateRangeChecks(options?.type ?? "http", monitorObjectId, startDate, endDate, dateString);
return this.findUptimeDateRangeChecks(options?.type ?? "http", monitorObjectId, start, end, dateString);
};
findSummaryByTeamId = async (teamId: string, dateRange: string) => {
findSummaryByTeamId = async (teamId: string, dateRange: DateRange) => {
const baseMatch = {
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
...(getDateForRange(dateRange) && {
createdAt: {
$gte: getDateForRange(dateRange),
},
}),
createdAt: { $gte: getDateForRange(dateRange) },
};
const [totalResult, downResult] = await Promise.all([
+7 -6
View File
@@ -6,6 +6,7 @@ import type { MonitorPayloadMap, MonitorStatusResponse } from "@/types/network.j
import type { HardwareStatusPayload, PageSpeedStatusPayload } from "@/types/network.js";
import { AppError } from "@/utils/AppError.js";
import { ILogger } from "@/utils/logger.js";
import { DateRange } from "@/types/query.js";
const SERVICE_NAME = "checkService";
@@ -18,7 +19,7 @@ export interface ICheckService {
monitorId: string;
teamId: string;
sortOrder: string;
dateRange: string;
dateRange: DateRange;
page: number;
rowsPerPage: number;
filter?: string;
@@ -27,12 +28,12 @@ export interface ICheckService {
getChecksByTeam(params: {
teamId: string;
sortOrder: string;
dateRange: string;
dateRange: DateRange;
page: number;
rowsPerPage: number;
filter?: string;
}): Promise<ChecksQueryResult>;
getChecksSummaryByTeamId(params: { teamId: string; dateRange: string }): Promise<ChecksSummary>;
getChecksSummaryByTeamId(params: { teamId: string; dateRange: DateRange }): Promise<ChecksSummary>;
deleteChecks(params: { monitorId: string; teamId: string }): Promise<number>;
deleteChecksByTeamId(params: { teamId: string }): Promise<number>;
deleteOlderThan(date: Date): Promise<number>;
@@ -164,7 +165,7 @@ export class CheckService implements ICheckService {
monitorId: string;
teamId: string;
sortOrder: string;
dateRange: string;
dateRange: DateRange;
page: number;
rowsPerPage: number;
status?: boolean;
@@ -198,7 +199,7 @@ export class CheckService implements ICheckService {
}: {
teamId: string;
sortOrder: string;
dateRange: string;
dateRange: DateRange;
page: number;
rowsPerPage: number;
filter?: string;
@@ -210,7 +211,7 @@ export class CheckService implements ICheckService {
return checkData;
};
getChecksSummaryByTeamId = async ({ teamId, dateRange }: { teamId: string; dateRange: string }) => {
getChecksSummaryByTeamId = async ({ teamId, dateRange }: { teamId: string; dateRange: DateRange }) => {
const summary = await this.checksRepository.findSummaryByTeamId(teamId, dateRange);
return summary;
};
@@ -1,5 +1,6 @@
import type { GeoCheck, GroupedGeoCheck } from "@/domain/geo-checks/geo-check.type.js";
import type { GeoContinent, FlatGeoCheck } from "@/domain/geo-checks/geo-check.type.js";
import { DateRange } from "@/types/query.js";
export interface GeoChecksQueryResult {
geoChecksCount: number;
@@ -16,19 +17,13 @@ export interface IGeoChecksRepository {
findByMonitorId(
monitorId: string,
sortOrder: string,
dateRange: string,
dateRange: DateRange,
page: number,
rowsPerPage: number,
continents?: GeoContinent[]
): Promise<FlatGeoChecksQueryResult>;
findByMonitorIdAndDateRange(monitorId: string, startDate: Date, endDate: Date): Promise<GeoCheck[]>;
findGroupedByMonitorIdAndDateRange(
monitorId: string,
startDate: Date,
endDate: Date,
dateFormat: string,
continents?: GeoContinent[]
): Promise<GroupedGeoCheck[]>;
findGroupedByMonitorIdAndDateRange(monitorId: string, DateRange: DateRange, continents?: GeoContinent[]): Promise<GroupedGeoCheck[]>;
deleteByMonitorId(monitorId: string): Promise<number>;
deleteByTeamId(teamId: string): Promise<number>;
deleteByMonitorIdsNotIn(monitorIds: string[]): Promise<number>;
@@ -3,9 +3,10 @@ import type { GeoCheck, GeoCheckMetadata, GeoCheckResult, GroupedGeoCheck, GeoCo
import type { FlatGeoChecksQueryResult } from "./geo-check.repository.interface.js";
import { GeoCheckMetadataDocument, GeoCheckModel, type GeoCheckDocument } from "@/domain/geo-checks/geo-check.model.js";
import mongoose, { PipelineStage } from "mongoose";
import { getDateForRange } from "@/utils/dataUtils.js";
import { getDateForRange, getDateFormat } from "@/utils/dataUtils.js";
import { ILogger } from "@/utils/logger.js";
import { toStringId, toDateString } from "@/utils/mongoMappers.js";
import { DateRange } from "@/types/query.js";
const SERVICE_NAME = "GeoChecksRepository";
@@ -90,7 +91,7 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
findByMonitorId = async (
monitorId: string,
sortOrder: string,
dateRange: string,
dateRange: DateRange,
page: number,
rowsPerPage: number,
continents?: GeoContinent[]
@@ -98,11 +99,7 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
try {
const matchStage: Record<string, unknown> = {
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
...(getDateForRange(dateRange) && {
createdAt: {
$gte: getDateForRange(dateRange),
},
}),
createdAt: { $gte: getDateForRange(dateRange) },
};
const convertedSortOrder = sortOrder === "asc" ? 1 : -1;
@@ -214,13 +211,11 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
}
};
findGroupedByMonitorIdAndDateRange = async (
monitorId: string,
startDate: Date,
endDate: Date,
dateFormat: string,
continents?: GeoContinent[]
): Promise<GroupedGeoCheck[]> => {
findGroupedByMonitorIdAndDateRange = async (monitorId: string, dateRange: DateRange, continents?: GeoContinent[]): Promise<GroupedGeoCheck[]> => {
const start = getDateForRange(dateRange);
const dateString = getDateFormat(dateRange);
const end = new Date();
try {
const pipeline: PipelineStage[] = [
// Match geo checks for this monitor in date range
@@ -228,8 +223,8 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
$match: {
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
createdAt: {
$gte: startDate,
$lte: endDate,
$gte: start,
$lte: end,
},
},
},
@@ -253,7 +248,7 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
_id: {
bucketDate: {
$dateToString: {
format: dateFormat,
format: dateString,
date: "$createdAt",
},
},
@@ -7,6 +7,7 @@ import type { IMonitorsRepository } from "@/domain/monitors/monitor.repository.i
import type { IGlobalPingService } from "@/service/globalPingService.js";
import type { ILogger } from "@/utils/logger.js";
import { AppError } from "@/utils/AppError.js";
import { DateRange } from "@/types/query.js";
const SERVICE_NAME = "GeoChecksService";
@@ -17,7 +18,7 @@ export interface IGeoChecksService {
monitorId: string;
teamId: string;
sortOrder: string;
dateRange: string;
dateRange: DateRange;
page?: number;
rowsPerPage?: number;
continent?: GeoContinent | GeoContinent[];
@@ -155,7 +156,7 @@ export class GeoChecksService implements IGeoChecksService {
monitorId: string;
teamId: string;
sortOrder: string;
dateRange: string;
dateRange: DateRange;
page?: number;
rowsPerPage?: number;
continent: GeoContinent | GeoContinent[];
@@ -11,6 +11,7 @@ import type { User } from "@/domain/users/user.type.js";
import type { MonitorActionDecision } from "@/worker/worker.helper.js";
import type { INotificationMessageBuilder } from "@/domain/notifications/notification.message-builder.js";
import type { ILogger } from "@/utils/logger.js";
import { DateRange } from "@/types/query.js";
export interface IIncidentService {
handleIncident(
@@ -23,7 +24,7 @@ export interface IIncidentService {
getIncidentsByTeam(
teamId: string,
sortOrder: string,
dateRange: string,
dateRange: DateRange,
page: number,
rowsPerPage: number,
status: boolean | undefined,
@@ -174,7 +175,7 @@ export class IncidentService implements IIncidentService {
getIncidentsByTeam = async (
teamId: string,
sortOrder: string,
dateRange: string,
dateRange: DateRange,
page: number,
rowsPerPage: number,
status: boolean | undefined,
+13 -52
View File
@@ -23,9 +23,9 @@ import { AppError } from "@/utils/AppError.js";
import type { ImportedMonitor } from "@/api/validation/monitorValidation.js";
import { ILogger } from "@/utils/logger.js";
import { IJobScheduler } from "@/worker/worker.interface.js";
import { DateRange } from "@/types/query.js";
const SERVICE_NAME = "MonitorService";
type DateRangeKey = "recent" | "day" | "week" | "month" | "all";
const isUptimeChecksResult = (result: UptimeChecksResult | HardwareChecksResult | PageSpeedChecksResult): result is UptimeChecksResult =>
supportsUptimeDetails(result.monitorType);
@@ -37,13 +37,13 @@ export interface IMonitorService {
addDemoMonitors(args: { userId: string; teamId: string }): Promise<Monitor[]>;
// read
getUptimeDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise<UptimeDetailsResult>;
getHardwareDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise<HardwareDetailsResult>;
getPageSpeedDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise<PageSpeedDetailsResult>;
getUptimeDetailsById(args: { teamId: string; monitorId: string; dateRange: DateRange }): Promise<UptimeDetailsResult>;
getHardwareDetailsById(args: { teamId: string; monitorId: string; dateRange: DateRange }): Promise<HardwareDetailsResult>;
getPageSpeedDetailsById(args: { teamId: string; monitorId: string; dateRange: DateRange }): Promise<PageSpeedDetailsResult>;
getGeoChecksByMonitorId(args: {
teamId: string;
monitorId: string;
dateRange: string;
dateRange: DateRange;
continents?: GeoContinent[];
}): Promise<GroupedGeoCheckResult>;
getMonitorById(args: { teamId: string; monitorId: string }): Promise<Monitor>;
@@ -133,31 +133,6 @@ export class MonitorService implements IMonitorService {
this.incidentsRepository = incidentsRepository;
}
private getDateRange = (dateRange: DateRangeKey) => {
const startDates = {
recent: new Date(new Date().setHours(new Date().getHours() - 2)),
day: new Date(new Date().setDate(new Date().getDate() - 1)),
week: new Date(new Date().setDate(new Date().getDate() - 7)),
month: new Date(new Date().setMonth(new Date().getMonth() - 1)),
all: new Date(0),
};
return {
start: startDates[dateRange],
end: new Date(),
};
};
private getDateFormat = (dateRange: DateRangeKey): string => {
const formatLookup = {
recent: "%Y-%m-%dT%H:%M:00Z",
day: "%Y-%m-%dT%H:00:00Z",
week: "%Y-%m-%dT00:00:00Z",
month: "%Y-%m-%dT00:00:00Z",
all: "%Y-%m-%dT00:00:00Z",
};
return formatLookup[dateRange];
};
createMonitor = async (teamId: string, userId: string, body: Monitor): Promise<void> => {
const monitor = await this.monitorsRepository.create(body, teamId, userId);
if (!monitor) {
@@ -200,15 +175,13 @@ export class MonitorService implements IMonitorService {
}: {
teamId: string;
monitorId: string;
dateRange: string;
dateRange: DateRange;
}): Promise<UptimeDetailsResult> => {
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
if (!monitor) {
throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 });
}
const rangeKey = dateRange as DateRangeKey;
const { start, end } = this.getDateRange(rangeKey);
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, start, end, this.getDateFormat(rangeKey), {
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, dateRange, {
type: monitor.type,
});
const monitorStats = await this.monitorStatsRepository.findByMonitorId(monitor.id);
@@ -237,7 +210,7 @@ export class MonitorService implements IMonitorService {
}: {
teamId: string;
monitorId: string;
dateRange: string;
dateRange: DateRange;
}): Promise<HardwareDetailsResult> => {
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
if (!monitor) {
@@ -247,9 +220,7 @@ export class MonitorService implements IMonitorService {
throw new AppError({ message: `${monitor.type} monitors are not supported for hardware details`, status: 400 });
}
const rangeKey = dateRange as DateRangeKey;
const { start, end } = this.getDateRange(rangeKey);
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, start, end, this.getDateFormat(rangeKey), {
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, dateRange, {
type: monitor.type,
});
@@ -279,7 +250,7 @@ export class MonitorService implements IMonitorService {
}: {
teamId: string;
monitorId: string;
dateRange: string;
dateRange: DateRange;
}): Promise<PageSpeedDetailsResult> => {
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
if (!monitor) {
@@ -289,9 +260,7 @@ export class MonitorService implements IMonitorService {
throw new AppError({ message: `${monitor.type} monitors are not supported for pagespeed details`, status: 400 });
}
const rangeKey = dateRange as DateRangeKey;
const { start, end } = this.getDateRange(rangeKey);
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, start, end, this.getDateFormat(rangeKey), {
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, dateRange, {
type: monitor.type,
});
@@ -318,7 +287,7 @@ export class MonitorService implements IMonitorService {
}: {
teamId: string;
monitorId: string;
dateRange: string;
dateRange: DateRange;
continents?: GeoContinent[];
}): Promise<GroupedGeoCheckResult> => {
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
@@ -330,15 +299,7 @@ export class MonitorService implements IMonitorService {
return { groupedGeoChecks: [] };
}
const rangeKey = dateRange as DateRangeKey;
const { start, end } = this.getDateRange(rangeKey);
const groupedGeoChecks = await this.geoChecksRepository.findGroupedByMonitorIdAndDateRange(
monitor.id,
start,
end,
this.getDateFormat(rangeKey),
continents
);
const groupedGeoChecks = await this.geoChecksRepository.findGroupedByMonitorIdAndDateRange(monitor.id, dateRange, continents);
return { groupedGeoChecks };
};
+16 -3
View File
@@ -1,6 +1,7 @@
import type { GroupedCheck, HasResponseTime, NormalizedCheck, NormalizedUptimeCheck } from "@/domain/checks/check.type.js";
import { type DateRange } from "@/types/query.js";
export const getDateForRange = (dateRange: string): Date | undefined => {
export const getDateForRange = (dateRange: DateRange): Date => {
const now = Date.now();
switch (dateRange) {
case "recent":
@@ -14,12 +15,24 @@ export const getDateForRange = (dateRange: string): Date | undefined => {
case "month":
return new Date(now - 30 * 24 * 60 * 60 * 1000); // 30 days
case "all":
return undefined;
return new Date(0);
default:
return undefined;
return new Date(0);
}
};
export const getDateFormat = (dateRange: DateRange): string => {
const formatLookup = {
hour: "%Y-%m-%dT%H:%M:00Z",
recent: "%Y-%m-%dT%H:%M:00Z",
day: "%Y-%m-%dT%H:00:00Z",
week: "%Y-%m-%dT00:00:00Z",
month: "%Y-%m-%dT00:00:00Z",
all: "%Y-%m-%dT00:00:00Z",
};
return formatLookup[dateRange];
};
const percentileBy = <T>(arr: T[], percentile: number, value: (item: T) => number): number => {
const sorted = arr.slice().sort((a, b) => value(a) - value(b));
const index = (percentile / 100) * (sorted.length - 1);