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 'eyes' reaction.
486+ For GitHub, reactions.eyes is already in the comment response (no extra call).
487+ For GitLab, has_eyes_reaction() makes a lazy API call per comment."""
488+ if getattr (c , "reactions" , {}).get ("eyes" ):
489+ return False
490+ if hasattr (scm , "has_eyes_reaction" ) and scm .has_eyes_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:
@@ -486,10 +499,53 @@ def main_code():
486499 # 3. Updates the comment to remove ignored alerts
487500 # This is completely separate from the main scanning functionality
488501 log .info ("Comment initiated flow" )
489-
502+
490503 comments = scm .get_comments_for_pr ()
504+
491505 log .debug ("Removing comment alerts" )
492506 scm .remove_comment_alerts (comments )
507+
508+ # Emit telemetry only for ignore comments not yet marked with 'eyes' reaction.
509+ # Process each comment individually so the comment author is recorded per event.
510+ if "ignore" in comments :
511+ unprocessed = [c for c in comments ["ignore" ] if _is_unprocessed (c )]
512+ if unprocessed :
513+ try :
514+ events = []
515+ for c in unprocessed :
516+ single = {"ignore" : [c ]}
517+ ignore_all , ignore_commands = Comments .get_ignore_options (single )
518+ user = getattr (c , "user" , None ) or getattr (c , "author" , None ) or {}
519+ now = datetime .now (timezone .utc ).isoformat ()
520+ shared_fields = {
521+ "event_kind" : "user-action" ,
522+ "client_action" : "ignore_alerts" ,
523+ "alert_action" : "ignore" ,
524+ "event_sender_created_at" : now ,
525+ "vcs_provider" : integration_type ,
526+ "owner" : config .repo .split ("/" )[0 ] if "/" in config .repo else "" ,
527+ "repo" : config .repo ,
528+ "pr_number" : pr_number ,
529+ "ignore_all" : ignore_all ,
530+ "sender_name" : user .get ("login" ) or user .get ("username" , "" ),
531+ "sender_id" : str (user .get ("id" , "" )),
532+ }
533+ if ignore_commands :
534+ for name , version in ignore_commands :
535+ events .append ({** shared_fields , "event_id" : str (uuid4 ()), "artifact_input" : f"{ name } @{ version } " })
536+ elif ignore_all :
537+ events .append ({** shared_fields , "event_id" : str (uuid4 ())})
538+
539+ if events :
540+ log .debug (f"Ignore telemetry: { len (events )} events to send" )
541+ client .post_telemetry_events (org_slug , events )
542+
543+ # Mark as processed with eyes reaction
544+ if hasattr (scm , "post_eyes_reaction" ):
545+ for c in unprocessed :
546+ scm .post_eyes_reaction (c .id )
547+ except Exception as e :
548+ log .warning (f"Failed to send ignore telemetry: { e } " )
493549
494550 elif scm is not None and scm .check_event_type () != "comment" and not force_api_mode :
495551 log .info ("Push initiated flow" )
@@ -500,7 +556,67 @@ def main_code():
500556 log .debug ("Removing comment alerts" )
501557
502558 # FIXME: this overwrites diff.new_alerts, which was previously populated by Core.create_issue_alerts
559+ alerts_before = list (diff .new_alerts )
503560 diff .new_alerts = Comments .remove_alerts (comments , diff .new_alerts )
561+
562+ ignored_alerts = [a for a in alerts_before if a not in diff .new_alerts ]
563+ # Emit telemetry per-comment so each event carries the comment author.
564+ unprocessed_ignore = [
565+ c for c in comments .get ("ignore" , [])
566+ if _is_unprocessed (c )
567+ ]
568+ if ignored_alerts and unprocessed_ignore :
569+ try :
570+ events = []
571+ now = datetime .now (timezone .utc ).isoformat ()
572+ for c in unprocessed_ignore :
573+ single = {"ignore" : [c ]}
574+ c_ignore_all , c_ignore_commands = Comments .get_ignore_options (single )
575+ user = getattr (c , "user" , None ) or getattr (c , "author" , None ) or {}
576+ sender_name = user .get ("login" ) or user .get ("username" , "" )
577+ sender_id = str (user .get ("id" , "" ))
578+
579+ # Match this comment's targets to the actual ignored alerts
580+ matched_alerts = []
581+ if c_ignore_all :
582+ matched_alerts = ignored_alerts
583+ else :
584+ for alert in ignored_alerts :
585+ full_name = f"{ alert .pkg_type } /{ alert .pkg_name } "
586+ purl = (full_name , alert .pkg_version )
587+ purl_star = (full_name , "*" )
588+ if purl in c_ignore_commands or purl_star in c_ignore_commands :
589+ matched_alerts .append (alert )
590+
591+ shared_fields = {
592+ "event_kind" : "user-action" ,
593+ "client_action" : "ignore_alerts" ,
594+ "alert_action" : "ignore" ,
595+ "event_sender_created_at" : now ,
596+ "vcs_provider" : integration_type ,
597+ "owner" : config .repo .split ("/" )[0 ] if "/" in config .repo else "" ,
598+ "repo" : config .repo ,
599+ "pr_number" : pr_number ,
600+ "ignore_all" : c_ignore_all ,
601+ "sender_name" : sender_name ,
602+ "sender_id" : sender_id ,
603+ }
604+ if matched_alerts :
605+ for alert in matched_alerts :
606+ events .append ({** shared_fields , "event_id" : str (uuid4 ()), "artifact_purl" : alert .purl })
607+ elif c_ignore_all :
608+ events .append ({** shared_fields , "event_id" : str (uuid4 ())})
609+
610+ if events :
611+ client .post_telemetry_events (org_slug , events )
612+
613+ # Mark ignore comments as processed
614+ if hasattr (scm , "post_eyes_reaction" ):
615+ for c in unprocessed_ignore :
616+ scm .post_eyes_reaction (c .id )
617+ except Exception as e :
618+ log .warning (f"Failed to send ignore telemetry: { e } " )
619+
504620 log .debug ("Creating Dependency Overview Comment" )
505621
506622 overview_comment = Messages .dependency_overview_template (diff )
0 commit comments