From 395b7fbe2a156717689ed11ed731503d530e5105 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 6 Jun 2026 16:08:00 -0700 Subject: [PATCH 1/6] add uptimetime stats method and aggregation --- .../monitors/IMonitorsRepository.ts | 1 + .../monitors/MongoMonitorsRepository.ts | 65 +++++++++++++++++++ server/src/service/business/monitorService.ts | 2 +- 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/server/src/repositories/monitors/IMonitorsRepository.ts b/server/src/repositories/monitors/IMonitorsRepository.ts index 90fcc5a94..dcd01f3c9 100644 --- a/server/src/repositories/monitors/IMonitorsRepository.ts +++ b/server/src/repositories/monitors/IMonitorsRepository.ts @@ -26,6 +26,7 @@ export interface IMonitorsRepository { // collection fetch findAll(): Promise; findByTeamId(teamId: string, config: TeamQueryConfig): Promise; + findByTeamIdWithStats(teamId: string, config: TeamQueryConfig): Promise; findByIds(monitorIds: string[]): Promise; findByIdsWithChecks(monitorIds: string[], checksCount?: number): Promise; diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/repositories/monitors/MongoMonitorsRepository.ts index 3d1f9c3a5..07382921a 100644 --- a/server/src/repositories/monitors/MongoMonitorsRepository.ts +++ b/server/src/repositories/monitors/MongoMonitorsRepository.ts @@ -85,6 +85,71 @@ class MongoMonitorsRepository implements IMonitorsRepository { return this.mapDocuments(documents); }; + findByTeamIdWithStats = async (teamId: string, config: TeamQueryConfig): Promise => { + const { page = 0, rowsPerPage = 0, filter, field = "createdAt", order = "desc", type, tags } = config ?? {}; + + const query: Record = { + teamId: new mongoose.Types.ObjectId(teamId), + }; + + if (type !== undefined) { + query.type = Array.isArray(type) ? { $in: type } : type; + } + + if (tags !== undefined) { + query.tags = Array.isArray(tags) ? { $in: tags } : tags; + } + + if (filter !== undefined) { + switch (field) { + case "name": + query.$or = [{ name: { $regex: filter, $options: "i" } }, { url: { $regex: filter, $options: "i" } }]; + break; + case "isActive": + query.isActive = filter === "true"; + break; + case "status": + query.status = filter; + break; + case "type": + query.type = filter; + break; + default: + break; + } + } + + const sort = { [field]: order === "asc" ? 1 : -1 } as const; + const skip = Math.max(page, 0) * rowsPerPage; + + const pipeline: PipelineStage[] = [ + { $match: query }, + { $sort: sort }, + ...(rowsPerPage ? [{ $skip: skip }, { $limit: rowsPerPage }] : []), + { + $lookup: { + from: "monitorstats", + localField: "_id", + foreignField: "monitorId", + as: "stats", + }, + }, + { + $addFields: { + uptimePercentage: { $arrayElemAt: ["$stats.uptimePercentage", 0] }, + }, + }, + { + $project: { + stats: 0, + }, + }, + ]; + + const documents = await MonitorModel.aggregate(pipeline); + return documents.map((doc) => this.toEntity(doc)); + }; + findByIds = async (monitorIds: string[]): Promise => { const objectIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id)); const monitors = await MonitorModel.find({ _id: { $in: objectIds } }); diff --git a/server/src/service/business/monitorService.ts b/server/src/service/business/monitorService.ts index 46b81a2ca..d172107c9 100644 --- a/server/src/service/business/monitorService.ts +++ b/server/src/service/business/monitorService.ts @@ -393,7 +393,7 @@ export class MonitorService implements IMonitorService { }): Promise => { const summary = await this.monitorsRepository.findMonitorsSummaryByTeamId(teamId, { type, tags }); const count = await this.monitorsRepository.findMonitorCountByTeamIdAndType(teamId, { type, tags, filter }); - const monitors = await this.monitorsRepository.findByTeamId(teamId, { + const monitors = await this.monitorsRepository.findByTeamIdWithStats(teamId, { limit, type, tags, From dfcf0309fe1b9884c51e6d0597b982da855de331 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 6 Jun 2026 16:08:35 -0700 Subject: [PATCH 2/6] add uptime percentage label --- .../Components/UptimeMonitorsTable.tsx | 21 +++++++++++++++++++ client/src/Utils/MonitorUtils.ts | 10 +++++++++ client/src/locales/en.json | 3 ++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx b/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx index 2c026c35d..c08ca7933 100644 --- a/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx +++ b/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx @@ -7,6 +7,7 @@ import { Pagination, StatusLabel, ColoredLabel, + ValueLabel, } from "@/Components/design-elements"; import { HeatmapResponseTime, HistogramResponseTime } from "@/Components/common"; import { ActionsMenu } from "@/Components/actions-menu"; @@ -25,6 +26,8 @@ import type { RootState } from "@/Types/state"; import { Checkbox } from "@/Components/inputs"; import type { Tag } from "@/Types/Tag"; import { SPACING } from "@/Utils/Theme/constants"; +import { getUptimePercentageColor } from "@/Utils/MonitorUtils"; +import { formatPercentage } from "@/Utils/FormatUtils"; interface MonitorTableProps { monitors: Monitor[]; @@ -259,6 +262,24 @@ export const MonitorTable = ({ } }, }, + { + id: "uptime", + content: t("pages.uptime.table.headers.uptime"), + render: (row) => { + const uptimePercentage = row.uptimePercentage ?? 0; + const formattedPercentage = formatPercentage(uptimePercentage); + const paletteKey = getUptimePercentageColor(uptimePercentage); + const color = theme.palette[paletteKey].main; + return ( + + + + ); + }, + }, { id: "tags", content: t("common.table.headers.tags"), diff --git a/client/src/Utils/MonitorUtils.ts b/client/src/Utils/MonitorUtils.ts index 256d523de..401771f94 100644 --- a/client/src/Utils/MonitorUtils.ts +++ b/client/src/Utils/MonitorUtils.ts @@ -63,6 +63,16 @@ export const getResponseTimeColor = (responseTime: number): PaletteKey => { } }; +export const getUptimePercentageColor = (uptimePercentage: number): PaletteKey => { + if (uptimePercentage >= 0.75) { + return "success"; + } else if (uptimePercentage >= 0.5) { + return "warning"; + } else { + return "error"; + } +}; + export const getInfraGaugeColor = (val: number, theme: any) => { if (val < 50) { return theme.palette.success.main; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 0b78ba1e9..e5f7073b6 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -1607,7 +1607,8 @@ }, "table": { "headers": { - "responseTime": "Response time" + "responseTime": "Response time", + "uptime": "Uptime %" } }, "fallback": { From 0f99c7004d5fa9495f503a1bc6454a70506de50b Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sun, 7 Jun 2026 09:44:44 -0700 Subject: [PATCH 3/6] remove unused import --- .../src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx b/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx index c08ca7933..694ab05d6 100644 --- a/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx +++ b/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx @@ -7,7 +7,6 @@ import { Pagination, StatusLabel, ColoredLabel, - ValueLabel, } from "@/Components/design-elements"; import { HeatmapResponseTime, HistogramResponseTime } from "@/Components/common"; import { ActionsMenu } from "@/Components/actions-menu"; From 19b873ca87de6221c0080a0cfca057ffe0aff28a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sun, 7 Jun 2026 10:02:25 -0700 Subject: [PATCH 4/6] convert findByIds to aggregate pipeline to include uptime percentage --- server/scripts/seed-monitors.js | 268 ++++++++++++++++++ .../monitors/MongoMonitorsRepository.ts | 31 +- 2 files changed, 297 insertions(+), 2 deletions(-) create mode 100644 server/scripts/seed-monitors.js diff --git a/server/scripts/seed-monitors.js b/server/scripts/seed-monitors.js new file mode 100644 index 000000000..b316eec4f --- /dev/null +++ b/server/scripts/seed-monitors.js @@ -0,0 +1,268 @@ +import mongoose from "mongoose"; +import { MonitorModel } from "../dist/db/models/Monitor.js"; +import { UserModel } from "../dist/db/models/User.js"; + +// Seeds a large number of HTTP monitors against real, resolving domains so the +// scheduler can be exercised under load. Run after `npm run build` (imports the +// compiled models from dist/, like scripts/generate-checks.js). +// +// node scripts/seed-monitors.js # 10k monitors, owner auto-detected +// COUNT=2000 INTERVAL=30000 node scripts/seed-monitors.js +// CLEAR=true node scripts/seed-monitors.js # remove prior seed monitors first +// TEAM_ID= USER_ID= node scripts/seed-monitors.js +// +// NOTE: the running queue only schedules monitors it finds at init(), so restart +// the server after seeding for the new monitors to actually start checking. + +const DEFAULT_COUNT = 5_000; +const DEFAULT_BATCH = 1_000; +const DEFAULT_INTERVAL = 60_000; +const NAME_PREFIX = "Seed"; +// Stored in `description` so a re-run with CLEAR=true can find and remove them. +const SEED_MARKER = "checkmate-scaling-seed"; + +// A spread of well-known, reliably-resolving domains across regions, TLDs and +// CDNs. Monitors round-robin through this pool, so the load is split across many +// hosts rather than hammering one. Every entry returns a real HTTP response +// (some may 403/429 under load — still a valid response for scaling purposes). +const DOMAINS = [ + "google.com", + "youtube.com", + "facebook.com", + "wikipedia.org", + "amazon.com", + "reddit.com", + "x.com", + "instagram.com", + "linkedin.com", + "microsoft.com", + "apple.com", + "cloudflare.com", + "github.com", + "gitlab.com", + "stackoverflow.com", + "netflix.com", + "bing.com", + "yahoo.com", + "duckduckgo.com", + "mozilla.org", + "wordpress.org", + "wordpress.com", + "adobe.com", + "salesforce.com", + "oracle.com", + "ibm.com", + "intel.com", + "nvidia.com", + "amd.com", + "dell.com", + "hp.com", + "cisco.com", + "vmware.com", + "redhat.com", + "ubuntu.com", + "debian.org", + "kernel.org", + "python.org", + "nodejs.org", + "npmjs.com", + "docker.com", + "kubernetes.io", + "golang.org", + "rust-lang.org", + "php.net", + "ruby-lang.org", + "java.com", + "spring.io", + "djangoproject.com", + "flask.palletsprojects.com", + "vuejs.org", + "reactjs.org", + "angular.io", + "svelte.dev", + "jquery.com", + "npm.im", + "pypi.org", + "rubygems.org", + "packagist.org", + "maven.apache.org", + "apache.org", + "nginx.org", + "postgresql.org", + "mysql.com", + "mongodb.com", + "redis.io", + "elastic.co", + "grafana.com", + "prometheus.io", + "hashicorp.com", + "digitalocean.com", + "linode.com", + "vultr.com", + "heroku.com", + "netlify.com", + "vercel.com", + "render.com", + "fly.io", + "railway.app", + "cloudinary.com", + "twilio.com", + "stripe.com", + "paypal.com", + "shopify.com", + "squarespace.com", + "wix.com", + "godaddy.com", + "namecheap.com", + "name.com", + "gandi.net", + "bbc.co.uk", + "theguardian.com", + "nytimes.com", + "washingtonpost.com", + "cnn.com", + "reuters.com", + "bloomberg.com", + "forbes.com", + "wsj.com", + "economist.com", + "nature.com", + "sciencedirect.com", + "arxiv.org", + "mit.edu", + "stanford.edu", + "harvard.edu", + "berkeley.edu", + "ox.ac.uk", + "cam.ac.uk", + "ethz.ch", + "spotify.com", + "soundcloud.com", + "twitch.tv", + "vimeo.com", + "dailymotion.com", + "imdb.com", + "wikimedia.org", + "archive.org", + "ietf.org", + "w3.org", + "who.int", + "un.org", + "europa.eu", + "gov.uk", + "canada.ca", + "alibaba.com", + "aliexpress.com", + "baidu.com", + "tencent.com", + "naver.com", + "rakuten.co.jp", + "yandex.com", + "ozon.ru", + "mercadolibre.com", + "flipkart.com", + "booking.com", + "airbnb.com", + "expedia.com", + "tripadvisor.com", + "uber.com", + "dropbox.com", + "box.com", + "atlassian.com", + "trello.com", + "notion.so", + "slack.com", + "zoom.us", + "asana.com", + "figma.com", + "canva.com", +]; + +const parseObjectId = (value) => { + if (!value) return null; + try { + return new mongoose.Types.ObjectId(value); + } catch { + console.warn(`Ignoring invalid ObjectId '${value}'`); + return null; + } +}; + +// Monitors need a real teamId/userId to show up for an account and to be picked +// up by the queue. Prefer explicit env ids, otherwise derive from the oldest +// existing user (the typical single-tenant dev setup). +async function resolveOwner() { + const teamId = parseObjectId(process.env.TEAM_ID); + const userId = parseObjectId(process.env.USER_ID); + if (teamId && userId) return { teamId, userId }; + + const user = await UserModel.findOne(userId ? { _id: userId } : {}).sort({ createdAt: 1 }); + if (!user?.teamId) { + throw new Error( + "Could not determine an owner: no existing user with a teamId. " + "Create an account first, or pass TEAM_ID and USER_ID explicitly." + ); + } + return { teamId: teamId ?? user.teamId, userId: user._id }; +} + +async function run() { + const mongoUri = process.env.MONGO_URI ?? "mongodb://localhost:27017/uptime_db"; + const total = Number(process.env.COUNT ?? DEFAULT_COUNT); + const batchSize = Number(process.env.BATCH_SIZE ?? DEFAULT_BATCH); + const interval = Number(process.env.INTERVAL ?? DEFAULT_INTERVAL); + + console.log(`Connecting to MongoDB at ${mongoUri}`); + await mongoose.connect(mongoUri); + + const { teamId, userId } = await resolveOwner(); + + if (process.env.CLEAR === "true") { + const { deletedCount } = await MonitorModel.deleteMany({ description: SEED_MARKER }); + console.log(`Removed ${deletedCount} previously seeded monitors`); + } + + console.log( + `Seeding ${total} http monitors for team ${teamId} (user ${userId}) across ${DOMAINS.length} domains, ` + + `interval ${interval}ms, in batches of ${batchSize}` + ); + + const docs = []; + const startTime = Date.now(); + let inserted = 0; + + for (let i = 0; i < total; i += 1) { + const domain = DOMAINS[i % DOMAINS.length]; + docs.push({ + userId, + teamId, + name: `${NAME_PREFIX} ${String(i + 1).padStart(5, "0")} ${domain}`, + description: SEED_MARKER, + type: "http", + url: `https://${domain}`, + isActive: true, + interval, + }); + + if (docs.length === batchSize) { + await MonitorModel.insertMany(docs, { ordered: false }); + inserted += docs.length; + console.log(`Inserted ${inserted} / ${total}`); + docs.length = 0; + } + } + + if (docs.length > 0) { + await MonitorModel.insertMany(docs, { ordered: false }); + inserted += docs.length; + } + + await mongoose.disconnect(); + const duration = ((Date.now() - startTime) / 1000).toFixed(2); + console.log(`Finished inserting ${inserted} monitors in ${duration}s`); + console.log("Restart the server so the queue schedules the new monitors."); +} + +run().catch((error) => { + console.error("Failed to seed monitors", error); + process.exit(1); +}); diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/repositories/monitors/MongoMonitorsRepository.ts index 07382921a..0b9de8ace 100644 --- a/server/src/repositories/monitors/MongoMonitorsRepository.ts +++ b/server/src/repositories/monitors/MongoMonitorsRepository.ts @@ -151,9 +151,36 @@ class MongoMonitorsRepository implements IMonitorsRepository { }; findByIds = async (monitorIds: string[]): Promise => { + if (!monitorIds.length) { + return []; + } + const objectIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id)); - const monitors = await MonitorModel.find({ _id: { $in: objectIds } }); - return this.mapDocuments(monitors); + + const pipeline: PipelineStage[] = [ + { $match: { _id: { $in: objectIds } } }, + { + $lookup: { + from: "monitorstats", + localField: "_id", + foreignField: "monitorId", + as: "stats", + }, + }, + { + $addFields: { + uptimePercentage: { $arrayElemAt: ["$stats.uptimePercentage", 0] }, + }, + }, + { + $project: { + stats: 0, + }, + }, + ]; + + const documents = await MonitorModel.aggregate(pipeline); + return documents.map((doc) => this.toEntity(doc)); }; findByIdsWithChecks = async (monitorIds: string[], checksCount: number = 25): Promise => { From eeac7849220dbb664be3cde083e0d771961b42dd Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sun, 7 Jun 2026 10:02:44 -0700 Subject: [PATCH 5/6] dispaly uptime percentage on status pages --- .../StatusPage/Status/themes/shared/BaseStatusPage.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/src/Pages/StatusPage/Status/themes/shared/BaseStatusPage.tsx b/client/src/Pages/StatusPage/Status/themes/shared/BaseStatusPage.tsx index 71aa85a89..b19eea0f4 100644 --- a/client/src/Pages/StatusPage/Status/themes/shared/BaseStatusPage.tsx +++ b/client/src/Pages/StatusPage/Status/themes/shared/BaseStatusPage.tsx @@ -27,6 +27,7 @@ import { statusBadgeKey, } from "@/Pages/StatusPage/Status/themes/shared/overallStatus"; import { useStatusPageTheme } from "@/Pages/StatusPage/Status/themes/StatusPageThemeProvider"; +import { formatPercentage } from "@/Utils/FormatUtils"; type StatusPageMonitor = Monitor & { checks?: Monitor["recentChecks"] }; @@ -161,6 +162,7 @@ export const BaseStatusPage = ({ statusPage, monitors, config }: Props) => { const showInfra = isHardware && statusPage.showInfrastructure !== false; const showChart = !isHardware && statusPage.showCharts !== false; const badgeTone = monitorBadgeTone(monitor.status); + const uptimePercentage = formatPercentage(monitor.uptimePercentage ?? 0); return ( { > {getMonitorTypeLabel(monitor.type, t)} + + {uptimePercentage} + {monitor.url && ( Date: Sun, 7 Jun 2026 10:16:31 -0700 Subject: [PATCH 6/6] update tests --- .../helpers/InMemoryMonitorsRepository.ts | 4 ++++ .../test/unit/services/monitorService.test.ts | 21 ++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/server/test/helpers/InMemoryMonitorsRepository.ts b/server/test/helpers/InMemoryMonitorsRepository.ts index a151ee41e..756a7bfb7 100644 --- a/server/test/helpers/InMemoryMonitorsRepository.ts +++ b/server/test/helpers/InMemoryMonitorsRepository.ts @@ -31,6 +31,10 @@ export class InMemoryMonitorsRepository implements IMonitorsRepository { return this.monitors.filter((m) => m.teamId === _teamId).map((m) => ({ ...m })); } + async findByTeamIdWithStats(_teamId: string, _config: TeamQueryConfig): Promise { + return this.monitors.filter((m) => m.teamId === _teamId).map((m) => ({ ...m })); + } + async findByIds(monitorIds: string[]): Promise { return this.monitors.filter((m) => monitorIds.includes(m.id)).map((m) => ({ ...m })); } diff --git a/server/test/unit/services/monitorService.test.ts b/server/test/unit/services/monitorService.test.ts index 5e73ab77b..a21967de8 100644 --- a/server/test/unit/services/monitorService.test.ts +++ b/server/test/unit/services/monitorService.test.ts @@ -16,6 +16,7 @@ const createMonitorsRepositoryMock = () => createMonitors: jest.fn(), findById: jest.fn(), findByTeamId: jest.fn(), + findByTeamIdWithStats: jest.fn(), findByIds: jest.fn(), findMonitorCountByTeamIdAndType: jest.fn(), findMonitorsSummaryByTeamId: jest.fn(), @@ -562,7 +563,7 @@ describe("MonitorService", () => { const monitorsRepository = createMonitorsRepositoryMock(); (monitorsRepository.findMonitorsSummaryByTeamId as jest.Mock).mockResolvedValue({ totalMonitors: 1, upMonitors: 1 }); (monitorsRepository.findMonitorCountByTeamIdAndType as jest.Mock).mockResolvedValue(1); - (monitorsRepository.findByTeamId as jest.Mock).mockResolvedValue([ + (monitorsRepository.findByTeamIdWithStats as jest.Mock).mockResolvedValue([ makeMonitor({ recentChecks: [ { responseTime: 10, status: true, message: "OK" }, @@ -583,7 +584,7 @@ describe("MonitorService", () => { const monitorsRepository = createMonitorsRepositoryMock(); (monitorsRepository.findMonitorsSummaryByTeamId as jest.Mock).mockResolvedValue(null); (monitorsRepository.findMonitorCountByTeamIdAndType as jest.Mock).mockResolvedValue(0); - (monitorsRepository.findByTeamId as jest.Mock).mockResolvedValue(null); + (monitorsRepository.findByTeamIdWithStats as jest.Mock).mockResolvedValue(null); const { service } = createService({ monitorsRepository }); const result = await service.getMonitorsWithChecksByTeamId({ teamId: TEAM_ID }); @@ -596,7 +597,7 @@ describe("MonitorService", () => { const monitorsRepository = createMonitorsRepositoryMock(); (monitorsRepository.findMonitorsSummaryByTeamId as jest.Mock).mockResolvedValue({ totalMonitors: 1 }); (monitorsRepository.findMonitorCountByTeamIdAndType as jest.Mock).mockResolvedValue(1); - (monitorsRepository.findByTeamId as jest.Mock).mockResolvedValue([ + (monitorsRepository.findByTeamIdWithStats as jest.Mock).mockResolvedValue([ makeMonitor({ type: "hardware", recentChecks: [ @@ -617,7 +618,7 @@ describe("MonitorService", () => { const monitorsRepository = createMonitorsRepositoryMock(); (monitorsRepository.findMonitorsSummaryByTeamId as jest.Mock).mockResolvedValue({ totalMonitors: 1 }); (monitorsRepository.findMonitorCountByTeamIdAndType as jest.Mock).mockResolvedValue(1); - (monitorsRepository.findByTeamId as jest.Mock).mockResolvedValue([ + (monitorsRepository.findByTeamIdWithStats as jest.Mock).mockResolvedValue([ makeMonitor({ type: "hardware", recentChecks: [ @@ -637,7 +638,7 @@ describe("MonitorService", () => { const monitorsRepository = createMonitorsRepositoryMock(); (monitorsRepository.findMonitorsSummaryByTeamId as jest.Mock).mockResolvedValue({ totalMonitors: 1 }); (monitorsRepository.findMonitorCountByTeamIdAndType as jest.Mock).mockResolvedValue(1); - (monitorsRepository.findByTeamId as jest.Mock).mockResolvedValue([ + (monitorsRepository.findByTeamIdWithStats as jest.Mock).mockResolvedValue([ makeMonitor({ type: "http", recentChecks: [ @@ -657,7 +658,7 @@ describe("MonitorService", () => { const monitorsRepository = createMonitorsRepositoryMock(); (monitorsRepository.findMonitorsSummaryByTeamId as jest.Mock).mockResolvedValue({ totalMonitors: 2 }); (monitorsRepository.findMonitorCountByTeamIdAndType as jest.Mock).mockResolvedValue(2); - (monitorsRepository.findByTeamId as jest.Mock).mockResolvedValue([ + (monitorsRepository.findByTeamIdWithStats as jest.Mock).mockResolvedValue([ makeMonitor({ id: "m1", type: "http", @@ -689,7 +690,7 @@ describe("MonitorService", () => { const monitorsRepository = createMonitorsRepositoryMock(); (monitorsRepository.findMonitorsSummaryByTeamId as jest.Mock).mockResolvedValue({ totalMonitors: 1 }); (monitorsRepository.findMonitorCountByTeamIdAndType as jest.Mock).mockResolvedValue(1); - (monitorsRepository.findByTeamId as jest.Mock).mockResolvedValue([makeMonitor({ recentChecks: [] })]); + (monitorsRepository.findByTeamIdWithStats as jest.Mock).mockResolvedValue([makeMonitor({ recentChecks: [] })]); const { service } = createService({ monitorsRepository }); const result = await service.getMonitorsWithChecksByTeamId({ teamId: TEAM_ID }); @@ -701,7 +702,7 @@ describe("MonitorService", () => { const monitorsRepository = createMonitorsRepositoryMock(); (monitorsRepository.findMonitorsSummaryByTeamId as jest.Mock).mockResolvedValue({ totalMonitors: 1 }); (monitorsRepository.findMonitorCountByTeamIdAndType as jest.Mock).mockResolvedValue(1); - (monitorsRepository.findByTeamId as jest.Mock).mockResolvedValue([makeMonitor({ recentChecks: undefined })]); + (monitorsRepository.findByTeamIdWithStats as jest.Mock).mockResolvedValue([makeMonitor({ recentChecks: undefined })]); const { service } = createService({ monitorsRepository }); const result = await service.getMonitorsWithChecksByTeamId({ teamId: TEAM_ID }); @@ -713,7 +714,7 @@ describe("MonitorService", () => { const monitorsRepository = createMonitorsRepositoryMock(); (monitorsRepository.findMonitorsSummaryByTeamId as jest.Mock).mockResolvedValue({ totalMonitors: 0 }); (monitorsRepository.findMonitorCountByTeamIdAndType as jest.Mock).mockResolvedValue(0); - (monitorsRepository.findByTeamId as jest.Mock).mockResolvedValue([]); + (monitorsRepository.findByTeamIdWithStats as jest.Mock).mockResolvedValue([]); const { service } = createService({ monitorsRepository }); await service.getMonitorsWithChecksByTeamId({ @@ -727,7 +728,7 @@ describe("MonitorService", () => { order: "asc", }); - expect(monitorsRepository.findByTeamId).toHaveBeenCalledWith(TEAM_ID, { + expect(monitorsRepository.findByTeamIdWithStats).toHaveBeenCalledWith(TEAM_ID, { limit: 10, type: "http", page: 1,