From: Kohei Yoshino Date: Wed, 27 Mar 2019 23:21:30 +0000 (-0400) Subject: Bug 1461492 - Add an optional regressed-by field in bugs X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=9094b3b99aa9805689616b08a886085389b323db;p=thirdparty%2Fbugzilla.git Bug 1461492 - Add an optional regressed-by field in bugs --- diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm index b76aa7edb..fca291770 100644 --- a/Bugzilla/Bug.pm +++ b/Bugzilla/Bug.pm @@ -57,6 +57,12 @@ use constant ID_FIELD => 'bug_id'; use constant NAME_FIELD => 'alias'; use constant LIST_ORDER => ID_FIELD; +# Set up Dependencies and Regressions +use constant BUG_RELATIONS => ( + [dependencies => qw(dependson blocked)], + [regressions => qw(regressed_by regresses)], +); + # Bugs have their own auditing table, bugs_activity. use constant AUDIT_CREATES => 0; use constant AUDIT_UPDATES => 0; @@ -272,6 +278,7 @@ use constant FIELD_MAP => { last_change_time => 'delta_ts', comment_count => 'longdescs.count', platform => 'rep_platform', + regressions => 'regresses', severity => 'bug_severity', status => 'bug_status', summary => 'short_desc', @@ -549,6 +556,8 @@ sub CLEANUP { next unless $bug; delete $bug->{depends_on_obj}; delete $bug->{blocks_obj}; + delete $bug->{regressed_by_obj}; + delete $bug->{regresses_obj}; } %CLEANUP = (); } @@ -580,7 +589,14 @@ sub check { } } - unless ($field && $field =~ /^(dependson|blocked|dup_id)$/) { + state $relation_field = { + dependson => 1, + blocked => 1, + regressed_by => 1, + regresses => 1, + dup_id => 1 + }; + unless ($field && $relation_field->{$field}) { $self->check_is_visible; } return $self; @@ -677,7 +693,8 @@ sub preload { # to the more complex method. my @all_dep_ids; foreach my $bug (@$bugs) { - push(@all_dep_ids, @{$bug->blocked}, @{$bug->dependson}); + push(@all_dep_ids, + map { @{$bug->$_ || []} } qw(dependson blocked regressed_by regresses)); } @all_dep_ids = uniq @all_dep_ids; @@ -902,12 +919,12 @@ sub create { # insert_create_data. my $cc_ids = delete $params->{cc}; my $groups = delete $params->{groups}; - my $depends_on = delete $params->{dependson}; - my $blocked = delete $params->{blocked}; my $keywords = delete $params->{keywords}; my $creation_comment = delete $params->{comment}; my $see_also = delete $params->{see_also}; my $comment_tags = delete $params->{comment_tags}; + my %relation_values = map { $_ => delete $params->{$_} } + qw(dependson blocked regressed_by regresses); # We don't want the bug to appear in the system until it's correctly # protected by groups. @@ -942,25 +959,27 @@ sub create { $sth_keyword->execute($bug->bug_id, $keyword_id); } - # Set up dependencies (blocked/dependson) - my $sth_deps = $dbh->prepare( - 'INSERT INTO dependencies (blocked, dependson) VALUES (?, ?)'); + foreach my $rel (BUG_RELATIONS) { + my ($table, $field1, $field2) = @$rel; + my $sth = $dbh->prepare("INSERT INTO $table ($field1, $field2) VALUES (?, ?)"); - foreach my $depends_on_id (@$depends_on) { - $sth_deps->execute($bug->bug_id, $depends_on_id); + foreach my $id (@{$relation_values{$field1}}) { + $sth->execute($id, $bug->bug_id); - # Log the reverse action on the other bug. - LogActivityEntry($depends_on_id, 'blocked', '', $bug->bug_id, - $bug->{reporter_id}, $timestamp); - _update_delta_ts($depends_on_id, $timestamp); - } - foreach my $blocked_id (@$blocked) { - $sth_deps->execute($blocked_id, $bug->bug_id); + # Log the reverse action on the other bug. + LogActivityEntry($id, $field2, '', $bug->bug_id, $bug->{reporter_id}, + $timestamp); + _update_delta_ts($id, $timestamp); + } - # Log the reverse action on the other bug. - LogActivityEntry($blocked_id, 'dependson', '', $bug->bug_id, - $bug->{reporter_id}, $timestamp); - _update_delta_ts($blocked_id, $timestamp); + foreach my $id (@{$relation_values{$field2}}) { + $sth->execute($bug->bug_id, $id); + + # Log the reverse action on the other bug. + LogActivityEntry($id, $field1, '', $bug->bug_id, $bug->{reporter_id}, + $timestamp); + _update_delta_ts($id, $timestamp); + } } # Insert the values into the multiselect value tables @@ -1057,9 +1076,15 @@ sub run_create_validators { $class->_check_strict_isolation($params->{cc}, $params->{assigned_to}, $params->{qa_contact}, $product); - ($params->{dependson}, $params->{blocked}) - = $class->_check_dependencies($params->{dependson}, $params->{blocked}, - $product); + my %dep_lists = $class->_check_relationship($product, 'dependencies', + map { $_ => $params->{$_} } qw(dependson blocked)); + my %reg_lists = $class->_check_relationship($product, 'regressions', + map { $_ => $params->{$_} } qw(regressed_by regresses)); + + $params->{dependson} = $dep_lists{'dependson'}; + $params->{blocked} = $dep_lists{'blocked'}; + $params->{regressed_by} = $reg_lists{'regressed_by'}; + $params->{regresses} = $reg_lists{'regresses'}; # You can't set these fields on bug creation (or sometimes ever). delete $params->{resolution}; @@ -1165,36 +1190,39 @@ sub update { $changes->{keywords} = [$removed_names, $added_names]; } - # Dependencies - foreach my $pair ([qw(dependson blocked)], [qw(blocked dependson)]) { - my ($type, $other) = @$pair; - my $old = $old_bug->$type; - my $new = $self->$type; + # Dependencies and Regressions + foreach my $rel (BUG_RELATIONS) { + my ($table, $field1, $field2) = @$rel; - my ($removed, $added) = diff_arrays($old, $new); - foreach my $removed_id (@$removed) { - $dbh->do("DELETE FROM dependencies WHERE $type = ? AND $other = ?", - undef, $removed_id, $self->id); + foreach my $pair ([$field1, $field2], [$field2, $field1]) { + my ($field, $other_field) = @$pair; + my ($removed, $added) = diff_arrays($old_bug->$field, $self->$field); - # Add an activity entry for the other bug. - LogActivityEntry($removed_id, $other, $self->id, '', $user->id, $delta_ts); + foreach my $id (@$removed) { + $dbh->do("DELETE FROM $table WHERE $field = ? AND $other_field = ?", + undef, $id, $self->id); - # Update delta_ts on the other bug so that we trigger mid-airs. - _update_delta_ts($removed_id, $delta_ts); - } - foreach my $added_id (@$added) { - $dbh->do("INSERT INTO dependencies ($type, $other) VALUES (?,?)", - undef, $added_id, $self->id); + # Add an activity entry for the other bug. + LogActivityEntry($id, $other_field, $self->id, '', $user->id, $delta_ts); + + # Update delta_ts on the other bug so that we trigger mid-airs. + _update_delta_ts($id, $delta_ts); + } - # Add an activity entry for the other bug. - LogActivityEntry($added_id, $other, '', $self->id, $user->id, $delta_ts); + foreach my $id (@$added) { + $dbh->do("INSERT INTO $table ($field, $other_field) VALUES (?, ?)", + undef, $id, $self->id); - # Update delta_ts on the other bug so that we trigger mid-airs. - _update_delta_ts($added_id, $delta_ts); - } + # Add an activity entry for the other bug. + LogActivityEntry($id, $other_field, '', $self->id, $user->id, $delta_ts); - if (scalar(@$removed) || scalar(@$added)) { - $changes->{$type} = [join(', ', @$removed), join(', ', @$added)]; + # Update delta_ts on the other bug so that we trigger mid-airs. + _update_delta_ts($id, $delta_ts); + } + + if (@$removed || @$added) { + $changes->{$field} = [join(', ', @$removed), join(', ', @$added)]; + } } } @@ -1533,6 +1561,8 @@ sub remove_from_db { $dbh->do("DELETE FROM cc WHERE bug_id = ?", undef, $bug_id); $dbh->do("DELETE FROM dependencies WHERE blocked = ? OR dependson = ?", undef, ($bug_id, $bug_id)); + $dbh->do("DELETE FROM regressions WHERE regresses = ? OR regressed_by = ?", + undef, ($bug_id, $bug_id)); $dbh->do("DELETE FROM duplicates WHERE dupe = ? OR dupe_of = ?", undef, ($bug_id, $bug_id)); $dbh->do("DELETE FROM flags WHERE bug_id = ?", undef, $bug_id); @@ -1616,12 +1646,16 @@ sub send_changes { } } - # To get a list of all changed dependencies, convert the "changes" arrays - # into a long string, then collapse that string into unique numbers in - # a hash. + # To get a list of all changed dependencies and regressoins, convert the + # "changes" arrays into a long string, then collapse that string into unique + # numbers in a hash. my $all_changed_deps = join(', ', @{$changes->{'dependson'} || []}); $all_changed_deps = join(', ', @{$changes->{'blocked'} || []}, $all_changed_deps); + $all_changed_deps + = join(', ', @{$changes->{'regressed_by'} || []}, $all_changed_deps); + $all_changed_deps + = join(', ', @{$changes->{'regresses'} || []}, $all_changed_deps); my %changed_deps = map { $_ => 1 } split(', ', $all_changed_deps); # When clearning one field (say, blocks) and filling in the other @@ -1924,10 +1958,11 @@ sub _check_deadline { return $date; } -# Takes two comma/space-separated strings and returns arrayrefs -# of valid bug IDs. -sub _check_dependencies { - my ($invocant, $depends_on, $blocks, $product) = @_; +# Take a hash containing two keys (dependson/blocked for dependencies or +# regressed_by/regresses for regressions) and bug IDs as a comma/space-separated +# string value for each, and return arrayrefs of valid bug IDs. +sub _check_relationship { + my ($invocant, $product, $table, %deps_in) = @_; if (!ref $invocant) { @@ -1935,13 +1970,11 @@ sub _check_dependencies { return ([], []) unless Bugzilla->user->in_group('editbugs', $product->id); } - my %deps_in = (dependson => $depends_on || '', blocked => $blocks || ''); - - foreach my $type (qw(dependson blocked)) { + foreach my $type (keys %deps_in) { my @bug_ids = ref($deps_in{$type}) ? @{$deps_in{$type}} - : split(/[\s,]+/, $deps_in{$type}); + : split(/[\s,]+/, $deps_in{$type} || ''); # Eliminate nulls. @bug_ids = grep {$_} @bug_ids; @@ -1996,10 +2029,9 @@ sub _check_dependencies { # And finally, check for dependency loops. my $bug_id = ref($invocant) ? $invocant->id : 0; - my %deps - = ValidateDependencies($deps_in{dependson}, $deps_in{blocked}, $bug_id); + my %deps = validate_relationship($bug_id, $table, %deps_in); - return ($deps{'dependson'}, $deps{'blocked'}); + return %deps; } sub _check_dup_id { @@ -2596,9 +2628,10 @@ sub fields { bug_status resolution dup_id see_also bug_file_loc status_whiteboard keywords priority bug_severity target_milestone - dependson blocked everconfirmed - reporter assigned_to cc estimated_time - remaining_time actual_time deadline), + dependson blocked regressed_by regresses + everconfirmed reporter assigned_to cc + estimated_time remaining_time actual_time + deadline), # Conditional Fields Bugzilla->params->{'useqacontact'} ? "qa_contact" : (), @@ -2681,9 +2714,14 @@ sub set_all { # immediately after changing the product. $self->_add_remove($params, 'groups'); - if (exists $params->{'dependson'} or exists $params->{'blocked'}) { + # Dependencies and Regressions + foreach my $rel (BUG_RELATIONS) { + my ($table, $field1, $field2) = @$rel; + + next unless (exists $params->{$field1} || exists $params->{$field2}); + my %set_deps; - foreach my $name (qw(dependson blocked)) { + foreach my $name ($field1, $field2) { my @dep_ids = @{$self->$name}; # If only one of the two fields was passed in, then we need to @@ -2709,7 +2747,7 @@ sub set_all { $set_deps{$name} = \@dep_ids; } - $self->set_dependencies($set_deps{'dependson'}, $set_deps{'blocked'}); + $self->set_relationship($table, %set_deps); } if (exists $params->{'keywords'}) { @@ -2863,18 +2901,20 @@ sub set_custom_field { } sub set_deadline { $_[0]->set('deadline', $_[1]); } -sub set_dependencies { - my ($self, $dependson, $blocked) = @_; - ($dependson, $blocked) = $self->_check_dependencies($dependson, $blocked); +sub set_relationship { + my ($self, $table, %fields) = @_; + my ($key1, $key2) = keys %fields; + my %lists = $self->_check_relationship(undef, $table, %fields); + my ($list1, $list2) = ($lists{$key1}, $lists{$key2}); # These may already be detainted, but all setters are supposed to # detaint their input if they've run a validator (just as though # we had used Bugzilla::Object::set), so we do that here. - detaint_natural($_) foreach (@$dependson, @$blocked); - $self->{'dependson'} = $dependson; - $self->{'blocked'} = $blocked; - delete $self->{depends_on_obj}; - delete $self->{blocks_obj}; + detaint_natural($_) foreach (@$list1, @$list2); + $self->{$key1} = $list1; + $self->{$key2} = $list2; + delete $self->{$key1 eq 'dependson' ? 'depends_on_obj' : "${key1}_obj"}; + delete $self->{$key2 eq 'dependson' ? 'depends_on_obj' : "${key2}_obj"}; } sub _clear_dup_id { $_[0]->{dup_id} = undef; } @@ -3754,7 +3794,8 @@ sub blocked { my ($self) = @_; return $self->{'blocked'} if exists $self->{'blocked'}; return [] if $self->{'error'}; - $self->{'blocked'} = EmitDependList("dependson", "blocked", $self->bug_id); + $self->{'blocked'} + = list_relationship('dependencies', 'dependson', 'blocked', $self->bug_id); return $self->{'blocked'}; } @@ -3853,7 +3894,8 @@ sub dependson { my ($self) = @_; return $self->{'dependson'} if exists $self->{'dependson'}; return [] if $self->{'error'}; - $self->{'dependson'} = EmitDependList("blocked", "dependson", $self->bug_id); + $self->{'dependson'} + = list_relationship('dependencies', 'blocked', 'dependson', $self->bug_id); return $self->{'dependson'}; } @@ -4042,6 +4084,38 @@ sub qa_contact { return $self->{'qa_contact_obj'}; } +sub regressed_by { + my ($self) = @_; + return $self->{'regressed_by'} if exists $self->{'regressed_by'}; + return [] if $self->{'error'}; + $self->{'regressed_by'} + = list_relationship('regressions', 'regresses', 'regressed_by', + $self->bug_id); + return $self->{'regressed_by'}; +} + +sub regressed_by_obj { + my ($self) = @_; + $self->{'regressed_by_obj'} ||= $self->_bugs_in_order($self->regressed_by); + return $self->{'regressed_by_obj'}; +} + +sub regresses { + my ($self) = @_; + return $self->{'regresses'} if exists $self->{'regresses'}; + return [] if $self->{'error'}; + $self->{'regresses'} + = list_relationship('regressions', 'regressed_by', 'regresses', + $self->bug_id); + return $self->{'regresses'}; +} + +sub regresses_obj { + my ($self) = @_; + $self->{'regresses_obj'} ||= $self->_bugs_in_order($self->regresses); + return $self->{'regresses_obj'}; +} + sub reporter { my ($self) = @_; return $self->{'reporter'} if exists $self->{'reporter'}; @@ -4381,13 +4455,11 @@ sub editable_bug_fields { return sort(@fields); } -# XXX - When Bug::update() will be implemented, we should make this routine -# a private method. -# Join with bug_status and bugs tables to show bugs with open statuses first, -# and then the others -sub EmitDependList { - my ($my_field, $target_field, $bug_id, $exclude_resolved) = @_; - my $cache = Bugzilla->request_cache->{bug_dependency_list} ||= {}; +# Emit a list of dependencies or regressions. Join with bug_status and bugs +# tables to show bugs with open statuses first, and then the others +sub list_relationship { + my ($table, $my_field, $target_field, $bug_id, $exclude_resolved) = @_; + my $cache = Bugzilla->request_cache->{"bug_$table"} ||= {}; my $dbh = Bugzilla->dbh; $exclude_resolved = $exclude_resolved ? 1 : 0; @@ -4395,8 +4467,8 @@ sub EmitDependList { $cache->{"${target_field}_sth_$exclude_resolved"} ||= $dbh->prepare( "SELECT $target_field - FROM dependencies - INNER JOIN bugs ON dependencies.$target_field = bugs.bug_id + FROM $table + INNER JOIN bugs ON $table.$target_field = bugs.bug_id INNER JOIN bug_status ON bugs.bug_status = bug_status.value WHERE $my_field = ? $is_open_clause ORDER BY is_open DESC, $target_field" @@ -4618,7 +4690,7 @@ sub _join_activity_entries { return $new_change if $current_change eq ''; # Buglists and see_also need the comma restored - if ($field eq 'dependson' || $field eq 'blocked' || $field eq 'see_also') { + if ($field =~ /^(?:dependson|blocked|regress(?:ed_by|es)|see_also)$/) { if (substr($new_change, 0, 1) eq ',' || substr($new_change, 0, 1) eq ' ') { return $current_change . $new_change; } @@ -4945,35 +5017,29 @@ sub _changes_everconfirmed { # Field Validation # -# Validate and return a hash of dependencies -sub ValidateDependencies { - my $fields = {}; +# Validate and return a hash of dependencies or regressions +sub validate_relationship { + my ($bug_id, $table, %fields) = @_; + my ($key1, $key2) = keys %fields; - # These can be arrayrefs or they can be strings. - $fields->{'dependson'} = shift; - $fields->{'blocked'} = shift; - my $id = shift || 0; - - unless (defined($fields->{'dependson'}) || defined($fields->{'blocked'})) { - return; - } + return unless (defined($fields{$key1}) || defined($fields{$key2})); my $dbh = Bugzilla->dbh; my %deps; my %deptree; - foreach my $pair (["blocked", "dependson"], ["dependson", "blocked"]) { + foreach my $pair ([$key1, $key2], [$key2, $key1]) { my ($me, $target) = @{$pair}; $deptree{$target} = []; $deps{$target} = []; - next unless $fields->{$target}; + next unless $fields{$target}; my %seen; my $target_array - = ref($fields->{$target}) - ? $fields->{$target} - : [split(/[\s,]+/, $fields->{$target})]; + = ref($fields{$target}) + ? $fields{$target} + : [split(/[\s,]+/, $fields{$target})]; foreach my $i (@$target_array) { - if ($id == $i) { + if ($bug_id == $i) { ThrowUserError("dependency_loop_single"); } if (!exists $seen{$i}) { @@ -4987,17 +5053,15 @@ sub ValidateDependencies { @{$deps{$target}} = @{$deptree{$target}}; my @stack = @{$deps{$target}}; while (@stack) { - my $i = shift @stack; - my $dep_list = $dbh->selectcol_arrayref( - "SELECT $target - FROM dependencies - WHERE $me = ?", undef, $i - ); + my $i = shift @stack; + my $dep_list + = $dbh->selectcol_arrayref("SELECT $target FROM $table WHERE $me = ?", + undef, $i); foreach my $t (@$dep_list) { # ignore any _current_ dependencies involving this bug, # as they will be overwritten with data from the form. - if ($t != $id && !exists $seen{$t}) { + if ($t != $bug_id && !exists $seen{$t}) { push(@{$deptree{$target}}, $t); push @stack, $t; $seen{$t} = 1; @@ -5006,8 +5070,8 @@ sub ValidateDependencies { } } - my @deps = @{$deptree{'dependson'}}; - my @blocks = @{$deptree{'blocked'}}; + my @deps = @{$deptree{$key1}}; + my @blocks = @{$deptree{$key2}}; my %union = (); my %isect = (); foreach my $b (@deps, @blocks) { $union{$b}++ && $isect{$b}++ } diff --git a/Bugzilla/BugMail.pm b/Bugzilla/BugMail.pm index 47edac751..28ff871da 100644 --- a/Bugzilla/BugMail.pm +++ b/Bugzilla/BugMail.pm @@ -133,10 +133,10 @@ sub Send { } } - # Add dependencies to referenced bug list on new bugs + # Add dependencies and regressions to referenced bug list on new bugs if (!$start) { - push @referenced_bugs, @{$bug->dependson}; - push @referenced_bugs, @{$bug->blocked}; + push(@referenced_bugs, + map { @{$bug->$_} } qw(dependson blocked regressed_by regresses)); push @referenced_bugs, _parse_see_also(map { $_->name } @{$bug->see_also}); } @@ -596,7 +596,7 @@ sub _get_diffs { $diff->{num} = $comment->count; $diff->{isprivate} = $diff->{new}; } - elsif ($diff->{field_name} eq 'dependson' || $diff->{field_name} eq 'blocked') { + elsif ($diff->{field_name} =~ /^(?:dependson|blocked|regress(?:ed_by|es))$/) { push @$referenced_bugs, grep {/^\d+$/} split(/[\s,]+/, $diff->{old}); push @$referenced_bugs, grep {/^\d+$/} split(/[\s,]+/, $diff->{new}); } diff --git a/Bugzilla/Config/BugFields.pm b/Bugzilla/Config/BugFields.pm index 87ecc6122..9c08065e8 100644 --- a/Bugzilla/Config/BugFields.pm +++ b/Bugzilla/Config/BugFields.pm @@ -35,6 +35,8 @@ sub get_param_list { {name => 'usebugaliases', type => 'b', default => 0}, + {name => 'use_regression_fields', type => 'b', default => 1}, + {name => 'use_see_also', type => 'b', default => 1}, { diff --git a/Bugzilla/Field.pm b/Bugzilla/Field.pm index d94d92f4e..7a061b43b 100644 --- a/Bugzilla/Field.pm +++ b/Bugzilla/Field.pm @@ -285,6 +285,20 @@ use constant DEFAULT_FIELDS => ( is_numeric => 1, buglist => 1 }, + { + name => 'regressed_by', + desc => 'Regressed by', + in_new_bugmail => 1, + is_numeric => 1, + buglist => 1 + }, + { + name => 'regresses', + desc => 'Regressions', + in_new_bugmail => 1, + is_numeric => 1, + buglist => 1 + }, { name => 'assignee_last_login', diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm index dabe52efd..c61f448e7 100644 --- a/Bugzilla/Search.pm +++ b/Bugzilla/Search.pm @@ -287,6 +287,8 @@ use constant OPERATOR_FIELD_OVERRIDE => { _default => \&_invalid_combination, }, product => {_non_changed => \&_product_nonchanged,}, + regressed_by => MULTI_SELECT_OVERRIDE, + regresses => MULTI_SELECT_OVERRIDE, tag => MULTI_SELECT_OVERRIDE, comment_tag => MULTI_SELECT_OVERRIDE, @@ -483,6 +485,8 @@ sub COLUMN_JOINS { }, 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',}, last_visit_ts => { as => 'bug_user_last_visit', @@ -566,6 +570,9 @@ sub COLUMNS { 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', @@ -677,6 +684,8 @@ use constant GROUP_BY_SKIP => qw( keywords longdescs.count percentage_complete + regressed_by + regresses ); ############### @@ -3097,6 +3106,12 @@ sub _multiselect_table { $args->{full_field} = $field; return "dependencies"; } + elsif ($field eq 'regresses' or $field eq 'regressed_by') { + my $select = $field eq 'regresses' ? 'regressed_by' : 'regresses'; + $args->{_select_field} = $select; + $args->{full_field} = $field; + return "regressions"; + } elsif ($field eq 'longdesc') { $args->{_extra_where} = " AND isprivate = 0" if !$self->_user->is_insider; $args->{full_field} = 'thetext'; @@ -3198,6 +3213,17 @@ sub _multiselect_isempty { }; return "dependencies_$chart_id.$to IS $not NULL"; } + elsif ($field eq 'regresses' or $field eq 'regressed_by') { + my $to = $field eq 'regresses' ? 'regressed_by' : 'regresses'; + push @$joins, + { + table => 'regressions', + as => "regressions_$chart_id", + from => 'bug_id', + to => $to, + }; + return "regressions_$chart_id.$to IS $not NULL"; + } elsif ($field eq 'longdesc') { my @extra = ("longdescs_$chart_id.type != " . CMT_HAS_DUPE); push @extra, "longdescs_$chart_id.isprivate = 0" diff --git a/Bugzilla/Search/Quicksearch.pm b/Bugzilla/Search/Quicksearch.pm index 7d152aafb..02d1cf6e5 100644 --- a/Bugzilla/Search/Quicksearch.pm +++ b/Bugzilla/Search/Quicksearch.pm @@ -34,6 +34,9 @@ use constant MAPPINGS => { "os" => "op_sys", "severity" => "bug_severity", + # Dependencies, Regressions + "regressions" => "regresses", + # People: AssignedTo, Reporter, QA Contact, CC, etc. "assignee" => "assigned_to", "owner" => "assigned_to", diff --git a/Bugzilla/Test/Util.pm b/Bugzilla/Test/Util.pm index ed46ec104..b668aedb3 100644 --- a/Bugzilla/Test/Util.pm +++ b/Bugzilla/Test/Util.pm @@ -129,6 +129,10 @@ sub create_bug { {default => ""}, blocked => Str, {default => ""}, + regressed_by => Str, + {default => ""}, + regresses => Str, + {default => ""}, assigned_to => Str, bug_mentors => ArrayRef [Str], { diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm index bf5211f11..ad929f1b4 100644 --- a/Bugzilla/User.pm +++ b/Bugzilla/User.pm @@ -2374,6 +2374,8 @@ our %names_to_events = ( 'attachments.ispatch' => EVT_ATTACHMENT_DATA, 'dependson' => EVT_DEPEND_BLOCK, 'blocked' => EVT_DEPEND_BLOCK, + 'regressed_by' => EVT_DEPEND_BLOCK, + 'regresses' => EVT_DEPEND_BLOCK, 'product' => EVT_COMPONENT, 'component' => EVT_COMPONENT ); diff --git a/Bugzilla/WebService/Bug.pm b/Bugzilla/WebService/Bug.pm index 44b68eaa4..65d5d78af 100644 --- a/Bugzilla/WebService/Bug.pm +++ b/Bugzilla/WebService/Bug.pm @@ -1531,6 +1531,18 @@ sub _bug_to_hash { $item{'flags'} = [map { $self->_flag_to_hash($_) } @{$bug->flags}]; } + # Regressions + if (Bugzilla->params->{use_regression_fields}) { + if (filter_wants $params, 'regressed_by') { + my @regressed_by = map { $self->type('int', $_) } @{$bug->regressed_by}; + $item{'regressed_by'} = \@regressed_by; + } + if (filter_wants $params, 'regressions') { + my @regressions = map { $self->type('int', $_) } @{$bug->regresses}; + $item{'regressions'} = \@regressions; + } + } + # And now custom fields my @custom_fields = Bugzilla->active_custom_fields( { @@ -2882,6 +2894,14 @@ C The login name of the current QA Contact on the bug. C A hash containing detailed user information for the qa_contact. To see the keys included in the user detail hash, see below. +=item C + +C of Cs. The ids of bugs that introduced this bug. + +=item C + +C of Cs. The ids of bugs bugs that are introduced by this bug. + =item C C The number of hours of work remaining until work on this bug @@ -3107,8 +3127,9 @@ and all custom fields. =item The C item was added to the C return value in Bugzilla B<4.4>. -=item The C, C, C, C and -C fields were added in Bugzilla B<6.0>. +=item The C, C, C, C, +C, C and C fields were added in +Bugzilla B<6.0>. =back @@ -3712,8 +3733,9 @@ You didn't specify a summary for the bug. =item 116 (Dependency Loop) -You specified values in the C or C fields -that would cause a circular dependency between bugs. +You specified values in the C and C fields, +or the C and C fields, that would cause a +circular dependency between bugs. =item 120 (Group Restriction Denied) @@ -4541,6 +4563,14 @@ C Whether or not the bug's reporter is allowed to access the bug, even if he or she isn't in a group that can normally access the bug. +=item C + +C of Cs. The ids of bugs that introduced this bug. + +=item C + +C of Cs. The ids of bugs bugs that are introduced by this bug. + =item C C How much work time is remaining to fix the bug, in hours. @@ -4742,8 +4772,9 @@ The error message will have more detail. =item 116 (Dependency Loop) -You specified a value in the C or C fields that causes -a dependency loop. +You specified values in the C and C fields, +or the C and C fields, that would cause a +circular dependency between bugs. =item 117 (Invalid Comment ID) diff --git a/bugzilla.dtd b/bugzilla.dtd index e22a2bfd1..57fe3aadb 100644 --- a/bugzilla.dtd +++ b/bugzilla.dtd @@ -9,9 +9,9 @@ cclist_accessible, classification_id, classification, product, component, version, rep_platform, op_sys, bug_status, resolution?, dup_id?, see_also*, bug_file_loc?, status_whiteboard?, keywords*, priority, bug_severity, - target_milestone?, dependson*, blocked*, everconfirmed, reporter, assigned_to, - cc*, (estimated_time, remaining_time, actual_time, deadline?)?, qa_contact?, - votes?, token?, group*, flag*, long_desc*, attachment*)?)> + target_milestone?, dependson*, blocked*, regressed_by*, regresses*, everconfirmed, + reporter, assigned_to, cc*, (estimated_time, remaining_time, actual_time, deadline?)?, + qa_contact?, votes?, token?, group*, flag*, long_desc*, attachment*)?)> @@ -54,6 +54,8 @@ + + diff --git a/docs/en/rst/administering/parameters.rst b/docs/en/rst/administering/parameters.rst index 1b657ff3c..f5e875f69 100644 --- a/docs/en/rst/administering/parameters.rst +++ b/docs/en/rst/administering/parameters.rst @@ -245,6 +245,12 @@ usestatuswhiteboard easily searchable field for indexing bugs that have some trait in common. +use_regression_fields + Do you wish to use the :field:`Regressions` and :field:`Regressed by` + fields? These allow you to efficiently track software regressions, + which might previously be managed using the :field:`Depends on` and + :field:`Blocks` fields along with the “regression” keyword. + use_see_also Do you wish to use the :field:`See Also` field? It allows you mark bugs in other bug tracker installations as being related. Disabling this field diff --git a/docs/en/rst/api/core/v1/bug.rst b/docs/en/rst/api/core/v1/bug.rst index 91adb3326..7da9018cb 100644 --- a/docs/en/rst/api/core/v1/bug.rst +++ b/docs/en/rst/api/core/v1/bug.rst @@ -121,6 +121,8 @@ name type description }, "cf_free_text": "", "blocks": [], + "regressed_by": [], + "regressions": [], "comment_count": 12 } ] @@ -205,6 +207,8 @@ qa_contact string The login name of the current QA Contact on the qa_contact_detail object An object containing detailed user information for the qa_contact. To see the keys included in the user detail object, see below. +regressed_by array The IDs of bugs that introduced this bug. +regressions array The IDs of bugs that are introduced by this bug. remaining_time double The number of hours of work remaining until work on this bug is complete. If you are not in the time-tracking group, this field will not be @@ -673,6 +677,7 @@ flags array Flags objects to add to the bug. The object format keywords array One or more valid keywords to add to this bug. dependson array One or more valid bug ids that this bug depends on. blocked array One or more valid bug ids that this bug blocks. +regressed_by array One or more valid bug ids that introduced this bug. ================== ======= ==================================================== Flag object: @@ -728,8 +733,9 @@ id int This is the ID of the newly-filed bug. * 107 (Invalid Summary) You didn't specify a summary for the bug. * 116 (Dependency Loop) - You specified values in the "blocks" or "depends_on" fields - that would cause a circular dependency between bugs. + You specified values in the "blocks" and "depends_on" fields, + or the "regressions" and "regressed_by" fields, that would cause a + circular dependency between bugs. * 120 (Group Restriction Denied) You tried to restrict the bug to a group which does not exist, or which you cannot use with this product. @@ -818,11 +824,14 @@ alias object These specify the aliases of a bug that can be if you specified the set key above. assigned_to string The full login name of the user this bug is assigned to. -blocks object (Same as ``depends_on`` below) -depends_on object These specify the bugs that this bug blocks or - depends on, respectively. To set these, you - should pass an object as the value. The object - may contain the following items: +blocks object (Same as ``regressed_by`` below) +depends_on object (Same as ``regressed_by`` below) +regressions object (Same as ``regressed_by`` below) +regressed_by object These specify the bugs that this bug blocks, + depends on, regresses, or is regressed by, + respectively. To set these, you should pass an + object as the value. The object may contain the + following items: * ``add`` (array) Bug IDs to add to this field. * ``remove`` (array) Bug IDs to remove from this diff --git a/docs/en/rst/api/core/v1/bugzilla.rst b/docs/en/rst/api/core/v1/bugzilla.rst index 220ee5fc5..571f274e1 100644 --- a/docs/en/rst/api/core/v1/bugzilla.rst +++ b/docs/en/rst/api/core/v1/bugzilla.rst @@ -208,6 +208,7 @@ Example response for authenticated user: "rememberlogin" : "on", "requirelogin" : "0", "urlbase" : "http://bugzilla.example.com/", + "use_regression_fields" : "1", "use_see_also" : "1", "useclassification" : "1", "usemenuforusers" : "0", @@ -247,6 +248,7 @@ A logged-in user can access the following parameters (listed alphabetically): * requirelogin * search_allow_no_criteria * urlbase +* use_regression_fields * use_see_also * useclassification * usemenuforusers diff --git a/enter_bug.cgi b/enter_bug.cgi index c721c048e..aa07f679a 100755 --- a/enter_bug.cgi +++ b/enter_bug.cgi @@ -262,6 +262,7 @@ if ($cloned_bug_id) { $vars->{'keywords'} = $cloned_bug->keywords; $vars->{'dependson'} = join(", ", $cloned_bug_id, @{$cloned_bug->dependson}); $vars->{'blocked'} = join(", ", @{$cloned_bug->blocked}); + $vars->{'regressed_by'} = join(", ", $cloned_bug_id, @{$cloned_bug->regressed_by}); $vars->{'deadline'} = $cloned_bug->deadline; $vars->{'estimated_time'} = $cloned_bug->estimated_time; $vars->{'status_whiteboard'} = $cloned_bug->status_whiteboard; @@ -328,6 +329,7 @@ else { $vars->{'keywords'} = formvalue('keywords'); $vars->{'dependson'} = formvalue('dependson'); $vars->{'blocked'} = formvalue('blocked'); + $vars->{'regressed_by'} = formvalue('regressed_by'); $vars->{'deadline'} = formvalue('deadline'); $vars->{'estimated_time'} = formvalue('estimated_time'); $vars->{'bug_ignored'} = formvalue('bug_ignored'); diff --git a/extensions/BMO/template/en/default/pages/user_activity.html.tmpl b/extensions/BMO/template/en/default/pages/user_activity.html.tmpl index 740f8129e..993f59314 100644 --- a/extensions/BMO/template/en/default/pages/user_activity.html.tmpl +++ b/extensions/BMO/template/en/default/pages/user_activity.html.tmpl @@ -193,7 +193,9 @@ change.fieldname == 'work_time' %] [% PROCESS formattimeunit time_unit=change_type %] [% ELSIF change.fieldname == 'blocked' || - change.fieldname == 'dependson' %] + change.fieldname == 'dependson' || + change.fieldname == 'regresses' || + change.fieldname == 'regressed_by' %] [% change_type FILTER bug_list_link FILTER none %] [% ELSIF change.fieldname == 'assigned_to' || change.fieldname == 'reporter' || diff --git a/extensions/BugModal/lib/ActivityStream.pm b/extensions/BugModal/lib/ActivityStream.pm index 0035d7ab0..a0e1b18fd 100644 --- a/extensions/BugModal/lib/ActivityStream.pm +++ b/extensions/BugModal/lib/ActivityStream.pm @@ -268,9 +268,7 @@ sub _add_activities_to_stream { } # identify buglist changes - if ( $change->{fieldname} eq 'blocked' - || $change->{fieldname} eq 'dependson' - || $change->{fieldname} eq 'dupe' + if ($change->{fieldname} =~ /^(?:dependson|blocked|regress(?:ed_by|es)|dupe)$/ || ($field_obj && $field_obj->type == FIELD_TYPE_BUG_ID)) { $change->{buglist} = 1; diff --git a/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl index d3d539426..51473ac7c 100644 --- a/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl +++ b/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl @@ -799,11 +799,18 @@ sub = []; open_deps = bug.depends_on_obj.only("resolution", "").size; IF open_deps; - sub.push("Depends on: " _ open_deps _ " bug" _ (open_deps == 1 ? "" : "s")); + sub.push("Depends on " _ open_deps _ " bug" _ (open_deps == 1 ? "" : "s")); END; open_deps = bug.blocks_obj.only("resolution", "").size; IF open_deps; - sub.push("Blocks: " _ open_deps _ " bug" _ (open_deps == 1 ? "" : "s")); + sub.push("Blocks " _ open_deps _ " bug" _ (open_deps == 1 ? "" : "s")); + END; + IF bug.regressed_by.size; + sub.push("Regression"); + END; + open_regs = bug.regresses_obj.only("resolution", "").size; + IF open_regs; + sub.push("Regressed " _ open_regs _ " bug" _ (open_regs == 1 ? "" : "s")); END; IF bug.keyword_objects.size; IF bug.keyword_objects.size <= 3; @@ -954,6 +961,24 @@ [% END %] [% END %] + [%# regressions %] + [% IF Param('use_regression_fields') %] + [% INCLUDE bug_modal/field.html.tmpl + field = bug_fields.regresses + field_type = constants.FIELD_TYPE_BUG_LIST + values = bug.regresses_obj + hide_on_view = bug.regresses.size == 0 + help = "https://wiki.mozilla.org/BMO/UserGuide/BugFields#regresses" + %] + [% INCLUDE bug_modal/field.html.tmpl + field = bug_fields.regressed_by + field_type = constants.FIELD_TYPE_BUG_LIST + values = bug.regressed_by_obj + hide_on_view = bug.regressed_by.size == 0 + help = "https://wiki.mozilla.org/BMO/UserGuide/BugFields#regressed_by" + %] + [% END %] + [%# duplicates %] [% IF bug.duplicates.size %] [% INCLUDE bug_modal/field.html.tmpl @@ -1398,6 +1423,10 @@ … that depends on this [% terms.bug %] +
  • + … that is regressed by this [% terms.bug %] +
  • {$field}) { $params->{$field} = {set => $params->{$field}}; } diff --git a/extensions/InlineHistory/Extension.pm b/extensions/InlineHistory/Extension.pm index 45f1120f8..7ccaf31e6 100644 --- a/extensions/InlineHistory/Extension.pm +++ b/extensions/InlineHistory/Extension.pm @@ -118,9 +118,7 @@ sub template_before_process { } # identify buglist changes - if ( $change->{fieldname} eq 'blocked' - || $change->{fieldname} eq 'dependson' - || $change->{fieldname} eq 'dupe' + if ($change->{fieldname} =~ /^(?:dependson|blocked|regress(?:ed_by|es)|dupe)$/ || ($field_obj && $field_obj->type == FIELD_TYPE_BUG_ID)) { $change->{buglist} = 1; diff --git a/extensions/MyDashboard/Extension.pm b/extensions/MyDashboard/Extension.pm index ae7921af7..c94ecba4a 100644 --- a/extensions/MyDashboard/Extension.pm +++ b/extensions/MyDashboard/Extension.pm @@ -223,7 +223,8 @@ sub bug_end_of_update { if ($changes->{bug_status}) { my ($old_status, $new_status) = @{$changes->{bug_status}}; if (is_open_state($old_status) && !is_open_state($new_status)) { - my @related_bugs = (@{$bug->blocks_obj}, @{$bug->depends_on_obj}); + my @related_bugs = map { @{$bug->{$_} || []} } + qw(blocks_obj depends_on_obj regresses_obj regressed_by_obj); my %involved; foreach my $related_bug (@related_bugs) { diff --git a/importxml.pl b/importxml.pl index 7e8ca0cfd..2c5aebdf4 100755 --- a/importxml.pl +++ b/importxml.pl @@ -548,6 +548,14 @@ sub process_bug { $comments .= "This bug blocked bug(s) " . join(' ', _to_array($bug_fields{'blocked'})) . ".\n"; } + if (defined $bug_fields{'regressed_by'}) { + $comments .= "This bug is regressed by bug(s) " + . join(' ', _to_array($bug_fields{'regressed_by'})) . ".\n"; + } + if (defined $bug_fields{'regresses'}) { + $comments .= "This bug regressed bug(s) " + . join(' ', _to_array($bug_fields{'regresses'})) . ".\n"; + } # Now we process each of the fields in turn and make sure they contain # valid data. We will create two parallel arrays, one for the query diff --git a/post_bug.cgi b/post_bug.cgi index cb4e5db99..cbcfc68e6 100755 --- a/post_bug.cgi +++ b/post_bug.cgi @@ -120,6 +120,8 @@ push( short_desc op_sys priority + regressed_by + regresses rep_platform version target_milestone @@ -276,7 +278,9 @@ $bug_sent->{type} = 'created'; $bug_sent->{id} = $id; my @all_mail_results = ($bug_sent); -foreach my $dep (@{$bug->dependson || []}, @{$bug->blocked || []}) { +foreach my $dep ( + map { @{$bug->{$_} || []} } qw(dependson blocked regressed_by regresses) +) { my $dep_sent = Bugzilla::BugMail::Send($dep, $recipients); $dep_sent->{type} = 'dep'; $dep_sent->{id} = $dep; diff --git a/process_bug.cgi b/process_bug.cgi index 062af5008..4741dfc57 100755 --- a/process_bug.cgi +++ b/process_bug.cgi @@ -284,7 +284,7 @@ if (should_set('see_also')) { if (should_set('remove_see_also')) { $set_all_fields{'see_also'}->{remove} = [$cgi->param('remove_see_also')]; } -foreach my $dep_field (qw(dependson blocked)) { +foreach my $dep_field (qw(dependson blocked regressed_by regresses)) { if (should_set($dep_field)) { if (my $dep_action = $cgi->param("${dep_field}_action")) { $set_all_fields{$dep_field}->{$dep_action} diff --git a/qa/t/webservice_bug_fields.t b/qa/t/webservice_bug_fields.t index 110ed5741..244a92514 100644 --- a/qa/t/webservice_bug_fields.t +++ b/qa/t/webservice_bug_fields.t @@ -14,7 +14,7 @@ use List::Util qw(first); use QA::Util; my ($config, @clients) = get_rpc_clients(); -plan tests => ($config->{test_extensions} ? 1338 : 1320); +plan tests => ($config->{test_extensions} ? 1374 : 1356); use constant INVALID_FIELD_NAME => 'invalid_field'; use constant INVALID_FIELD_ID => -1; @@ -56,6 +56,8 @@ sub GLOBAL_GENERAL_FIELDS { owner_idle_time product qa_contact + regressed_by + regresses reporter reporter_accessible see_also diff --git a/qa/t/webservice_bug_update.t b/qa/t/webservice_bug_update.t index 4012dccc6..c92debbbb 100644 --- a/qa/t/webservice_bug_update.t +++ b/qa/t/webservice_bug_update.t @@ -399,7 +399,7 @@ sub invalid_values { }, { value => {add => [$public_id]}, - error => 'block itself or depend on itself', + error => 'block itself, depend on itself', test => "can't add this bug itself in a dep field" }, diff --git a/sanitycheck.cgi b/sanitycheck.cgi index 71ad9eab0..cf95950b2 100755 --- a/sanitycheck.cgi +++ b/sanitycheck.cgi @@ -306,7 +306,8 @@ if ($cgi->param('remove_invalid_bug_references')) { 'cc/', 'dependencies/blocked', 'dependencies/dependson', 'duplicates/dupe', 'duplicates/dupe_of', 'flags/', - 'keywords/', 'longdescs/' + 'keywords/', 'longdescs/', + 'regressions/regresses', 'regressions/regressed_by' ) { @@ -516,6 +517,8 @@ CrossCheck( ["longdescs", "bug_id"], ["dependencies", "blocked"], ["dependencies", "dependson"], + ["regressions", "regresses"], + ["regressions", "regressed_by"], ['flags', 'bug_id'], ["keywords", "bug_id"], ["duplicates", "dupe_of", "dupe"], diff --git a/scripts/remove-non-public-data.pl b/scripts/remove-non-public-data.pl index bca2440ea..e72c49d44 100755 --- a/scripts/remove-non-public-data.pl +++ b/scripts/remove-non-public-data.pl @@ -150,6 +150,11 @@ my %whitelist = ( userid login_name realname is_enabled creation_ts ) ], + regressions => [ + qw( + regresses regressed_by + ) + ], tracking_flags => [ qw( id field_id name description type sortkey is_active diff --git a/showdependencygraph.cgi b/showdependencygraph.cgi index 40de2af9e..be0699412 100755 --- a/showdependencygraph.cgi +++ b/showdependencygraph.cgi @@ -165,7 +165,8 @@ if ($display eq 'web') { else { my @blocker_stack = @stack; foreach my $id (@blocker_stack) { - my $blocker_ids = Bugzilla::Bug::EmitDependList('blocked', 'dependson', $id); + my $blocker_ids + = Bugzilla::Bug::list_relationship('dependencies', 'blocked', 'dependson', $id); foreach my $blocker_id (@$blocker_ids) { push(@blocker_stack, $blocker_id) unless $seen{$blocker_id}; $AddLink->($id, $blocker_id, $fh); @@ -173,7 +174,8 @@ else { } my @dependent_stack = @stack; foreach my $id (@dependent_stack) { - my $dep_bug_ids = Bugzilla::Bug::EmitDependList('dependson', 'blocked', $id); + my $dep_bug_ids + = Bugzilla::Bug::list_relationship('dependencies', 'dependson', 'blocked', $id); foreach my $dep_bug_id (@$dep_bug_ids) { push(@dependent_stack, $dep_bug_id) unless $seen{$dep_bug_id}; $AddLink->($dep_bug_id, $id, $fh); diff --git a/showdependencytree.cgi b/showdependencytree.cgi index 08fee75b6..dd8fce7a8 100755 --- a/showdependencytree.cgi +++ b/showdependencytree.cgi @@ -143,9 +143,9 @@ sub _get_dependencies { my $cache = Bugzilla->request_cache->{dependency_cache} ||= {}; return $cache->{$bug_id}->{$relationship} ||= $relationship eq 'dependson' - ? Bugzilla::Bug::EmitDependList('blocked', 'dependson', $bug_id, - $hide_resolved) - : Bugzilla::Bug::EmitDependList('dependson', 'blocked', $bug_id, - $hide_resolved); + ? Bugzilla::Bug::list_relationship('dependencies', 'blocked', 'dependson', + $bug_id, $hide_resolved) + : Bugzilla::Bug::list_relationship('dependencies', 'dependson', 'blocked', + $bug_id, $hide_resolved); } diff --git a/summarize_time.cgi b/summarize_time.cgi index 979629729..b6a87830a 100755 --- a/summarize_time.cgi +++ b/summarize_time.cgi @@ -16,7 +16,7 @@ use Date::Parse; # strptime use Bugzilla; use Bugzilla::Constants; # LOGIN_* -use Bugzilla::Bug; # EmitDependList +use Bugzilla::Bug; # list_relationship use Bugzilla::Util; # trim use Bugzilla::Error; @@ -170,7 +170,8 @@ sub sqlize_dates { sub get_blocker_ids { my ($bug_id, $unique) = @_; $unique ||= {$bug_id => 1}; - my $deps = Bugzilla::Bug::EmitDependList("blocked", "dependson", $bug_id); + my $deps + = Bugzilla::Bug::list_relationship('dependencies', 'blocked', 'dependson', $bug_id); my @unseen = grep { !$unique->{$_}++ } @$deps; foreach $bug_id (@unseen) { get_blocker_ids($bug_id, $unique); diff --git a/template/en/default/admin/params/bugfields.html.tmpl b/template/en/default/admin/params/bugfields.html.tmpl index 9c09610e1..ef5ec55e5 100644 --- a/template/en/default/admin/params/bugfields.html.tmpl +++ b/template/en/default/admin/params/bugfields.html.tmpl @@ -37,6 +37,11 @@ usebugaliases => "Do you wish to use $terms.bug aliases, which allow you to assign " _ "$terms.bugs an easy-to-remember name by which you can refer to them?", + use_regression_fields => + "Do you wish to use the “Regressions” and “Regressed by” fields? These allow you to" + _ " efficiently track software regressions, which might previously be managed using" + _ " the “Depends on” and “Blocks” fields along with the “regression” keyword." + use_see_also => "Do you wish to use the See Also field? It allows you refer to" _ " $terms.bugs in other installations. Even if you disable this field," diff --git a/template/en/default/bug/activity/table.html.tmpl b/template/en/default/bug/activity/table.html.tmpl index 17fbd9f33..97683590b 100644 --- a/template/en/default/bug/activity/table.html.tmpl +++ b/template/en/default/bug/activity/table.html.tmpl @@ -101,7 +101,9 @@ change.fieldname == 'work_time' %] [% PROCESS formattimeunit time_unit=change_type %] [% ELSIF change.fieldname == 'blocked' || - change.fieldname == 'dependson' %] + change.fieldname == 'dependson' || + change.fieldname == 'regresses' || + change.fieldname == 'regressed_by' %] [% change_type FILTER bug_list_link FILTER none %] [% ELSIF change.fieldname == 'assigned_to' || change.fieldname == 'reporter' || diff --git a/template/en/default/bug/create/create.html.tmpl b/template/en/default/bug/create/create.html.tmpl index 223209efd..3383954f1 100644 --- a/template/en/default/bug/create/create.html.tmpl +++ b/template/en/default/bug/create/create.html.tmpl @@ -608,6 +608,21 @@ TUI_hide_default('expert_fields'); + [% IF Param('use_regression_fields') %] + + [% INCLUDE "bug/field-label.html.tmpl" + field = bug_fields.regressed_by + editable = 1 + %] + + + + + [%~# Regressions field is hidden from new bugs ~%] + + + [% END %] + [% IF use_keywords %] [% INCLUDE bug/field.html.tmpl diff --git a/template/en/default/bug/edit.html.tmpl b/template/en/default/bug/edit.html.tmpl index 0788b2d7f..9e51d9fba 100644 --- a/template/en/default/bug/edit.html.tmpl +++ b/template/en/default/bug/edit.html.tmpl @@ -133,11 +133,15 @@ [% PROCESS section_spacer %] - [%# *** Dependencies and duplicates *** %] + [%# *** Duplicates, Dependencies and Regressions *** %] [% PROCESS section_duplicates %] [% PROCESS section_dependson_blocks %] + [% IF Param('use_regression_fields') %] + [% PROCESS section_regressions %] + [% END %] + [% IF user.id %] @@ -704,6 +708,24 @@ [% END %] +[%############################################################################%] +[%# Block for Regressed by / Regressions #%] +[%############################################################################%] + +[% BLOCK section_regressions %] + + [% INCLUDE dependencies + field = bug_fields.regresses + deps = bug.regresses_obj + %] + + + [% INCLUDE dependencies + field = bug_fields.regressed_by + deps = bug.regressed_by_obj + %] + +[% END %] [%############################################################################%] [%# Block for Restricting Visibility #%] diff --git a/template/en/default/bug/field-help.none.tmpl b/template/en/default/bug/field-help.none.tmpl index 782318c8f..9209525b9 100644 --- a/template/en/default/bug/field-help.none.tmpl +++ b/template/en/default/bug/field-help.none.tmpl @@ -121,6 +121,12 @@ qa_contact => _ " unconfirmed, and for verifying the fix once the $terms.bug" _ " has been resolved.", +regressed_by => + "This $terms.bug has been introduced by the $terms.bugs listed here.", + +regresses => + "This $terms.bug has introduced the $terms.bugs listed here.", + remaining_time => "The number of hours of work left on this $terms.bug, calculated by subtracting the $vars.field_descs.work_time from the diff --git a/template/en/default/bug/show-multiple.html.tmpl b/template/en/default/bug/show-multiple.html.tmpl index 288af30af..d335c5559 100644 --- a/template/en/default/bug/show-multiple.html.tmpl +++ b/template/en/default/bug/show-multiple.html.tmpl @@ -214,6 +214,11 @@ [% PROCESS dependencies name = "blocked" %] [% END %] + [% IF (bug.regressed_by.size || bug.regresses.size) %] + [% PROCESS dependencies name = "regresses" %] + [% PROCESS dependencies name = "regressed_by" %] + [% END %] + [% IF user.is_timetracker %] Time tracking: diff --git a/template/en/default/global/field-descs.none.tmpl b/template/en/default/global/field-descs.none.tmpl index cb6240c29..99584f8fd 100644 --- a/template/en/default/global/field-descs.none.tmpl +++ b/template/en/default/global/field-descs.none.tmpl @@ -136,6 +136,8 @@ if ( $stash->get("in_template_var") ) { "product" => "Product", "qa_contact" => "QA Contact", "qa_contact_realname" => "QA Contact Real Name", + "regressed_by" => "Regressed by", + "regresses" => "Regressions", "remaining_time" => "Hours Left", "rep_platform" => "Hardware", "reporter" => "Reporter", diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl index 40ac8c5b0..3bdb5c6a8 100644 --- a/template/en/default/global/user-error.html.tmpl +++ b/template/en/default/global/user-error.html.tmpl @@ -507,9 +507,10 @@ [% ELSIF error == "dependency_loop_multi" %] [% title = "Dependency Loop Detected" %] - The following [% terms.bug %](s) would appear on both the "depends on" - and "blocks" parts of the dependency tree if these changes - are committed: + The following [% IF deps.size > 1; terms.bugs; ELSE; terms.bug; END %] + would appear on both the “Depends on” and “blocks” fields, or both the + “Regressions” and “Regressed by” fields, of the dependency tree if these + changes are committed: [% FOREACH dep = deps %] [%+ dep FILTER bug_link(dep) FILTER none %] [% END %]. @@ -517,7 +518,8 @@ [% ELSIF error == "dependency_loop_single" %] [% title = "Dependency Loop Detected" %] - You can't make [% terms.abug %] block itself or depend on itself. + You can’t make [% terms.abug %] block itself, depend on itself, regress + itself, or regressed by itself. [% ELSIF error == "dupe_id_required" %] [% title = "Duplicate $terms.Bug Id Required" %] diff --git a/template/en/default/list/edit-multiple.html.tmpl b/template/en/default/list/edit-multiple.html.tmpl index 88525de12..0637eb159 100644 --- a/template/en/default/list/edit-multiple.html.tmpl +++ b/template/en/default/list/edit-multiple.html.tmpl @@ -253,6 +253,38 @@ + [% IF Param('use_regression_fields') %] + + + + + + + + + + + + + + + + + + + + [% END %] + [% IF Param('usestatuswhiteboard') %] diff --git a/template/en/default/list/table.html.tmpl b/template/en/default/list/table.html.tmpl index c4a853624..fd572f433 100644 --- a/template/en/default/list/table.html.tmpl +++ b/template/en/default/list/table.html.tmpl @@ -62,6 +62,8 @@ "keywords" => { maxlength => 0, wrap => 1 } , "dependson" => { maxlength => 0, wrap => 1 } , "blocked" => { maxlength => 0, wrap => 1 } , + "regressed_by" => { maxlength => 0, wrap => 1 } , + "regresses" => { maxlength => 0, wrap => 1 } , "flagtypes.name" => { maxlength => 0, wrap => 1 } , "component" => { maxlength => 8 , title => "Comp" } , "product" => { maxlength => 8 } , diff --git a/template/en/default/pages/bugzilla.dtd.tmpl b/template/en/default/pages/bugzilla.dtd.tmpl index 09d83fb7f..f0da8d78e 100644 --- a/template/en/default/pages/bugzilla.dtd.tmpl +++ b/template/en/default/pages/bugzilla.dtd.tmpl @@ -61,6 +61,8 @@ target_milestone?, dependson*, blocked*, + regressed_by*, + regresses*, everconfirmed, reporter, assigned_to, @@ -122,6 +124,8 @@ + + diff --git a/xt/lib/Bugzilla/Test/Search.pm b/xt/lib/Bugzilla/Test/Search.pm index b1651916e..30dbfb47d 100644 --- a/xt/lib/Bugzilla/Test/Search.pm +++ b/xt/lib/Bugzilla/Test/Search.pm @@ -987,7 +987,8 @@ sub _setup_dependencies { my $bug = $self->bug($number); my @original_delta = ($bug2->delta_ts, $bug3->delta_ts); Bugzilla->set_user($bug->reporter); - $bug->set_dependencies([$bug2->id], [$bug3->id]); + $bug->set_relationship('dependencies', + ('dependson' => $bug2->id, 'blocked' => $bug3->id)); $bug->update($bug->delta_ts); # Setting dependencies changed the delta_ts on bug2 and bug3, so