1#!/usr/bin/python3 2 3import argparse 4import json 5import os 6import re 7from collections import defaultdict 8from typing import Dict 9 10import libvoters.acceptable as acceptable 11from libvoters import UserChanges, UserComments, changes_factory, comments_factory 12from libvoters.time import TimeOfDay, timestamp 13 14 15class subcmd: 16 def __init__(self, parser: argparse._SubParsersAction) -> None: 17 p = parser.add_parser( 18 "analyze-reviews", help="Determine points for reviews" 19 ) 20 21 p.add_argument( 22 "--before", 23 "-b", 24 help="Before timestamp (YYYY-MM-DD)", 25 required=True, 26 ) 27 p.add_argument( 28 "--after", 29 "-a", 30 help="After timestamp (YYYY-MM-DD)", 31 required=True, 32 ) 33 34 p.set_defaults(cmd=self) 35 36 def run(self, args: argparse.Namespace) -> int: 37 before = timestamp(args.before, TimeOfDay.AM) 38 after = timestamp(args.after, TimeOfDay.PM) 39 40 changes_per_user: Dict[str, UserChanges] = defaultdict(changes_factory) 41 42 for f in sorted(os.listdir(args.dir)): 43 path = os.path.join(args.dir, f) 44 if not os.path.isfile(path): 45 continue 46 47 if not re.match(r"[0-9]*\.json", f): 48 continue 49 50 with open(path, "r") as file: 51 data = json.load(file) 52 53 project = data["project"] 54 id_number = data["number"] 55 author = data["owner"]["username"] 56 57 if not acceptable.project(project): 58 print("Rejected project:", project, id_number) 59 60 comments_per_user: Dict[str, UserComments] = defaultdict( 61 comments_factory 62 ) 63 64 for patch_set in data["patchSets"]: 65 created_on = data["createdOn"] 66 67 if created_on > before or created_on < after: 68 continue 69 70 if "comments" not in patch_set: 71 continue 72 73 for comment in patch_set["comments"]: 74 reviewer = comment["reviewer"]["username"] 75 76 if reviewer == author: 77 continue 78 if not acceptable.file(project, comment["file"]): 79 continue 80 81 user = comments_per_user[reviewer] 82 user["name"] = comment["reviewer"]["name"] 83 # We actually have a case where a reviewer does not have an email recorded[1]: 84 # 85 # [1]: https://gerrit.openbmc.org/c/openbmc/phosphor-pid-control/+/60303/comment/ceff60b9_9d2debe0/ 86 # 87 # {"file": "conf.hpp", 88 # "line": 39, 89 # "reviewer": {"name": "Akshat Jain", "username": "AkshatZen"}, 90 # "message": "If we design SensorInput as base class and have derived ..."} 91 # Traceback (most recent call last): 92 # File "/mnt/host/andrew/home/andrew/src/openbmc/openbmc-tools/tof-voters/./voters", line 7, in <module> 93 # sys.exit(main()) 94 # ^^^^^^ 95 # File "/mnt/host/andrew/home/andrew/src/openbmc/openbmc-tools/tof-voters/libvoters/entry_point.py", line 33, in main 96 # return int(args.cmd.run(args)) 97 # ^^^^^^^^^^^^^^^^^^ 98 # File "/mnt/host/andrew/home/andrew/src/openbmc/openbmc-tools/tof-voters/libvoters/subcmd/analyze-reviews.py", line 82, in run 99 # user["email"] = comment["reviewer"]["email"] 100 # ~~~~~~~~~~~~~~~~~~~^^^^^^^^^ 101 # KeyError: 'email' 102 if "email" in comment["reviewer"]: 103 user["email"] = comment["reviewer"]["email"] 104 user["comments"] += 1 105 106 print(project, id_number) 107 for username, review in comments_per_user.items(): 108 if review["comments"] < 3: 109 continue 110 print(" ", user, review["comments"]) 111 user = changes_per_user[username] 112 user["name"] = review["name"] 113 user["email"] = review["email"] 114 user["changes"].append(id_number) 115 116 with open(os.path.join(args.dir, "reviews.json"), "w") as outfile: 117 outfile.write(json.dumps(changes_per_user, indent=4)) 118 119 return 0 120