• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / relman-auto-nag / #5119

25 Jun 2024 03:41PM CUT coverage: 21.7% (-0.02%) from 21.722%
#5119

push

coveralls-python

benjaminmah
Added unassignment

716 of 3628 branches covered (19.74%)

0 of 11 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

1933 of 8908 relevant lines covered (21.7%)

0.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/bugbot/rules/inactive_patch_author.py
1
# This Source Code Form is subject to the terms of the Mozilla Public
2
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
3
# You can obtain one at http://mozilla.org/MPL/2.0/.
4

5
import re
×
6
from typing import Dict, List
×
7

8
from libmozdata.connection import Connection
×
9
from libmozdata.phabricator import PhabricatorAPI
×
10
from tenacity import retry, stop_after_attempt, wait_exponential
×
11

NEW
12
from bugbot import people, utils
×
13
from bugbot.bzcleaner import BzCleaner
×
14
from bugbot.user_activity import PHAB_CHUNK_SIZE, UserActivity, UserStatus
×
15

16
PHAB_FILE_NAME_PAT = re.compile(r"phabricator-D([0-9]+)-url\.txt")
×
17

18

19
class InactivePatchAuthors(BzCleaner):
×
20
    """Bugs with patches authored by inactive patch authors"""
21

22
    def __init__(self):
×
23
        super(InactivePatchAuthors, self).__init__()
×
24
        self.phab = PhabricatorAPI(utils.get_login_info()["phab_api_key"])
×
25
        self.user_activity = UserActivity(include_fields=["nick"], phab=self.phab)
×
NEW
26
        self.default_assignees = utils.get_default_assignees()
×
NEW
27
        self.people = people.People.get_instance()
×
28

29
    def description(self):
×
30
        return "Bugs with inactive patch authors"
×
31

32
    def columns(self):
×
NEW
33
        return ["id", "summary"]
×
34

35
    def get_bugs(self, date="today", bug_ids=[], chunk_size=None):
×
36
        bugs = super().get_bugs(date, bug_ids, chunk_size)
×
37
        rev_ids = {rev_id for bug in bugs.values() for rev_id in bug["rev_ids"]}
×
38
        inactive_authors = self._get_inactive_patch_authors(list(rev_ids))
×
39

40
        for bugid, bug in list(bugs.items()):
×
41
            inactive_patches = [
×
42
                {"rev_id": rev_id, "author": inactive_authors[rev_id]}
43
                for rev_id in bug["rev_ids"]
44
                if rev_id in inactive_authors
45
            ]
46

47
            if inactive_patches:
×
48
                bug["inactive_patches"] = inactive_patches
×
NEW
49
                self.unassign_inactive_author(bugid, bug, inactive_patches)
×
UNCOV
50
                print(f"Bug {bugid} has inactive patches: {inactive_patches}")
×
51
            else:
52
                del bugs[bugid]
×
53

54
        return bugs
×
55

NEW
56
    def unassign_inactive_author(self, bugid, bug, inactive_patches):
×
57
        # print(f"\n BUG >> {bugid} >> {bug}\n")
58
        # prod = bug["product"]
59
        # comp = bug["component"]
NEW
60
        default_assignee = "benjaminmah2004@gmail.com"
×
NEW
61
        autofix = {"assigned_to": default_assignee}
×
62

NEW
63
        comment = (
×
64
            "The patch author is inactive on Bugzilla, so the assignee is being reset."
65
        )
NEW
66
        autofix["comment"] = {"body": comment}
×
67

NEW
68
        self.autofix_changes[bugid] = autofix
×
69

70
    def _get_inactive_patch_authors(self, rev_ids: list) -> Dict[int, dict]:
×
71
        revisions: List[dict] = []
×
72

73
        for _rev_ids in Connection.chunks(rev_ids, PHAB_CHUNK_SIZE):
×
74
            for revision in self._fetch_revisions(_rev_ids):
×
75
                author_phid = revision["fields"]["authorPHID"]
×
76
                created_at = revision["fields"]["dateCreated"]
×
77
                if author_phid == "PHID-USER-eltrc7x5oplwzfguutrb":
×
78
                    continue
×
79
                revisions.append(
×
80
                    {
81
                        "rev_id": revision["id"],
82
                        "author_phid": author_phid,
83
                        "created_at": created_at,
84
                    }
85
                )
86

87
        user_phids = set()
×
88

89
        for revision in revisions:
×
90
            user_phids.add(revision["author_phid"])
×
91

92
        users = self.user_activity.get_phab_users_with_status(
×
93
            list(user_phids), keep_active=False
94
        )
95

96
        result: Dict[int, dict] = {}
×
97
        for revision in revisions:
×
98
            author_phid = revision["author_phid"]
×
99

100
            if author_phid not in users:
×
101
                continue
×
102

103
            author_info = users[author_phid]
×
104
            if author_info["status"] == UserStatus.INACTIVE:
×
105
                result[revision["rev_id"]] = {
×
106
                    "name": author_info["name"],
107
                    "status": author_info["status"],
108
                    "last_active": author_info.get("last_seen_date"),
109
                }
110

111
        return result
×
112

113
    @retry(
×
114
        wait=wait_exponential(min=4),
115
        stop=stop_after_attempt(5),
116
    )
117
    def _fetch_revisions(self, ids: list):
×
118
        return self.phab.request(
×
119
            "differential.revision.search",
120
            constraints={"ids": ids},
121
        )["data"]
122

123
    def handle_bug(self, bug, data):
×
124
        rev_ids = [
×
125
            int(attachment["file_name"][13:-8])
126
            for attachment in bug["attachments"]
127
            if attachment["content_type"] == "text/x-phabricator-request"
128
            and PHAB_FILE_NAME_PAT.match(attachment["file_name"])
129
            and not attachment["is_obsolete"]
130
        ]
131

132
        if not rev_ids:
×
133
            return
×
134

135
        bugid = str(bug["id"])
×
136
        data[bugid] = {
×
137
            "rev_ids": rev_ids,
138
        }
139
        return bug
×
140

141
    def get_bz_params(self, date):
×
142
        fields = [
×
143
            "comments.raw_text",
144
            "comments.creator",
145
            "attachments.file_name",
146
            "attachments.content_type",
147
            "attachments.is_obsolete",
148
            "product",
149
            "component",
150
        ]
151
        params = {
×
152
            "include_fields": fields,
153
            "resolution": "---",
154
            "f1": "attachments.ispatch",
155
            "o1": "equals",
156
            "v1": "1",
157
        }
158

159
        return params
×
160

161

162
if __name__ == "__main__":
×
163
    InactivePatchAuthors().run()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc