mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-14 13:38:39 -05:00
all continents in table
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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 ?? [];
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user