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,52 @@ 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+ "event_sender_created_at" : now ,
524+ "vcs_provider" : integration_type ,
525+ "owner" : config .repo .split ("/" )[0 ] if "/" in config .repo else "" ,
526+ "repo" : config .repo ,
527+ "pr_number" : pr_number ,
528+ "ignore_all" : ignore_all ,
529+ "sender_name" : user .get ("login" ) or user .get ("username" , "" ),
530+ "sender_id" : str (user .get ("id" , "" )),
531+ }
532+ if ignore_commands :
533+ for name , version in ignore_commands :
534+ events .append ({** shared_fields , "event_id" : str (uuid4 ()), "artifact_input" : f"{ name } @{ version } " })
535+ elif ignore_all :
536+ events .append ({** shared_fields , "event_id" : str (uuid4 ())})
537+
538+ if events :
539+ log .debug (f"Ignore telemetry: { len (events )} events to send" )
540+ client .post_telemetry_events (org_slug , events )
541+
542+ # Mark as processed with eyes reaction
543+ if hasattr (scm , "post_eyes_reaction" ):
544+ for c in unprocessed :
545+ scm .post_eyes_reaction (c .id )
546+ except Exception as e :
547+ log .warning (f"Failed to send ignore telemetry: { e } " )
493548
494549 elif scm is not None and scm .check_event_type () != "comment" and not force_api_mode :
495550 log .info ("Push initiated flow" )
@@ -500,7 +555,66 @@ def main_code():
500555 log .debug ("Removing comment alerts" )
501556
502557 # FIXME: this overwrites diff.new_alerts, which was previously populated by Core.create_issue_alerts
558+ alerts_before = list (diff .new_alerts )
503559 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_alerts" ,
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+ events .append ({** shared_fields , "event_id" : str (uuid4 ()), "artifact_purl" : alert .purl })
605+ elif c_ignore_all :
606+ events .append ({** shared_fields , "event_id" : str (uuid4 ())})
607+
608+ if events :
609+ client .post_telemetry_events (org_slug , events )
610+
611+ # Mark ignore comments as processed
612+ if hasattr (scm , "post_eyes_reaction" ):
613+ for c in unprocessed_ignore :
614+ scm .post_eyes_reaction (c .id )
615+ except Exception as e :
616+ log .warning (f"Failed to send ignore telemetry: { e } " )
617+
504618 log .debug ("Creating Dependency Overview Comment" )
505619
506620 overview_comment = Messages .dependency_overview_template (diff )
0 commit comments