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