From 96a45ffe6bbd80d3755c96d054871c0e859b2ce1 Mon Sep 17 00:00:00 2001 From: goldpulpy Date: Sat, 14 Feb 2026 03:09:51 +0300 Subject: [PATCH 1/7] fix(status): correct incident filtering logic in history processor --- .../ts/shared/services/status.processor.ts | 55 +++++++------------ 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/src/app/frontend/assets/ts/shared/services/status.processor.ts b/src/app/frontend/assets/ts/shared/services/status.processor.ts index 17d9a73..e07cc97 100644 --- a/src/app/frontend/assets/ts/shared/services/status.processor.ts +++ b/src/app/frontend/assets/ts/shared/services/status.processor.ts @@ -19,7 +19,6 @@ import { BaseIncident, IncidentStatus } from "@/shared/types/incident"; const HISTORY_DAYS = 30; const HISTORY_OFFSET = HISTORY_DAYS - 1; -const MS_PER_DAY = 24 * 60 * 60 * 1000; export class StatusProcessor { buildEnrichedIncidents( @@ -97,12 +96,10 @@ export class StatusProcessor { private buildHistoryForMonitors(monitors: MonitorForStatus[]): Days[] { const now = new Date(); - const startDate = subDays(now, HISTORY_OFFSET); - - const incidentsByDay = this.indexIncidentsByDay(monitors, startDate); return Array.from({ length: HISTORY_DAYS }, (_, dayIndex) => { const day = subDays(now, HISTORY_OFFSET - dayIndex); + const dayStart = startOfDay(day); const dayEnd = endOfDay(day); const hasActiveMonitor = monitors.some((monitor) => { @@ -113,7 +110,25 @@ export class StatusProcessor { if (!hasActiveMonitor) { return { color: "bg-empty", index: dayIndex }; } - const dayIncidents = incidentsByDay.get(dayIndex) ?? []; + + const dayIncidents: EnrichedIncident[] = []; + for (const monitor of monitors) { + for (const incident of monitor.incidents) { + const incidentStartDate = parseISO(incident.created_at); + const incidentEndDate = incident.ended_at + ? parseISO(incident.ended_at) + : null; + + const startedBeforeOrDuringDay = !isBefore(dayEnd, incidentStartDate); + const endedAfterDay = + !incidentEndDate || !isBefore(incidentEndDate, dayStart); + + if (startedBeforeOrDuringDay && endedAfterDay) { + const enrichedIncident = this.buildIncident(monitor, incident); + dayIncidents.push(enrichedIncident); + } + } + } if (dayIncidents.length === 0) { return { @@ -133,36 +148,6 @@ export class StatusProcessor { }); } - private indexIncidentsByDay( - monitors: MonitorForStatus[], - startDate: Date, - ): Map { - const incidentsByDay = new Map(); - const startTime = startOfDay(startDate).getTime(); - - for (const monitor of monitors) { - for (const incident of monitor.incidents) { - const incidentDate = parseISO(incident.created_at); - const dayIndex = Math.floor( - (incidentDate.getTime() - startTime) / MS_PER_DAY, - ); - - if (dayIndex < 0 || dayIndex >= HISTORY_DAYS) { - continue; - } - - const enrichedIncident = this.buildIncident(monitor, incident); - - if (!incidentsByDay.has(dayIndex)) { - incidentsByDay.set(dayIndex, []); - } - incidentsByDay.get(dayIndex)!.push(enrichedIncident); - } - } - - return incidentsByDay; - } - private buildIncident( monitor: MonitorForStatus, incident: IncidentForStatus, From 02078c97dfea101f42e6ab09b018451c48a1b0bd Mon Sep 17 00:00:00 2001 From: goldpulpy Date: Sat, 14 Feb 2026 03:23:18 +0300 Subject: [PATCH 2/7] refactor(status): improve incident processing and history building logic --- .../ts/shared/services/status.processor.ts | 294 +++++++++++++----- 1 file changed, 215 insertions(+), 79 deletions(-) diff --git a/src/app/frontend/assets/ts/shared/services/status.processor.ts b/src/app/frontend/assets/ts/shared/services/status.processor.ts index e07cc97..35969e6 100644 --- a/src/app/frontend/assets/ts/shared/services/status.processor.ts +++ b/src/app/frontend/assets/ts/shared/services/status.processor.ts @@ -19,21 +19,27 @@ import { BaseIncident, IncidentStatus } from "@/shared/types/incident"; const HISTORY_DAYS = 30; const HISTORY_OFFSET = HISTORY_DAYS - 1; +const LOCALE = "en-US" as const; + +const DATE_FORMAT_OPTIONS = { + date: { month: "long", day: "numeric" } as const, + time: { hour: "2-digit", minute: "2-digit" } as const, +} as const; export class StatusProcessor { buildEnrichedIncidents( components: StatusComponent[], ): Map { - const enrichedIncidentsByMonitor = new Map(); - components.forEach((component) => { - const monitors = isGroup(component) ? component.monitors : [component]; - monitors.forEach((monitor) => { - const incidents = monitor.incidents.map((incident) => - this.buildIncident(monitor, incident), - ); - enrichedIncidentsByMonitor.set(monitor.id, incidents); - }); - }); + const enrichedIncidentsByMonitor = new Map(); + + const monitors = this.flattenMonitors(components); + + for (const monitor of monitors) { + const enrichedIncidents = monitor.incidents.map((incident) => + this.buildEnrichedIncident(monitor, incident), + ); + enrichedIncidentsByMonitor.set(monitor.id, enrichedIncidents); + } return enrichedIncidentsByMonitor; } @@ -44,40 +50,59 @@ export class StatusProcessor { ): EnrichedStatusComponent[] { return components.map((component) => { if (isGroup(component)) { - return { - ...component, - monitors: component.monitors.map((monitor) => ({ - ...monitor, - currentIncident: this.findActiveIncident( - enrichedIncidentsByMonitor.get(monitor.id) ?? [], - ), - history: this.buildHistoryForMonitors([monitor]), - })), - }; - } else { - return { - ...component, - currentIncident: this.findActiveIncident( - enrichedIncidentsByMonitor.get(component.id) ?? [], - ), - history: this.buildHistoryForMonitors([component]), - }; + return this.enrichGroupComponent(component, enrichedIncidentsByMonitor); } + return this.enrichMonitorComponent(component, enrichedIncidentsByMonitor); }); } findActiveIncident( incidents: T[], ): T | typeof OPERATIONAL_STATUS { - const prioritizedIncidents = this.getPrioritizedIncidents(incidents); + const prioritizedIncidents = this.prioritizeIncidents(incidents); const activeIncident = prioritizedIncidents.find( - (inc) => inc.status === IncidentStatus.OPEN, + (incident) => incident.status === IncidentStatus.OPEN, ); return activeIncident ?? OPERATIONAL_STATUS; } - private getPrioritizedIncidents(incidents: T[]): T[] { + private flattenMonitors(components: StatusComponent[]): MonitorForStatus[] { + return components.flatMap((component) => + isGroup(component) ? component.monitors : [component], + ); + } + + private enrichGroupComponent( + component: StatusComponent & { monitors: MonitorForStatus[] }, + enrichedIncidentsByMonitor: Map, + ): EnrichedStatusComponent { + return { + ...component, + monitors: component.monitors.map((monitor) => ({ + ...monitor, + currentIncident: this.findActiveIncident( + enrichedIncidentsByMonitor.get(monitor.id) ?? [], + ), + history: this.buildHistoryForMonitors([monitor]), + })), + }; + } + + private enrichMonitorComponent( + component: MonitorForStatus, + enrichedIncidentsByMonitor: Map, + ): EnrichedStatusComponent { + return { + ...component, + currentIncident: this.findActiveIncident( + enrichedIncidentsByMonitor.get(component.id) ?? [], + ), + history: this.buildHistoryForMonitors([component]), + }; + } + + private prioritizeIncidents(incidents: T[]): T[] { return [...incidents].sort( (a, b) => INCIDENT_PRIORITY_ORDER.indexOf(a.type) - @@ -102,33 +127,17 @@ export class StatusProcessor { const dayStart = startOfDay(day); const dayEnd = endOfDay(day); - const hasActiveMonitor = monitors.some((monitor) => { - const componentCreated = parseISO(monitor.created_at); - return !isBefore(dayEnd, componentCreated); - }); + const hasActiveMonitor = this.hasActiveMonitorOnDay(monitors, dayEnd); if (!hasActiveMonitor) { return { color: "bg-empty", index: dayIndex }; } - const dayIncidents: EnrichedIncident[] = []; - for (const monitor of monitors) { - for (const incident of monitor.incidents) { - const incidentStartDate = parseISO(incident.created_at); - const incidentEndDate = incident.ended_at - ? parseISO(incident.ended_at) - : null; - - const startedBeforeOrDuringDay = !isBefore(dayEnd, incidentStartDate); - const endedAfterDay = - !incidentEndDate || !isBefore(incidentEndDate, dayStart); - - if (startedBeforeOrDuringDay && endedAfterDay) { - const enrichedIncident = this.buildIncident(monitor, incident); - dayIncidents.push(enrichedIncident); - } - } - } + const dayIncidents = this.filterIncidentsByDateRange( + monitors, + dayStart, + dayEnd, + ); if (dayIncidents.length === 0) { return { @@ -138,7 +147,7 @@ export class StatusProcessor { }; } - const prioritizedIncidents = this.getPrioritizedIncidents(dayIncidents); + const prioritizedIncidents = this.prioritizeIncidents(dayIncidents); return { index: dayIndex, @@ -148,42 +157,169 @@ export class StatusProcessor { }); } - private buildIncident( + private hasActiveMonitorOnDay( + monitors: MonitorForStatus[], + dayEnd: Date, + ): boolean { + return monitors.some((monitor) => { + const componentCreated = this.parseDateSafe(monitor.created_at); + return componentCreated && !isBefore(dayEnd, componentCreated); + }); + } + + private filterIncidentsByDateRange( + monitors: MonitorForStatus[], + dayStart: Date, + dayEnd: Date, + ): EnrichedIncident[] { + return monitors.flatMap((monitor) => + monitor.incidents + .filter((incident) => + this.isIncidentInDateRange(incident, dayStart, dayEnd), + ) + .map((incident) => + this.buildDaySpecificIncident(monitor, incident, dayStart, dayEnd), + ), + ); + } + + private isIncidentInDateRange( + incident: IncidentForStatus, + dayStart: Date, + dayEnd: Date, + ): boolean { + const incidentStartDate = this.parseDateSafe(incident.created_at); + if (!incidentStartDate) return false; + + const incidentEndDate = incident.ended_at + ? this.parseDateSafe(incident.ended_at) + : null; + + const startedBeforeOrDuringDay = incidentStartDate <= dayEnd; + const endedAfterDay = !incidentEndDate || incidentEndDate >= dayStart; + + return startedBeforeOrDuringDay && endedAfterDay; + } + + private buildDaySpecificIncident( monitor: MonitorForStatus, incident: IncidentForStatus, + dayStart: Date, + dayEnd: Date, ): EnrichedIncident { - const config = INCIDENT_CONFIG[incident.type]; - const startedDateTime = new Date(incident.created_at); - const startedDate = startedDateTime.toLocaleDateString("en-US", { - month: "long", - day: "numeric", - }); - const startedTime = startedDateTime.toLocaleTimeString("en-US", { - hour: "2-digit", - minute: "2-digit", - }); - - const endedDateTime = incident.ended_at - ? new Date(incident.ended_at) + const incidentStartDate = this.parseDateSafe(incident.created_at)!; + const incidentEndDate = incident.ended_at + ? this.parseDateSafe(incident.ended_at) : null; - const endedTime = - endedDateTime?.toLocaleTimeString("en-US", { - hour: "2-digit", - minute: "2-digit", - }) ?? null; + const actualStart = + incidentStartDate > dayStart ? incidentStartDate : dayStart; + const actualEnd = + incidentEndDate && incidentEndDate < dayEnd ? incidentEndDate : dayEnd; - if (!config) { - throw new Error(`Unknown incident type: ${incident.type}`); + const config = this.getIncidentConfig(incident.type); + const message = this.buildIncidentMessage(monitor, incidentStartDate); + + const tooltip = this.buildTooltip( + incident.message, + actualStart, + actualEnd, + incident.ended_at !== null, + ); + + return { + ...incident, + ...config, + message, + tooltip, + }; + } + + private buildEnrichedIncident( + monitor: MonitorForStatus, + incident: IncidentForStatus, + ): EnrichedIncident { + const startedDateTime = this.parseDateSafe(incident.created_at); + if (!startedDateTime) { + throw new Error(`Invalid created_at date for incident: ${incident.id}`); } + const config = this.getIncidentConfig(incident.type); + const message = this.buildIncidentMessage(monitor, startedDateTime); + + const endedDateTime = incident.ended_at + ? this.parseDateSafe(incident.ended_at) + : null; + + const tooltip = this.buildTooltip( + incident.message, + startedDateTime, + endedDateTime, + incident.ended_at !== null, + ); + return { ...incident, ...config, - message: `${monitor.name} is currently affected. Issue started on ${startedDate} at ${startedTime}`, - tooltip: incident.ended_at - ? `${incident.message} (${startedTime} - ${endedTime})` - : `${incident.message}`, + message, + tooltip, }; } + + private buildIncidentMessage( + monitor: MonitorForStatus, + startedDateTime: Date, + ): string { + const formattedDate = startedDateTime.toLocaleDateString( + LOCALE, + DATE_FORMAT_OPTIONS.date, + ); + const formattedTime = startedDateTime.toLocaleTimeString( + LOCALE, + DATE_FORMAT_OPTIONS.time, + ); + + const message = `${monitor.name} is currently affected. Issue started on ${formattedDate} at ${formattedTime}`; + + return message; + } + + private buildTooltip( + incidentMessage: string, + startTime: Date, + endTime: Date | null, + isEnded: boolean, + ): string { + const startFormatted = startTime.toLocaleTimeString( + LOCALE, + DATE_FORMAT_OPTIONS.time, + ); + + if (!isEnded) { + return `${incidentMessage} (started at ${startFormatted})`; + } + + const endFormatted = endTime + ? endTime.toLocaleTimeString(LOCALE, DATE_FORMAT_OPTIONS.time) + : startFormatted; + + return `${incidentMessage} (${startFormatted} - ${endFormatted})`; + } + + private getIncidentConfig(type: string) { + const config = INCIDENT_CONFIG[type as keyof typeof INCIDENT_CONFIG]; + if (!config) { + throw new Error(`Unknown incident type: ${type}`); + } + return config; + } + + private parseDateSafe(dateString: string): Date | null { + try { + const date = parseISO(dateString); + return isNaN(date.getTime()) ? null : date; + } catch { + return null; + } + } } From 5f36fafea2cdcd61d0b5516d64e5dc7ddec68b30 Mon Sep 17 00:00:00 2001 From: goldpulpy Date: Sat, 14 Feb 2026 03:57:17 +0300 Subject: [PATCH 3/7] refactor(status): improve incident processing and history building logic --- .../ts/shared/services/status.processor.ts | 163 ++++++++++-------- 1 file changed, 95 insertions(+), 68 deletions(-) diff --git a/src/app/frontend/assets/ts/shared/services/status.processor.ts b/src/app/frontend/assets/ts/shared/services/status.processor.ts index 35969e6..f118da9 100644 --- a/src/app/frontend/assets/ts/shared/services/status.processor.ts +++ b/src/app/frontend/assets/ts/shared/services/status.processor.ts @@ -26,7 +26,25 @@ const DATE_FORMAT_OPTIONS = { time: { hour: "2-digit", minute: "2-digit" } as const, } as const; +type IncidentType = keyof typeof INCIDENT_CONFIG; + +interface DateProvider { + now(): Date; +} + +class DefaultDateProvider implements DateProvider { + now(): Date { + return new Date(); + } +} + export class StatusProcessor { + private dateProvider: DateProvider; + + constructor(dateProvider: DateProvider = new DefaultDateProvider()) { + this.dateProvider = dateProvider; + } + buildEnrichedIncidents( components: StatusComponent[], ): Map { @@ -35,9 +53,10 @@ export class StatusProcessor { const monitors = this.flattenMonitors(components); for (const monitor of monitors) { - const enrichedIncidents = monitor.incidents.map((incident) => - this.buildEnrichedIncident(monitor, incident), - ); + const enrichedIncidents = monitor.incidents + .map((incident) => this.buildEnrichedIncident(monitor, incident)) + .filter((incident): incident is EnrichedIncident => incident !== null); + enrichedIncidentsByMonitor.set(monitor.id, enrichedIncidents); } @@ -59,7 +78,7 @@ export class StatusProcessor { findActiveIncident( incidents: T[], ): T | typeof OPERATIONAL_STATUS { - const prioritizedIncidents = this.prioritizeIncidents(incidents); + const prioritizedIncidents = this.prioritizeIncidents(incidents, false); const activeIncident = prioritizedIncidents.find( (incident) => incident.status === IncidentStatus.OPEN, ); @@ -102,25 +121,38 @@ export class StatusProcessor { }; } - private prioritizeIncidents(incidents: T[]): T[] { - return [...incidents].sort( + private prioritizeIncidents( + incidents: T[], + immutable = true, + ): T[] { + const arr = immutable ? [...incidents] : incidents; + return arr.sort( (a, b) => INCIDENT_PRIORITY_ORDER.indexOf(a.type) - INCIDENT_PRIORITY_ORDER.indexOf(b.type), ); } - private sortIncidentsByDate( + private sortIncidentsByLatest( incidents: EnrichedIncident[], + immutable = true, ): EnrichedIncident[] { - return [...incidents].sort( - (a, b) => - new Date(b.created_at).getTime() - new Date(a.created_at).getTime(), - ); + const arr = immutable ? [...incidents] : incidents; + return arr.sort((a, b) => { + const statusDiff = + (a.status === IncidentStatus.OPEN ? 0 : 1) - + (b.status === IncidentStatus.OPEN ? 0 : 1); + + if (statusDiff !== 0) return statusDiff; + + return ( + new Date(b.created_at).getTime() - new Date(a.created_at).getTime() + ); + }); } private buildHistoryForMonitors(monitors: MonitorForStatus[]): Days[] { - const now = new Date(); + const now = this.dateProvider.now(); return Array.from({ length: HISTORY_DAYS }, (_, dayIndex) => { const day = subDays(now, HISTORY_OFFSET - dayIndex); @@ -147,12 +179,15 @@ export class StatusProcessor { }; } - const prioritizedIncidents = this.prioritizeIncidents(dayIncidents); + const prioritizedIncidents = this.prioritizeIncidents( + dayIncidents, + false, + ); return { index: dayIndex, color: prioritizedIncidents[0].color, - incidents: this.sortIncidentsByDate(prioritizedIncidents), + incidents: this.sortIncidentsByLatest(prioritizedIncidents, false), }; }); } @@ -172,15 +207,21 @@ export class StatusProcessor { dayStart: Date, dayEnd: Date, ): EnrichedIncident[] { - return monitors.flatMap((monitor) => - monitor.incidents - .filter((incident) => - this.isIncidentInDateRange(incident, dayStart, dayEnd), - ) - .map((incident) => - this.buildDaySpecificIncident(monitor, incident, dayStart, dayEnd), - ), - ); + const result: EnrichedIncident[] = []; + + for (const monitor of monitors) { + for (const incident of monitor.incidents) { + if (!this.isIncidentInDateRange(incident, dayStart, dayEnd)) continue; + + const enriched = this.enrichIncident(monitor, incident, { + start: dayStart, + end: dayEnd, + }); + if (enriched) result.push(enriched); + } + } + + return result; } private isIncidentInDateRange( @@ -201,25 +242,40 @@ export class StatusProcessor { return startedBeforeOrDuringDay && endedAfterDay; } - private buildDaySpecificIncident( + private enrichIncident( monitor: MonitorForStatus, incident: IncidentForStatus, - dayStart: Date, - dayEnd: Date, - ): EnrichedIncident { - const incidentStartDate = this.parseDateSafe(incident.created_at)!; + dateRange?: { start: Date; end: Date }, + ): EnrichedIncident | null { + const incidentStartDate = this.parseDateSafe(incident.created_at); + if (!incidentStartDate) { + return null; + } + const incidentEndDate = incident.ended_at ? this.parseDateSafe(incident.ended_at) : null; - const actualStart = - incidentStartDate > dayStart ? incidentStartDate : dayStart; - const actualEnd = - incidentEndDate && incidentEndDate < dayEnd ? incidentEndDate : dayEnd; + let actualStart = incidentStartDate; + let actualEnd = incidentEndDate; + + if (dateRange) { + actualStart = + incidentStartDate > dateRange.start + ? incidentStartDate + : dateRange.start; + actualEnd = + incidentEndDate && incidentEndDate < dateRange.end + ? incidentEndDate + : dateRange.end; + } const config = this.getIncidentConfig(incident.type); - const message = this.buildIncidentMessage(monitor, incidentStartDate); + if (!config) { + return null; + } + const message = this.buildIncidentMessage(monitor, incidentStartDate); const tooltip = this.buildTooltip( incident.message, actualStart, @@ -238,32 +294,8 @@ export class StatusProcessor { private buildEnrichedIncident( monitor: MonitorForStatus, incident: IncidentForStatus, - ): EnrichedIncident { - const startedDateTime = this.parseDateSafe(incident.created_at); - if (!startedDateTime) { - throw new Error(`Invalid created_at date for incident: ${incident.id}`); - } - - const config = this.getIncidentConfig(incident.type); - const message = this.buildIncidentMessage(monitor, startedDateTime); - - const endedDateTime = incident.ended_at - ? this.parseDateSafe(incident.ended_at) - : null; - - const tooltip = this.buildTooltip( - incident.message, - startedDateTime, - endedDateTime, - incident.ended_at !== null, - ); - - return { - ...incident, - ...config, - message, - tooltip, - }; + ): EnrichedIncident | null { + return this.enrichIncident(monitor, incident); } private buildIncidentMessage( @@ -279,9 +311,7 @@ export class StatusProcessor { DATE_FORMAT_OPTIONS.time, ); - const message = `${monitor.name} is currently affected. Issue started on ${formattedDate} at ${formattedTime}`; - - return message; + return `${monitor.name} is currently affected. Issue started on ${formattedDate} at ${formattedTime}`; } private buildTooltip( @@ -307,18 +337,15 @@ export class StatusProcessor { } private getIncidentConfig(type: string) { - const config = INCIDENT_CONFIG[type as keyof typeof INCIDENT_CONFIG]; - if (!config) { - throw new Error(`Unknown incident type: ${type}`); - } - return config; + if (!(type in INCIDENT_CONFIG)) return null; + return INCIDENT_CONFIG[type as IncidentType]; } private parseDateSafe(dateString: string): Date | null { try { const date = parseISO(dateString); return isNaN(date.getTime()) ? null : date; - } catch { + } catch (error) { return null; } } From 15a901df86aedee8173d2a5458021cd5cd7acb82 Mon Sep 17 00:00:00 2001 From: goldpulpy Date: Sat, 14 Feb 2026 04:00:35 +0300 Subject: [PATCH 4/7] fix(status): remove unused error parameter in status processor --- src/app/frontend/assets/ts/shared/services/status.processor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/frontend/assets/ts/shared/services/status.processor.ts b/src/app/frontend/assets/ts/shared/services/status.processor.ts index f118da9..6369bfb 100644 --- a/src/app/frontend/assets/ts/shared/services/status.processor.ts +++ b/src/app/frontend/assets/ts/shared/services/status.processor.ts @@ -345,7 +345,7 @@ export class StatusProcessor { try { const date = parseISO(dateString); return isNaN(date.getTime()) ? null : date; - } catch (error) { + } catch { return null; } } From 2349e5967d0a49b5b8a17b02b4a6b7c02908bf75 Mon Sep 17 00:00:00 2001 From: goldpulpy Date: Sat, 14 Feb 2026 04:14:49 +0300 Subject: [PATCH 5/7] refactor(status): optimize incident processing with functional programming --- .../ts/shared/services/status.processor.ts | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/app/frontend/assets/ts/shared/services/status.processor.ts b/src/app/frontend/assets/ts/shared/services/status.processor.ts index 6369bfb..8da7089 100644 --- a/src/app/frontend/assets/ts/shared/services/status.processor.ts +++ b/src/app/frontend/assets/ts/shared/services/status.processor.ts @@ -138,10 +138,14 @@ export class StatusProcessor { immutable = true, ): EnrichedIncident[] { const arr = immutable ? [...incidents] : incidents; + + const OPEN_PRIORITY = 0; + const CLOSED_PRIORITY = 1; + return arr.sort((a, b) => { const statusDiff = - (a.status === IncidentStatus.OPEN ? 0 : 1) - - (b.status === IncidentStatus.OPEN ? 0 : 1); + (a.status === IncidentStatus.OPEN ? OPEN_PRIORITY : CLOSED_PRIORITY) - + (b.status === IncidentStatus.OPEN ? OPEN_PRIORITY : CLOSED_PRIORITY); if (statusDiff !== 0) return statusDiff; @@ -207,21 +211,14 @@ export class StatusProcessor { dayStart: Date, dayEnd: Date, ): EnrichedIncident[] { - const result: EnrichedIncident[] = []; - - for (const monitor of monitors) { - for (const incident of monitor.incidents) { - if (!this.isIncidentInDateRange(incident, dayStart, dayEnd)) continue; - - const enriched = this.enrichIncident(monitor, incident, { - start: dayStart, - end: dayEnd, - }); - if (enriched) result.push(enriched); - } - } - - return result; + return monitors.flatMap((monitor) => + monitor.incidents + .filter((i) => this.isIncidentInDateRange(i, dayStart, dayEnd)) + .map((i) => + this.enrichIncident(monitor, i, { start: dayStart, end: dayEnd }), + ) + .filter((e): e is EnrichedIncident => e !== null), + ); } private isIncidentInDateRange( From be0d34cac07793af8e99f5f62d19d983d7eea770 Mon Sep 17 00:00:00 2001 From: goldpulpy Date: Sat, 14 Feb 2026 04:20:29 +0300 Subject: [PATCH 6/7] fix(status): correct incident tooltip logic for postponed incidents --- .../assets/ts/shared/services/status.processor.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/app/frontend/assets/ts/shared/services/status.processor.ts b/src/app/frontend/assets/ts/shared/services/status.processor.ts index 8da7089..137528d 100644 --- a/src/app/frontend/assets/ts/shared/services/status.processor.ts +++ b/src/app/frontend/assets/ts/shared/services/status.processor.ts @@ -273,11 +273,21 @@ export class StatusProcessor { } const message = this.buildIncidentMessage(monitor, incidentStartDate); + + let isEndedForTooltip = incident.ended_at !== null; + + if (!isEndedForTooltip && dateRange) { + const now = this.dateProvider.now(); + const isToday = + startOfDay(now).getTime() === startOfDay(dateRange.start).getTime(); + isEndedForTooltip = !isToday; + } + const tooltip = this.buildTooltip( incident.message, actualStart, actualEnd, - incident.ended_at !== null, + isEndedForTooltip, ); return { From 4fbf620e974fbf60d6a692295c10b7cb1a263545 Mon Sep 17 00:00:00 2001 From: goldpulpy Date: Sat, 14 Feb 2026 04:21:35 +0300 Subject: [PATCH 7/7] refactor(status): rename immutable parameter to copy for clarity --- .../assets/ts/shared/services/status.processor.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/frontend/assets/ts/shared/services/status.processor.ts b/src/app/frontend/assets/ts/shared/services/status.processor.ts index 137528d..86db262 100644 --- a/src/app/frontend/assets/ts/shared/services/status.processor.ts +++ b/src/app/frontend/assets/ts/shared/services/status.processor.ts @@ -123,9 +123,9 @@ export class StatusProcessor { private prioritizeIncidents( incidents: T[], - immutable = true, + copy = true, ): T[] { - const arr = immutable ? [...incidents] : incidents; + const arr = copy ? [...incidents] : incidents; return arr.sort( (a, b) => INCIDENT_PRIORITY_ORDER.indexOf(a.type) - @@ -135,9 +135,9 @@ export class StatusProcessor { private sortIncidentsByLatest( incidents: EnrichedIncident[], - immutable = true, + copy = true, ): EnrichedIncident[] { - const arr = immutable ? [...incidents] : incidents; + const arr = copy ? [...incidents] : incidents; const OPEN_PRIORITY = 0; const CLOSED_PRIORITY = 1;