diff --git a/bugbot/rules/bugscore.py b/bugbot/rules/bugscore.py
new file mode 100644
index 000000000..365a10515
--- /dev/null
+++ b/bugbot/rules/bugscore.py
@@ -0,0 +1,144 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from bugbot import utils
+from bugbot.bzcleaner import BzCleaner
+
+
+class BugScore(BzCleaner):
+ def __init__(self):
+ super(BugScore, self).__init__()
+ self.ndups = self.get_config("number_dups")
+ self.votes = self.get_config("number_votes")
+ self.cc = self.get_config("number_cc")
+ self.see_also = self.get_config("number_see_also")
+ self.comments = self.get_config("number_comments")
+
+ # Weights for each category
+ self.votes_weight = 0.5
+ self.see_also_weight = 0.8
+ self.dups_weight = 1.0
+ self.comments_weight = 0.4
+ self.ccs_weight = 0.3
+ self.threshold = 20
+
+ self.extra_ni = {}
+
+ def description(self):
+ return "Bugs scores"
+
+ def sort_columns(self):
+ return lambda p: (-p[9], -int(p[0]))
+
+ def columns(self):
+ return [
+ "id",
+ "summary",
+ "creation",
+ "last_change",
+ "severity",
+ "dups_count",
+ "votes",
+ "cc_count",
+ "see_also_count",
+ "user_impact_score",
+ "comment_count",
+ "product",
+ "component",
+ "type",
+ ]
+
+ def get_extra_for_template(self):
+ return {
+ "dups_threshold": self.ndups,
+ "votes_threshold": self.votes,
+ "cc_threshold": self.cc,
+ "see_also_threshold": self.see_also,
+ "comments_threshold": self.comments,
+ "votes_weight": self.votes_weight,
+ "see_also_weight": self.see_also_weight,
+ "dups_weight": self.dups_weight,
+ "comments_weight": self.comments_weight,
+ "ccs_weight": self.ccs_weight,
+ "threshold": self.threshold,
+ }
+
+ def handle_bug(self, bug, data):
+ bugid = str(bug["id"])
+ cc_count = len(bug["cc"])
+ dups_count = len(bug["duplicates"])
+ votes_count = bug["votes"]
+ see_also_count = len(bug["see_also"])
+ comment_count = bug["comment_count"]
+
+ # Calculating the User Impact Score
+ user_impact_score = round(
+ (votes_count * self.votes_weight)
+ + (see_also_count * self.see_also_weight)
+ + (dups_count * self.dups_weight)
+ + (comment_count * self.comments_weight)
+ + (cc_count * self.ccs_weight)
+ )
+ if user_impact_score < self.threshold:
+ return
+ data[bugid] = {
+ "creation": utils.get_human_lag(bug["creation_time"]),
+ "last_change": utils.get_human_lag(bug["last_change_time"]),
+ "severity": bug["severity"],
+ "dups_count": dups_count,
+ "votes": votes_count,
+ "cc_count": cc_count,
+ "see_also_count": see_also_count,
+ "user_impact_score": user_impact_score,
+ "comment_count": comment_count,
+ "product": bug["product"],
+ "component": bug["component"],
+ "type": bug["type"],
+ }
+
+ return bug
+
+ def get_bz_params(self, date):
+ fields = [
+ "creation_time",
+ "last_change_time",
+ "severity",
+ "votes",
+ "cc",
+ "duplicates",
+ "see_also",
+ "comment_count",
+ "product",
+ "component",
+ "type",
+ ]
+
+ params = {
+ "include_fields": fields,
+ "resolution": "---",
+ "f1": "keywords",
+ "o1": "nowords",
+ "v1": ["meta", "intermittent"],
+ "j3": "OR",
+ "f3": "OP",
+ "f4": "dupe_count",
+ "o4": "greaterthaneq",
+ "v4": "5",
+ "f5": "votes",
+ "o5": "greaterthaneq",
+ "v5": "10",
+ "f6": "cc_count",
+ "o6": "greaterthaneq",
+ "v6": "20",
+ "f7": "see_also_count",
+ "o7": "greaterthaneq",
+ "v7": "2",
+ "f8": "CP",
+ }
+
+ return params
+
+
+if __name__ == "__main__":
+ BugScore().run()
diff --git a/configs/rules.json b/configs/rules.json
index 7bbdd4db7..f75a7fc3a 100644
--- a/configs/rules.json
+++ b/configs/rules.json
@@ -466,5 +466,14 @@
"leave_open_sec": {
"additional_receivers": "rm",
"days_lookup": 2
+ },
+ "bugscore": {
+ "number_dups": 30,
+ "number_votes": 200,
+ "number_cc": 100,
+ "number_see_also": 10,
+ "number_comments": 150,
+ "additional_receivers": "rm",
+ "must_run": ["Mon"]
}
}
diff --git a/scripts/check_rules_on_wiki.py b/scripts/check_rules_on_wiki.py
index 1e7a1a34a..c024b9b1f 100644
--- a/scripts/check_rules_on_wiki.py
+++ b/scripts/check_rules_on_wiki.py
@@ -52,6 +52,7 @@ class CheckWikiPage:
"pdfjs_tag_change.py",
"pdfjs_update.py",
"leave_open_sec.py",
+ "bugscore.py"
}
def __init__(self) -> None:
diff --git a/scripts/cron_run_weekdays.sh b/scripts/cron_run_weekdays.sh
index 1976a4c8a..30618c30a 100755
--- a/scripts/cron_run_weekdays.sh
+++ b/scripts/cron_run_weekdays.sh
@@ -88,6 +88,9 @@ python -m bugbot.rules.several_comments --production
# Bugs with a lot of see also
python -m bugbot.rules.several_see_also --production
+# List of papercuts
+python -m bugbot.rules.bugscore --production
+
# Bug caused several regressions recently reported
# Pretty rare
python -m bugbot.rules.warn_regressed_by --production
diff --git a/templates/bugscore.html b/templates/bugscore.html
new file mode 100644
index 000000000..1f1a004b1
--- /dev/null
+++ b/templates/bugscore.html
@@ -0,0 +1,302 @@
+
+
+
+
+Bug score is a metric that combines the following metrics:
+
+
+
+Threshold score: {{ extra["threshold"] }}
+
+
+
+
+ | Bug |
+ Summary |
+ Product |
+ Type |
+ Creation |
+ Bug Score |
+ Severity |
+ Duplicates |
+ Votes |
+ CC |
+ See Also |
+ # of comments |
+
+
+
+ {% for i, (bugid, summary, creation, last_change, severity, dups_count, votes, cc_count, see_also_count, bugscore, comment_count, product, component, type) in enumerate(data) -%}
+
+ |
+ {{ bugid }}
+ |
+ {{ summary | e }} |
+ {{ product | e }} / {{ component | e }} |
+ {{ type }} |
+ {{ creation }} |
+ |
+ {{ severity }} |
+ = extra["dups_threshold"] %}bgcolor="#FFB8B8"{% endif -%}>{{ dups_count }} |
+ = extra["votes_threshold"] %}bgcolor="#FFB8B8"{% endif -%}>{{ votes }} |
+ = extra["cc_threshold"] %}bgcolor="#FFB8B8"{% endif -%}>{{ cc_count }} |
+ = extra["see_also_threshold"] %}bgcolor="#FFB8B8"{% endif -%}>{{ see_also_count }} |
+ = extra["comments_threshold"] %}bgcolor="#FFB8B8"{% endif -%}>{{ comment_count }} |
+
+ {% endfor -%}
+
+