]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 1562364 - Enforce setting bug type when creating bug through API
authorKohei Yoshino <kohei.yoshino@gmail.com>
Tue, 6 Aug 2019 16:17:34 +0000 (12:17 -0400)
committerdklawren <dklawren@users.noreply.github.com>
Tue, 6 Aug 2019 16:17:34 +0000 (12:17 -0400)
22 files changed:
Bugzilla/Bug.pm
Bugzilla/Config/BugFields.pm
Bugzilla/Field.pm
Bugzilla/WebService/Bug.pm
Bugzilla/WebService/Constants.pm
docs/en/rst/administering/parameters.rst
docs/en/rst/api/core/v1/bug.rst
docs/en/rst/api/core/v1/bugzilla.rst
extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl
extensions/BugModal/template/en/default/bug_modal/field.html.tmpl
js/field.js
qa/t/lib/QA/Tests.pm
qa/t/webservice_bug_create.t
qa/t/webservice_bug_fields.t
qa/t/webservice_bug_get.t
qa/t/webservice_bug_get_bugs.t
qa/t/webservice_bug_search.t
template/en/default/admin/params/bugfields.html.tmpl
template/en/default/bug/create/create.html.tmpl
template/en/default/bug/field-label.html.tmpl
template/en/default/bug/field.html.tmpl
template/en/default/global/user-error.html.tmpl

