Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion client/src/Hooks/v1/monitorHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ const useFetchStatsByMonitorId = ({
dateRange,
numToDisplay,
normalize,
pageOffset,
updateTrigger,
}) => {
const [monitor, setMonitor] = useState(undefined);
Expand All @@ -187,6 +188,7 @@ const useFetchStatsByMonitorId = ({
dateRange,
numToDisplay,
normalize,
pageOffset,
});
setMonitor(res?.data?.data ?? undefined);
setAudits(res?.data?.data?.checks?.[0]?.audits ?? undefined);
Expand All @@ -198,7 +200,7 @@ const useFetchStatsByMonitorId = ({
}
};
fetchMonitor();
}, [monitorId, dateRange, numToDisplay, normalize, sortOrder, limit, updateTrigger]);
}, [monitorId, dateRange, numToDisplay, normalize, sortOrder, limit, pageOffset, updateTrigger]);
return [monitor, audits, isLoading, networkError];
};

Expand Down
2 changes: 1 addition & 1 deletion client/src/Utils/NetworkService.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ class NetworkService {
if (config.dateRange) params.append("dateRange", config.dateRange);
if (config.numToDisplay) params.append("numToDisplay", config.numToDisplay);
if (config.normalize) params.append("normalize", config.normalize);

if (config.pageOffset) params.append("pageOffset", config.pageOffset);
return this.axiosInstance.get(
`/monitors/stats/${config.monitorId}?${params.toString()}`
);
Expand Down
3 changes: 2 additions & 1 deletion server/src/controllers/v1/monitorController.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class MonitorController extends BaseController {
await getMonitorStatsByIdParamValidation.validateAsync(req.params);
await getMonitorStatsByIdQueryValidation.validateAsync(req.query);

let { limit, sortOrder, dateRange, numToDisplay, normalize } = req.query;
let { limit, sortOrder, dateRange, numToDisplay, normalize, pageOffset } = req.query;
const monitorId = req?.params?.monitorId;

const teamId = req?.user?.teamId;
Expand All @@ -98,6 +98,7 @@ class MonitorController extends BaseController {
dateRange,
numToDisplay,
normalize,
pageOffset,
});

return res.success({
Expand Down
26 changes: 24 additions & 2 deletions server/src/db/v1/modules/monitorModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,21 @@ class MonitorModule {
};
};

// Helper
offsetDatesByPage = (dates, dateRange, pageOffset) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 | Confidence: High

The date offset calculation is missing support for "month" dateRange and incorrectly handles "hour" dateRange. When dateRange is "month", dateOffsets[dateRange] will be undefined, resulting in NaN date calculations that will break the query. The "hour" dateRange is supported by getDateRange() but not handled here, creating inconsistency. This will cause runtime errors for these date ranges.

Suggested change
offsetDatesByPage = (dates, dateRange, pageOffset) => {
offsetDatesByPage = (dates, dateRange, pageOffset) => {
const dateOffsets = {
hour: 60 * 60 * pageOffset * 1000,
recent: 60 * 60 * 2 * pageOffset * 1000,
day: 60 * 60 * 24 * pageOffset * 1000,
week: 60 * 60 * 24 * 7 * pageOffset * 1000,
month: 60 * 60 * 24 * 30 * pageOffset * 1000, // Approximate month as 30 days
all: 0,
};
return {
start: new Date(dates.start.getTime() - dateOffsets[dateRange]),
end: new Date(new Date().getTime() - dateOffsets[dateRange]),
};
};

Evidence: symbol:offsetDatesByPage, method:getMonitorStatsById

const dateOffsets = {
recent: 60 * 60 * 2 * pageOffset * 1000,
day: 60 * 60 * 24 * pageOffset * 1000,
week: 60 * 60 * 24 * 7 * pageOffset * 1000,
all: 0,
};

return {
start: new Date(dates.start.getTime() - dateOffsets[dateRange]),
end: new Date(new Date().getTime() - dateOffsets[dateRange]),
};
};

//Helper
getMonitorChecks = async (monitorId, dateRange, sortOrder) => {
const indexSpec = {
Expand Down Expand Up @@ -258,7 +273,7 @@ class MonitorModule {
}
};

getMonitorStatsById = async ({ monitorId, sortOrder, dateRange, numToDisplay, normalize }) => {
getMonitorStatsById = async ({ monitorId, sortOrder, dateRange, numToDisplay, normalize, pageOffset }) => {
try {
// Get monitor, if we can't find it, abort with error
const monitor = await this.Monitor.findById(monitorId);
Expand All @@ -269,9 +284,15 @@ class MonitorModule {
// Get query params
const sort = sortOrder === "asc" ? 1 : -1;

let dates = this.getDateRange(dateRange);

if (pageOffset) {
dates = this.offsetDatesByPage(dates, dateRange, pageOffset);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 | Confidence: Medium

The condition if (pageOffset) treats 0 as falsy, preventing the first page (pageOffset=0) from applying date offsets. This creates inconsistent behavior where page 0 uses current dates but subsequent pages use offset dates. The pagination should work consistently starting from page 0.

Suggested change
let dates = this.getDateRange(dateRange);
if (pageOffset) {
dates = this.offsetDatesByPage(dates, dateRange, pageOffset);
if (pageOffset !== undefined && pageOffset !== null) {
dates = this.offsetDatesByPage(dates, dateRange, pageOffset);
}

Evidence: symbol:getMonitorStatsById

}

// Get Checks for monitor in date range requested
const dates = this.getDateRange(dateRange);
const { checksAll, checksForDateRange } = await this.getMonitorChecks(monitorId, dates, sort);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 | Confidence: Medium

Speculative: The current implementation loads all checks (checksAll) into memory for every paginated request. For monitors with extensive history, this could cause performance issues. Consider implementing database-level pagination or counting instead of loading all records.

const checksSkipped = checksAll.filter((check) => check.createdAt > dates.end);

// Build monitor stats
const monitorStats = {
Expand All @@ -281,6 +302,7 @@ class MonitorModule {
latestResponseTime: this.getLatestResponseTime(checksAll),
periodIncidents: this.getIncidents(checksForDateRange),
periodTotalChecks: checksForDateRange.length,
remainingChecks: checksAll.length - checksForDateRange.length - checksSkipped.length,
checks: this.processChecksForDisplay(this.NormalizeData, checksForDateRange, numToDisplay, normalize),
};

Expand Down
3 changes: 2 additions & 1 deletion server/src/service/v1/business/monitorService.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class MonitorService {
return data;
};

getMonitorStatsById = async ({ teamId, monitorId, limit, sortOrder, dateRange, numToDisplay, normalize }) => {
getMonitorStatsById = async ({ teamId, monitorId, limit, sortOrder, dateRange, numToDisplay, normalize, pageOffset }) => {
await this.verifyTeamAccess({ teamId, monitorId });
const monitorStats = await this.db.monitorModule.getMonitorStatsById({
monitorId,
Expand All @@ -52,6 +52,7 @@ class MonitorService {
dateRange,
numToDisplay,
normalize,
pageOffset,
});

return monitorStats;
Expand Down
1 change: 1 addition & 0 deletions server/src/validation/joi.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ const getMonitorStatsByIdQueryValidation = joi.object({
dateRange: joi.string().valid("hour", "day", "week", "month", "all"),
numToDisplay: joi.number(),
normalize: joi.boolean(),
pageOffset: joi.number().integer(),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 | Confidence: Medium

The validation allows negative pageOffset values which don't make logical sense for pagination (can't page into the future). This could lead to confusing API behavior. Adding a minimum constraint would make the API more robust.

Suggested change
pageOffset: joi.number().integer(),
pageOffset: joi.number().integer().min(0),

Evidence: symbol:getMonitorStatsByIdQueryValidation

});

const getCertificateParamValidation = joi.object({
Expand Down