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