-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
87754a2
commit 0d08f44
Showing
4 changed files
with
262 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"name": "pchevrel/bzkarma", | ||
"description": "Uplift scoring library for bugzilla.mozilla.org", | ||
"autoload": { | ||
"psr-0": { | ||
"pchevrel": "src" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace BzKarma; | ||
|
||
use BzKarma\Train; | ||
|
||
class Scoring | ||
{ | ||
/* | ||
This array contains our uplift value business logic. | ||
*/ | ||
public array $karma = [ | ||
'priority' => [ | ||
'P1' => 5, | ||
'P2' => 4, | ||
'P3' => 3, | ||
'P4' => 2, | ||
'P5' => 1, | ||
'--' => 0, | ||
], | ||
'severity' => [ | ||
'S1' => 8, | ||
'S2' => 4, | ||
'S3' => 2, | ||
'S4' => 1, | ||
'N/A' => 0, | ||
'--' => 0, | ||
], | ||
'keywords' => [ | ||
'topcrash-startup' => 10, | ||
'topcrash' => 5, | ||
'dataloss' => 3, | ||
'crash' => 1, | ||
'regression' => 1, | ||
'perf' => 1, | ||
], | ||
'duplicates' => 2, // Points for each duplicate | ||
'regressions' => -2, // Negative Points for regressions | ||
'tracking_firefox_nightly' => [ | ||
'blocking' => 100, | ||
'+' => 4, | ||
'?' => 2, | ||
'-' => 0, | ||
'---' => 0, | ||
], | ||
'tracking_firefox_beta' => [ | ||
'blocking' => 100, | ||
'+' => 4, | ||
'?' => 2, | ||
'-' => 0, | ||
'---' => 0, | ||
], | ||
'tracking_firefox_release' => [ | ||
'blocking' => 100, | ||
'+' => 4, | ||
'?' => 2, | ||
'-' => 0, | ||
'---' => 0, | ||
], | ||
'webcompat' => [ | ||
'P1' => 5, | ||
'P2' => 4, | ||
'P3' => 3, | ||
'?' => 1, | ||
'---' => 0, | ||
], | ||
]; | ||
|
||
/* | ||
This array stores the bug data provided by the Bugzilla rest API | ||
The list of fields retrieved are: | ||
id, summary, priority, severity, keywords, duplicates, regressions, cf_webcompat_priority, | ||
cf_tracking_firefox_nightly, cf_tracking_firefox_beta, cf_tracking_firefox_release | ||
The fields actually retrieved for tracking requests have release numbers, ex: | ||
cf_tracking_firefox112, cf_tracking_firefox111, cf_tracking_firefox110 | ||
See Bug 1819638 - JSON API should support release aliases (_nightly / _beta / _release) for the cf_tracking_firefoxXXX and cf_status_firefoxXXX fields - https://bugzil.la/1819638 | ||
*/ | ||
public array $bugsData; | ||
|
||
/** | ||
* We work from a dataset provided by the Bugzilla rest API | ||
*/ | ||
public function __construct(array $bugsData) | ||
{ | ||
$this->bugsData = $bugsData; | ||
} | ||
|
||
public function getAllBugsScores(): array | ||
{ | ||
$bugs = []; | ||
|
||
foreach ($this->bugsData as $bug => $details) { | ||
$bugs[$bug] = $this->getBugScore($bug); | ||
} | ||
|
||
// We sort them in reverse order to list best nugs first | ||
arsort($bugs); | ||
|
||
return $bugs; | ||
} | ||
|
||
/* | ||
This is the method that contains the business logic. | ||
*/ | ||
public function getBugScoreDetails(int $bug): array | ||
{ | ||
/* | ||
If we don't have the bug in store (private bugs), return 0. | ||
This part of the logic is only needed when using the external public API. | ||
*/ | ||
if (! isset($this->bugsData[$bug])) { | ||
return [ | ||
'priority' => 0, | ||
'severity' => 0, | ||
'keywords' => 0, | ||
'duplicates' => 0, | ||
'regressions' => 0, | ||
'webcompat' => 0, | ||
'tracking_firefox'. Train::NIGHTLY->value => 0, | ||
'tracking_firefox'. Train::BETA->value => 0, | ||
'tracking_firefox'. Train::RELEASE->value => 0, | ||
]; | ||
} | ||
|
||
$keywords_value = 0; | ||
|
||
/* | ||
We loop through all the bug keywords and check if they have an internal value. | ||
Then we add the points they have to the total for keywords. | ||
*/ | ||
foreach ($this->bugsData[$bug]['keywords'] as $keyword) { | ||
if (array_key_exists($keyword, $this->karma['keywords'])) { | ||
$keywords_value += $this->karma['keywords'][$keyword]; | ||
} | ||
} | ||
|
||
/* | ||
Some fields are not available for all components so we need | ||
to check for their availability and we set it to a 0 karma if it doesn't exist. | ||
*/ | ||
$value = function (int $bug, string $bz_field, string $local_field): int { | ||
return isset($this->bugsData[$bug][$bz_field]) | ||
? $this->karma[$local_field][$this->bugsData[$bug][$bz_field]] | ||
: 0; | ||
}; | ||
|
||
$webcompat = $value($bug, 'cf_webcompat_priority', 'webcompat'); | ||
$nightly = $value($bug, 'cf_tracking_firefox'. Train::NIGHTLY->value, 'tracking_firefox_nightly'); | ||
$beta = $value($bug, 'cf_tracking_firefox'. Train::BETA->value, 'tracking_firefox_beta'); | ||
$release = $value($bug, 'cf_tracking_firefox'. Train::RELEASE->value, 'tracking_firefox_release'); | ||
|
||
$impact = [ | ||
/* | ||
Severity and Priority fields had other values in the past like normal, trivial… | ||
We ignore these values for now. | ||
*/ | ||
'priority' => $this->karma['priority'][$this->bugsData[$bug]['priority']] ?? 0, | ||
'severity' => $this->karma['severity'][$this->bugsData[$bug]['severity']] ?? 0, | ||
'keywords' => $keywords_value, | ||
'duplicates' => count($this->bugsData[$bug]['duplicates']) * $this->karma['duplicates'], | ||
'regressions' => count($this->bugsData[$bug]['regressions']) * $this->karma['regressions'], | ||
'webcompat' => $webcompat, | ||
|
||
/* | ||
If a bug is tracked across all our releases, it is likely higher value | ||
*/ | ||
'tracking_firefox'. Train::NIGHTLY->value => $nightly, | ||
'tracking_firefox'. Train::BETA->value => $beta, | ||
'tracking_firefox'. Train::RELEASE->value => $release, | ||
]; | ||
|
||
return $impact; | ||
} | ||
|
||
public function getBugScore(int $bug): int { | ||
return array_sum($this->getBugScoreDetails($bug)); | ||
} | ||
} | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<?php | ||
|
||
namespace BzKarma; | ||
|
||
enum Train: string { | ||
case NIGHTLY = '112'; | ||
case BETA = '111'; | ||
case RELEASE = '110'; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace BzKarma; | ||
|
||
class Utils | ||
{ | ||
|
||
/** | ||
* Transform a string of bugs into a list of valid integers | ||
* | ||
* @return array<int> | ||
*/ | ||
public static function getBugsFromString(string $commaSeparatedList, string $separator = ','): array | ||
{ | ||
// Convert the list of comma-separated bug numbers to an array | ||
$bugs = explode($separator, $commaSeparatedList); | ||
|
||
// Remove extra whitespaces | ||
$bugs = array_map('trim', $bugs); | ||
|
||
// Filter out all strings that can't be turned into valid bug numbers | ||
$bugs = array_filter($bugs, 'ctype_digit'); | ||
|
||
// Turn all strings as integers for security | ||
$bugs = array_map('intval', $bugs); | ||
|
||
// Remove duplicates | ||
$bugs = array_unique($bugs); | ||
|
||
// Reorder array keys now that we have removed items | ||
$bugs = array_values($bugs); | ||
|
||
return $bugs; | ||
} | ||
|
||
public static function getJson(string $url): mixed | ||
{ | ||
$data = file_get_contents($url); | ||
return json_decode($data, true, 512, JSON_THROW_ON_ERROR); | ||
} | ||
|
||
|
||
public static function getBugDetails(array $bugList, array $bugFields): array | ||
{ | ||
$data = self::getJson( | ||
'https://bugzilla.mozilla.org/rest/bug?include_fields=' | ||
. implode(',', $bugFields) | ||
. '&bug_id=' . implode('%2C', $bugList) | ||
)['bugs']; | ||
|
||
// Replace numeric keys by the real bug number | ||
$data = array_combine(array_column($data, 'id'), $data); | ||
|
||
return $data; | ||
} | ||
} |