diff --git a/Common/Types/Date.ts b/Common/Types/Date.ts index daa8bdaf79..ff2fc017b7 100644 --- a/Common/Types/Date.ts +++ b/Common/Types/Date.ts @@ -359,6 +359,12 @@ export default class OneUptimeDate { return moment(date).isAfter(startDate); } + public static isEqualBySeconds(date: Date, startDate: Date): boolean { + date = this.fromString(date); + startDate = this.fromString(startDate); + return moment(date).isSame(startDate, 'seconds'); + } + public static hasExpired(expirationDate: Date): boolean { expirationDate = this.fromString(expirationDate); return !moment(this.getCurrentDate()).isBefore(expirationDate); diff --git a/CommonServer/Services/MonitorGroupService.ts b/CommonServer/Services/MonitorGroupService.ts index 2163f55276..dc4a86a636 100644 --- a/CommonServer/Services/MonitorGroupService.ts +++ b/CommonServer/Services/MonitorGroupService.ts @@ -46,9 +46,7 @@ export class Service extends DatabaseService { limit: LIMIT_PER_PROJECT, skip: 0, select: { - monitor: { - currentMonitorStatusId: true, - }, + monitorId: true, }, props: { isRoot: true, @@ -68,8 +66,8 @@ export class Service extends DatabaseService { createdAt: QueryHelper.inBetween(startDate, endDate), }, select: { - monitorId: true, createdAt: true, + monitorId: true, monitorStatus: { name: true, color: true, diff --git a/CommonUI/src/Components/MonitorGraphs/Uptime.tsx b/CommonUI/src/Components/MonitorGraphs/Uptime.tsx index 9c0c24e089..0d96a975f8 100644 --- a/CommonUI/src/Components/MonitorGraphs/Uptime.tsx +++ b/CommonUI/src/Components/MonitorGraphs/Uptime.tsx @@ -12,6 +12,11 @@ import OneUptimeDate from 'Common/Types/Date'; import DayUptimeGraph, { Event } from '../Graphs/DayUptimeGraph'; import { Green } from 'Common/Types/BrandColors'; import ErrorMessage from '../ErrorMessage/ErrorMessage'; +import ObjectID from 'Common/Types/ObjectID'; + +export interface MonitorEvent extends Event { + monitorId: ObjectID; +} export interface ComponentProps { startDate: Date; @@ -28,27 +33,144 @@ const MonitorUptimeGraph: FunctionComponent = ( ): ReactElement => { const [events, setEvents] = useState>([]); - useEffect(() => { - const eventList: Array = []; - // convert data to events. - for (let i: number = 0; i < props.items.length; i++) { - if (!props.items[i]) { - break; + /** + * This function, `getMonitorEventsForId`, takes a `monitorId` as an argument and returns an array of `MonitorEvent` objects. + * @param {ObjectID} monitorId - The ID of the monitor for which events are to be fetched. + * @returns {Array} - An array of `MonitorEvent` objects. + */ + const getMonitorEventsForId: (monitorId: ObjectID) => Array = (monitorId: ObjectID): Array => { + // Initialize an empty array to store the monitor events. + const eventList: Array = []; + + const monitorEvents = props.items.filter((item) => item.monitorId?.toString() === monitorId.toString()); + + // Loop through the items in the props object. + for (let i: number = 0; i < monitorEvents.length; i++) { + + // If the current item is null or undefined, skip to the next iteration. + if (!monitorEvents[i]) { + continue; } + + // Set the start date of the event to the creation date of the current item. If it doesn't exist, use the current date. + const startDate: Date = monitorEvents[i]!.createdAt || OneUptimeDate.getCurrentDate(); + + // Initialize the end date as the current date. + let endDate: Date = OneUptimeDate.getCurrentDate(); + + // If there is a next item and it has a creation date, use that as the end date. + if (monitorEvents[i + 1] && monitorEvents[i + 1]!.createdAt) { + endDate = monitorEvents[i + 1]!.createdAt!; + } + + // Push a new MonitorEvent object to the eventList array with properties from the current item and calculated dates. eventList.push({ - startDate: - props.items[i]!.createdAt || OneUptimeDate.getCurrentDate(), - endDate: - props.items[i + 1] && props.items[i + 1]!.createdAt - ? (props.items[i + 1]?.createdAt as Date) - : OneUptimeDate.getCurrentDate(), - label: props.items[i]?.monitorStatus?.name || 'Operational', - priority: props.items[i]?.monitorStatus?.priority || 0, - color: props.items[i]?.monitorStatus?.color || Green, + startDate: startDate, + endDate: endDate, + label: monitorEvents[i]?.monitorStatus?.name || 'Operational', + priority: monitorEvents[i]?.monitorStatus?.priority || 0, + color: monitorEvents[i]?.monitorStatus?.color || Green, + monitorId: monitorEvents[i]?.monitorId!, }); } + // Return the populated eventList array. + return eventList; + } + + + + const getMonitorEvents: () => Array = (): Array => { + + + // get all distinct monitor ids. + const monitorIds: Array = []; + + for (let i: number = 0; i < props.items.length; i++) { + if (!props.items[i]) { + continue; + } + + const monitorId: string | undefined = props.items[i]!.monitorId?.toString(); + + if (!monitorId) { + continue; + } + + if (!monitorIds.find((item) => item.toString() === monitorId)) { + monitorIds.push(new ObjectID(monitorId)); + } + } + + const eventList: Array = []; + // convert data to events. + + for (const monitorId of monitorIds) { + const monitorEvents: Array = getMonitorEventsForId(monitorId); + eventList.push(...monitorEvents); + } + + // sort event list by start date. + eventList.sort((a: MonitorEvent, b: MonitorEvent) => { + if (OneUptimeDate.isAfter(a.startDate, b.startDate)) { + return 1; + } + + if (OneUptimeDate.isAfter(b.startDate, a.startDate)) { + return -1; + } + + return 0; + }); + + return [...eventList]; + } + + + useEffect(() => { + let monitorEventList: Array = getMonitorEvents(); + + let eventList: Array = []; + + for(const monitorEvent of monitorEventList) { + + // if this event starts after the last event, then add it to the list directly. + if(eventList.length === 0 || OneUptimeDate.isAfter(monitorEvent.startDate, eventList[eventList.length - 1]!.endDate) || OneUptimeDate.isEqualBySeconds(monitorEvent.startDate, eventList[eventList.length - 1]!.endDate)) { + eventList.push(monitorEvent); + continue; + } + + // if this event starts before the last event, then we need to check if it ends before the last event. If it does, then we can skip this event if the monitrEvent is of lower priority than the last event. If it is of higher priority, then we need to add it to the list and remove the last event from the list. + if(OneUptimeDate.isBefore(monitorEvent.startDate, eventList[eventList.length - 1]!.endDate)) { + + if(monitorEvent.priority > eventList[eventList.length - 1]!.priority) { + // end the last event at the start of this event. + + const tempLastEvent: Event = {...eventList[eventList.length - 1]} as any; + + eventList[eventList.length - 1]!.endDate = monitorEvent.startDate; + eventList.push(monitorEvent); + + // if the monitorEvent endDate is before the end of the last event, then we need to add the end of the last event to the list. + + if(OneUptimeDate.isBefore(monitorEvent.endDate, tempLastEvent.endDate)) { + eventList.push({ + startDate: monitorEvent.endDate, + endDate: tempLastEvent.endDate, + label: tempLastEvent.label, + priority: tempLastEvent.priority, + color: tempLastEvent.color, + }); + } + } + + continue; + } + + } + + setEvents(eventList); }, [props.items]); diff --git a/Dashboard/src/Pages/MonitorGroup/View/Index.tsx b/Dashboard/src/Pages/MonitorGroup/View/Index.tsx index 6f619a0aab..3e09da14a8 100644 --- a/Dashboard/src/Pages/MonitorGroup/View/Index.tsx +++ b/Dashboard/src/Pages/MonitorGroup/View/Index.tsx @@ -58,7 +58,7 @@ const MonitorGroupView: FunctionComponent = ( DASHBOARD_API_URL.toString() ) .addRoute(new MonitorGroup().getCrudApiPath()!) - .addRoute('/current-status/') + .addRoute('/timeline/') .addRoute(`/${modelId.toString()}`), } ); @@ -116,7 +116,7 @@ const MonitorGroupView: FunctionComponent = ( }, ]} cardProps={{ - title: 'Monitor Group Hello', + title: 'Monitor Group Details', description: 'Here are more details for this monitor group.', }} @@ -230,7 +230,7 @@ const MonitorGroupView: FunctionComponent = (