Bugzilla Administration/BugzillaFetch
From GnuCash
#!/usr/bin/perl
#
# Fetch bugzilla data to migrate to new bugzilla!
#
# Created by: Derek Atkins <derek@ihtfp.com>
#
use JSON;
use URI::Escape;
use LWP::Simple;
use List::MoreUtils qw(natatime);
$URL="https://bugzilla.gnome.org/jsonrpc.cgi";
$FILEBASE="/root/Bugzilla/data";
my $debug_RPC;
# RPC(method, params)
# method: method name (e.g. Products.get or Bug.search
# params: an arrayref of a hashref of parameters for the API
#
# Returns the json hashref of the result
sub RPC(@@) {
my ($method, $params) = @_;
my $url = "$URL?method=$method¶ms=" . uri_escape(encode_json($params));
print "URL: $url\n" if ($debug_RPC);
my $res = decode_json(get($url));
my $json = JSON->new;
die $json->pretty->encode($res) if ($res->{error} != null || $res->{result} == null);
return $res->{result};
}
# GetProductByName(name)
# Returns a hashref of the product "name"
sub GetProductByName(@) {
my ($name) = @_;
my $params = [{names => [ $name ]}];
my $json = RPC("Product.get", $params);
return $json->{products}[0];
}
# GetBugsForProduct(name)
# Returns an arrayref of each bug-id, where each bug-id is a
# hashref with fields id and last_change_time
sub GetBugsForProduct(@) {
my ($name) = @_;
my $params = [{product => [$name],
include_fields => ["id", "last_change_time"]}];
#$debug_RPC = 1;
my $json = RPC("Bug.search", $params);
undef $debug_RPC;
#my @ids = ();
#foreach $id (@{$json->{bugs}}) {
#push @ids, $id->{id};
#}
#return \@ids;
return $json->{bugs};
}
# Check bug's last_change_time to see if it needs to be refreshed
# Argument is a hashref with fields id and last_change_time
# Returns null or the bug reference
sub CheckBug(@) {
my ($b_id) = @_;
my $filename = "$FILEBASE/bugs/bug_" . $b_id->{id} . ".json";
#print "Checking bug " . $b_id->{id} . ", last_change_time=" . $b_id->{last_change_time} . "...";
if (! -f $filename) {
#print "No file found\n";
return null;
}
open FH, $filename || die "Can't open $filename: $!";
my $bug = decode_json <FH>;
close FH;
#print "Found file...";
if ($b_id->{last_change_time} eq $bug->{last_change_time}) {
#print "No changes.\n";
return $bug;
}
#print "Refresh.\n";
return null;
}
# GetBugById(id)
# Return a hashref of the Bug with comments, history, and attachments
sub GetBugById(@) {
my ($id) = @_;
my $params = [{ids => [$id]}];
my $json = RPC("Bug.get", $params);
my $bug = $json->{bugs}[0];
# Pull down the comments for this bug
# comment #0 is the bug description (sorted by count)
$json = RPC("Bug.comments", $params);
$bug->{comments} = $json->{bugs}->{$id}->{comments};
#@comments = sort { a->{count} <=> b->{count} } @comments;
$json = RPC("Bug.history", $params);
$bug->{history} = $json->{bugs}[0]->{history};
$json = RPC("Bug.attachments", $params);
$bug->{attachments} = $json->{bugs}->{$id};
return $bug;
}
# AddUsernames(hash, names..)
# Add the names to the hashref
sub AddUsernames(@@) {
my ($hash, @names) = @_;
foreach my $n (@names) {
next if ($n =~ /^$/);
$hash->{$n} = 1;
}
}
# SortUsernames(hash)
# Return an arrayref of the sorted (unique) names (i.e. keys of hashref)
sub SortUsernames(@) {
my ($hash) = @_;
my @names = sort keys(%{$hash});
return \@names;
}
# GetUsernamesFromBug(bug)
# Given a hashref of a bug, return an arrayref of the list of usernames
# associated with the bug. These names include the submitter, CCs,
# commenters, assignee, etc.
sub GetUsernamesFromBug(@) {
my ($bug) = @_;
my %names;
# my @top_levels_keys = ("qa_contact", "creator", "assigned_to"
# "cc" (array)
# comments -> "creator", "author"
# history -> "who"
# attachments -> "attacher", "creator");
# Push top-level ids (creator, qa_contact, assigned_to, cc):
#print "Creator: " . $bug->{creator} . "\n";
$names{$bug->{creator}} = 1;
#print "QA Contact: " . $bug->{qa_contact} . "\n";
$names{$bug->{qa_contact}} = 1;
#print "Assigned To: " . $bug->{assigned_to} . "\n";
$names{$bug->{assigned_to}} = 1;
AddUsernames(\%names, @{$bug->{cc}});
# Pull data from comments:
foreach my $c (@{$bug->{comments}}) {
#print "Comment Creator: " . $c->{creator} . "\n";
$names{$c->{creator}} = 1;
#print "Comment Author: " . $c->{author} . "\n";
$names{$c->{author}} = 1;
}
# Pull data from history:
foreach my $h (@{$bug->{history}}) {
#print "Comment History Who: " . $h->{who} . "\n";
$names{$h->{who}} = 1;
}
# Pull data from attachments:
foreach my $a (@{$bug->{attachments}}) {
#print "Attachment Creator: " . $a->{creator} . "\n";
$names{$a->{creator}} = 1;
#print "Attachment Attacher: " . $a->{attacher} . "\n";
$names{$a->{attacher}} = 1;
}
# Reduce down to unique names
return SortUsernames(\%names);
}
# GetUsersByNames(names)
# names: an arrayref to a list of names to get (email addresses)
# Returns an arrayref of the user data for all the names in the input array
sub GetUsersByNames(@) {
my ($names) = @_;
print "Trying to download " . scalar(@{$names}) . " users\n";
my @users;
my $iterator = natatime 100, @{$names};
#$debug_RPC = true;
my $j_ob = JSON->new;
while (my @items = $iterator->() ) {
#print "Downloading " . scalar(@items) . " users ...\n";
my $params = [{names => \@items}];
#print $j_ob->pretty->encode($params);
my $json = RPC("User.get", $params);
push @users, @{$json->{users}};
}
undef $debug_RPC;
return \@users;
}
# SaveObjectToFile(obj, filename)
# encodes the object to JSON and saves it to the file
sub SaveObjectToFile(@@) {
my ($obj, $filename) = @_;
open FH, ">$filename" || die "Failed to open $filename";
print FH encode_json($obj);
close FH;
}
# DownloadBugzilla(projectName)
# download all the data for the product and save it to the data location
sub DownloadBugzilla(@) {
my ($prodname) = @_;
my %usernames;
# Ensure storage exists
if (! -d "$FILEBASE/bugs") {
`mkdir -p $FILEBASE/bugs`;
}
# Download product
print "Downloading product $prodname...\n";
my $prod = GetProductByName($prodname);
SaveObjectToFile([$prod], "$FILEBASE/products.json");
foreach my $comp (@{$prod->{components}}) {
AddUsernames(\%usernames, ($comp->{default_qa_contact}, $comp->{default_assigned_to} ));
}
# Download all the bugs for the product and extract the users
print "Extracting bug list...\n";
my @buglist = @{GetBugsForProduct($prodname)};
print "Found " . $#buglist . " bugs to extract.\n";
# Download the bugs
foreach my $b_id (@buglist) {
my $bug = CheckBug($b_id);
if ($bug == null) {
print "Downloading bug #" . $b_id->{id} . "...\n";
$bug = GetBugById($b_id->{id});
SaveObjectToFile($bug, "$FILEBASE/bugs/bug_" . $bug->{id} . ".json");
}
AddUsernames(\%usernames, @{GetUsernamesFromBug($bug)});
}
# Download the users
print "Downloading users...\n";
my $users = GetUsersByNames(SortUsernames(\%usernames));
SaveObjectToFile($users, "$FILEBASE/users.json");
# Download the available Bug Fields
print "Downloading bug fields...\n";
my $json = RPC("Bug.fields", []);
SaveObjectToFile($json->{fields}, "$FILEBASE/bug_fields.json");
print "Done!\n";
}
DownloadBugzilla("gnucash");