@@ -150,6 +150,26 @@ const fireChannelEvent = async ({
150150 } ) ;
151151} ;
152152
153+ /**
154+ * Compute a composite map key for tracking alert history per group.
155+ * For non-grouped alerts, returns just the alert ID.
156+ * For grouped alerts, returns "alertId:groupKey" to track per-group state.
157+ */
158+ const computeHistoryMapKey = ( alertId : string , groupKey : string ) : string => {
159+ return groupKey ? `${ alertId } :${ groupKey } ` : alertId ;
160+ } ;
161+
162+ /**
163+ * Extract the group key from a composite history map key.
164+ * Handles group names that may contain colons by removing the alertId prefix.
165+ */
166+ const extractGroupKeyFromMapKey = ( mapKey : string , alertId : string ) : string => {
167+ const alertIdPrefix = `${ alertId } :` ;
168+ return mapKey . startsWith ( alertIdPrefix )
169+ ? mapKey . substring ( alertIdPrefix . length )
170+ : '' ;
171+ } ;
172+
153173export const processAlert = async (
154174 now : Date ,
155175 details : AlertDetails ,
@@ -400,7 +420,34 @@ export const processAlert = async (
400420 }
401421
402422 // Handle missing groups: If current check found no data, check if any previously alerting groups need to be resolved
403- // TODO: Should we 'treat missing data as breaching'?
423+ // For group-by alerts, check if any previously alerting groups are missing from current data
424+ if ( hasGroupBy && previousMap && previousMap . size > 0 ) {
425+ for ( const [ previousKey , previousHistory ] of previousMap . entries ( ) ) {
426+ const groupKey = extractGroupKeyFromMapKey ( previousKey , alert . id ) ;
427+
428+ // If this group was previously ALERT but is missing from current data, create an OK history
429+ if (
430+ previousHistory . state === AlertState . ALERT &&
431+ ! histories . has ( groupKey )
432+ ) {
433+ logger . info ( {
434+ message : `Group "${ groupKey } " is missing from current data but was previously alerting - creating OK history` ,
435+ alertId : alert . id ,
436+ group : groupKey ,
437+ } ) ;
438+ histories . set ( groupKey , {
439+ alert : new mongoose . Types . ObjectId ( alert . id ) ,
440+ createdAt : nowInMinsRoundDown ,
441+ state : AlertState . OK ,
442+ counts : 0 ,
443+ lastValues : [ ] ,
444+ group : groupKey || undefined ,
445+ } ) ;
446+ }
447+ }
448+ }
449+
450+ // If no histories exist at all (no current data and no previous alerting groups), create a default OK history
404451 if ( histories . size === 0 ) {
405452 histories . set ( '' , {
406453 alert : new mongoose . Types . ObjectId ( alert . id ) ,
@@ -414,7 +461,7 @@ export const processAlert = async (
414461
415462 // Check for auto-resolve: for each group, check if it transitioned from ALERT to OK
416463 for ( const [ groupKey , history ] of histories . entries ( ) ) {
417- const previousKey = groupKey ? ` ${ alert . id } : ${ groupKey } ` : alert . id ;
464+ const previousKey = computeHistoryMapKey ( alert . id , groupKey ) ;
418465 const groupPrevious = previousMap ?. get ( previousKey ) || previous ; // Use previousMap first, fallback to previous for backwards compatibility
419466
420467 if (
0 commit comments