2424from jwt .exceptions import InvalidKeyError
2525
2626from ._gh_api import get_github_secrets_from_env , run_graphql_query
27+ from ._stats import OrgChanges
2728
2829
2930@dataclass
@@ -48,6 +49,7 @@ class GHorg: # pylint: disable=too-many-instance-attributes, too-many-lines
4849 configured_repos_collaborators : dict [str , dict [str , str ]] = field (default_factory = dict )
4950 archived_repos : list [Repository ] = field (default_factory = list )
5051 unconfigured_team_repo_permissions : dict [str , dict [str , str ]] = field (default_factory = dict )
52+ stats : OrgChanges = field (default_factory = OrgChanges )
5153
5254 # Re-usable Constants
5355 TEAM_CONFIG_FIELDS : dict [str , dict [str , str | None ]] = field ( # pylint: disable=invalid-name
@@ -315,6 +317,7 @@ def sync_org_owners(self, dry: bool = False, force: bool = False) -> None:
315317 for user in owners_add :
316318 if gh_user := self ._resolve_gh_username (user , "<org owners>" ):
317319 logging .info ("Adding user '%s' as organization owner" , gh_user .login )
320+ self .stats .add_owner (gh_user .login )
318321 if not dry :
319322 self .org .add_to_members (gh_user , "admin" )
320323
@@ -326,6 +329,7 @@ def sync_org_owners(self, dry: bool = False, force: bool = False) -> None:
326329 "Will make them a normal member" ,
327330 gh_user .login ,
328331 )
332+ self .stats .degrade_owner (gh_user .login )
329333 # Handle authenticated user being the same as the one you want to degrade
330334 if self ._is_user_authenticated_user (gh_user ):
331335 logging .warning (
@@ -374,6 +378,7 @@ def create_missing_teams(self, dry: bool = False):
374378 parent_id = self .org .get_team_by_slug (self ._sluggify_teamname (parent )).id
375379
376380 logging .info ("Creating team '%s' with parent ID '%s'" , team , parent_id )
381+ self .stats .create_team (team )
377382 # NOTE: We do not specify any team settings (description etc)
378383 # here, this will happen later
379384 if not dry :
@@ -386,6 +391,7 @@ def create_missing_teams(self, dry: bool = False):
386391
387392 else :
388393 logging .info ("Creating team '%s' without parent" , team )
394+ self .stats .create_team (team )
389395 if not dry :
390396 self .org .create_team (
391397 team ,
@@ -479,9 +485,10 @@ def sync_current_teams_settings(self, dry: bool = False) -> None:
479485 team .name ,
480486 )
481487 for setting , diff in differences .items ():
482- logging .info (
483- "Setting '%s': '%s' --> '%s'" , setting , diff ["dict2" ], diff ["dict1" ]
484- )
488+ change_str = f"Setting '{ setting } ': '{ diff ['dict2' ]} ' --> '{ diff ['dict1' ]} '"
489+ logging .info (change_str )
490+ self .stats .edit_team_config (team .name , new_config = change_str )
491+
485492 # Execute team setting changes
486493 if not dry :
487494 try :
@@ -612,6 +619,7 @@ def sync_teams_members(self, dry: bool = False) -> None: # pylint: disable=too-
612619 team .name ,
613620 config_role ,
614621 )
622+ self .stats .pending_team_member (team = team .name , user = gh_user .login )
615623 continue
616624
617625 logging .info (
@@ -620,6 +628,7 @@ def sync_teams_members(self, dry: bool = False) -> None: # pylint: disable=too-
620628 team .name ,
621629 config_role ,
622630 )
631+ self .stats .add_team_member (team = team .name , user = gh_user .login )
623632 if not dry :
624633 self ._add_or_update_user_in_team (team = team , user = gh_user , role = config_role )
625634
@@ -634,6 +643,7 @@ def sync_teams_members(self, dry: bool = False) -> None: # pylint: disable=too-
634643 team .name ,
635644 config_role ,
636645 )
646+ self .stats .change_team_member_role (team = team .name , user = gh_user .login )
637647 if not dry :
638648 self ._add_or_update_user_in_team (team = team , user = gh_user , role = config_role )
639649
@@ -652,6 +662,7 @@ def sync_teams_members(self, dry: bool = False) -> None: # pylint: disable=too-
652662 gh_user .login ,
653663 team .name ,
654664 )
665+ self .stats .remove_team_member (team = team .name , user = gh_user .login )
655666 if not dry :
656667 team .remove_membership (gh_user )
657668 else :
@@ -676,6 +687,7 @@ def get_unconfigured_teams(
676687 if delete_unconfigured_teams :
677688 for team in unconfigured_teams :
678689 logging .info ("Deleting team '%s' as it is not configured locally" , team .name )
690+ self .stats .delete_team (team = team .name , deleted = True )
679691 if not dry :
680692 team .delete ()
681693 else :
@@ -685,6 +697,8 @@ def get_unconfigured_teams(
685697 "configured locally: %s. Taking no action about these teams." ,
686698 ", " .join (unconfigured_teams_str ),
687699 )
700+ for team in unconfigured_teams :
701+ self .stats .delete_team (team = team .name , deleted = False )
688702
689703 def get_members_without_team (
690704 self , dry : bool = False , remove_members_without_team : bool = False
@@ -714,6 +728,7 @@ def get_members_without_team(
714728 "Removing user '%s' from organisation as they are not member of any team" ,
715729 user .login ,
716730 )
731+ self .stats .remove_member_without_team (user = user .login , removed = True )
717732 if not dry :
718733 self .org .remove_from_membership (user )
719734 else :
@@ -723,6 +738,8 @@ def get_members_without_team(
723738 "member of any team: %s" ,
724739 ", " .join (members_without_team_str ),
725740 )
741+ for user in members_without_team :
742+ self .stats .remove_member_without_team (user = user .login , removed = False )
726743
727744 # --------------------------------------------------------------------------
728745 # Repos
@@ -850,6 +867,7 @@ def sync_repo_permissions(self, dry: bool = False, ignore_archived: bool = False
850867 team .name ,
851868 perm ,
852869 )
870+ self .stats .change_repo_team_permissions (repo = repo .name , team = team .name , perm = perm )
853871 if not dry :
854872 # Update permissions or newly add a team to a repo
855873 team .update_team_repository (repo , perm )
@@ -875,6 +893,10 @@ def sync_repo_permissions(self, dry: bool = False, ignore_archived: bool = False
875893 self ._document_unconfigured_team_repo_permissions (
876894 team = team , team_permission = teams [team ], repo_name = repo .name
877895 )
896+ # Collect this status in the stats
897+ self .stats .document_unconfigured_team_permissions (
898+ team = team .name , repo = repo .name , perm = teams [team ]
899+ )
878900 # Abort handling the repo sync as we don't touch unconfigured teams
879901 continue
880902 # Handle: Team is configured, but contains no config
@@ -893,6 +915,7 @@ def sync_repo_permissions(self, dry: bool = False, ignore_archived: bool = False
893915 # Remove if any mismatch has been found
894916 if remove :
895917 logging .info ("Removing team '%s' from repository '%s'" , team .name , repo .name )
918+ self .stats .remove_team_from_repo (repo = repo .name , team = team .name )
896919 if not dry :
897920 team .remove_from_repos (repo )
898921
@@ -1338,5 +1361,6 @@ def sync_repo_collaborator_permissions(self, dry: bool = False):
13381361 )
13391362
13401363 # Remove collaborator
1364+ self .stats .remove_repo_collaborator (repo = repo .name , user = username )
13411365 if not dry :
13421366 repo .remove_from_collaborators (username )
0 commit comments