44import traceback
55import shutil
66import warnings
7+ from datetime import datetime , timezone
8+ from uuid import uuid4
79
810from dotenv import load_dotenv
911from git import InvalidGitRepositoryError , NoSuchPathError
@@ -478,6 +480,17 @@ def main_code():
478480
479481 # Handle SCM-specific flows
480482 log .debug (f"Flow decision: scm={ scm is not None } , force_diff_mode={ force_diff_mode } , force_api_mode={ force_api_mode } , enable_diff={ config .enable_diff } " )
483+
484+ def _is_unprocessed (c ):
485+ """Check if an ignore comment has not yet been marked with '+1' reaction.
486+ For GitHub, reactions['+1'] is already in the comment response (no extra call).
487+ For GitLab, has_thumbsup_reaction() makes a lazy API call per comment."""
488+ if getattr (c , "reactions" , {}).get ("+1" ):
489+ return False
490+ if hasattr (scm , "has_thumbsup_reaction" ) and scm .has_thumbsup_reaction (c .id ):
491+ return False
492+ return True
493+
481494 if scm is not None and scm .check_event_type () == "comment" :
482495 # FIXME: This entire flow should be a separate command called "filter_ignored_alerts_in_comments"
483496 # It's not related to scanning or diff generation - it just:
@@ -489,6 +502,44 @@ def main_code():
489502
490503 if not config .disable_ignore :
491504 comments = scm .get_comments_for_pr ()
505+
506+ # Emit telemetry for ignore comments before +1 reaction is added.
507+ # The +1 reaction (added by remove_comment_alerts) serves as the "processed" marker.
508+ if "ignore" in comments :
509+ unprocessed = [c for c in comments ["ignore" ] if _is_unprocessed (c )]
510+ if unprocessed :
511+ try :
512+ events = []
513+ for c in unprocessed :
514+ single = {"ignore" : [c ]}
515+ ignore_all , ignore_commands = Comments .get_ignore_options (single )
516+ user = getattr (c , "user" , None ) or getattr (c , "author" , None ) or {}
517+ now = datetime .now (timezone .utc ).isoformat ()
518+ shared_fields = {
519+ "event_kind" : "user-action" ,
520+ "client_action" : "ignore" ,
521+ "alert_action" : "error" ,
522+ "event_sender_created_at" : now ,
523+ "vcs_provider" : integration_type ,
524+ "owner" : config .repo .split ("/" )[0 ] if "/" in config .repo else "" ,
525+ "repo" : config .repo ,
526+ "pr_number" : pr_number ,
527+ "ignore_all" : ignore_all ,
528+ "sender_name" : user .get ("login" ) or user .get ("username" , "" ),
529+ "sender_id" : str (user .get ("id" , "" )),
530+ }
531+ if ignore_commands :
532+ for name , version in ignore_commands :
533+ events .append ({** shared_fields , "event_id" : str (uuid4 ()), "artifact_input" : f"{ name } @{ version } " })
534+ elif ignore_all :
535+ events .append ({** shared_fields , "event_id" : str (uuid4 ())})
536+
537+ if events :
538+ log .debug (f"Ignore telemetry: { len (events )} events to send" )
539+ client .post_telemetry_events (org_slug , events )
540+ except Exception as e :
541+ log .warning (f"Failed to send ignore telemetry: { e } " )
542+
492543 log .debug ("Removing comment alerts" )
493544 scm .remove_comment_alerts (comments )
494545 else :
@@ -504,9 +555,76 @@ def main_code():
504555 # FIXME: this overwrites diff.new_alerts, which was previously populated by Core.create_issue_alerts
505556 if not config .disable_ignore :
506557 log .debug ("Removing comment alerts" )
558+ alerts_before = list (diff .new_alerts )
507559 diff .new_alerts = Comments .remove_alerts (comments , diff .new_alerts )
560+
561+ ignored_alerts = [a for a in alerts_before if a not in diff .new_alerts ]
562+ # Emit telemetry per-comment so each event carries the comment author.
563+ unprocessed_ignore = [
564+ c for c in comments .get ("ignore" , [])
565+ if _is_unprocessed (c )
566+ ]
567+ if ignored_alerts and unprocessed_ignore :
568+ try :
569+ events = []
570+ now = datetime .now (timezone .utc ).isoformat ()
571+ for c in unprocessed_ignore :
572+ single = {"ignore" : [c ]}
573+ c_ignore_all , c_ignore_commands = Comments .get_ignore_options (single )
574+ user = getattr (c , "user" , None ) or getattr (c , "author" , None ) or {}
575+ sender_name = user .get ("login" ) or user .get ("username" , "" )
576+ sender_id = str (user .get ("id" , "" ))
577+
578+ # Match this comment's targets to the actual ignored alerts
579+ matched_alerts = []
580+ if c_ignore_all :
581+ matched_alerts = ignored_alerts
582+ else :
583+ for alert in ignored_alerts :
584+ full_name = f"{ alert .pkg_type } /{ alert .pkg_name } "
585+ purl = (full_name , alert .pkg_version )
586+ purl_star = (full_name , "*" )
587+ if purl in c_ignore_commands or purl_star in c_ignore_commands :
588+ matched_alerts .append (alert )
589+
590+ shared_fields = {
591+ "event_kind" : "user-action" ,
592+ "client_action" : "ignore" ,
593+ "event_sender_created_at" : now ,
594+ "vcs_provider" : integration_type ,
595+ "owner" : config .repo .split ("/" )[0 ] if "/" in config .repo else "" ,
596+ "repo" : config .repo ,
597+ "pr_number" : pr_number ,
598+ "ignore_all" : c_ignore_all ,
599+ "sender_name" : sender_name ,
600+ "sender_id" : sender_id ,
601+ }
602+ if matched_alerts :
603+ for alert in matched_alerts :
604+ # Derive alert_action from the alert's resolved action flags
605+ if getattr (alert , "error" , False ):
606+ alert_action = "error"
607+ elif getattr (alert , "warn" , False ):
608+ alert_action = "warn"
609+ elif getattr (alert , "monitor" , False ):
610+ alert_action = "monitor"
611+ else :
612+ alert_action = "error"
613+ events .append ({** shared_fields , "alert_action" : alert_action , "event_id" : str (uuid4 ()), "artifact_purl" : alert .purl })
614+ elif c_ignore_all :
615+ events .append ({** shared_fields , "event_id" : str (uuid4 ())})
616+
617+ if events :
618+ client .post_telemetry_events (org_slug , events )
619+
620+ # Mark ignore comments as processed with +1 reaction
621+ if hasattr (scm , "handle_ignore_reactions" ):
622+ scm .handle_ignore_reactions (comments )
623+ except Exception as e :
624+ log .warning (f"Failed to send ignore telemetry: { e } " )
508625 else :
509626 log .info ("Ignore commands disabled (--disable-ignore), all alerts will be reported" )
627+
510628 log .debug ("Creating Dependency Overview Comment" )
511629
512630 overview_comment = Messages .dependency_overview_template (diff )
0 commit comments