Skip to content

Instantly share code, notes, and snippets.

@ironprogrammer
Last active November 8, 2023 23:16
Show Gist options
  • Save ironprogrammer/7abcdac3686027363aefd73934b8d36a to your computer and use it in GitHub Desktop.
Save ironprogrammer/7abcdac3686027363aefd73934b8d36a to your computer and use it in GitHub Desktop.
Test Contributor Badge Checker

Test Contributor Badge Checker

This script checks a list of WordPress.org contributors for whether they have the Test Contributor badge, and if not, if they've recently submitted a test report in Trac.

To Get Started

Copy these files into a folder. Create a contribs.txt file with a list of WPORG usernames or Slack IDs to check. One entry per line works best.

Where Do I Get Contributor IDs?

Here is an incomplete list of sources to check for new test contributions:

  • #core-test triage participants who conduct repro or patch testing
  • #core <release-party> events
  • new contributors list from #core's regular "Week in Core" posts
  • Contributor Day events (in-person or online)
  • contributors who write or improve unit or e2e test coverage for WordPress projects (Core, Gutenberg, canonical plugins, etc)

Run It!

You'll need to be able to execute PHP scripts from a terminal:

php badge-me.php

If you wish to use a different contributors list file, simply pass it along in the command, e.g. php badge-me.php contributors-november.txt.

This script has been iterated on since June 2022.

<?php
/**
* Test Contributor Badge Checker
*
* @TODO:
* - Add support for `@first last` Slack names copied directly from chats (e.g. from release parties).
* These would be prefaced by `@`, and may/may not have spaces.
* When encountered, check Slack (API) for the corresponding ID, then pass that to the profiles URL.
* - Check for test reports in GitHub, e.g. in Gutenberg issues.
* - Track who's been badged already between runs of the script, to optimize checks.
*/
// Fake out Trac that these are not curl scraping requests 😇.
// Example full user_agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6.1 Safari/605.1.15
ini_set('user_agent', 'Mozilla/5.0');
const MAX_RECORDS = 100;
const DAYS_BACK = 90;
$badge = 'badge-test-contributor';
$history = array();
// Set contrib list source
$file = 'contribs.txt'; // default; testing
if (isset($argv[1]) && file_exists($argv[1])) {
$file = $argv[1];
}
// Load into lines array
$lines = file($file);
echo 'WPORG | Slack | GitHub | Badged | Reports' . PHP_EOL;
echo '-----------------------------------------' . PHP_EOL;
// Create list of users and badge status
foreach ($lines as $line) {
if ($line === '') {
continue;
}
// Check if line needs splitting
$users = array_map('trim', explode(',', $line));
foreach ($users as $user) {
$user = clean_username($user);
// Don't reprocess known users
if (isset($history[$user])) {
continue;
}
$profile = get_profile($user);
if ($profile) {
// Get username, which might resolve to something different from $user
$username = get_username($profile);
if ($username === 'github') { // means user didn't match in GitHub either, e.g. https://profiles.wordpress.org/github:U036HHRABB8
echo 'NOT FOUND: ' . $user . PHP_EOL;
continue;
}
$slack_id = get_username_slack($profile);
$github_id = get_username_github($profile);
// Check if user is badged
$is_badged = check_badge($profile, $badge);
// Check for a recent test report contribution
// Use `from=mm/dd/yyyy` param to offset range; defaults to today's date
$url = sprintf('https://core.trac.wordpress.org/timeline?ticket_details=on&max=%d&authors=%s&daysback=%d', MAX_RECORDS, $user, DAYS_BACK);
$timeline = get_timeline($url);
$has_report = check_reports($timeline);
// Output results
echo sprintf(
'%s | %s | %s',
$username,
$slack_id ? $slack_id : $username,
$github_id ? $github_id : 'NONE'
);
echo sprintf(' | %s', $is_badged ? '🧪' : '❌');
echo sprintf(' | %s', $has_report ? '📄' : '❌');
echo PHP_EOL;
$history[$username]['badge'] = $is_badged;
$history[$username]['report'] = $has_report;
$history[$username]['slack'] = $slack_id ? $slack_id : $username;
} else {
echo 'ERROR: ' . $user . PHP_EOL;
$history[$user] = false;
}
}
}
echo '-----------------------------------------' . PHP_EOL;
echo 'Drum roll, please...🥁' . PHP_EOL;
$badges_given = 0;
// Show users who qualify but haven't been badged yet.
foreach ($history as $user => $data) {
if (! $data['badge'] && $data['report']) {
echo sprintf('Badge Me: %s | @%s', $user, $data['slack']);
echo PHP_EOL;
$badges_given++;
}
}
// Show final result.
if ($badges_given === 0) {
echo '😢 No badges to give today.' . PHP_EOL;
// Set stdout exit code to 1 to indicate failure.
exit(1);
} else {
echo sprintf('🎉 Badges given: %d', $badges_given);
echo PHP_EOL;
}
/* ---------------------------------------------------------------- */
/*
* Get the user's profile from WPORG or GitHub. Uses the following URLs:
* - https://profiles.wordpress.org/{query}
* - https://profiles.wordpress.org/github:{query}
* Returns profile page content or false.
*/
function get_profile($user) {
$context = stream_context_create(
array(
'http' => array(
'follow_location' => true,
)
)
);
// Check native .Org username and Slack ID redirect
$url = get_profile_url($user);
$profile = @file_get_contents($url, false, $context);
// If it fails, try using the username as a GitHub ID
if ($profile === false) {
$url = get_profile_url_github($user);
$profile = @file_get_contents($url, false, $context);
}
return $profile;
}
function get_profile_url($user) {
return sprintf('https://profiles.wordpress.org/%s/', $user);
}
function get_profile_url_github($user) {
return sprintf('https://profiles.wordpress.org/github:%s/', $user);
}
function get_timeline($url) {
return @file_get_contents($url . '&format=rss', false);
}
function check_badge($profile, $badge) {
return strpos($profile, $badge) > 0 ? true : false;
}
function clean_username($user) {
return str_replace('@', '', $user);
}
function get_username($profile) {
preg_match('/<meta property="profile:username" content="(\w+)".*?>/i', $profile, $matches);
return isset($matches[1]) ? $matches[1] : false;
}
function get_username_slack($profile) {
preg_match('/<span class="username-on-slack".*? class="username">@([\w ]+)<\/span>/is', $profile, $matches);
return isset($matches[1]) ? $matches[1] : false;
}
function get_username_github($profile) {
preg_match('/<li id="user-github".*?<a href="https:\/\/github.com\/([\w-]+)"/is', $profile, $matches);
return isset($matches[1]) ? $matches[1] : false;
}
/*
* Check for a recent test report contribution based on common tokens found in the timeline.
* Returns true or false.
*/
function check_reports($timeline) {
// These can be found as `id="TOKEN"` in formatted test reports.
$report_tokens = array(
'id="TestReport',
'id="Environment',
'id="ActualResult',
'id="ReproductionReport',
'id="ExpectedResult',
'id="TestingInstructions',
'id="Stepsto',
);
foreach ($report_tokens as $token) {
// Return true as soon as we find one.
if (strpos($timeline, $token) > 0) {
return true;
}
}
return false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment