summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bugzilla/Search.pm10
-rw-r--r--Bugzilla/User.pm3
-rw-r--r--Bugzilla/WebService/Bug.pm8
-rw-r--r--extensions/Review/Extension.pm229
-rw-r--r--extensions/Review/template/en/default/hook/bug/create/create-after_custom_fields.html.tmpl19
-rw-r--r--extensions/Review/template/en/default/hook/bug/edit-after_custom_fields.html.tmpl50
-rw-r--r--extensions/Review/template/en/default/hook/bug/show-bug_end.xml.tmpl12
-rw-r--r--skins/custom/create_bug.css13
-rw-r--r--template/en/default/bug/create/create.html.tmpl12
-rw-r--r--template/en/default/search/form.html.tmpl6
10 files changed, 287 insertions, 75 deletions
diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm
index 519ebe69f..356af1870 100644
--- a/Bugzilla/Search.pm
+++ b/Bugzilla/Search.pm
@@ -384,6 +384,9 @@ sub SPECIAL_PARSING {
# last_visit field that accept both a 1d, 1w, 1m, 1y format and the
# %last_changed% pronoun.
last_visit_ts => \&_last_visit_datetime,
+
+ # BMO - Add ability to use pronoun for bug mentors field
+ bug_mentor => \&_commenter_pronoun,
};
foreach my $field (Bugzilla->active_custom_fields) {
if ($field->type == FIELD_TYPE_DATETIME) {
@@ -425,6 +428,11 @@ use constant USER_FIELDS => {
field => 'setter_id',
join => { table => 'flags' },
},
+ # BMO - Ability to search for bugs with specific mentors
+ 'bug_mentor' => {
+ field => 'user_id',
+ join => { table => 'bug_mentors' },
+ }
};
# Backwards compatibility for times that we changed the names of fields
@@ -1701,7 +1709,7 @@ sub _special_parse_email {
$type = "anyexact" if $type eq "exact";
my $or_clause = new Bugzilla::Search::Clause('OR');
- foreach my $field (qw(assigned_to reporter cc qa_contact)) {
+ foreach my $field (qw(assigned_to reporter cc qa_contact bug_mentor)) {
if ($params->{"email$field$id"}) {
$or_clause->add($field, $type, $email);
}
diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm
index f707a8e80..8526c42d7 100644
--- a/Bugzilla/User.pm
+++ b/Bugzilla/User.pm
@@ -775,6 +775,9 @@ sub is_involved_in_bug {
return 1 if $user_id == $bug->qa_contact->id;
}
+ # BMO - Bug mentors are considered involved with the bug
+ return 1 if $bug->is_mentor($self);
+
return unless $bug->cc;
return any { $user_login eq $_ } @{ $bug->cc };
}
diff --git a/Bugzilla/WebService/Bug.pm b/Bugzilla/WebService/Bug.pm
index a82f55d3b..0a8145d37 100644
--- a/Bugzilla/WebService/Bug.pm
+++ b/Bugzilla/WebService/Bug.pm
@@ -1376,6 +1376,14 @@ sub _bug_to_hash {
$item{'is_creator_accessible'} = $self->type('boolean', $bug->reporter_accessible);
}
+ # BMO - support for special mentors field
+ if (filter_wants $params, 'mentors') {
+ $item{'mentors'}
+ = [ map { $self->type('email', $_->login) } @{ $bug->mentors || [] } ];
+ $item{'mentors_detail'}
+ = [ map { $self->_user_to_hash($_, $params, undef, 'mentors') } @{ $bug->mentors } ];
+ }
+
return \%item;
}
diff --git a/extensions/Review/Extension.pm b/extensions/Review/Extension.pm
index 6509218f3..ac7cb7233 100644
--- a/extensions/Review/Extension.pm
+++ b/extensions/Review/Extension.pm
@@ -18,8 +18,9 @@ use Bugzilla::Error;
use Bugzilla::Extension::Review::FlagStateActivity;
use Bugzilla::Extension::Review::Util;
use Bugzilla::Install::Filesystem;
+use Bugzilla::Search;
use Bugzilla::User;
-use Bugzilla::Util qw(clean_text);
+use Bugzilla::Util qw(clean_text diff_arrays);
use constant UNAVAILABLE_RE => qr/\b(?:unavailable|pto|away)\b/i;
@@ -77,42 +78,19 @@ sub _reviewers_objs {
sub _bug_mentors {
my ($self) = @_;
- # extract the mentors from the status_whiteboard
- # when the mentors gets its own field, this will be easier
- if (!exists $self->{mentors}) {
- my @mentors;
- my $whiteboard = $self->status_whiteboard;
- my $logout = 0;
- while ($whiteboard =~ /\[mentor=([^\]]+)\]/g) {
- my $mentor_string = $1;
- my $user;
- if ($mentor_string =~ /\@/) {
- # assume it's a full username if it contains an @
- $user = Bugzilla::User->new({ name => $mentor_string });
- } else {
- # otherwise assume it's a : prefixed nick. only works if a
- # single user matches.
-
- # we need to be logged in to do user searching
- if (!Bugzilla->user->id) {
- Bugzilla->set_user(Bugzilla::User->check({ name => 'nobody@mozilla.org' }));
- $logout = 1;
- }
-
- foreach my $query ("*:$mentor_string*", "*$mentor_string*") {
- my $matches = Bugzilla::User::match($query, 2);
- if ($matches && scalar(@$matches) == 1) {
- $user = $matches->[0];
- last;
- }
- }
- }
- push @mentors, $user if $user;
+ my $dbh = Bugzilla->dbh;
+ if (!$self->{bug_mentors}) {
+ my $mentor_ids = $dbh->selectcol_arrayref("
+ SELECT user_id FROM bug_mentors WHERE bug_id = ?",
+ undef,
+ $self->id);
+ $self->{bug_mentors} = [];
+ foreach my $mentor_id (@$mentor_ids) {
+ push(@{ $self->{bug_mentors} },
+ Bugzilla::User->new({ id => $mentor_id, cache => 1 }));
}
- Bugzilla->logout_request() if $logout;
- $self->{mentors} = \@mentors;
}
- return $self->{mentors};
+ return [ sort { $a->login cmp $b->login } @{ $self->{bug_mentors} } ];
}
sub _bug_is_mentor {
@@ -191,8 +169,8 @@ sub object_end_of_create {
my ($object, $params) = @$args{qw(object params)};
if ($object->isa('Bugzilla::Product') || $object->isa('Bugzilla::Component')) {
- my ($new, $new_users) = _new_reviewers_from_input();
- _update_reviewers($object, [], $new_users);
+ my ($new, $new_users) = _new_users_from_input('reviewers');
+ _update_users($object, [], $new_users);
}
elsif (_is_countable_flag($object) && $object->requestee_id && $object->status eq '?') {
_adjust_request_count($object, +1);
@@ -200,6 +178,10 @@ sub object_end_of_create {
if (_is_countable_flag($object)) {
$self->_log_flag_state_activity($object, $object->status);
}
+ elsif ($object->isa('Bugzilla::Bug')) {
+ my ($new, $new_users) = _new_users_from_input('bug_mentors');
+ _update_users($object, [], $new_users);
+ }
}
sub object_end_of_update {
@@ -207,10 +189,10 @@ sub object_end_of_update {
my ($object, $old_object, $changes) = @$args{qw(object old_object changes)};
if ($object->isa('Bugzilla::Product') || $object->isa('Bugzilla::Component')) {
- my ($new, $new_users) = _new_reviewers_from_input();
+ my ($new, $new_users) = _new_users_from_input('reviewers');
my $old = $old_object->reviewers(1);
if ($old ne $new) {
- _update_reviewers($object, $old_object->reviewers_objs(1), $new_users);
+ _update_users($object, $old_object->reviewers_objs(1), $new_users);
$changes->{reviewers} = [ $old ? $old : undef, $new ? $new : undef ];
}
}
@@ -240,6 +222,19 @@ sub object_end_of_update {
_adjust_request_count($object, +1);
}
}
+ elsif ($object->isa('Bugzilla::Bug')) {
+ my ($new, $new_mentors) = _new_users_from_input('bug_mentors');
+ my $old = join(", ", map { $_->login } @{ $old_object->mentors });
+ if ($old ne $new) {
+ _update_users($object, $old_object->mentors, $new_mentors);
+
+ my @old_names = map { $_->login } @{ $old_object->mentors };
+ my @new_names = map { $_->login } @{ $new_mentors };
+ my ($removed, $added) = diff_arrays(\@old_names, \@new_names);
+ $changes->{bug_mentor} = [ @$removed ? join(", ", @$removed) : undef,
+ @$added ? join(", ", @$added) : undef ];
+ }
+ }
}
sub flag_updated {
@@ -310,12 +305,13 @@ sub _adjust_request_count {
Bugzilla->memcached->clear({ table => 'profiles', id => $requestee_id });
}
-sub _new_reviewers_from_input {
- if (!Bugzilla->input_params->{reviewers}) {
+sub _new_users_from_input {
+ my $field = shift;
+ if (!Bugzilla->input_params->{$field}) {
return ('', []);
}
- Bugzilla::User::match_field({ 'reviewers' => {'type' => 'multi'} });
- my $new = Bugzilla->input_params->{reviewers};
+ Bugzilla::User::match_field({ $field => {'type' => 'multi'} });
+ my $new = Bugzilla->input_params->{$field};
$new = [ $new ] unless ref($new);
my $new_users = [];
foreach my $login (@$new) {
@@ -325,39 +321,64 @@ sub _new_reviewers_from_input {
return ($new, $new_users);
}
-sub _update_reviewers {
+sub _update_users {
my ($object, $old_users, $new_users) = @_;
my $dbh = Bugzilla->dbh;
- my $type = $object->isa('Bugzilla::Product') ? 'product' : 'component';
- # remove deleted users
- foreach my $old_user (@$old_users) {
- if (!grep { $_->id == $old_user->id } @$new_users) {
- $dbh->do(
- "DELETE FROM ${type}_reviewers WHERE ${type}_id=? AND user_id=?",
- undef,
- $object->id, $old_user->id,
- );
+ if ($object->isa('Bugzilla::Bug')) {
+ # remove deleted users
+ foreach my $old_user (@$old_users) {
+ if (!grep { $_->id == $old_user->id } @$new_users) {
+ $dbh->do(
+ "DELETE FROM bug_mentors WHERE bug_id = ? AND user_id = ?",
+ undef,
+ $object->id, $old_user->id,
+ );
+ }
+ }
+ # add new users
+ foreach my $new_user (@$new_users) {
+ if (!grep { $_->id == $new_user->id } @$old_users) {
+ $dbh->do(
+ "INSERT INTO bug_mentors (bug_id,user_id) VALUES (?, ?)",
+ undef,
+ $object->id, $new_user->id,
+ );
+ }
}
}
- # add new users
- foreach my $new_user (@$new_users) {
- if (!grep { $_->id == $new_user->id } @$old_users) {
+ else {
+ my $type = $object->isa('Bugzilla::Product') ? 'product' : 'component';
+
+ # remove deleted users
+ foreach my $old_user (@$old_users) {
+ if (!grep { $_->id == $old_user->id } @$new_users) {
+ $dbh->do(
+ "DELETE FROM ${type}_reviewers WHERE ${type}_id=? AND user_id=?",
+ undef,
+ $object->id, $old_user->id,
+ );
+ }
+ }
+ # add new users
+ foreach my $new_user (@$new_users) {
+ if (!grep { $_->id == $new_user->id } @$old_users) {
+ $dbh->do(
+ "INSERT INTO ${type}_reviewers(${type}_id,user_id) VALUES (?,?)",
+ undef,
+ $object->id, $new_user->id,
+ );
+ }
+ }
+ # and update the sortkey for all users
+ for (my $i = 0; $i < scalar(@$new_users); $i++) {
$dbh->do(
- "INSERT INTO ${type}_reviewers(${type}_id,user_id) VALUES (?,?)",
+ "UPDATE ${type}_reviewers SET sortkey=? WHERE ${type}_id=? AND user_id=?",
undef,
- $object->id, $new_user->id,
+ ($i + 1) * 10, $object->id, $new_users->[$i]->id,
);
}
}
- # and update the sortkey for all users
- for (my $i = 0; $i < scalar(@$new_users); $i++) {
- $dbh->do(
- "UPDATE ${type}_reviewers SET sortkey=? WHERE ${type}_id=? AND user_id=?",
- undef,
- ($i + 1) * 10, $object->id, $new_users->[$i]->id,
- );
- }
}
# bugzilla's handling of requestee matching when creating bugs is "if it's
@@ -439,6 +460,51 @@ sub flag_end_of_update {
}
#
+# search
+#
+
+sub buglist_columns {
+ my ($self, $args) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $columns = $args->{columns};
+ $columns->{bug_mentor} = { title => 'Mentor' };
+ if (Bugzilla->user->id) {
+ $columns->{bug_mentor}->{name}
+ = $dbh->sql_group_concat('map_mentors_names.login_name');
+ }
+ else {
+ $columns->{bug_mentor}->{name}
+ = $dbh->sql_group_concat('map_mentors_names.realname');
+
+ }
+}
+
+sub buglist_column_joins {
+ my ($self, $args) = @_;
+ my $column_joins = $args->{column_joins};
+ $column_joins->{bug_mentor} = {
+ as => 'map_mentors',
+ table => 'bug_mentors',
+ then_to => {
+ as => 'map_mentors_names',
+ table => 'profiles',
+ from => 'map_mentors.user_id',
+ to => 'userid',
+ },
+ },
+}
+
+sub search_operator_field_override {
+ my ($self, $args) = @_;
+ my $operators = $args->{operators};
+ $operators->{bug_mentor} = {
+ _non_changed => sub {
+ Bugzilla::Search::_user_nonchanged(@_)
+ }
+ };
+}
+
+#
# web service / pages
#
@@ -700,6 +766,36 @@ sub db_schema_abstract_schema {
bug_mentors_bug_id_idx => [ 'bug_id' ],
],
};
+
+ $args->{'schema'}->{'bug_mentors'} = {
+ FIELDS => [
+ bug_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {
+ TABLE => 'bugs',
+ COLUMN => 'bug_id',
+ DELETE => 'CASCADE',
+ },
+ },
+ user_id => {
+ TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => {
+ TABLE => 'profiles',
+ COLUMN => 'userid',
+ DELETE => 'CASCADE',
+ }
+ },
+ ],
+ INDEXES => [
+ bug_mentors_idx => {
+ FIELDS => [ 'bug_id', 'user_id' ],
+ TYPE => 'UNIQUE',
+ },
+ bug_mentors_bug_id_idx => [ 'bug_id' ],
+ ],
+ };
}
sub install_update_db {
@@ -720,6 +816,7 @@ sub install_update_db {
'profiles',
'needinfo_request_count', { TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0 }
);
+
my $field = Bugzilla::Field->new({ name => 'bug_mentor' });
if (!$field) {
Bugzilla::Field->create({
diff --git a/extensions/Review/template/en/default/hook/bug/create/create-after_custom_fields.html.tmpl b/extensions/Review/template/en/default/hook/bug/create/create-after_custom_fields.html.tmpl
new file mode 100644
index 000000000..6d890d975
--- /dev/null
+++ b/extensions/Review/template/en/default/hook/bug/create/create-after_custom_fields.html.tmpl
@@ -0,0 +1,19 @@
+[%# 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.
+ #%]
+
+<tr>
+ <th class="field_label">Mentors:</th>
+ <td colspan="3" class="field_value">
+ [% INCLUDE global/userselect.html.tmpl
+ id = "bug_mentors"
+ name = "bug_mentors"
+ size = 30
+ multiple = 5
+ %]
+ </td>
+</tr>
diff --git a/extensions/Review/template/en/default/hook/bug/edit-after_custom_fields.html.tmpl b/extensions/Review/template/en/default/hook/bug/edit-after_custom_fields.html.tmpl
new file mode 100644
index 000000000..352dfa6c7
--- /dev/null
+++ b/extensions/Review/template/en/default/hook/bug/edit-after_custom_fields.html.tmpl
@@ -0,0 +1,50 @@
+[%# 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.
+ #%]
+
+[% mentor_logins = [] %]
+[% FOREACH mentor = bug.mentors %]
+ [% mentor_logins.push(mentor.login) %]
+[% END %]
+<tr>
+ <th class="field_label">Mentors:</th>
+ <td>
+ [% IF bug.check_can_change_field("bug_mentors", 0, 1) %]
+ <div id="bz_bug_mentors_edit_container" class="bz_default_hidden">
+ <span>
+ [% FOREACH mentor = bug.mentors %]
+ [% INCLUDE global/user.html.tmpl who = mentor %]<br>
+ [% END %]
+ (<a href="#" id="bz_bug_mentors_edit_action">edit</a>)
+ </span>
+ </div>
+ <div id="bz_bug_mentors_input">
+ [% INCLUDE global/userselect.html.tmpl
+ id = "bug_mentors"
+ name = "bug_mentors"
+ value = mentor_logins.join(", ")
+ classes = ["bz_userfield"]
+ size = 30
+ multiple = 5
+ %]
+ <br>
+ </div>
+ <script type="text/javascript">
+ hideEditableField('bz_bug_mentors_edit_container',
+ 'bz_bug_mentors_input',
+ 'bz_bug_mentors_edit_action',
+ 'bug_mentors',
+ '[% mentor_logins.join(", ") FILTER js %]' );
+ </script>
+ [% ELSE %]
+ [% FOREACH mentor = bug.mentors %]
+ [% INCLUDE global/user.html.tmpl who = mentor %]<br>
+ [% END %]
+ [% END %]
+ </td>
+</tr>
+
diff --git a/extensions/Review/template/en/default/hook/bug/show-bug_end.xml.tmpl b/extensions/Review/template/en/default/hook/bug/show-bug_end.xml.tmpl
new file mode 100644
index 000000000..9ad650b2f
--- /dev/null
+++ b/extensions/Review/template/en/default/hook/bug/show-bug_end.xml.tmpl
@@ -0,0 +1,12 @@
+[%# 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.
+ #%]
+
+[% FOREACH mentor = bug.mentors %]
+ <mentor name="[% mentor.name FILTER xml %]">
+ [% mentor.login FILTER email FILTER xml %]</mentor>
+[% END %]
diff --git a/skins/custom/create_bug.css b/skins/custom/create_bug.css
index b3088048d..1ca28a101 100644
--- a/skins/custom/create_bug.css
+++ b/skins/custom/create_bug.css
@@ -1,3 +1,10 @@
+/* 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. */
+
.tracking_flags .field_label a {
font-weight: normal !important;
@@ -32,3 +39,9 @@
#bug_create_warning_text {
margin-left: 42px;
}
+
+#keyword_container,
+#bug_mentors_autocomplete,
+#container_see_also {
+ width: 25em
+}
diff --git a/template/en/default/bug/create/create.html.tmpl b/template/en/default/bug/create/create.html.tmpl
index d41dc9d35..2e3ba66c6 100644
--- a/template/en/default/bug/create/create.html.tmpl
+++ b/template/en/default/bug/create/create.html.tmpl
@@ -633,10 +633,11 @@ TUI_hide_default('attachment_text_field');
[% IF Param('use_see_also') %]
<tr>
[% INCLUDE bug/field.html.tmpl
- bug = default
- field = bug_fields.see_also
- editable = 1
- value = see_also
+ bug = default
+ field = bug_fields.see_also
+ editable = 1
+ value = see_also
+ value_span = 3
%]
</tr>
[% END %]
@@ -656,9 +657,8 @@ TUI_hide_default('attachment_text_field');
value_span = 3 %]
</tr>
[% END %]
-</tbody>
-
[% Hook.process('after_custom_fields') %]
+</tbody>
[% display_flags = 0 %]
[% any_flags_requesteeble = 0 %]
diff --git a/template/en/default/search/form.html.tmpl b/template/en/default/search/form.html.tmpl
index 5a97dd4d3..1b9b8310f 100644
--- a/template/en/default/search/form.html.tmpl
+++ b/template/en/default/search/form.html.tmpl
@@ -330,6 +330,8 @@ TUI_hide_default('information_query');
label=> "a ${field_descs.cc} list member" } %]
[% PROCESS role_types field = { count => n, name => "emaillongdesc",
label=> " a ${field_descs.commenter}" } %]
+ [% PROCESS role_types field = { count => n, name => "emailbug_mentor",
+ label => " a ${field_descs.bug_mentor}" } %]
<select name="emailtype[% n %]">
[% FOREACH qv = [
{ name => "substring", description => "contains" },
@@ -357,8 +359,8 @@ TUI_hide_default('information_query');
[% END %]
</div>
[% END %]
- [% Hook.process('email_numbering_end') %]
- </div>
+ [% Hook.process('email_numbering_end') %]
+ </div>
[%# *** Bug Changes *** %]
<div class="bz_section_title" id="history_filter">
<div id="history_query_controller" class="arrow">&#9660;</div>