@@ -74,120 +74,125 @@ export class ErrorAlertEvaluator {
7474 }
7575
7676 const allEnvTypes = this . collectEnvironmentTypes ( channels ) ;
77- const [ project , environments ] = await Promise . all ( [
78- this . _replica . project . findFirst ( {
79- where : { id : projectId } ,
80- select : { organizationId : true } ,
81- } ) ,
82- this . resolveEnvironments ( projectId , allEnvTypes ) ,
83- ] ) ;
84-
85- if ( ! project ) {
86- logger . error ( "[ErrorAlertEvaluator] Project not found" , { projectId } ) ;
87- return ;
88- }
8977
90- if ( environments . length === 0 ) {
91- logger . info ( "[ErrorAlertEvaluator] No matching environments found" , { projectId } ) ;
92- await this . selfChain ( projectId , nextScheduledAt , minIntervalMs ) ;
93- return ;
94- }
78+ try {
79+ const [ project , environments ] = await Promise . all ( [
80+ this . _replica . project . findFirst ( {
81+ where : { id : projectId } ,
82+ select : { organizationId : true } ,
83+ } ) ,
84+ this . resolveEnvironments ( projectId , allEnvTypes ) ,
85+ ] ) ;
86+
87+ if ( ! project ) {
88+ logger . error ( "[ErrorAlertEvaluator] Project not found" , { projectId } ) ;
89+ return ;
90+ }
9591
96- const envIds = environments . map ( ( e ) => e . id ) ;
97- const envMap = new Map ( environments . map ( ( e ) => [ e . id , e ] ) ) ;
98- const channelsByEnvId = this . buildChannelsByEnvId ( channels , environments ) ;
92+ if ( environments . length === 0 ) {
93+ return ;
94+ }
9995
100- const activeErrors = await this . getActiveErrors (
101- project . organizationId ,
102- projectId ,
103- envIds ,
104- scheduledAt
105- ) ;
96+ const envIds = environments . map ( ( e ) => e . id ) ;
97+ const envMap = new Map ( environments . map ( ( e ) => [ e . id , e ] ) ) ;
98+ const channelsByEnvId = this . buildChannelsByEnvId ( channels , environments ) ;
10699
107- if ( activeErrors . length === 0 ) {
108- await this . selfChain ( projectId , nextScheduledAt , minIntervalMs ) ;
109- return ;
110- }
100+ const activeErrors = await this . getActiveErrors (
101+ project . organizationId ,
102+ projectId ,
103+ envIds ,
104+ scheduledAt
105+ ) ;
111106
112- const states = await this . getErrorGroupStates ( activeErrors ) ;
113- const stateMap = this . buildStateMap ( states ) ;
107+ if ( activeErrors . length === 0 ) {
108+ return ;
109+ }
114110
115- const occurrenceCounts = await this . getOccurrenceCountsSince (
116- project . organizationId ,
117- projectId ,
118- envIds ,
119- scheduledAt
120- ) ;
121- const occurrenceMap = this . buildOccurrenceMap ( occurrenceCounts ) ;
111+ const states = await this . getErrorGroupStates ( activeErrors ) ;
112+ const stateMap = this . buildStateMap ( states ) ;
122113
123- const alertableErrors : AlertableError [ ] = [ ] ;
114+ const occurrenceCounts = await this . getOccurrenceCountsSince (
115+ project . organizationId ,
116+ projectId ,
117+ envIds ,
118+ scheduledAt
119+ ) ;
120+ const occurrenceMap = this . buildOccurrenceMap ( occurrenceCounts ) ;
124121
125- for ( const error of activeErrors ) {
126- const key = `${ error . environment_id } :${ error . task_identifier } :${ error . error_fingerprint } ` ;
127- const state = stateMap . get ( key ) ;
128- const env = envMap . get ( error . environment_id ) ;
129- const firstSeenMs = Number ( error . first_seen ) ;
122+ const alertableErrors : AlertableError [ ] = [ ] ;
130123
131- const classification = this . classifyError ( error , state , firstSeenMs , scheduledAt , {
132- occurrencesSince : occurrenceMap . get ( key ) ?? 0 ,
133- windowMs ,
134- totalOccurrenceCount : error . occurrence_count ,
135- } ) ;
124+ for ( const error of activeErrors ) {
125+ const key = ` ${ error . environment_id } : ${ error . task_identifier } : ${ error . error_fingerprint } ` ;
126+ const state = stateMap . get ( key ) ;
127+ const env = envMap . get ( error . environment_id ) ;
128+ const firstSeenMs = Number ( error . first_seen ) ;
136129
137- if ( classification ) {
138- alertableErrors . push ( {
139- classification,
140- error,
141- environmentSlug : env ?. slug ?? "" ,
142- environmentName : env ?. displayName ?? error . environment_id ,
130+ const classification = this . classifyError ( error , state , firstSeenMs , scheduledAt , {
131+ occurrencesSince : occurrenceMap . get ( key ) ?? 0 ,
132+ windowMs,
133+ totalOccurrenceCount : error . occurrence_count ,
143134 } ) ;
135+
136+ if ( classification ) {
137+ alertableErrors . push ( {
138+ classification,
139+ error,
140+ environmentSlug : env ?. slug ?? "" ,
141+ environmentName : env ?. displayName ?? error . environment_id ,
142+ } ) ;
143+ }
144144 }
145- }
146145
147- for ( const alertable of alertableErrors ) {
148- const envChannels = channelsByEnvId . get ( alertable . error . environment_id ) ?? [ ] ;
149- for ( const channel of envChannels ) {
150- await alertsWorker . enqueue ( {
151- id : `deliverErrorGroupAlert:${ channel . id } :${ alertable . error . error_fingerprint } :${ scheduledAt } ` ,
152- job : "v3.deliverErrorGroupAlert" ,
153- payload : {
154- channelId : channel . id ,
155- projectId,
156- classification : alertable . classification ,
157- error : {
158- fingerprint : alertable . error . error_fingerprint ,
159- environmentId : alertable . error . environment_id ,
160- environmentSlug : alertable . environmentSlug ,
161- environmentName : alertable . environmentName ,
162- taskIdentifier : alertable . error . task_identifier ,
163- errorType : alertable . error . error_type ,
164- errorMessage : alertable . error . error_message ,
165- sampleStackTrace : alertable . error . sample_stack_trace ,
166- firstSeen : alertable . error . first_seen ,
167- lastSeen : alertable . error . last_seen ,
168- occurrenceCount : alertable . error . occurrence_count ,
146+ for ( const alertable of alertableErrors ) {
147+ const envChannels = channelsByEnvId . get ( alertable . error . environment_id ) ?? [ ] ;
148+ for ( const channel of envChannels ) {
149+ await alertsWorker . enqueue ( {
150+ id : `deliverErrorGroupAlert:${ channel . id } :${ alertable . error . error_fingerprint } :${ scheduledAt } ` ,
151+ job : "v3.deliverErrorGroupAlert" ,
152+ payload : {
153+ channelId : channel . id ,
154+ projectId,
155+ classification : alertable . classification ,
156+ error : {
157+ fingerprint : alertable . error . error_fingerprint ,
158+ environmentId : alertable . error . environment_id ,
159+ environmentSlug : alertable . environmentSlug ,
160+ environmentName : alertable . environmentName ,
161+ taskIdentifier : alertable . error . task_identifier ,
162+ errorType : alertable . error . error_type ,
163+ errorMessage : alertable . error . error_message ,
164+ sampleStackTrace : alertable . error . sample_stack_trace ,
165+ firstSeen : alertable . error . first_seen ,
166+ lastSeen : alertable . error . last_seen ,
167+ occurrenceCount : alertable . error . occurrence_count ,
168+ } ,
169169 } ,
170- } ,
171- } ) ;
170+ } ) ;
171+ }
172172 }
173- }
174173
175- const stateUpdates = alertableErrors . filter (
176- ( a ) => a . classification === "regression" || a . classification === "unignored"
177- ) ;
178- await this . updateErrorGroupStates ( stateUpdates , stateMap ) ;
179-
180- logger . info ( "[ErrorAlertEvaluator] Evaluation complete" , {
181- projectId,
182- activeErrors : activeErrors . length ,
183- alertableErrors : alertableErrors . length ,
184- deliveryJobsEnqueued : alertableErrors . reduce (
185- ( sum , a ) => sum + ( channelsByEnvId . get ( a . error . environment_id ) ?. length ?? 0 ) ,
186- 0
187- ) ,
188- } ) ;
174+ const stateUpdates = alertableErrors . filter (
175+ ( a ) => a . classification === "regression" || a . classification === "unignored"
176+ ) ;
177+ await this . updateErrorGroupStates ( stateUpdates , stateMap ) ;
189178
190- await this . selfChain ( projectId , nextScheduledAt , minIntervalMs ) ;
179+ logger . info ( "[ErrorAlertEvaluator] Evaluation complete" , {
180+ projectId,
181+ activeErrors : activeErrors . length ,
182+ alertableErrors : alertableErrors . length ,
183+ deliveryJobsEnqueued : alertableErrors . reduce (
184+ ( sum , a ) => sum + ( channelsByEnvId . get ( a . error . environment_id ) ?. length ?? 0 ) ,
185+ 0
186+ ) ,
187+ } ) ;
188+ } catch ( error ) {
189+ logger . error ( "[ErrorAlertEvaluator] Evaluation failed, will retry on next cycle" , {
190+ projectId,
191+ error,
192+ } ) ;
193+ } finally {
194+ await this . selfChain ( projectId , nextScheduledAt , minIntervalMs ) ;
195+ }
191196 }
192197
193198 private classifyError (
0 commit comments