Commit 89cf83ec authored by Max Kanat-Alexander's avatar Max Kanat-Alexander

Bug 622203 - Allow filing closed bugs via the WebService

r=dkl, a=mkanat
parent b308699b
...@@ -124,6 +124,7 @@ sub VALIDATORS { ...@@ -124,6 +124,7 @@ sub VALIDATORS {
my $validators = { my $validators = {
alias => \&_check_alias, alias => \&_check_alias,
assigned_to => \&_check_assigned_to, assigned_to => \&_check_assigned_to,
blocked => \&_check_dependencies,
bug_file_loc => \&_check_bug_file_loc, bug_file_loc => \&_check_bug_file_loc,
bug_severity => \&_check_select_field, bug_severity => \&_check_select_field,
bug_status => \&_check_bug_status, bug_status => \&_check_bug_status,
...@@ -132,6 +133,7 @@ sub VALIDATORS { ...@@ -132,6 +133,7 @@ sub VALIDATORS {
component => \&_check_component, component => \&_check_component,
creation_ts => \&_check_creation_ts, creation_ts => \&_check_creation_ts,
deadline => \&_check_deadline, deadline => \&_check_deadline,
dependson => \&_check_dependencies,
dup_id => \&_check_dup_id, dup_id => \&_check_dup_id,
estimated_time => \&_check_time_field, estimated_time => \&_check_time_field,
everconfirmed => \&Bugzilla::Object::check_boolean, everconfirmed => \&Bugzilla::Object::check_boolean,
...@@ -187,14 +189,16 @@ sub VALIDATOR_DEPENDENCIES { ...@@ -187,14 +189,16 @@ sub VALIDATOR_DEPENDENCIES {
my %deps = ( my %deps = (
assigned_to => ['component'], assigned_to => ['component'],
blocked => ['product'],
bug_status => ['product', 'comment', 'target_milestone'], bug_status => ['product', 'comment', 'target_milestone'],
cc => ['component'], cc => ['component'],
comment => ['creation_ts'], comment => ['creation_ts'],
component => ['product'], component => ['product'],
dependson => ['product'],
dup_id => ['bug_status', 'resolution'], dup_id => ['bug_status', 'resolution'],
groups => ['product'], groups => ['product'],
keywords => ['product'], keywords => ['product'],
resolution => ['bug_status'], resolution => ['bug_status', 'dependson'],
qa_contact => ['component'], qa_contact => ['component'],
target_milestone => ['product'], target_milestone => ['product'],
version => ['product'], version => ['product'],
...@@ -749,12 +753,7 @@ sub run_create_validators { ...@@ -749,12 +753,7 @@ sub run_create_validators {
$class->_check_strict_isolation($params->{cc}, $params->{assigned_to}, $class->_check_strict_isolation($params->{cc}, $params->{assigned_to},
$params->{qa_contact}, $product); $params->{qa_contact}, $product);
($params->{dependson}, $params->{blocked}) = # You can't set these fields.
$class->_check_dependencies($params->{dependson}, $params->{blocked},
$product);
# You can't set these fields on bug creation (or sometimes ever).
delete $params->{resolution};
delete $params->{lastdiffed}; delete $params->{lastdiffed};
delete $params->{bug_id}; delete $params->{bug_id};
...@@ -769,6 +768,11 @@ sub run_create_validators { ...@@ -769,6 +768,11 @@ sub run_create_validators {
$params); $params);
} }
# And this is not a valid DB field, it's just used as part of
# _check_dependencies to avoid running it twice for both blocked
# and dependson.
delete $params->{_dependencies_validated};
return $params; return $params;
} }
...@@ -1370,9 +1374,9 @@ sub _check_bug_status { ...@@ -1370,9 +1374,9 @@ sub _check_bug_status {
# Check if a comment is required for this change. # Check if a comment is required for this change.
if ($new_status->comment_required_on_change_from($old_status) && !$comment) if ($new_status->comment_required_on_change_from($old_status) && !$comment)
{ {
ThrowUserError('comment_required', { old => $old_status, ThrowUserError('comment_required',
new => $new_status }); { old => $old_status->name, new => $new_status->name,
field => 'bug_status' });
} }
if (ref $invocant if (ref $invocant
...@@ -1453,7 +1457,24 @@ sub _check_comment { ...@@ -1453,7 +1457,24 @@ sub _check_comment {
); );
return $comment; return $comment;
}
sub _check_commenton {
my ($invocant, $new_value, $field, $params) = @_;
my $has_comment =
ref($invocant) ? $invocant->{added_comments}
: (defined $params->{comment}
and $params->{comment}->{thetext} ne '');
my $is_changing = ref($invocant) ? $invocant->$field ne $new_value
: $new_value ne '';
if ($is_changing && !$has_comment) {
my $old_value = ref($invocant) ? $invocant->$field : undef;
ThrowUserError('comment_required',
{ field => $field, old => $old_value, new => $new_value });
}
} }
sub _check_component { sub _check_component {
...@@ -1492,15 +1513,23 @@ sub _check_deadline { ...@@ -1492,15 +1513,23 @@ sub _check_deadline {
# Takes two comma/space-separated strings and returns arrayrefs # Takes two comma/space-separated strings and returns arrayrefs
# of valid bug IDs. # of valid bug IDs.
sub _check_dependencies { sub _check_dependencies {
my ($invocant, $depends_on, $blocks, $product) = @_; my ($invocant, $value, $field, $params) = @_;
return $value if $params->{_dependencies_validated};
if (!ref $invocant) { if (!ref $invocant) {
# Only editbugs users can set dependencies on bug entry. # Only editbugs users can set dependencies on bug entry.
return ([], []) unless Bugzilla->user->in_group('editbugs', return ([], []) unless Bugzilla->user->in_group(
$product->id); 'editbugs', $params->{product}->id);
} }
my %deps_in = (dependson => $depends_on || '', blocked => $blocks || ''); # This is done this way so that dependson and blocked can be in
# VALIDATORS, meaning that they can be in VALIDATOR_DEPENDENCIES,
# which means that they can be checked in the right order during
# bug creation.
my $opposite = $field eq 'dependson' ? 'blocked' : 'dependson';
my %deps_in = ($field => $value || '',
$opposite => $params->{$opposite} || '');
foreach my $type (qw(dependson blocked)) { foreach my $type (qw(dependson blocked)) {
my @bug_ids = ref($deps_in{$type}) my @bug_ids = ref($deps_in{$type})
...@@ -1546,9 +1575,12 @@ sub _check_dependencies { ...@@ -1546,9 +1575,12 @@ sub _check_dependencies {
# And finally, check for dependency loops. # And finally, check for dependency loops.
my $bug_id = ref($invocant) ? $invocant->id : 0; my $bug_id = ref($invocant) ? $invocant->id : 0;
my %deps = ValidateDependencies($deps_in{dependson}, $deps_in{blocked}, $bug_id); my %deps = ValidateDependencies($deps_in{dependson}, $deps_in{blocked},
$bug_id);
return ($deps{'dependson'}, $deps{'blocked'}); $params->{$opposite} = $deps{$opposite};
$params->{_dependencies_validated} = 1;
return $deps{$field};
} }
sub _check_dup_id { sub _check_dup_id {
...@@ -1760,41 +1792,46 @@ sub _check_reporter { ...@@ -1760,41 +1792,46 @@ sub _check_reporter {
} }
sub _check_resolution { sub _check_resolution {
my ($self, $resolution) = @_; my ($invocant, $resolution, undef, $params) = @_;
$resolution = trim($resolution); $resolution = trim($resolution);
my $status = ref($invocant) ? $invocant->status->name
: $params->{bug_status};
my $is_open = ref($invocant) ? $invocant->status->is_open
: is_open_state($status);
# Throw a special error for resolving bugs without a resolution # Throw a special error for resolving bugs without a resolution
# (or trying to change the resolution to '' on a closed bug without # (or trying to change the resolution to '' on a closed bug without
# using clear_resolution). # using clear_resolution).
ThrowUserError('missing_resolution', { status => $self->status->name }) ThrowUserError('missing_resolution', { status => $status })
if !$resolution && !$self->status->is_open; if !$resolution && !$is_open;
# Make sure this is a valid resolution. # Make sure this is a valid resolution.
$resolution = $self->_check_select_field($resolution, 'resolution'); $resolution = $invocant->_check_select_field($resolution, 'resolution');
# Don't allow open bugs to have resolutions. # Don't allow open bugs to have resolutions.
ThrowUserError('resolution_not_allowed') if $self->status->is_open; ThrowUserError('resolution_not_allowed') if $is_open;
# Check noresolveonopenblockers. # Check noresolveonopenblockers.
my $dependson = ref($invocant) ? $invocant->dependson
: ($params->{dependson} || []);
if (Bugzilla->params->{"noresolveonopenblockers"} if (Bugzilla->params->{"noresolveonopenblockers"}
&& $resolution eq 'FIXED' && $resolution eq 'FIXED'
&& (!$self->resolution || $resolution ne $self->resolution) && (!ref $invocant or !$invocant->resolution
&& scalar @{$self->dependson}) or $resolution ne $invocant->resolution)
&& scalar @$dependson)
{ {
my $dep_bugs = Bugzilla::Bug->new_from_list($self->dependson); my $dep_bugs = Bugzilla::Bug->new_from_list($dependson);
my $count_open = grep { $_->isopened } @$dep_bugs; my $count_open = grep { $_->isopened } @$dep_bugs;
if ($count_open) { if ($count_open) {
my $bug_id = ref($invocant) ? $invocant->id : undef;
ThrowUserError("still_unresolved_bugs", ThrowUserError("still_unresolved_bugs",
{ bug_id => $self->id, dep_count => $count_open }); { bug_id => $bug_id, dep_count => $count_open });
} }
} }
# Check if they're changing the resolution and need to comment. # Check if they're changing the resolution and need to comment.
if (Bugzilla->params->{'commentonchange_resolution'} if (Bugzilla->params->{'commentonchange_resolution'}) {
&& $self->resolution && $resolution ne $self->resolution $invocant->_check_commenton($resolution, 'resolution', $params);
&& !$self->{added_comments})
{
ThrowUserError('comment_required');
} }
return $resolution; return $resolution;
...@@ -2327,7 +2364,9 @@ sub set_custom_field { ...@@ -2327,7 +2364,9 @@ sub set_custom_field {
sub set_deadline { $_[0]->set('deadline', $_[1]); } sub set_deadline { $_[0]->set('deadline', $_[1]); }
sub set_dependencies { sub set_dependencies {
my ($self, $dependson, $blocked) = @_; my ($self, $dependson, $blocked) = @_;
($dependson, $blocked) = $self->_check_dependencies($dependson, $blocked); my %extra = ( blocked => $blocked );
$dependson = $self->_check_dependencies($dependson, 'dependson', \%extra);
$blocked = $extra{blocked};
# These may already be detainted, but all setters are supposed to # These may already be detainted, but all setters are supposed to
# detaint their input if they've run a validator (just as though # detaint their input if they've run a validator (just as though
# we had used Bugzilla::Object::set), so we do that here. # we had used Bugzilla::Object::set), so we do that here.
...@@ -3854,6 +3893,8 @@ sub map_fields { ...@@ -3854,6 +3893,8 @@ sub map_fields {
my %field_values; my %field_values;
foreach my $field (keys %$params) { foreach my $field (keys %$params) {
# Don't allow setting private fields via email_in or the WebService.
next if $field =~ /^_/;
my $field_name; my $field_name;
if ($except->{$field}) { if ($except->{$field}) {
$field_name = $field; $field_name = $field;
......
...@@ -2288,6 +2288,11 @@ the component's default QA Contact. ...@@ -2288,6 +2288,11 @@ the component's default QA Contact.
=item C<status> (string) - The status that this bug should start out as. =item C<status> (string) - The status that this bug should start out as.
Note that only certain statuses can be set on bug creation. Note that only certain statuses can be set on bug creation.
=item C<resolution> (string) - If you are filing a closed bug, then
you will have to specify a resolution. You cannot currently specify
a resolution of C<DUPLICATE> for new bugs, though. That must be done
with L</update>.
=item C<target_milestone> (string) - A valid target milestone for this =item C<target_milestone> (string) - A valid target milestone for this
product. product.
...@@ -2370,6 +2375,9 @@ argument. ...@@ -2370,6 +2375,9 @@ argument.
=item Error 116 was added in Bugzilla B<4.0>. Before that, dependency =item Error 116 was added in Bugzilla B<4.0>. Before that, dependency
loop errors had a generic code of C<32000>. loop errors had a generic code of C<32000>.
=item The ability to file new bugs with a C<resolution> was added in
Bugzilla B<4.2>.
=back =back
=back =back
......
...@@ -87,7 +87,7 @@ elsif ($action eq 'update') { ...@@ -87,7 +87,7 @@ elsif ($action eq 'update') {
# Part 1: Initial bug statuses. # Part 1: Initial bug statuses.
foreach my $new (@$statuses) { foreach my $new (@$statuses) {
if ($new->is_open && $cgi->param('w_0_' . $new->id)) { if ($cgi->param('w_0_' . $new->id)) {
$sth_insert->execute(undef, $new->id) $sth_insert->execute(undef, $new->id)
unless defined $workflow->{0}->{$new->id}; unless defined $workflow->{0}->{$new->id};
} }
......
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
</th> </th>
[% FOREACH new_status = statuses %] [% FOREACH new_status = statuses %]
[% IF status.id != new_status.id && (status.id || new_status.is_open) %] [% IF status.id != new_status.id %]
[% checked = workflow.${status.id}.${new_status.id}.defined ? 1 : 0 %] [% checked = workflow.${status.id}.${new_status.id}.defined ? 1 : 0 %]
[% mandatory = (status.id && new_status.name == Param("duplicate_or_move_bug_status")) ? 1 : 0 %] [% mandatory = (status.id && new_status.name == Param("duplicate_or_move_bug_status")) ? 1 : 0 %]
<td align="center" class="checkbox-cell[% " checked" IF checked || mandatory %]" <td align="center" class="checkbox-cell[% " checked" IF checked || mandatory %]"
......
...@@ -308,9 +308,10 @@ ...@@ -308,9 +308,10 @@
[% ELSIF error == "comment_required" %] [% ELSIF error == "comment_required" %]
[% title = "Comment Required" %] [% title = "Comment Required" %]
You have to specify a You have to specify a
[% IF old && new %] [% IF old.defined && new.defined %]
<b>comment</b> when changing the status of [% terms.abug %] from <b>comment</b> when changing the [% field_descs.$field FILTER html %]
[%+ old.name FILTER html %] to [% new.name FILTER html %]. of [% terms.abug %] from [% (old || "(empty)") FILTER html %]
to [% (new || "(empty)") FILTER html %].
[% ELSIF new %] [% ELSIF new %]
description for this [% terms.bug %]. description for this [% terms.bug %].
[% ELSE %] [% ELSE %]
...@@ -1527,7 +1528,11 @@ ...@@ -1527,7 +1528,11 @@
[% ELSIF error == "still_unresolved_bugs" %] [% ELSIF error == "still_unresolved_bugs" %]
[% title = "Unresolved Dependencies" %] [% title = "Unresolved Dependencies" %]
[% IF bug_id %]
[% terms.Bug %] [%+ bug_id FILTER bug_link(bug_id) FILTER none %] [% terms.Bug %] [%+ bug_id FILTER bug_link(bug_id) FILTER none %]
[% ELSE %]
This [% terms.bug %]
[% END %]
has [% dep_count FILTER none %] unresolved has [% dep_count FILTER none %] unresolved
[% IF dep_count == 1 %] [% IF dep_count == 1 %]
dependency dependency
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment