]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 1546717 - Add the ability to search on count gt/lt/eq
authorKohei Yoshino <kohei.yoshino@gmail.com>
Tue, 16 Jul 2019 22:01:16 +0000 (18:01 -0400)
committerGitHub <noreply@github.com>
Tue, 16 Jul 2019 22:01:16 +0000 (18:01 -0400)
13 files changed:
Bugzilla/Bug.pm
Bugzilla/Field.pm
Bugzilla/Search.pm
Bugzilla/Search/ClauseGroup.pm
Bugzilla/WebService/Bug.pm
bugzilla.dtd
docs/en/rst/api/core/v1/bug.rst
extensions/BMO/Extension.pm
extensions/BMO/template/en/default/hook/global/field-descs-end.none.tmpl [deleted file]
extensions/BugmailFilter/lib/Constants.pm
template/en/default/global/field-descs.none.tmpl
template/en/default/list/table.html.tmpl
template/en/default/pages/bugzilla.dtd.tmpl

index 30c9462d9dead6a348acfc45b69242c48356f2e2..8ab96c1ee76f7c0323fe16b2f82382340bd60dc2 100644 (file)
@@ -4053,6 +4053,27 @@ sub comment_count {
   );
 }
 
+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;
index 5aedef21ba0251a222170cf30b4e37b78b2c9e1e..7d0351b0621cfddfd821b982df5768e4665ad25a 100644 (file)
@@ -242,6 +242,13 @@ use constant DEFAULT_FIELDS => (
     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',
@@ -297,38 +304,82 @@ use constant DEFAULT_FIELDS => (
     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',
@@ -337,6 +388,13 @@ use constant DEFAULT_FIELDS => (
     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'},
index eb58602c23575439c313decde68377625c7101ab..e3e2a06e25e817bae1a252ea9e78a74aca005423 100644 (file)
@@ -234,6 +234,16 @@ use constant MULTI_SELECT_OVERRIDE => {
   _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
@@ -273,15 +283,6 @@ use constant OPERATOR_FIELD_OVERRIDE => {
     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,
@@ -293,9 +294,29 @@ use constant OPERATOR_FIELD_OVERRIDE => {
   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,},
@@ -490,11 +511,20 @@ sub COLUMN_JOINS {
         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',
@@ -572,15 +602,23 @@ sub COLUMNS {
       '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',
@@ -684,15 +722,24 @@ sub REPORT_COLUMNS {
 # 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
 );
 
 ###############
@@ -2759,18 +2806,38 @@ sub _content_matches {
   $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 {
@@ -3173,6 +3240,11 @@ sub _multiselect_table {
     $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';
@@ -3285,6 +3357,16 @@ sub _multiselect_isempty {
       };
     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"
index f7f7b338bb8bb0fcc0c04185dedaf10416bf4e24..a040442f542f12c7cc072c4f2878dbd08f0379c3 100644 (file)
@@ -18,12 +18,20 @@ use Bugzilla::Search::Condition qw(condition);
 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 {
index 107313db602072f369f69ed11af9e1564abb5967..a300bfaa59eaa8da0c9f3f766f52ca979ce7624d 100644 (file)
@@ -1659,6 +1659,14 @@ sub _bug_to_hash {
     $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;
 }
 
@@ -2784,6 +2792,10 @@ members. To see the keys included in the user detail hash, see below.
 
 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
@@ -2796,6 +2808,17 @@ in C<include_fields>.
 
 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.
@@ -3194,9 +3217,9 @@ and all custom fields.
 =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
 
index 91678849d6ce66c19f55e37f7e23fb85a10eb4f5..88ebab14dc5137ae5f68e030e098afe0e9878052 100644 (file)
@@ -7,7 +7,7 @@
 >
 <!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?)?,
@@ -50,6 +50,7 @@
 <!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)>
index 1596e3cd0f1ed591518906bc257b3761b06ab85d..f0990548337b731a878760d7b1158636df5ff781 100644 (file)
@@ -256,6 +256,11 @@ attachments          array   Each array item is an Attachment object. See
                              :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.
index aeeb5d07ada3b9a8837ce9221552aa4bc29754eb..0f6a16134dda2f3e7d9e8afde6df5b7c408efeec 100644 (file)
@@ -2305,15 +2305,6 @@ sub _detect_content_type {
 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,
diff --git a/extensions/BMO/template/en/default/hook/global/field-descs-end.none.tmpl b/extensions/BMO/template/en/default/hook/global/field-descs-end.none.tmpl
deleted file mode 100644 (file)
index 8c543b3..0000000
+++ /dev/null
@@ -1,12 +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.
-  #%]
-
-[% IF in_template_var %]
-  [% vars.field_descs.cc_count = "CC Count" %]
-  [% vars.field_descs.dupe_count = "Duplicate Count" %]
-[% END %]
index bed8a37d35a4bf2939c8a55bed077d930420b2dc..655cd7fd6670440ec3a167068d76248be25c5e57 100644 (file)
@@ -35,18 +35,26 @@ use constant FAKE_FIELD_NAMES => [
 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
index 4a7bf48f6a17be09846956e09fcbc1ee24ea8959..e1186bb12667a527f6ae5df9599c10ab618565e8 100644 (file)
@@ -99,6 +99,7 @@ if ( $stash->get("in_template_var") ) {
         "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",
@@ -107,6 +108,7 @@ if ( $stash->get("in_template_var") ) {
         "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",
@@ -115,6 +117,7 @@ if ( $stash->get("in_template_var") ) {
         "bug_type"                => "Type",
         "changeddate"             => "Updated",
         "cc"                      => "CC",
+        "cc_count"                => "Number of CC",
         "classification"          => "Classification",
         "cclist_accessible"       => "CC list accessible",
         "commenter"               => "Commenter",
@@ -126,11 +129,15 @@ if ( $stash->get("in_template_var") ) {
         "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",
@@ -146,7 +153,9 @@ if ( $stash->get("in_template_var") ) {
         "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",
index 1d4e6f93ccf57bd1eb500d7c6df3b7ddc67aff95..78cef0f9489e08b46619e000fb5ae45140323197 100644 (file)
     "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 } ,
index e9619f78872b0a50dda4b254a151e320acd342a9..59bc954fc3dd38a812c20bb57e04cbd3ed7a8520 100644 (file)
@@ -52,6 +52,7 @@
                 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)>