);
}
+sub counts {
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $extra = !Bugzilla->user->is_insider ? 'AND isprivate = 0' : '';
+ my $id = $self->id;
+
+ $self->{counts} ||= $dbh->selectrow_hashref("SELECT
+ (SELECT COUNT(*) FROM attachments WHERE bug_id = ? $extra) AS attachments,
+ (SELECT COUNT(*) FROM cc WHERE bug_id = ?) AS cc,
+ (SELECT COUNT(*) FROM longdescs WHERE bug_id = ? $extra) AS comments,
+ (SELECT COUNT(*) FROM keywords WHERE bug_id = ?) AS keywords,
+ (SELECT COUNT(*) FROM dependencies WHERE blocked = ?) AS blocks,
+ (SELECT COUNT(*) FROM dependencies WHERE dependson = ?) AS depends_on,
+ (SELECT COUNT(*) FROM regressions WHERE regressed_by = ?) AS regressed_by,
+ (SELECT COUNT(*) FROM regressions WHERE regresses = ?) AS regressions,
+ (SELECT COUNT(*) FROM duplicates WHERE dupe_of = ?) AS duplicates
+ ", undef, $id, $id, $id, $id, $id, $id, $id, $id, $id);
+
+ return $self->{counts};
+}
+
# This is needed by xt/search.t.
sub percentage_complete {
my $self = shift;
type => FIELD_TYPE_KEYWORDS,
buglist => 1
},
+ {
+ name => 'keywords.count',
+ desc => 'Number of Keywords',
+ type => FIELD_TYPE_INTEGER,
+ buglist => 1,
+ is_numeric => 1,
+ },
{
name => 'resolution',
desc => 'Resolution',
type => FIELD_TYPE_USERS,
in_new_bugmail => 1,
},
+ {
+ name => 'cc_count', # Originated in BMO extension
+ desc => 'Number of CC',
+ type => FIELD_TYPE_INTEGER,
+ buglist => 1,
+ is_numeric => 1,
+ },
{
name => 'dependson',
desc => 'Depends on',
type => FIELD_TYPE_BUG_LIST,
in_new_bugmail => 1,
- is_numeric => 1,
buglist => 1
},
+ {
+ name => 'dependson.count',
+ desc => 'Number of Depends on',
+ type => FIELD_TYPE_INTEGER,
+ buglist => 1,
+ is_numeric => 1,
+ },
{
name => 'blocked',
desc => 'Blocks',
type => FIELD_TYPE_BUG_LIST,
in_new_bugmail => 1,
- is_numeric => 1,
buglist => 1
},
+ {
+ name => 'blocked.count',
+ desc => 'Number of Blocks',
+ type => FIELD_TYPE_INTEGER,
+ buglist => 1,
+ is_numeric => 1,
+ },
{
name => 'regressed_by',
desc => 'Regressed by',
type => FIELD_TYPE_BUG_LIST,
in_new_bugmail => 1,
- is_numeric => 1,
buglist => 1
},
+ {
+ name => 'regressed_by.count',
+ desc => 'Number of Regressed by',
+ type => FIELD_TYPE_INTEGER,
+ buglist => 1,
+ is_numeric => 1,
+ },
{
name => 'regresses',
desc => 'Regressions',
type => FIELD_TYPE_BUG_LIST,
in_new_bugmail => 1,
- is_numeric => 1,
buglist => 1
},
+ {
+ name => 'regresses.count',
+ desc => 'Number of Regressions',
+ type => FIELD_TYPE_INTEGER,
+ buglist => 1,
+ is_numeric => 1,
+ },
+ {
+ name => 'dupe_count', # Originated in BMO extension
+ desc => 'Number of Duplicates',
+ type => FIELD_TYPE_INTEGER,
+ buglist => 1,
+ is_numeric => 1,
+ },
+ {
+ name => 'duplicates',
+ desc => 'Duplicates',
+ type => FIELD_TYPE_BUG_ID,
+ buglist => 1,
+ },
{
name => 'assignee_last_login',
buglist => 1
},
+ {
+ name => 'attachments.count',
+ desc => 'Number of Attachments',
+ type => FIELD_TYPE_INTEGER,
+ buglist => 1,
+ is_numeric => 1,
+ },
{name => 'attachments.description', desc => 'Attachment description'},
{name => 'attachments.filename', desc => 'Attachment filename'},
{name => 'attachments.mimetype', desc => 'Attachment mime type'},
_non_changed => \&_multiselect_nonchanged,
};
+use constant RELATION_COUNT_OVERRIDE => {
+ changedby => \&_relation_count_changed,
+ everchanged => \&_relation_count_changed,
+ changedbefore => \&_relation_count_changed,
+ changedafter => \&_relation_count_changed,
+ changedfrom => \&_invalid_combination,
+ changedto => \&_invalid_combination,
+ _default => \&_relation_count_default,
+};
+
use constant OPERATOR_FIELD_OVERRIDE => {
# User fields
changedafter => \&_long_desc_changedbefore_after,
_non_changed => \&_long_desc_nonchanged,
},
- 'longdescs.count' => {
- changedby => \&_long_desc_changedby,
- everchanged => \&_long_desc_everchanged,
- changedbefore => \&_long_desc_changedbefore_after,
- changedafter => \&_long_desc_changedbefore_after,
- changedfrom => \&_invalid_combination,
- changedto => \&_invalid_combination,
- _default => \&_long_descs_count,
- },
'longdescs.isprivate' => MULTI_SELECT_OVERRIDE,
owner_idle_time => {
greaterthan => \&_owner_idle_time_greater_less,
product => {_non_changed => \&_product_nonchanged,},
regressed_by => MULTI_SELECT_OVERRIDE,
regresses => MULTI_SELECT_OVERRIDE,
+ duplicates => MULTI_SELECT_OVERRIDE,
tag => MULTI_SELECT_OVERRIDE,
comment_tag => MULTI_SELECT_OVERRIDE,
+ # Count Fields
+ 'attachments.count' => RELATION_COUNT_OVERRIDE,
+ 'cc_count' => RELATION_COUNT_OVERRIDE,
+ 'keywords.count' => RELATION_COUNT_OVERRIDE,
+ 'blocked.count' => RELATION_COUNT_OVERRIDE,
+ 'dependson.count' => RELATION_COUNT_OVERRIDE,
+ 'regressed_by.count' => RELATION_COUNT_OVERRIDE,
+ 'regresses.count' => RELATION_COUNT_OVERRIDE,
+ 'dupe_count' => RELATION_COUNT_OVERRIDE,
+ 'longdescs.count' => {
+ changedby => \&_long_desc_changedby,
+ everchanged => \&_long_desc_everchanged,
+ changedbefore => \&_long_desc_changedbefore_after,
+ changedafter => \&_long_desc_changedbefore_after,
+ changedfrom => \&_invalid_combination,
+ changedto => \&_invalid_combination,
+ _default => \&_relation_count_default,
+ },
+
# Timetracking Fields
deadline => {_non_changed => \&_deadline},
percentage_complete => {_non_changed => \&_percentage_complete,},
to => 'id',
},
},
- blocked => {table => 'dependencies', to => 'dependson',},
- dependson => {table => 'dependencies', to => 'blocked',},
- regresses => {table => 'regressions', to => 'regressed_by',},
- regressed_by => {table => 'regressions', to => 'regresses',},
- 'longdescs.count' => {table => 'longdescs', join => 'INNER',},
+ 'attachments.count' => {table => 'attachments',},
+ 'cc_count' => {table => 'cc',},
+ 'keywords.count' => {table => 'keywords',},
+ 'longdescs.count' => {table => 'longdescs', join => 'INNER',},
+ 'blocked' => {table => 'dependencies', to => 'dependson',},
+ 'blocked.count' => {table => 'dependencies', to => 'dependson',},
+ 'dependson' => {table => 'dependencies', to => 'blocked',},
+ 'dependson.count' => {table => 'dependencies', to => 'blocked',},
+ 'regressed_by' => {table => 'regressions', to => 'regresses',},
+ 'regressed_by.count' => {table => 'regressions', to => 'regresses',},
+ 'regresses' => {table => 'regressions', to => 'regressed_by',},
+ 'regresses.count' => {table => 'regressions', to => 'regressed_by',},
+ 'dupe_count' => {table => 'duplicates', to => 'dupe_of',},
+ 'duplicates' => {table => 'duplicates', to => 'dupe_of',},
last_visit_ts => {
as => 'bug_user_last_visit',
table => 'bug_user_last_visit',
'DISTINCT ' . $dbh->sql_string_concat('map_flagtypes.name', 'map_flags.status')
),
- 'keywords' => $dbh->sql_group_concat('DISTINCT map_keyworddefs.name'),
+ 'keywords' => $dbh->sql_group_concat('DISTINCT map_keyworddefs.name'),
+ 'blocked' => $dbh->sql_group_concat('DISTINCT map_blocked.blocked'),
+ 'dependson' => $dbh->sql_group_concat('DISTINCT map_dependson.dependson'),
+ 'regressed_by' => $dbh->sql_group_concat('DISTINCT map_regressed_by.regressed_by'),
+ 'regresses' => $dbh->sql_group_concat('DISTINCT map_regresses.regresses'),
+ 'duplicates' => $dbh->sql_group_concat('DISTINCT map_duplicates.dupe'),
+
+ 'attachments.count' => 'COUNT(DISTINCT map_attachments_count.attach_id)',
+ 'cc_count' => 'COUNT(DISTINCT map_cc_count.who)',
+ 'keywords.count' => 'COUNT(DISTINCT map_keywords_count.keywordid)',
+ 'longdescs.count' => 'COUNT(DISTINCT map_longdescs_count.comment_id)',
+ 'blocked.count' => 'COUNT(DISTINCT map_blocked_count.blocked)',
+ 'dependson.count' => 'COUNT(DISTINCT map_dependson_count.dependson)',
+ 'regressed_by.count' => 'COUNT(DISTINCT map_regressed_by_count.regressed_by)',
+ 'regresses.count' => 'COUNT(DISTINCT map_regresses_count.regresses)',
+ 'dupe_count' => 'COUNT(DISTINCT map_dupe_count.dupe)',
- blocked => $dbh->sql_group_concat('DISTINCT map_blocked.blocked'),
- dependson => $dbh->sql_group_concat('DISTINCT map_dependson.dependson'),
-
- regresses => $dbh->sql_group_concat('DISTINCT map_regresses.regresses'),
- regressed_by => $dbh->sql_group_concat('DISTINCT map_regressed_by.regressed_by'),
-
- 'longdescs.count' => 'COUNT(DISTINCT map_longdescs_count.comment_id)',
last_visit_ts => 'bug_user_last_visit.last_visit_ts',
bug_interest_ts => 'bug_interest.modification_time',
assignee_last_login => 'assignee.last_seen_date',
# is here because it *always* goes into the GROUP BY as the first item,
# so it should be skipped when determining extra GROUP BY columns.
use constant GROUP_BY_SKIP => qw(
+ attachments.count
blocked
+ blocked.count
bug_id
+ cc_count
dependson
+ dependson.count
+ dupe_count
+ duplicates
flagtypes.name
keywords
+ keywords.count
longdescs.count
percentage_complete
regressed_by
+ regressed_by.count
regresses
+ regresses.count
);
###############
$self->COLUMNS->{'relevance'}->{name} = $select_term;
}
-sub _long_descs_count {
- my ($self, $args) = @_;
- my ($chart_id, $joins) = @$args{qw(chart_id joins)};
- my $table = "longdescs_count_$chart_id";
- my $extra = $self->_user->is_insider ? "" : "WHERE isprivate = 0";
- my $join = {
- table => "(SELECT bug_id, COUNT(*) AS num"
- . " FROM longdescs $extra GROUP BY bug_id)",
- as => $table,
- };
- push(@$joins, $join);
- $args->{full_field} = "${table}.num";
+sub _relation_count_changed {
+ my ($self, $args) = @_;
+ $args->{field} =~ /^(\w+)\.count$/;
+ $args->{field} = $args->{full_field} = $1;
+ $self->_do_operator_function($args);
+}
+
+sub _relation_count_default {
+ my ($self, $args) = @_;
+ my ($chart_id, $field, $joins) = @$args{qw(chart_id field joins)};
+ my $extra = !$self->_user->is_insider
+ && $field =~ /^(?:attachments|longdescs)\.count$/ ? 'WHERE isprivate = 0' : '';
+ my ($table, $column, $other_column)
+ = $field eq 'attachments.count' ? ('attachments', 'attach_id', 'bug_id')
+ : $field eq 'cc_count' ? ('cc', 'cc', 'bug_id')
+ : $field eq 'keywords.count' ? ('keywords', 'keywords', 'bug_id')
+ : $field eq 'longdescs.count' ? ('longdescs', 'comment_id', 'bug_id')
+ : $field eq 'blocked.count' ? ('dependencies', 'blocked', 'dependson')
+ : $field eq 'dependson.count' ? ('dependencies', 'dependson', 'blocked')
+ : $field eq 'regressed_by.count' ? ('regressions', 'regressed_by', 'regresses')
+ : $field eq 'regresses.count' ? ('regressions', 'regresses', 'regressed_by')
+ : $field eq 'dupe_count' ? ('duplicates', 'dupe', 'dupe_of')
+ : undef;
+ my $alias = "${column}_count_${chart_id}";
+
+ push(@$joins, {
+ table => "(SELECT $other_column AS bug_id, COUNT(*) AS num"
+ . " FROM $table $extra GROUP BY $other_column)",
+ as => $alias,
+ });
+
+ $args->{full_field} = "COALESCE(${alias}.num, 0)";
}
sub _work_time_changedby {
$args->{full_field} = $field;
return "regressions";
}
+ elsif ($field eq 'duplicates') {
+ $args->{_select_field} = 'dupe_of';
+ $args->{full_field} = 'dupe';
+ return "duplicates";
+ }
elsif ($field eq 'longdesc') {
$args->{_extra_where} = " AND isprivate = 0" if !$self->_user->is_insider;
$args->{full_field} = 'thetext';
};
return "regressions_$chart_id.$to IS $not NULL";
}
+ elsif ($field eq 'duplicates') {
+ push @$joins,
+ {
+ table => 'duplicates',
+ as => "duplicates_$chart_id",
+ from => 'bug_id',
+ to => 'dupe_of',
+ };
+ return "duplicates_$chart_id.dupe_of IS $not NULL";
+ }
elsif ($field eq 'longdesc') {
my @extra = ("longdescs_$chart_id.type != " . CMT_HAS_DUPE);
push @extra, "longdescs_$chart_id.isprivate = 0"
use List::MoreUtils qw(uniq);
use constant UNSUPPORTED_FIELDS => qw(
+ attachments.count
+ blocked.count
+ cc_count
classification
commenter
component
+ dependson.count
+ dupe_count
+ keywords.count
longdescs.count
product
owner_idle_time
+ regressed_by.count
+ regresses.count
);
sub new {
$item{'comment_count'} = $self->type('int', $bug->comment_count);
}
+ if (filter_wants $params, 'counts', ['extra']) {
+ $item{'counts'} = {};
+
+ while (my ($key, $value) = each %{$bug->counts}) {
+ $item{'counts'}->{$key} = $self->type('int', $value);
+ }
+ }
+
return \%item;
}
C<string> The name of the current classification the bug is in.
+=item C<comment_count>
+
+C<int> The number of the comments left on this bug.
+
=item C<comments>
C<array> of hashes containing comment details of the bug. See the comments
C<string> The name of the current component of this bug.
+=item C<counts>
+
+B<UNSTABLE>
+
+C<hash> A hash containing the numbers of the items in the following fields:
+C<attachments>, C<cc>, C<comments>, C<keywords>, C<blocks>, C<depends_on>,
+C<regressed_by>, C<regressions> and C<duplicates>.
+
+This is an B<extra> field returned only by specifying C<counts> or C<_extra> in
+C<include_fields>.
+
=item C<creation_time>
C<dateTime> When the bug was created.
=item The C<actual_time> item was added to the C<bugs> return value
in Bugzilla B<4.4>.
-=item The C<attachments>, C<comments>, C<description>, C<duplicates>,
-C<history>, C<regressed_by>, C<regressions>, C<triage_owner> and C<type> fields
-were added in Bugzilla B<6.0>.
+=item The C<attachments>, C<comment_count>, C<comments>, C<counts>,
+C<description>, C<duplicates>, C<history>, C<regressed_by>, C<regressions>,
+C<triage_owner> and C<type> fields were added in Bugzilla B<6.0>.
=back
>
<!ELEMENT bug (bug_id, (alias?, creation_ts, short_desc, delta_ts, reporter_accessible,
cclist_accessible, classification_id, classification, product, component,
- version, rep_platform, op_sys, bug_status, resolution?, dup_id?, see_also*,
+ version, rep_platform, op_sys, bug_status, resolution?, dup_id?, duplicates*, see_also*,
bug_file_loc?, status_whiteboard?, keywords*, bug_type, priority, bug_severity,
target_milestone?, dependson*, blocked*, regressed_by*, regresses*, everconfirmed,
reporter, assigned_to, cc*, (estimated_time, remaining_time, actual_time, deadline?)?,
<!ELEMENT op_sys (#PCDATA)>
<!ELEMENT resolution (#PCDATA)>
<!ELEMENT dup_id (#PCDATA)>
+<!ELEMENT duplicates (#PCDATA)>
<!ELEMENT bug_file_loc (#PCDATA)>
<!ELEMENT short_desc (#PCDATA)>
<!ELEMENT keywords (#PCDATA)>
:ref:`rest_attachments` for details of the object.
comments array Each array item is a Comment object. See
:ref:`rest_comments` for details of the object.
+counts object An object containing the numbers of the items in the
+ following fields: ``attachments``, ``cc``,
+ ``comments``, ``keywords``, ``blocks``,
+ ``depends_on``, ``regressed_by``, ``regressions``
+ and ``duplicates``.
description string The description (initial comment) of the bug.
history array Each array item is a History object. See
:ref:`rest_history` for details of the object.
sub buglist_columns {
my ($self, $args) = @_;
my $columns = $args->{columns};
- $columns->{'cc_count'} = {
- name => '(SELECT COUNT(*) FROM cc WHERE cc.bug_id = bugs.bug_id)',
- title => 'CC Count',
- };
- $columns->{'dupe_count'} = {
- name =>
- '(SELECT COUNT(*) FROM duplicates WHERE duplicates.dupe_of = bugs.bug_id)',
- title => 'Duplicate Count',
- };
$columns->{'attachments.ispatch'} = {
# Return `1` if the bug has any regular patch or external review request,
+++ /dev/null
-[%# 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.
- #%]
-
-[% IF in_template_var %]
- [% vars.field_descs.cc_count = "CC Count" %]
- [% vars.field_descs.dupe_count = "Duplicate Count" %]
-[% END %]
use constant IGNORE_FIELDS => qw(
assignee_last_login
attach_data.thedata
+ attachments.count
attachments.submitter
+ blocked.count
+ cc_count
cf_last_resolved
commenter
comment_tag
creation_ts
days_elapsed
delta_ts
+ dependson.count
+ dupe_count
everconfirmed
+ keywords.count
last_visit_ts
longdesc
longdescs.count
owner_idle_time
+ regressed_by.count
+ regresses.count
reporter
reporter_accessible
setters.login_name
"assigned_to_realname" => "Assignee Real Name",
"assignee_last_login" => "Assignee Last Login Date",
"attach_data.thedata" => "Attachment data",
+ "attachments.count" => "Number of Attachments",
"attachments.description" => "Attachment description",
"attachments.filename" => "Attachment filename",
"attachments.mimetype" => "Attachment mime type",
"attachments.isprivate" => "Attachment is private",
"attachments.submitter" => "Attachment creator",
"blocked" => "Blocks",
+ "blocked.count" => "Number of Blocks",
"bug_file_loc" => "URL",
"bug_group" => "Group",
"bug_id" => "$terms->{Bug} ID",
"bug_type" => "Type",
"changeddate" => "Updated",
"cc" => "CC",
+ "cc_count" => "Number of CC",
"classification" => "Classification",
"cclist_accessible" => "CC list accessible",
"commenter" => "Commenter",
"deadline" => "Deadline",
"delta_ts" => "Updated",
"dependson" => "Depends on",
- "dup_id" => "Duplicate",
+ "dependson.count" => "Number of Depends on",
+ "dup_id" => "Duplicate of",
+ "dupe_count" => "Number of Duplicates",
+ "duplicates" => "Duplicates",
"estimated_time" => "Orig. Est.",
"everconfirmed" => "Ever confirmed",
"flagtypes.name" => "Flags",
"keywords" => "Keywords",
+ "keywords.count" => "Number of Keywords",
"last_visit_ts" => "Last Visit",
"longdesc" => "Comment",
"longdescs.count" => "Number of Comments",
"qa_contact" => "QA Contact",
"qa_contact_realname" => "QA Contact Real Name",
"regressed_by" => "Regressed by",
+ "regressed_by.count" => "Number of Regressed by",
"regresses" => "Regressions",
+ "regresses.count" => "Number of Regressions",
"remaining_time" => "Hours Left",
"rep_platform" => "Hardware",
"reporter" => "Reporter",
"short_short_desc" => { maxlength => 60 , ellipsis => "..." , wrap => 1 } ,
"status_whiteboard" => { maxlength => 0, title => "Whiteboard" , wrap => 1 } ,
"keywords" => { maxlength => 0, wrap => 1 } ,
+ "keywords.count" => { maxlength => 0, title => "# Keywords" },
"dependson" => { maxlength => 0, wrap => 1 } ,
+ "dependson.count" => { maxlength => 0, title => "# Depends on" },
"blocked" => { maxlength => 0, wrap => 1 } ,
+ "blocked.count" => { maxlength => 0, title => "# Blocks" },
"regressed_by" => { maxlength => 0, wrap => 1 } ,
+ "regressed_by.count" => { maxlength => 0, title => "# Regressed by" },
"regresses" => { maxlength => 0, wrap => 1 } ,
+ "regresses.count" => { maxlength => 0, title => "# Regressions" },
+ "dupe_count" => { maxlength => 0, title => "# Duplicates" },
+ "duplicates" => { maxlength => 0, wrap => 1 } ,
+ "attachments.count" => { maxlength => 0, title => "# Attachments" },
+ "cc_count" => { maxlength => 0, title => "# CC" },
"flagtypes.name" => { maxlength => 0, wrap => 1 } ,
"component" => { maxlength => 8 , title => "Comp" } ,
"product" => { maxlength => 8 } ,
bug_status,
resolution?,
dup_id?,
+ duplicates*,
see_also*,
bug_file_loc?,
status_whiteboard?,
<!ELEMENT op_sys (#PCDATA)>
<!ELEMENT resolution (#PCDATA)>
<!ELEMENT dup_id (#PCDATA)>
+<!ELEMENT duplicates (#PCDATA)>
<!ELEMENT bug_file_loc (#PCDATA)>
<!ELEMENT short_desc (#PCDATA)>
<!ELEMENT keywords (#PCDATA)>