index bc5416fc4100c7bb73b80ac4a63c5f601dbcc1d7..73d59e75fae96378527ee207197b162f2ee61fb4 100644 (file)
@@ -121,7 +121,7 @@ sub VALIDATORS {
     bug_file_loc      => \&_check_bug_file_loc,
     bug_severity      => \&_check_select_field,
     bug_status        => \&_check_bug_status,
-    bug_type          => \&_check_select_field,
+    bug_type          => \&_check_bug_type,
     cc                => \&_check_cc,
     comment           => \&_check_comment,
     component         => \&_check_component,
@@ -904,24 +904,6 @@ sub create {
   $params->{priority} = Bugzilla->params->{defaultpriority}
     unless defined $params->{priority};
 
-  # Bug type can be defined at the component, product or instance level
-  unless (defined $params->{bug_type}) {
-    my $product
-      = (defined $params->{product})
-      ? Bugzilla::Product->new({name => $params->{product}, cache => 1})
-      : undef;
-    my $component
-      = ($product && defined $params->{component})
-      ? Bugzilla::Component->new({name => $params->{component}, product => $product, cache => 1})
-      : undef;
-    # The component's default bug type inherits or overrides the default bug
-    # type of the product or instance
-    $params->{bug_type}
-      = ($component)
-      ? $component->default_bug_type
-      : Bugzilla->params->{default_bug_type};
-  }
-
   # BMO - per-product hw/os defaults
   if (!defined $params->{rep_platform} || !defined $params->{op_sys}) {
     if (my $product
@@ -1879,6 +1861,35 @@ sub _check_bug_status {
   return $new_status->name;
 }
 
+sub _check_bug_type {
+  my ($invocant, $type, undef, $params) = @_;
+
+  if (defined $type && trim($type)) {
+    return $invocant->_check_select_field($type, 'bug_type');
+  }
+
+  if (Bugzilla->params->{'require_bug_type'}) {
+    ThrowUserError('bug_type_required');
+  }
+
+  if (blessed $invocant) {
+    return $invocant->component_obj->default_bug_type;
+  }
+
+  my $product
+    = (defined $params->{product})
+    ? Bugzilla::Product->new({name => $params->{product}, cache => 1})
+    : undef;
+  my $component
+    = ($product && defined $params->{component})
+    ? Bugzilla::Component->new({name => $params->{component}, product => $product, cache => 1})
+    : undef;
+
+  return $component
+    ? $component->default_bug_type
+    : Bugzilla->params->{default_bug_type};
+}
+
 sub _check_cc {
   my ($invocant, $ccs, undef, $params) = @_;
   my $component
index 3100412d3700a1e7a56e4969bd5af4ad31d1ca8b..cd913e419ce2b82acf43a1b18f6c5d48f66dc218 100644 (file)
@@ -40,6 +40,8 @@ sub get_param_list {
 
     {name => 'use_see_also', type => 'b', default => 1},
 
+    {name => 'require_bug_type', type => 'b', default => 1},
+
     {
       name    => 'default_bug_type',
       type    => 's',
index 503befdb94956e38339d6493772366e670aae87b..271c19cc928f159d73f490f62b9ae9b52512dad6 100644 (file)
@@ -259,7 +259,6 @@ use constant DEFAULT_FIELDS => (
     name           => 'bug_type',
     desc           => 'Type',
     in_new_bugmail => 1,
-    is_mandatory   => 1,
     type           => FIELD_TYPE_SINGLE_SELECT,
     buglist        => 1
   },
index 5b1dca146269fd938f0eab38ce0dd4d852bb6f65..6faaba147d7b6e1e97f924c48bb2bd42a799b32a 100644 (file)
@@ -3737,7 +3737,8 @@ in by the developer, compared to the developer's other bugs.
 
 =item C<severity> (string) B<Defaulted> - How severe the bug is.
 
-=item C<type> (string) B<Defaulted> - The basic category of the bug.
+=item C<type> (string) B<Defaulted> - The basic category of the bug. Some
+Bugzilla installations require this to be specified.
 
 =item C<alias> (string) - A brief alias for the bug that can be used
 instead of a bug number when accessing this bug. Must be unique in
@@ -3873,6 +3874,10 @@ the type id value to update or add a flag.
 
 The flag type is inactive and cannot be used to create new flags.
 
+=item 135 (Bug Type Required)
+
+You didn't specify a type for the bug.
+
 =item 504 (Invalid User)
 
 Either the QA Contact, Assignee, or CC lists have some invalid user
index 7198dac2c9da3c36cf636858a6d1fcbbf646417d..92be217946ddba5ae22211180be5be7dbad177e0 100644 (file)
@@ -157,6 +157,9 @@ use constant WS_ERROR_CODE => {
   flag_type_not_unique         => 133,
   flag_type_inactive           => 134,
 
+  # Bug Type errors
+  bug_type_required => 135,
+
   # Authentication errors are usually 300-400.
   invalid_username_or_password => 300,
   account_disabled             => 301,
index f30691aa3cdebcc4236400b32568065c08abdd2a..50f09973a1543addd75e93822b5e8bdda2b9c4fc 100644 (file)
@@ -257,6 +257,9 @@ use_see_also
     prevents addition of new relationships, but existing ones will continue to
     appear.
 
+require_bug_type
+    If this is on, users are asked to choose a type when they file a new bug.
+
 default_bug_type
     This is the type that newly entered bugs are set to.
 
index f42e268cff8ab0e18a0ee878d608d4a36b79136b..8945219d00f1980aa4dc94bf310d3e368965b822 100644 (file)
@@ -653,7 +653,8 @@ priority            string   (defaulted) What order the bug will be fixed in by
                              the developer, compared to the developer's other
                              bugs.
 severity            string   (defaulted) How severe the bug is.
-type                string   (defaulted) The basic category of the bug.
+type                string   (defaulted) The basic category of the bug. Some
+                             Bugzilla installations require this to be specified.
 alias               string   The alias for the bug that can be used instead of a
                              bug number when accessing this bug. Must be unique
                              in all of this Bugzilla.
@@ -762,6 +763,8 @@ id    int   This is the ID of the newly-filed bug.
   the type id value to update or add a flag.
 * 134 (Inactive Flag Type)
   The flag type is inactive and cannot be used to create new flags.
+* 135 (Bug Type Required)
+  You didn't specify a type for the bug.
 * 504 (Invalid User)
   Either the QA Contact, Assignee, or CC lists have some invalid user
   in them. The error message will have more details.
index c934d33ac7a6418b79ac142effb054ab823a343d..98ba80e5e3008dfa8e38ba3fcb935d1ef1b15fd3 100644 (file)
@@ -207,6 +207,7 @@ Example response for authenticated user:
           "noresolveonopenblockers" : "0",
           "password_complexity" : "no_constraints",
           "rememberlogin" : "on",
+          "require_bug_type" : "1",
           "requirelogin" : "0",
           "urlbase" : "http://bugzilla.example.com/",
           "use_regression_fields" : "1",
@@ -247,6 +248,7 @@ A logged-in user can access the following parameters (listed alphabetically):
 * noresolveonopenblockers
 * password_complexity
 * rememberlogin
+* require_bug_type
 * requirelogin
 * search_allow_no_criteria
 * urlbase
index 9841d7f3769660164a09850e48c365b85f65d85c..9bdd150ab4167541566ac8961e2518e9e2cadc3d 100644 (file)
         field      = bug_fields.bug_type
         field_type = constants.FIELD_TYPE_SINGLE_SELECT
         use_buttons  = 1
+        required   = Param('require_bug_type')
         help       = "https://wiki.mozilla.org/BMO/UserGuide/BugFields#bug_type"
     %]
       <span class="bug-type-label iconic-text" data-type="[% bug.bug_type FILTER html %]">
index e3dedd68557d24832374b71dddc2c2d6933c3953..fcaa08f2373f5abb49631265e15066f5f8bafd08 100644 (file)
@@ -29,7 +29,8 @@
   #                 edit HTML instead of replacing it.  forces edit_only (default: false);
   # default: (string) default value (e.g. used as a placeholder in user fields)
   # help: (string) optional URL that describes the field (requires a label to be defined and visible)
-  # required: (boolean) if the field is required. (At present, only implemented for FIELD_TYPE_FREETEXT)
+  # required: (boolean) if the field is required. (At present, only implemented for FIELD_TYPE_FREETEXT
+  #           and FIELD_TYPE_SINGLE_SELECT)
   # action: (hash) show a button to the right of the edit field (user fields only currently).  keys:
   #   id: (string) optional button id
   #   class: (string) optional button class
@@ -191,12 +192,12 @@ END;
           <input type="hidden" id="[% name FILTER html %]-dirty">
           [% IF use_buttons %]
           <div role="radiogroup" class="buttons toggle" id="[% name FILTER html %]"
-               [%= 'aria-required="true"' IF field.is_mandatory %] [%= aria_labelledby_html FILTER none %]>
+               [%= 'aria-required="true"' IF (field.is_mandatory || required) %] [%= aria_labelledby_html FILTER none %]>
             [% IF values.defined %]
               [% FOREACH v IN values %]
                 [% NEXT IF NOT v.is_active AND NOT value.contains(v.name).size %]
                 [% NEXT IF NOT bug.check_can_change_field(name, bug.${field_name}, v.name) %]
-                [% NEXT IF field.is_mandatory && v.name == '--' %]
+                [% NEXT IF (field.is_mandatory || required) && v.name == '--' %]
                 <div class="item">
                   <input id="v[% v.id FILTER html %]_[% name FILTER html %]" type="radio" name="[% name FILTER html %]"
                          value="[% v.name FILTER html %]" [% " checked" IF value.contains(v.name).size %]>
index ea738b6bfd3435d32d83a436f2031196f3e8b87f..b21704cd314ac868b8f37ac074ac918247d355c7 100644 (file)
@@ -40,6 +40,8 @@ function validateEnterBug(theform) {
     var attach_data = theform.data;
     var attach_desc = theform.description;
 
+    const $bug_type_group = document.querySelector('#bug_type');
+
     var current_errors = YAHOO.util.Dom.getElementsByClassName(
         'validation_error_text', null, theform);
     for (var i = 0; i < current_errors.length; i++) {
@@ -77,8 +79,8 @@ function validateEnterBug(theform) {
         _errorFor(component);
         focus_me = component;
     }
-    if (!bug_type.value) {
-        _errorFor(document.querySelector('#bug_type'));
+    if ($bug_type_group.matches('[aria-required="true"]') && !bug_type.value) {
+        _errorFor($bug_type_group);
         focus_me = bug_type[0];
     }
 
index 3d53cefccfac01efa0c6c9643fce630d72a4d094..92eb40bc1f9f4dce3c47ee69eef5ae608c4bbac9 100644 (file)
@@ -22,6 +22,7 @@ use constant INVALID_BUG_ALIAS => 'aaaaaaa12345';
 use constant PRIVATE_BUG_USER  => 'QA_Selenium_TEST';
 
 use constant CREATE_BUG => {
+  'type'        => 'defect',
   'priority'    => 'Highest',
   'status'      => 'CONFIRMED',
   'version'     => 'unspecified',
index 34a03778ac487e11f4976c0ad77e1da0fe8c7a76..c36aefb25c4fc6cab05406c91a2f6c8347489a2e 100644 (file)
@@ -13,7 +13,7 @@ use strict;
 use warnings;
 use lib qw(lib ../../lib ../../local/lib/perl5);
 use Storable qw(dclone);
-use Test::More tests => 293;
+use Test::More tests => 311;
 use QA::Util;
 use QA::Tests qw(create_bug_fields PRIVATE_BUG_USER);
 
@@ -71,6 +71,14 @@ my $fields = {
     },
   },
 
+  type => {
+    undefined => {faultstring => 'you must first choose a type', value => undef},
+    invalid   => {
+      faultstring => "There is no Type named 'does-not-exist'.",
+      value       => 'does-not-exist'
+    },
+  },
+
   severity => {
     undefined => {faultstring => 'You must select/enter a Severity.', value => ''},
     invalid   => {
@@ -155,6 +163,7 @@ my @tests = (
     user => PRIVATE_BUG_USER,
     args => {
       %$bug_fields,
+      type             => 'defect',
       product          => 'QA-Selenium-TEST',
       component        => 'QA-Selenium-TEST',
       target_milestone => 'QAMilestone',
index f0e280353b337ce775e00835811ecbc9372a3d8d..e60f7667724e3af08b2dd307d371393f5db4bbf1 100644 (file)
@@ -83,7 +83,7 @@ use constant ALL_SELECT_FIELDS =>
 use constant PRODUCT_FIELDS => qw(version target_milestone component);
 use constant ALL_FIELDS =>
   (GLOBAL_GENERAL_FIELDS, ALL_SELECT_FIELDS, PRODUCT_FIELDS);
-use constant MANDATORY_FIELDS => qw(short_desc product version component bug_type);
+use constant MANDATORY_FIELDS => qw(short_desc product version component);
 
 use constant PUBLIC_PRODUCT  => 'Another Product';
 use constant PRIVATE_PRODUCT => 'QA-Selenium-TEST';
index d4df37f17d117494de600655f0bcaad80542581c..5a6918a14e861620727b386f6e04af00077ba799 100644 (file)
@@ -16,7 +16,7 @@ use Data::Dumper;
 use DateTime;
 use QA::Util;
 use QA::Tests qw(bug_tests PRIVATE_BUG_USER);
-use Test::More tests => 1012;
+use Test::More tests => 1036;
 my ($config, @clients) = get_rpc_clients();
 
 my $xmlrpc = $clients[0];
index fae9d0842d4efb31ac6fab23637c8daa0a927917..cdb261f3625b6a316f7f1de550a96db2eccfd6e8 100644 (file)
@@ -16,7 +16,7 @@ use Data::Dumper;
 use DateTime;
 use QA::Util;
 use QA::Tests qw(bug_tests PRIVATE_BUG_USER);
-use Test::More tests => 1012;
+use Test::More tests => 1036;
 my ($config, @clients) = get_rpc_clients();
 
 my $xmlrpc = $clients[0];
index 7b6d7dadedf14d3fe975db1a171d430a9afd363e..c873c74e7c93d2cd6be02070c75dac590ab5ab58 100644 (file)
@@ -19,7 +19,7 @@ use List::MoreUtils qw(uniq);
 use Test::More;
 
 my ($config, @clients) = get_rpc_clients();
-plan tests => $config->{test_extensions} ? 515 : 506;
+plan tests => $config->{test_extensions} ? 533 : 524;
 
 my ($public_bug, $private_bug) = $clients[0]->bz_create_test_bugs('private');
 
index 2052d48b39aded3c3527ff49a4224846f881dc6e..bec6a61dd7166c3cd08aa08d753af41065abba12 100644 (file)
@@ -66,6 +66,9 @@
 
   default_bug_type => "This is the type that newly entered $terms.bugs are set to.",
 
+  require_bug_type =>
+    "If this is on, users are asked to choose a type when they file a new ${terms.bug}.",
+
   collapsed_comment_tags => "A comma separated list of tags which, when applied " _
                             "to comments, will cause them to be collapsed by default",
   }
index 843da73606d42c78a56f2c9072c4903837d825ee..9075b1c98d0c1709f8706d0de3b1c722d6dc175a 100644 (file)
@@ -341,7 +341,7 @@ TUI_hide_default('expert_fields');
 
     [% INCLUDE bug/field.html.tmpl
       bug = default, field = bug_fields.bug_type, editable = 1, use_buttons = 1,
-      value = default.bug_type %]
+      value = default.bug_type, required = Param('require_bug_type') %]
   </tr>
 
   [% needs_extra_tr = 1 %]
index 5d5c5452cedf0e2634cdff1aa3fe84d7cf7249aa..2625c3707f03f74644cfaa3c34af0bdc1d6f8493 100644 (file)
   #   rowspan: a "rowspan" value for the label's <th>.
   #   tag_name: the tag to use to surround the label
   #   accesskey: access key for the label
+  #   required: Whether the field should be entered or selected. This can override
+  #             the field definition's `is_mandatory` property.
+  #   use_buttons: Whether the field should be displayed as rich radio buttons.
+  #                Use this for a single-select field with a few options.
   #%]
 
 [% DEFAULT tag_name = "th" %]
 <[% tag_name FILTER html %] class="field_label [% ' bz_hidden_field' IF hidden %]
-           [%- ' required' IF field.is_mandatory && NOT bug.id; ' for-buttons' IF use_buttons %]"
+           [%- ' required' IF (field.is_mandatory || required) && NOT bug.id; ' for-buttons' IF use_buttons %]"
     id="field_label_[% field.name FILTER html %]"
     [% IF rowspan %] rowspan="[% rowspan FILTER html %]"[% END %]>
 
index e47542ada793c1b4ed870584c011251c55fe399b..4aa4dddfc38bd0651b0b1e9ffb39dd0da67da299 100644 (file)
   #   override_legal_values (optional): The list of legal values, for select fields.
   #   editable: Whether the field should be displayed as an editable
   #             <input> or as just the plain text of its value.
+  #   required: Whether the field should be entered or selected. This can override
+  #             the field definition's `is_mandatory` property.
+  #   use_buttons: Whether the field should be displayed as rich radio buttons.
+  #                Use this for a single-select field with a few options.
   #   allow_dont_change: display the --do_not_change-- option for select fields.
   #   value_span: A colspan for the table cell containing
   #               the field value.
                value="[% value FILTER html %]" size="40"
                maxlength="[% constants.MAX_FREETEXT_LENGTH FILTER none %]"
                [% IF field.type == constants.FIELD_TYPE_INTEGER %] pattern="\d+[% IF dontchange %]|[% dontchange FILTER html %][% END %]" [% END %]
-               [% ' aria-required="true"' IF field.is_mandatory %]>
+               [% ' aria-required="true"' IF (field.is_mandatory || required) %]>
     [% CASE [constants.FIELD_TYPE_DATETIME, constants.FIELD_TYPE_DATE] %]
       [% size = (field.type == constants.FIELD_TYPE_DATE) ? 10 : 20 %]
       <input name="[% field.name FILTER html %]" size="[% size FILTER none %]"
              id="[% field.name FILTER html %]"
              value="[% value FILTER html %]"
-             [% ' aria-required="true"' IF field.is_mandatory %]
+             [% ' aria-required="true"' IF (field.is_mandatory || required) %]
              onchange="updateCalendarFromField(this)">
       <button type="button" class="calendar_button"
               id="button_calendar_[% field.name FILTER html %]"
@@ -81,7 +85,7 @@
         <span id="[% field.name FILTER html %]_input_area">
           <input name="[% field.name FILTER html %]" id="[% field.name FILTER html %]"
                  value="[% value FILTER html %]" size="7"
-                 [% ' aria-required="true"' IF field.is_mandatory %]>
+                 [% ' aria-required="true"' IF (field.is_mandatory || required) %]>
 
         </span>
 
         <input type="hidden" id="[% field.name FILTER html %]_dirty">
         [% IF use_buttons %]
         <div role="radiogroup" class="buttons toggle" id="[% field.name FILTER html %]"
-             [% ' aria-required="true"' IF field.is_mandatory %]>
+             [% ' aria-required="true"' IF (field.is_mandatory || required) %]>
         [% ELSE %]
         <select id="[% field.name FILTER html %]"
                 name="[% field.name FILTER html %]"
                         [% SET field_size = field.legal_values.size %]
                     [% END %]
                     size="[% field_size FILTER html %]" multiple="multiple"
-                    [% ' aria-required="true"' IF field.is_mandatory %]
+                    [% ' aria-required="true"' IF (field.is_mandatory || required) %]
                 [% END %]
                 >
           [% IF allow_dont_change %]
             [% END %]
 
             [% IF use_buttons %]
-            [% NEXT IF field.is_mandatory && legal_value.name == '--' %]
+            [% NEXT IF (field.is_mandatory || required) && legal_value.name == '--' %]
             <div class="item">
               <input id="[% option_id FILTER html %]" type="radio"
                      name="[% field.name FILTER html %]" value="[% legal_value.name FILTER html %]"
        <div id="[% field.name FILTER html %]_input">
          [% INCLUDE global/textarea.html.tmpl
             id = field.name name = field.name minrows = 4 maxrows = 8
-            cols = 60 defaultcontent = value mandatory = field.is_mandatory %]
+            cols = 60 defaultcontent = value mandatory = (field.is_mandatory || required) %]
        </div>
        <script [% script_nonce FILTER none %]>
          hideEditableField('[% field.name FILTER js %]_edit_container',
index b6aeeb054a7305fe657a5df39fca606e54a11338..22a71e75be2f12190cb0601cb714b8984f09d4ee 100644 (file)
                    [% bug_id FILTER uri %]&amp;GoAheadAndLogIn=1">log
     in to an account</a> with the appropriate permissions.
 
+  [% ELSIF error == "bug_type_required" %]
+    [% title = "Bug Type Required" %]
+    To file a new [% terms.bug %], you must first choose a type:
+    [% FOREACH type = bug_fields.bug_type.legal_values.pluck('name') %]
+      [% IF loop.last %] or [% ELSIF !loop.first %], [% END %]
+      <strong>[% type FILTER html %]</strong>
+    [% END %].
+
   [% ELSIF error == "bug_url_invalid" %]
     [% title = "Invalid $terms.Bug URL" %]
     <code>[% url FILTER html %]</code> is not a valid URL to [% terms.abug %].