all continents in table

This commit is contained in:
Alex Holliday
2026-02-25 19:34:24 +00:00
parent 477c30286d
commit 885acf6fe6
7 changed files with 124 additions and 134 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ const fetcher = async <T>(url: string, config?: AxiosRequestConfig) => {
};
export const useGet = <T>(
url: string | null,
url: string | null | undefined,
axiosConfig?: AxiosRequestConfig,
swrConfig?: SWRConfiguration
) => {
@@ -1,50 +1,15 @@
import { Table, Pagination, StatusLabel } from "@/Components/design-elements";
import Box from "@mui/material/Box";
import type { Header } from "@/Components/design-elements";
import type { GeoCheck } from "@/Types/GeoCheck";
import type { FlatGeoCheck } from "@/Types/GeoCheck";
import { useTranslation } from "react-i18next";
import { formatDateWithTz } from "@/Utils/TimeUtils";
import type { RootState } from "@/Types/state";
import { useSelector } from "react-redux";
import prettyMilliseconds from "pretty-ms";
// Flatten geo check results - each location becomes a separate row
interface FlattenedGeoCheckResult {
id: string;
monitorId: string;
createdAt: string;
location: {
continent: string;
country: string;
city: string;
};
status: boolean;
statusCode: number;
timings: {
total: number;
};
}
const flattenGeoChecks = (geoChecks: GeoCheck[]): FlattenedGeoCheckResult[] => {
const flattened: FlattenedGeoCheckResult[] = [];
for (const check of geoChecks) {
for (const result of check.results) {
flattened.push({
id: `${check.id}-${result.location.continent}-${result.location.city}`,
monitorId: check.metadata.monitorId,
createdAt: check.createdAt,
location: result.location,
status: result.status,
statusCode: result.statusCode,
timings: result.timings,
});
}
}
return flattened;
};
const getHeaders = (t: Function, uiTimezone: string) => {
const headers: Header<FlattenedGeoCheckResult>[] = [
const headers: Header<FlatGeoCheck>[] = [
{
id: "status",
content: t("common.table.headers.status"),
@@ -71,7 +36,9 @@ const getHeaders = (t: Function, uiTimezone: string) => {
id: "location",
content: t("pages.checks.table.headers.location"),
render: (row) => {
const { continent, country, city } = row.location;
const location = row.location;
if (!location) return "N/A";
const { continent, country, city } = location;
return `${continent} - ${country}, ${city}`;
},
},
@@ -95,7 +62,7 @@ export const GeoChecksTable = ({
rowsPerPage,
setRowsPerPage,
}: {
geoChecks: GeoCheck[];
geoChecks: FlatGeoCheck[];
count: number;
page: number;
setPage: (page: number) => void;
@@ -105,7 +72,6 @@ export const GeoChecksTable = ({
const { t } = useTranslation();
const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
const headers = getHeaders(t, uiTimezone);
const flattenedData = flattenGeoChecks(geoChecks);
const handlePageChange = (
_e: React.MouseEvent<HTMLButtonElement> | null,
@@ -126,7 +92,7 @@ export const GeoChecksTable = ({
<Box>
<Table
headers={headers}
data={flattenedData}
data={geoChecks}
/>
<Pagination
component="div"
+6 -3
View File
@@ -21,7 +21,11 @@ import { useSelector } from "react-redux";
import { useGet } from "@/Hooks/UseApi";
import type { MonitorDetailsResponse } from "@/Types/Monitor";
import type { ChecksResponse } from "@/Types/Check";
import type { GeoChecksResult, GeoChecksResponse, GeoContinent } from "@/Types/GeoCheck";
import type {
GeoChecksResult,
FlatGeoChecksResponse,
GeoContinent,
} from "@/Types/GeoCheck";
import type { RootState } from "@/Types/state";
import { formatDateWithTz } from "@/Utils/TimeUtils";
import { t } from "i18next";
@@ -154,7 +158,7 @@ const UptimeDetailsPage = () => {
geoRowsPerPage,
]);
const { data: geoChecksTableData } = useGet<GeoChecksResponse>(
const { data: geoChecksTableData } = useGet<FlatGeoChecksResponse>(
geoChecksTableUrl,
{},
{ keepPreviousData: true, revalidateOnFocus: false }
@@ -163,7 +167,6 @@ const UptimeDetailsPage = () => {
const geoChecksForTable = geoChecksTableData?.geoChecks ?? [];
const geoChecksCount = geoChecksTableData?.geoChecksCount ?? 0;
// Extract unique locations from geo checks data
const geoLocations = monitor?.geoCheckLocations;
const checks = checksData?.checks ?? [];
+18
View File
@@ -43,6 +43,19 @@ export interface GeoCheck {
updatedAt: string;
}
export interface FlatGeoCheck {
id: string;
monitorId: string;
teamId: string;
type: string;
location: GeoCheckLocation;
status: boolean;
statusCode: number;
timings: GeoCheckTimings;
createdAt: string;
updatedAt: string;
}
export interface GroupedGeoCheck {
bucketDate: string;
continent: GeoContinent;
@@ -60,3 +73,8 @@ export interface GeoChecksResponse {
geoChecks: GeoCheck[];
geoChecksCount: number;
}
export interface FlatGeoChecksResponse {
geoChecks: FlatGeoCheck[];
geoChecksCount: number;
}
@@ -1,11 +1,16 @@
import type { GeoCheck, GroupedGeoCheck } from "@/types/geoCheck.js";
import type { GeoContinent } from "@/types/geoCheck.js";
import type { GeoContinent, FlatGeoCheck } from "@/types/geoCheck.js";
export interface GeoChecksQueryResult {
geoChecksCount: number;
geoChecks: GeoCheck[];
}
export interface FlatGeoChecksQueryResult {
geoChecksCount: number;
geoChecks: FlatGeoCheck[];
}
export interface IGeoChecksRepository {
createGeoChecks(geoChecks: Omit<GeoCheck, "id" | "__v" | "createdAt" | "updatedAt">[]): Promise<GeoCheck[]>;
findByMonitorId(
@@ -15,7 +20,7 @@ export interface IGeoChecksRepository {
page: number,
rowsPerPage: number,
continents?: GeoContinent[]
): Promise<GeoChecksQueryResult>;
): Promise<FlatGeoChecksQueryResult>;
findByMonitorIdAndDateRange(monitorId: string, startDate: Date, endDate: Date): Promise<GeoCheck[]>;
findGroupedByMonitorIdAndDateRange(
monitorId: string,
@@ -1,6 +1,6 @@
import { IGeoChecksRepository } from "./IGeoChecksRepository.js";
import type { GeoCheck, GeoCheckMetadata, GeoCheckResult, GroupedGeoCheck, GeoContinent } from "@/types/geoCheck.js";
import type { GeoChecksQueryResult } from "./IGeoChecksRepository.js";
import type { GeoCheck, GeoCheckMetadata, GeoCheckResult, GroupedGeoCheck, GeoContinent, FlatGeoCheck } from "@/types/geoCheck.js";
import type { GeoChecksQueryResult, FlatGeoChecksQueryResult } from "./IGeoChecksRepository.js";
import { GeoCheckModel, type GeoCheckDocument } from "@/db/models/index.js";
import mongoose from "mongoose";
@@ -112,15 +112,8 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
page: number,
rowsPerPage: number,
continents?: GeoContinent[]
): Promise<GeoChecksQueryResult> => {
): Promise<FlatGeoChecksQueryResult> => {
try {
this.logger.debug({
message: "findByMonitorId called",
service: SERVICE_NAME,
method: "findByMonitorId",
details: { monitorId, continents, continentsLength: continents?.length },
});
const matchStage: Record<string, any> = {
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
...(dateRangeLookup[dateRange] && {
@@ -138,12 +131,6 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
}
if (continents && continents.length > 0) {
this.logger.debug({
message: "Filtering by continents",
service: SERVICE_NAME,
method: "findByMonitorId",
details: { continents },
});
const pipeline: any[] = [
{ $match: matchStage },
{ $unwind: "$results" },
@@ -152,80 +139,78 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
"results.location.continent": { $in: continents },
},
},
{
$group: {
_id: "$_id",
doc: { $first: "$$ROOT" },
results: { $push: "$results" },
},
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: ["$doc", { results: "$results" }],
},
},
},
{ $sort: { createdAt: convertedSortOrder } },
{ $skip: skip },
{ $limit: rowsPerPage },
];
const [countPipeline, dataResults] = await Promise.all([
GeoCheckModel.aggregate([
{ $match: matchStage },
{ $unwind: "$results" },
{ $match: { "results.location.continent": { $in: continents } } },
{ $group: { _id: "$_id" } },
{ $count: "count" },
]),
GeoCheckModel.aggregate(pipeline),
]);
const geoChecksCount = countPipeline[0]?.count || 0;
const geoChecks = dataResults.map(this.toEntity);
return { geoChecksCount, geoChecks };
} else {
this.logger.debug({
message: "No continent filtering - returning all checks",
service: SERVICE_NAME,
method: "findByMonitorId",
});
// Still need to unwind to show each location as a separate row
const pipeline: any[] = [
{ $match: matchStage },
{ $unwind: "$results" },
{
$group: {
_id: "$_id",
doc: { $first: "$$ROOT" },
results: { $push: "$results" },
},
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: ["$doc", { results: "$results" }],
},
},
},
{ $sort: { createdAt: convertedSortOrder } },
{ $skip: skip },
{ $limit: rowsPerPage },
];
const [countPipeline, dataResults] = await Promise.all([
GeoCheckModel.aggregate([{ $match: matchStage }, { $unwind: "$results" }, { $group: { _id: "$_id" } }, { $count: "count" }]),
GeoCheckModel.aggregate(pipeline),
]);
const geoChecksCount = countPipeline[0]?.count || 0;
const geoChecks = dataResults.map(this.toEntity);
return { geoChecksCount, geoChecks };
const pipeline: any[] = [{ $match: matchStage }, { $unwind: "$results" }];
}
// Common pipeline stages for both paths
const pipeline: any[] = [
{ $match: matchStage },
{ $unwind: "$results" },
// Filter by continent if specified
...(continents && continents.length > 0
? [
{
$match: {
"results.location.continent": { $in: continents },
},
},
]
: []),
// Project to flat structure
{
$project: {
_id: 0,
monitorId: "$metadata.monitorId",
teamId: "$metadata.teamId",
type: "$metadata.type",
location: "$results.location",
status: "$results.status",
statusCode: "$results.statusCode",
timings: "$results.timings",
createdAt: 1,
updatedAt: 1,
},
},
{ $sort: { createdAt: convertedSortOrder } },
{ $skip: skip },
{ $limit: rowsPerPage },
];
// Count pipeline
const countPipeline: any[] = [
{ $match: matchStage },
{ $unwind: "$results" },
...(continents && continents.length > 0
? [
{
$match: {
"results.location.continent": { $in: continents },
},
},
]
: []),
{ $count: "count" },
];
const [countResult, dataResults] = await Promise.all([GeoCheckModel.aggregate(countPipeline), GeoCheckModel.aggregate(pipeline)]);
const geoChecksCount = countResult[0]?.count || 0;
const geoChecks: FlatGeoCheck[] = dataResults.map((doc) => ({
id: `${doc.monitorId.toString()}-${new Date(doc.createdAt).getTime()}-${doc.location.continent}-${doc.location.city}-${Math.random().toString(36).substring(2, 15)}`,
monitorId: doc.monitorId.toString(),
teamId: doc.teamId.toString(),
type: doc.type,
location: doc.location,
status: doc.status,
statusCode: doc.statusCode,
timings: doc.timings,
createdAt: new Date(doc.createdAt).toISOString(),
updatedAt: new Date(doc.updatedAt).toISOString(),
}));
return { geoChecksCount, geoChecks };
} catch (error: any) {
this.logger.error({
message: `Error finding geo checks by monitor ID: ${error.message}`,
+13
View File
@@ -45,6 +45,19 @@ export interface GeoCheck {
updatedAt: string;
}
export interface FlatGeoCheck {
id: string;
monitorId: string;
teamId: string;
type: string;
location: GeoCheckLocation;
status: boolean;
statusCode: number;
timings: GeoCheckTimings;
createdAt: string;
updatedAt: string;
}
export interface GroupedGeoCheck {
bucketDate: string;
continent: GeoContinent;