diff options
author | Byron Jones <bjones@mozilla.com> | 2013-04-24 06:52:26 +0200 |
---|---|---|
committer | Byron Jones <bjones@mozilla.com> | 2013-04-24 06:52:26 +0200 |
commit | 290029c1c1ba8b3f1ef18b56c94f947bd550d056 (patch) | |
tree | 8b2bf889f629b48dd3336adbdd88661c012cbc39 /extensions/BMO/lib/Reports.pm | |
parent | 54fe9455d23b292eadaa851c7a3fdbb43e09f814 (diff) | |
download | bugzilla-290029c1c1ba8b3f1ef18b56c94f947bd550d056.tar.gz bugzilla-290029c1c1ba8b3f1ef18b56c94f947bd550d056.tar.xz |
Bug 853483: Triage report times out on Firefox (Any) query
Diffstat (limited to 'extensions/BMO/lib/Reports.pm')
-rw-r--r-- | extensions/BMO/lib/Reports.pm | 1191 |
1 files changed, 0 insertions, 1191 deletions
diff --git a/extensions/BMO/lib/Reports.pm b/extensions/BMO/lib/Reports.pm deleted file mode 100644 index 0f3a391d9..000000000 --- a/extensions/BMO/lib/Reports.pm +++ /dev/null @@ -1,1191 +0,0 @@ -# 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/. -# -# This Source Code Form is "Incompatible With Secondary Licenses", as -# defined by the Mozilla Public License, v. 2.0. - -package Bugzilla::Extension::BMO::Reports; -use strict; - -use Bugzilla::Extension::BMO::Data qw($cf_disabled_flags); - -use Bugzilla::Constants; -use Bugzilla::Error; -use Bugzilla::Field; -use Bugzilla::Group; -use Bugzilla::User; -use Bugzilla::Util qw(trim detaint_natural trick_taint correct_urlbase); - -use Date::Parse; -use DateTime; -use JSON qw(-convert_blessed_universally); -use List::MoreUtils qw(uniq); - -use base qw(Exporter); - -our @EXPORT = qw( user_activity_report - triage_reports - group_admins_report - email_queue_report - release_tracking_report - group_membership_report - group_members_report ); - -sub user_activity_report { - my ($vars) = @_; - my $dbh = Bugzilla->dbh; - my $input = Bugzilla->input_params; - - my @who = (); - my $from = trim($input->{'from'} || ''); - my $to = trim($input->{'to'} || ''); - my $action = $input->{'action'} || ''; - - # fix non-breaking hyphens - $from =~ s/\N{U+2011}/-/g; - $to =~ s/\N{U+2011}/-/g; - - if ($from eq '') { - my $dt = DateTime->now()->subtract('weeks' => 8); - $from = $dt->ymd('-'); - } - if ($to eq '') { - my $dt = DateTime->now(); - $to = $dt->ymd('-'); - } - - if ($action eq 'run') { - if ($input->{'who'} eq '') { - ThrowUserError('user_activity_missing_username'); - } - Bugzilla::User::match_field({ 'who' => {'type' => 'multi'} }); - - my $from_dt = _string_to_datetime($from); - $from = $from_dt->ymd(); - - my $to_dt = _string_to_datetime($to); - $to = $to_dt->ymd(); - # add one day to include all activity that happened on the 'to' date - $to_dt->add(days => 1); - - my ($activity_joins, $activity_where) = ('', ''); - my ($attachments_joins, $attachments_where) = ('', ''); - if (Bugzilla->params->{"insidergroup"} - && !Bugzilla->user->in_group(Bugzilla->params->{'insidergroup'})) - { - $activity_joins = "LEFT JOIN attachments - ON attachments.attach_id = bugs_activity.attach_id"; - $activity_where = "AND COALESCE(attachments.isprivate, 0) = 0"; - $attachments_where = $activity_where; - } - - my @who_bits; - foreach my $who ( - ref $input->{'who'} - ? @{$input->{'who'}} - : $input->{'who'} - ) { - push @who, $who; - push @who_bits, '?'; - } - my $who_bits = join(',', @who_bits); - - if (!@who) { - my $template = Bugzilla->template; - my $cgi = Bugzilla->cgi; - my $vars = {}; - $vars->{'script'} = $cgi->url(-relative => 1); - $vars->{'fields'} = {}; - $vars->{'matches'} = []; - $vars->{'matchsuccess'} = 0; - $vars->{'matchmultiple'} = 1; - print $cgi->header(); - $template->process("global/confirm-user-match.html.tmpl", $vars) - || ThrowTemplateError($template->error()); - exit; - } - - $from_dt = $from_dt->ymd() . ' 00:00:00'; - $to_dt = $to_dt->ymd() . ' 23:59:59'; - my @params; - for (1..4) { - push @params, @who; - push @params, ($from_dt, $to_dt); - } - - my $order = ($input->{'sort'} && $input->{'sort'} eq 'bug') - ? 'bug_id, bug_when' : 'bug_when'; - - my $comment_filter = ''; - if (!Bugzilla->user->is_insider) { - $comment_filter = 'AND longdescs.isprivate = 0'; - } - - my $query = " - SELECT - fielddefs.name, - bugs_activity.bug_id, - bugs_activity.attach_id, - ".$dbh->sql_date_format('bugs_activity.bug_when', '%Y.%m.%d %H:%i:%s')." AS ts, - bugs_activity.removed, - bugs_activity.added, - profiles.login_name, - bugs_activity.comment_id, - bugs_activity.bug_when - FROM bugs_activity - $activity_joins - LEFT JOIN fielddefs - ON bugs_activity.fieldid = fielddefs.id - INNER JOIN profiles - ON profiles.userid = bugs_activity.who - WHERE profiles.login_name IN ($who_bits) - AND bugs_activity.bug_when >= ? AND bugs_activity.bug_when <= ? - $activity_where - - UNION ALL - - SELECT - 'bug_id' AS name, - bugs.bug_id, - NULL AS attach_id, - ".$dbh->sql_date_format('bugs.creation_ts', '%Y.%m.%d %H:%i:%s')." AS ts, - '(new bug)' AS removed, - bugs.short_desc AS added, - profiles.login_name, - NULL AS comment_id, - bugs.creation_ts AS bug_when - FROM bugs - INNER JOIN profiles - ON profiles.userid = bugs.reporter - WHERE profiles.login_name IN ($who_bits) - AND bugs.creation_ts >= ? AND bugs.creation_ts <= ? - - UNION ALL - - SELECT - 'longdesc' AS name, - longdescs.bug_id, - NULL AS attach_id, - DATE_FORMAT(longdescs.bug_when, '%Y.%m.%d %H:%i:%s') AS ts, - '' AS removed, - '' AS added, - profiles.login_name, - longdescs.comment_id AS comment_id, - longdescs.bug_when - FROM longdescs - INNER JOIN profiles - ON profiles.userid = longdescs.who - WHERE profiles.login_name IN ($who_bits) - AND longdescs.bug_when >= ? AND longdescs.bug_when <= ? - $comment_filter - - UNION ALL - - SELECT - 'attachments.description' AS name, - attachments.bug_id, - attachments.attach_id, - ".$dbh->sql_date_format('attachments.creation_ts', '%Y.%m.%d %H:%i:%s')." AS ts, - '(new attachment)' AS removed, - attachments.description AS added, - profiles.login_name, - NULL AS comment_id, - attachments.creation_ts AS bug_when - FROM attachments - INNER JOIN profiles - ON profiles.userid = attachments.submitter_id - WHERE profiles.login_name IN ($who_bits) - AND attachments.creation_ts >= ? AND attachments.creation_ts <= ? - $attachments_where - - ORDER BY $order "; - - my $list = $dbh->selectall_arrayref($query, undef, @params); - - if ($input->{debug}) { - while (my $param = shift @params) { - $query =~ s/\?/$dbh->quote($param)/e; - } - $vars->{debug_sql} = $query; - } - - my @operations; - my $operation = {}; - my $changes = []; - my $incomplete_data = 0; - my %bug_ids; - - foreach my $entry (@$list) { - my ($fieldname, $bugid, $attachid, $when, $removed, $added, $who, - $comment_id) = @$entry; - my %change; - my $activity_visible = 1; - - next unless Bugzilla->user->can_see_bug($bugid); - - # check if the user should see this field's activity - if ($fieldname eq 'remaining_time' - || $fieldname eq 'estimated_time' - || $fieldname eq 'work_time' - || $fieldname eq 'deadline') - { - $activity_visible = Bugzilla->user->is_timetracker; - } - elsif ($fieldname eq 'longdescs.isprivate' - && !Bugzilla->user->is_insider - && $added) - { - $activity_visible = 0; - } - else { - $activity_visible = 1; - } - - if ($activity_visible) { - # Check for the results of an old Bugzilla data corruption bug - if (($added eq '?' && $removed eq '?') - || ($added =~ /^\? / || $removed =~ /^\? /)) { - $incomplete_data = 1; - } - - # Start a new changeset if required (depends on the sort order) - my $is_new_changeset; - if ($order eq 'bug_when') { - $is_new_changeset = - $operation->{'who'} && - ( - $who ne $operation->{'who'} - || $when ne $operation->{'when'} - || $bugid != $operation->{'bug'} - ); - } else { - $is_new_changeset = - $operation->{'bug'} && - $bugid != $operation->{'bug'}; - } - if ($is_new_changeset) { - $operation->{'changes'} = $changes; - push (@operations, $operation); - $operation = {}; - $changes = []; - } - - $bug_ids{$bugid} = 1; - - $operation->{'bug'} = $bugid; - $operation->{'who'} = $who; - $operation->{'when'} = $when; - - $change{'fieldname'} = $fieldname; - $change{'attachid'} = $attachid; - $change{'removed'} = $removed; - $change{'added'} = $added; - $change{'when'} = $when; - - if ($comment_id) { - $change{'comment'} = Bugzilla::Comment->new($comment_id); - next if $change{'comment'}->count == 0; - } - - if ($attachid) { - $change{'attach'} = Bugzilla::Attachment->new($attachid); - } - - push (@$changes, \%change); - } - } - - if ($operation->{'who'}) { - $operation->{'changes'} = $changes; - push (@operations, $operation); - } - - $vars->{'incomplete_data'} = $incomplete_data; - $vars->{'operations'} = \@operations; - - my @bug_ids = sort { $a <=> $b } keys %bug_ids; - $vars->{'bug_ids'} = \@bug_ids; - } - - $vars->{'action'} = $action; - $vars->{'who'} = join(',', @who); - $vars->{'who_count'} = scalar @who; - $vars->{'from'} = $from; - $vars->{'to'} = $to; - $vars->{'sort'} = $input->{'sort'}; -} - -sub _string_to_datetime { - my $input = shift; - my $time = _parse_date($input) - or ThrowUserError('report_invalid_date', { date => $input }); - return _time_to_datetime($time); -} - -sub _time_to_datetime { - my $time = shift; - return DateTime->from_epoch(epoch => $time) - ->set_time_zone('local') - ->truncate(to => 'day'); -} - -sub _parse_date { - my ($str) = @_; - if ($str =~ /^(-|\+)?(\d+)([hHdDwWmMyY])$/) { - # relative date - my ($sign, $amount, $unit, $date) = ($1, $2, lc $3, time); - my ($sec, $min, $hour, $mday, $month, $year, $wday) = localtime($date); - $amount = -$amount if $sign && $sign eq '+'; - if ($unit eq 'w') { - # convert weeks to days - $amount = 7*$amount + $wday; - $unit = 'd'; - } - if ($unit eq 'd') { - $date -= $sec + 60*$min + 3600*$hour + 24*3600*$amount; - return $date; - } - elsif ($unit eq 'y') { - return str2time(sprintf("%4d-01-01 00:00:00", $year+1900-$amount)); - } - elsif ($unit eq 'm') { - $month -= $amount; - while ($month<0) { $year--; $month += 12; } - return str2time(sprintf("%4d-%02d-01 00:00:00", $year+1900, $month+1)); - } - elsif ($unit eq 'h') { - # Special case 0h for 'beginning of this hour' - if ($amount == 0) { - $date -= $sec + 60*$min; - } else { - $date -= 3600*$amount; - } - return $date; - } - return undef; - } - return str2time($str); -} - -sub triage_reports { - my ($vars, $filter) = @_; - my $dbh = Bugzilla->dbh; - my $input = Bugzilla->input_params; - my $user = Bugzilla->user; - - if (exists $input->{'action'} && $input->{'action'} eq 'run' && $input->{'product'}) { - - # load product and components from input - - my $product = Bugzilla::Product->new({ name => $input->{'product'} }) - || ThrowUserError('invalid_object', { object => 'Product', value => $input->{'product'} }); - - my @component_ids; - if ($input->{'component'} ne '') { - my $ra_components = ref($input->{'component'}) - ? $input->{'component'} : [ $input->{'component'} ]; - foreach my $component_name (@$ra_components) { - my $component = Bugzilla::Component->new({ name => $component_name, product => $product }) - || ThrowUserError('invalid_object', { object => 'Component', value => $component_name }); - push @component_ids, $component->id; - } - } - - # determine which comment filters to run - - my $filter_commenter = $input->{'filter_commenter'}; - my $filter_commenter_on = $input->{'commenter'}; - my $filter_last = $input->{'filter_last'}; - my $filter_last_period = $input->{'last'}; - - if (!$filter_commenter || $filter_last) { - $filter_commenter = '1'; - $filter_commenter_on = 'reporter'; - } - - my $filter_commenter_id; - if ($filter_commenter && $filter_commenter_on eq 'is') { - Bugzilla::User::match_field({ 'commenter_is' => {'type' => 'single'} }); - my $user = Bugzilla::User->new({ name => $input->{'commenter_is'} }) - || ThrowUserError('invalid_object', { object => 'User', value => $input->{'commenter_is'} }); - $filter_commenter_id = $user ? $user->id : 0; - } - - my $filter_last_time; - if ($filter_last) { - if ($filter_last_period eq 'is') { - $filter_last_period = -1; - $filter_last_time = str2time($input->{'last_is'} . " 00:00:00") || 0; - } else { - detaint_natural($filter_last_period); - $filter_last_period = 14 if $filter_last_period < 14; - } - } - - # form sql queries - - my $now = (time); - my $bugs_sql = " - SELECT bug_id, short_desc, reporter, creation_ts - FROM bugs - WHERE product_id = ? - AND bug_status = 'UNCONFIRMED'"; - if (@component_ids) { - $bugs_sql .= " AND component_id IN (" . join(',', @component_ids) . ")"; - } - $bugs_sql .= " - ORDER BY creation_ts - "; - - my $comment_count_sql = " - SELECT COUNT(*) - FROM longdescs - WHERE bug_id = ? - "; - - my $comment_sql = " - SELECT who, bug_when, type, thetext, extra_data - FROM longdescs - WHERE bug_id = ? - "; - if (!Bugzilla->user->is_insider) { - $comment_sql .= " AND isprivate = 0 "; - } - $comment_sql .= " - ORDER BY bug_when DESC - LIMIT 1 - "; - - my $attach_sql = " - SELECT description, isprivate - FROM attachments - WHERE attach_id = ? - "; - - # work on an initial list of bugs - - my $list = $dbh->selectall_arrayref($bugs_sql, undef, $product->id); - my @bugs; - - foreach my $entry (@$list) { - my ($bug_id, $summary, $reporter_id, $creation_ts) = @$entry; - - next unless $user->can_see_bug($bug_id); - - # get last comment information - - my ($comment_count) = $dbh->selectrow_array($comment_count_sql, undef, $bug_id); - my ($commenter_id, $comment_ts, $type, $comment, $extra) - = $dbh->selectrow_array($comment_sql, undef, $bug_id); - my $commenter = 0; - - # apply selected filters - - if ($filter_commenter) { - next if $comment_count <= 1; - - if ($filter_commenter_on eq 'reporter') { - next if $commenter_id != $reporter_id; - - } elsif ($filter_commenter_on eq 'noconfirm') { - $commenter = Bugzilla::User->new($commenter_id); - next if $commenter_id != $reporter_id - || $commenter->in_group('canconfirm'); - - } elsif ($filter_commenter_on eq 'is') { - next if $commenter_id != $filter_commenter_id; - } - } else { - $input->{'commenter'} = ''; - $input->{'commenter_is'} = ''; - } - - if ($filter_last) { - my $comment_time = str2time($comment_ts) - or next; - if ($filter_last_period == -1) { - next if $comment_time >= $filter_last_time; - } else { - next if $now - $comment_time <= 60 * 60 * 24 * $filter_last_period; - } - } else { - $input->{'last'} = ''; - $input->{'last_is'} = ''; - } - - # get data for attachment comments - - if ($comment eq '' && $type == CMT_ATTACHMENT_CREATED) { - my ($description, $is_private) = $dbh->selectrow_array($attach_sql, undef, $extra); - next if $is_private && !Bugzilla->user->is_insider; - $comment = "(Attachment) " . $description; - } - - # truncate long comments - - if (length($comment) > 80) { - $comment = substr($comment, 0, 80) . '...'; - } - - # build bug hash for template - - my $bug = {}; - $bug->{id} = $bug_id; - $bug->{summary} = $summary; - $bug->{reporter} = Bugzilla::User->new($reporter_id); - $bug->{creation_ts} = $creation_ts; - $bug->{commenter} = $commenter || Bugzilla::User->new($commenter_id); - $bug->{comment_ts} = $comment_ts; - $bug->{comment} = $comment; - $bug->{comment_count} = $comment_count; - push @bugs, $bug; - } - - @bugs = sort { $b->{comment_ts} cmp $a->{comment_ts} } @bugs; - - $vars->{bugs} = \@bugs; - } else { - $input->{action} = ''; - } - - if (!$input->{filter_commenter} && !$input->{filter_last}) { - $input->{filter_commenter} = 1; - } - - $vars->{'input'} = $input; -} - -sub group_admins_report { - my ($vars) = @_; - my $dbh = Bugzilla->dbh; - my $user = Bugzilla->user; - - ($user->in_group('editusers') || $user->in_group('infrasec')) - || ThrowUserError('auth_failure', { group => 'editusers', - action => 'run', - object => 'group_admins' }); - - my $query = " - SELECT groups.name, " . - $dbh->sql_group_concat('profiles.login_name', "','", 1) . " - FROM groups - LEFT JOIN user_group_map - ON user_group_map.group_id = groups.id - AND user_group_map.isbless = 1 - AND user_group_map.grant_type = 0 - LEFT JOIN profiles - ON user_group_map.user_id = profiles.userid - WHERE groups.isbuggroup = 1 - GROUP BY groups.name"; - - my @groups; - foreach my $group (@{ $dbh->selectall_arrayref($query) }) { - my @admins; - if ($group->[1]) { - foreach my $admin (split(/,/, $group->[1])) { - push(@admins, Bugzilla::User->new({ name => $admin })); - } - } - push(@groups, { name => $group->[0], admins => \@admins }); - } - - $vars->{'groups'} = \@groups; -} - -sub group_membership_report { - my ($page, $vars) = @_; - my $dbh = Bugzilla->dbh; - my $user = Bugzilla->user; - my $cgi = Bugzilla->cgi; - - ($user->in_group('editusers') || $user->in_group('infrasec')) - || ThrowUserError('auth_failure', { group => 'editusers', - action => 'run', - object => 'group_admins' }); - - my $who = $cgi->param('who'); - if (!defined($who) || $who eq '') { - if ($page eq 'group_membership.txt') { - print $cgi->redirect("page.cgi?id=group_membership.html&output=txt"); - exit; - } - $vars->{'output'} = $cgi->param('output'); - return; - } - - Bugzilla::User::match_field({ 'who' => {'type' => 'multi'} }); - $who = Bugzilla->input_params->{'who'}; - $who = ref($who) ? $who : [ $who ]; - - my @users; - foreach my $login (@$who) { - my $u = Bugzilla::User->new(login_to_id($login, 1)); - - # this is lifted from $user->groups() - # we need to show which groups are direct and which are inherited - - my $groups_to_check = $dbh->selectcol_arrayref( - q{SELECT DISTINCT group_id - FROM user_group_map - WHERE user_id = ? AND isbless = 0}, undef, $u->id); - - my $rows = $dbh->selectall_arrayref( - "SELECT DISTINCT grantor_id, member_id - FROM group_group_map - WHERE grant_type = " . GROUP_MEMBERSHIP); - - my %group_membership; - foreach my $row (@$rows) { - my ($grantor_id, $member_id) = @$row; - push (@{ $group_membership{$member_id} }, $grantor_id); - } - - my %checked_groups; - my %direct_groups; - my %indirect_groups; - my %groups; - - foreach my $member_id (@$groups_to_check) { - $direct_groups{$member_id} = 1; - } - - while (scalar(@$groups_to_check) > 0) { - my $member_id = shift @$groups_to_check; - if (!$checked_groups{$member_id}) { - $checked_groups{$member_id} = 1; - my $members = $group_membership{$member_id}; - my @new_to_check = grep(!$checked_groups{$_}, @$members); - push(@$groups_to_check, @new_to_check); - foreach my $id (@new_to_check) { - $indirect_groups{$id} = $member_id; - } - $groups{$member_id} = 1; - } - } - - my @groups; - my $ra_groups = Bugzilla::Group->new_from_list([keys %groups]); - foreach my $group (@$ra_groups) { - my $via; - if ($direct_groups{$group->id}) { - $via = ''; - } else { - foreach my $g (@$ra_groups) { - if ($g->id == $indirect_groups{$group->id}) { - $via = $g->name; - last; - } - } - } - push @groups, { - name => $group->name, - desc => $group->description, - via => $via, - }; - } - - push @users, { - user => $u, - groups => \@groups, - }; - } - - $vars->{'who'} = $who; - $vars->{'users'} = \@users; -} - -sub group_members_report { - my ($vars) = @_; - my $dbh = Bugzilla->dbh; - my $user = Bugzilla->user; - my $cgi = Bugzilla->cgi; - - ($user->in_group('editusers') || $user->in_group('infrasec')) - || ThrowUserError('auth_failure', { group => 'editusers', - action => 'run', - object => 'group_admins' }); - - my $include_disabled = $cgi->param('include_disabled') ? 1 : 0; - $vars->{'include_disabled'} = $include_disabled; - - # don't allow all groups, to avoid putting pain on the servers - my @group_names = - sort - grep { !/^(?:bz_.+|canconfirm|editbugs|everyone)$/ } - map { lc($_->name) } - Bugzilla::Group->get_all; - unshift(@group_names, ''); - $vars->{'groups'} = \@group_names; - - # load selected group - my $group = lc(trim($cgi->param('group') || '')); - $group = '' unless grep { $_ eq $group } @group_names; - return if $group eq ''; - my $group_obj = Bugzilla::Group->new({ name => $group }); - $vars->{'group'} = $group; - - # direct members - my @types = ( - { - name => 'direct', - members => _filter_userlist($group_obj->members_direct, $include_disabled), - }, - ); - - # indirect members, by group - foreach my $member_group (sort @{ $group_obj->grant_direct(GROUP_MEMBERSHIP) }) { - push @types, { - name => $member_group->name, - members => _filter_userlist($member_group->members_direct, $include_disabled), - }, - } - - # make it easy for the template to detect an empty group - my $has_members = 0; - foreach my $type (@types) { - $has_members += scalar(@{ $type->{members} }); - last if $has_members; - } - @types = () unless $has_members; - - if (@types) { - # add last-login - my $user_ids = join(',', map { map { $_->id } @{ $_->{members} } } @types); - my $tokens = $dbh->selectall_hashref(" - SELECT profiles.userid, - (SELECT DATEDIFF(curdate(), logincookies.lastused) lastseen - FROM logincookies - WHERE logincookies.userid = profiles.userid - ORDER BY lastused DESC - LIMIT 1) lastseen - FROM profiles - WHERE userid IN ($user_ids)", - 'userid'); - foreach my $type (@types) { - foreach my $member (@{ $type->{members} }) { - $member->{lastseen} = - defined $tokens->{$member->id}->{lastseen} - ? $tokens->{$member->id}->{lastseen} - : '>' . MAX_LOGINCOOKIE_AGE; - } - } - } - - $vars->{'types'} = \@types; -} - -sub _filter_userlist { - my ($list, $include_disabled) = @_; - $list = [ grep { $_->is_enabled } @$list ] unless $include_disabled; - return [ sort { lc($a->identity) cmp lc($b->identity) } @$list ]; -} - -sub email_queue_report { - my ($vars, $filter) = @_; - my $dbh = Bugzilla->dbh; - my $user = Bugzilla->user; - - $user->in_group('admin') || $user->in_group('infra') - || ThrowUserError('auth_failure', { group => 'admin', - action => 'run', - object => 'email_queue' }); - - my $query = " - SELECT j.jobid, - j.insert_time, - j.run_after AS run_time, - COUNT(e.jobid) AS error_count, - MAX(e.error_time) AS error_time, - e.message AS error_message - FROM ts_job j - LEFT JOIN ts_error e ON e.jobid = j.jobid - GROUP BY j.jobid - ORDER BY j.run_after"; - - $vars->{'jobs'} = $dbh->selectall_arrayref($query, { Slice => {} }); - $vars->{'now'} = (time); -} - -sub release_tracking_report { - my ($vars) = @_; - my $dbh = Bugzilla->dbh; - my $input = Bugzilla->input_params; - my $user = Bugzilla->user; - - my @flag_names = qw( - approval-mozilla-release - approval-mozilla-beta - approval-mozilla-aurora - approval-mozilla-central - approval-comm-release - approval-comm-beta - approval-comm-aurora - approval-calendar-release - approval-calendar-beta - approval-calendar-aurora - approval-mozilla-esr10 - ); - - my @flags_json; - my @fields_json; - my @products_json; - - # - # tracking flags - # - - my $all_products = $user->get_selectable_products; - my @usable_products; - - # build list of flags and their matching products - - my @invalid_flag_names; - foreach my $flag_name (@flag_names) { - # grab all matching flag_types - my @flag_types = @{Bugzilla::FlagType::match({ name => $flag_name, is_active => 1 })}; - - # remove invalid flags - if (!@flag_types) { - push @invalid_flag_names, $flag_name; - next; - } - - # we need a list of products, based on inclusions/exclusions - my @products; - my %flag_types; - foreach my $flag_type (@flag_types) { - $flag_types{$flag_type->name} = $flag_type->id; - my $has_all = 0; - my @exclusion_ids; - my @inclusion_ids; - foreach my $flag_type (@flag_types) { - if (scalar keys %{$flag_type->inclusions}) { - my $inclusions = $flag_type->inclusions; - foreach my $key (keys %$inclusions) { - push @inclusion_ids, ($inclusions->{$key} =~ /^(\d+)/); - } - } elsif (scalar keys %{$flag_type->exclusions}) { - my $exclusions = $flag_type->exclusions; - foreach my $key (keys %$exclusions) { - push @exclusion_ids, ($exclusions->{$key} =~ /^(\d+)/); - } - } else { - $has_all = 1; - last; - } - } - - if ($has_all) { - push @products, @$all_products; - } elsif (scalar @exclusion_ids) { - push @products, @$all_products; - foreach my $exclude_id (uniq @exclusion_ids) { - @products = grep { $_->id != $exclude_id } @products; - } - } else { - foreach my $include_id (uniq @inclusion_ids) { - push @products, grep { $_->id == $include_id } @$all_products; - } - } - } - @products = uniq @products; - push @usable_products, @products; - my @product_ids = map { $_->id } sort { lc($a->name) cmp lc($b->name) } @products; - - push @flags_json, { - name => $flag_name, - id => $flag_types{$flag_name} || 0, - products => \@product_ids, - fields => [], - }; - } - foreach my $flag_name (@invalid_flag_names) { - @flag_names = grep { $_ ne $flag_name } @flag_names; - } - @usable_products = uniq @usable_products; - - # build a list of tracking flags for each product - # also build the list of all fields - - my @unlink_products; - foreach my $product (@usable_products) { - my @fields = - grep { _is_active_status_field($_->name) } - Bugzilla->active_custom_fields({ product => $product }); - my @field_ids = map { $_->id } @fields; - if (!scalar @fields) { - push @unlink_products, $product; - next; - } - - # product - push @products_json, { - name => $product->name, - id => $product->id, - fields => \@field_ids, - }; - - # add fields to flags - foreach my $rh (@flags_json) { - if (grep { $_ eq $product->id } @{$rh->{products}}) { - push @{$rh->{fields}}, @field_ids; - } - } - - # add fields to fields_json - foreach my $field (@fields) { - my $existing = 0; - foreach my $rh (@fields_json) { - if ($rh->{id} == $field->id) { - $existing = 1; - last; - } - } - if (!$existing) { - push @fields_json, { - name => $field->name, - id => $field->id, - }; - } - } - } - foreach my $rh (@flags_json) { - my @fields = uniq @{$rh->{fields}}; - $rh->{fields} = \@fields; - } - - # remove products which aren't linked with status fields - - foreach my $rh (@flags_json) { - my @product_ids; - foreach my $id (@{$rh->{products}}) { - unless (grep { $_->id == $id } @unlink_products) { - push @product_ids, $id; - } - $rh->{products} = \@product_ids; - } - } - - # - # rapid release dates - # - - my @ranges; - my $start_date = _string_to_datetime('2011-08-16'); - my $end_date = $start_date->clone->add(weeks => 6)->add(days => -1); - my $now_date = _string_to_datetime('2012-11-19'); - - while ($start_date <= $now_date) { - unshift @ranges, { - value => sprintf("%s-%s", $start_date->ymd(''), $end_date->ymd('')), - label => sprintf("%s and %s", $start_date->ymd('-'), $end_date->ymd('-')), - }; - - $start_date = $end_date->clone;; - $start_date->add(days => 1); - $end_date->add(weeks => 6); - } - - # 2012-11-20 - 2013-01-06 was a 7 week release cycle instead of 6 - $start_date = _string_to_datetime('2012-11-20'); - $end_date = $start_date->clone->add(weeks => 7)->add(days => -1); - unshift @ranges, { - value => sprintf("%s-%s", $start_date->ymd(''), $end_date->ymd('')), - label => sprintf("%s and %s", $start_date->ymd('-'), $end_date->ymd('-')), - }; - - # Back on track with 6 week releases - $start_date = _string_to_datetime('2013-01-08'); - $end_date = $start_date->clone->add(weeks => 6)->add(days => -1); - $now_date = _time_to_datetime((time)); - - while ($start_date <= $now_date) { - unshift @ranges, { - value => sprintf("%s-%s", $start_date->ymd(''), $end_date->ymd('')), - label => sprintf("%s and %s", $start_date->ymd('-'), $end_date->ymd('-')), - }; - - $start_date = $end_date->clone;; - $start_date->add(days => 1); - $end_date->add(weeks => 6); - } - - push @ranges, { - value => '*', - label => 'Anytime', - }; - - # - # run report - # - - if ($input->{q} && !$input->{edit}) { - my $q = _parse_query($input->{q}); - - my @where; - my @params; - my $query = " - SELECT DISTINCT b.bug_id - FROM bugs b - INNER JOIN flags f ON f.bug_id = b.bug_id "; - - if ($q->{start_date}) { - $query .= "INNER JOIN bugs_activity a ON a.bug_id = b.bug_id "; - } - - $query .= "WHERE "; - - if ($q->{start_date}) { - push @where, "(a.fieldid = ?)"; - push @params, $q->{field_id}; - - push @where, "(a.bug_when >= ?)"; - push @params, $q->{start_date} . ' 00:00:00'; - push @where, "(a.bug_when < ?)"; - push @params, $q->{end_date} . ' 00:00:00'; - - push @where, "(a.added LIKE ?)"; - push @params, '%' . $q->{flag_name} . $q->{flag_status} . '%'; - } - - push @where, "(f.type_id IN (SELECT id FROM flagtypes WHERE name = ?))"; - push @params, $q->{flag_name}; - - push @where, "(f.status = ?)"; - push @params, $q->{flag_status}; - - if ($q->{product_id}) { - push @where, "(b.product_id = ?)"; - push @params, $q->{product_id}; - } - - if (scalar @{$q->{fields}}) { - my @fields; - foreach my $field (@{$q->{fields}}) { - push @fields, - "(" . - ($field->{value} eq '+' ? '' : '!') . - "(b.".$field->{name}." IN ('fixed','verified'))" . - ") "; - } - my $join = uc $q->{join}; - push @where, '(' . join(" $join ", @fields) . ')'; - } - - $query .= join("\nAND ", @where); - - if ($input->{debug}) { - print "Content-Type: text/plain\n\n"; - $query =~ s/\?/\000/g; - foreach my $param (@params) { - $query =~ s/\000/$param/; - } - print "$query\n"; - exit; - } - - my $bugs = $dbh->selectcol_arrayref($query, undef, @params); - push @$bugs, 0 unless @$bugs; - - my $urlbase = correct_urlbase(); - my $cgi = Bugzilla->cgi; - print $cgi->redirect( - -url => "${urlbase}buglist.cgi?bug_id=" . join(',', @$bugs) - ); - exit; - } - - # - # set template vars - # - - my $json = JSON->new(); - if (0) { - # debugging - $json->shrink(0); - $json->canonical(1); - $vars->{flags_json} = $json->pretty->encode(\@flags_json); - $vars->{products_json} = $json->pretty->encode(\@products_json); - $vars->{fields_json} = $json->pretty->encode(\@fields_json); - } else { - $json->shrink(1); - $vars->{flags_json} = $json->encode(\@flags_json); - $vars->{products_json} = $json->encode(\@products_json); - $vars->{fields_json} = $json->encode(\@fields_json); - } - - $vars->{flag_names} = \@flag_names; - $vars->{ranges} = \@ranges; - $vars->{default_query} = $input->{q}; - foreach my $field (qw(product flags range)) { - $vars->{$field} = $input->{$field}; - } -} - -sub _parse_query { - my $q = shift; - my @query = split(/:/, $q); - my $query; - - # field_id for flag changes - $query->{field_id} = get_field_id('flagtypes.name'); - - # flag_name - my $flag_name = shift @query; - @{Bugzilla::FlagType::match({ name => $flag_name, is_active => 1 })} - or ThrowUserError('report_invalid_parameter', { name => 'flag_name' }); - trick_taint($flag_name); - $query->{flag_name} = $flag_name; - - # flag_status - my $flag_status = shift @query; - $flag_status =~ /^([\?\-\+])$/ - or ThrowUserError('report_invalid_parameter', { name => 'flag_status' }); - $query->{flag_status} = $1; - - # date_range -> from_ymd to_ymd - my $date_range = shift @query; - if ($date_range ne '*') { - $date_range =~ /^(\d\d\d\d)(\d\d)(\d\d)-(\d\d\d\d)(\d\d)(\d\d)$/ - or ThrowUserError('report_invalid_parameter', { name => 'date_range' }); - $query->{start_date} = "$1-$2-$3"; - $query->{end_date} = "$4-$5-$6"; - } - - # product_id - my $product_id = shift @query; - $product_id =~ /^(\d+)$/ - or ThrowUserError('report_invalid_parameter', { name => 'product_id' }); - $query->{product_id} = $1; - - # join - my $join = shift @query; - $join =~ /^(and|or)$/ - or ThrowUserError('report_invalid_parameter', { name => 'join' }); - $query->{join} = $1; - - # fields - my @fields; - foreach my $field (@query) { - $field =~ /^(\d+)([\-\+])$/ - or ThrowUserError('report_invalid_parameter', { name => 'fields' }); - my ($id, $value) = ($1, $2); - my $field_obj = Bugzilla::Field->new($id) - or ThrowUserError('report_invalid_parameter', { name => 'field_id' }); - push @fields, { id => $id, value => $value, name => $field_obj->name }; - } - $query->{fields} = \@fields; - - return $query; -} - -sub _is_active_status_field { - my ($field_name) = @_; - if ($field_name =~ /^cf_status/) { - return !grep { $field_name eq $_ } @$cf_disabled_flags - } - return 0; -} - -1; |