Commit a806b298 authored by lpsolit%gmail.com's avatar lpsolit%gmail.com

Bug 287326: Ability to add custom single-select fields to a bug - Patch by…

Bug 287326: Ability to add custom single-select fields to a bug - Patch by Frédéric Buclin <LpSolit@gmail.com> and Max Kanat-Alexander <mkanat@bugzilla.org> r=mkanat a=myk
parent 27c1be36
...@@ -110,20 +110,30 @@ use constant REQUIRED_CREATE_FIELDS => qw( ...@@ -110,20 +110,30 @@ use constant REQUIRED_CREATE_FIELDS => qw(
# There are also other, more complex validators that are called # There are also other, more complex validators that are called
# from run_create_validators. # from run_create_validators.
use constant VALIDATORS => { sub VALIDATORS {
alias => \&_check_alias, my $validators = {
bug_file_loc => \&_check_bug_file_loc, alias => \&_check_alias,
bug_severity => \&_check_bug_severity, bug_file_loc => \&_check_bug_file_loc,
cc => \&_check_cc, bug_severity => \&_check_bug_severity,
deadline => \&_check_deadline, cc => \&_check_cc,
estimated_time => \&_check_estimated_time, deadline => \&_check_deadline,
op_sys => \&_check_op_sys, estimated_time => \&_check_estimated_time,
priority => \&_check_priority, op_sys => \&_check_op_sys,
product => \&_check_product, priority => \&_check_priority,
remaining_time => \&_check_remaining_time, product => \&_check_product,
rep_platform => \&_check_rep_platform, remaining_time => \&_check_remaining_time,
short_desc => \&_check_short_desc, rep_platform => \&_check_rep_platform,
status_whiteboard => \&_check_status_whiteboard, short_desc => \&_check_short_desc,
status_whiteboard => \&_check_status_whiteboard,
};
my @select_fields = Bugzilla->get_fields({custom => 1, obsolete => 0,
type => FIELD_TYPE_SINGLE_SELECT});
foreach my $field (@select_fields) {
$validators->{$field->name} = \&_check_select_field;
}
return $validators;
}; };
# Used in LogActivityEntry(). Gives the max length of lines in the # Used in LogActivityEntry(). Gives the max length of lines in the
...@@ -279,7 +289,7 @@ sub run_create_validators { ...@@ -279,7 +289,7 @@ sub run_create_validators {
$params->{remaining_time} = $params->{estimated_time}; $params->{remaining_time} = $params->{estimated_time};
$class->_check_strict_isolation($product, $params->{cc}, $class->_check_strict_isolation($product, $params->{cc},
$params->{assigned_to}, $params->{qa_contact}); $params->{assigned_to}, $params->{qa_contact});
return $params; return $params;
} }
...@@ -657,6 +667,12 @@ sub _check_version { ...@@ -657,6 +667,12 @@ sub _check_version {
return $version; return $version;
} }
sub _check_select_field {
my ($invocant, $value, $field) = @_;
$value = trim($value);
check_field($field, $value);
return $value;
}
##################################################################### #####################################################################
# Class Accessors # Class Accessors
......
...@@ -106,6 +106,7 @@ use File::Basename; ...@@ -106,6 +106,7 @@ use File::Basename;
FIELD_TYPE_UNKNOWN FIELD_TYPE_UNKNOWN
FIELD_TYPE_FREETEXT FIELD_TYPE_FREETEXT
FIELD_TYPE_SINGLE_SELECT
BUG_STATE_OPEN BUG_STATE_OPEN
...@@ -296,6 +297,7 @@ use constant SENDMAIL_EXE => '/usr/lib/sendmail.exe'; ...@@ -296,6 +297,7 @@ use constant SENDMAIL_EXE => '/usr/lib/sendmail.exe';
use constant FIELD_TYPE_UNKNOWN => 0; use constant FIELD_TYPE_UNKNOWN => 0;
use constant FIELD_TYPE_FREETEXT => 1; use constant FIELD_TYPE_FREETEXT => 1;
use constant FIELD_TYPE_SINGLE_SELECT => 2;
# The maximum number of days a token will remain valid. # The maximum number of days a token will remain valid.
use constant MAX_TOKEN_AGE => 3; use constant MAX_TOKEN_AGE => 3;
......
...@@ -577,10 +577,25 @@ sub bz_add_table { ...@@ -577,10 +577,25 @@ sub bz_add_table {
sub _bz_add_table_raw { sub _bz_add_table_raw {
my ($self, $name) = @_; my ($self, $name) = @_;
my @statements = $self->_bz_schema->get_table_ddl($name); my @statements = $self->_bz_schema->get_table_ddl($name);
print "Adding new table $name ...\n"; print "Adding new table $name ...\n" unless i_am_cgi();
$self->do($_) foreach (@statements); $self->do($_) foreach (@statements);
} }
sub bz_add_field_table {
my ($self, $name) = @_;
my $table_schema = $self->_bz_schema->FIELD_TABLE_SCHEMA;
my $indexes = $table_schema->{INDEXES};
# $indexes is an arrayref, not a hash. In order to fix the keys,
# we have to fix every other item.
for (my $i = 0; $i < scalar @$indexes; $i++) {
next if ($i % 2 && $i != 0); # We skip 1, 3, 5, 7, etc.
$indexes->[$i] = $name . "_" . $indexes->[$i];
}
# We add this to the abstract schema so that bz_add_table can find it.
$self->_bz_schema->add_table($name, $table_schema);
$self->bz_add_table($name);
}
sub bz_drop_column { sub bz_drop_column {
my ($self, $table, $column) = @_; my ($self, $table, $column) = @_;
......
...@@ -1090,6 +1090,23 @@ use constant ABSTRACT_SCHEMA => { ...@@ -1090,6 +1090,23 @@ use constant ABSTRACT_SCHEMA => {
}, },
}; };
use constant FIELD_TABLE_SCHEMA => {
FIELDS => [
id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
PRIMARYKEY => 1},
value => {TYPE => 'varchar(64)', NOTNULL => 1},
sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
DEFAULT => 'TRUE'},
],
# Note that bz_add_field_table should prepend the table name
# to these index names.
INDEXES => [
value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
sortkey_idx => ['sortkey', 'value'],
],
};
#-------------------------------------------------------------------------- #--------------------------------------------------------------------------
=head1 METHODS =head1 METHODS
......
...@@ -103,7 +103,9 @@ use constant DB_COLUMNS => ( ...@@ -103,7 +103,9 @@ use constant DB_COLUMNS => (
use constant SQL_DEFINITIONS => { use constant SQL_DEFINITIONS => {
# Using commas because these are constants and they shouldn't # Using commas because these are constants and they shouldn't
# be auto-quoted by the "=>" operator. # be auto-quoted by the "=>" operator.
FIELD_TYPE_FREETEXT, { TYPE => 'varchar(255)' }, FIELD_TYPE_FREETEXT, { TYPE => 'varchar(255)' },
FIELD_TYPE_SINGLE_SELECT, { TYPE => 'varchar(64)', NOTNULL => 1,
DEFAULT => "'---'" },
}; };
# Field definitions for the fields that ship with Bugzilla. # Field definitions for the fields that ship with Bugzilla.
...@@ -266,6 +268,24 @@ enter_bug.cgi ...@@ -266,6 +268,24 @@ enter_bug.cgi
sub enter_bug { return $_[0]->{enter_bug} } sub enter_bug { return $_[0]->{enter_bug} }
=over
=item C<legal_values>
A reference to an array with valid active values for this field.
=back
=cut
sub legal_values {
my $self = shift;
if (!defined $self->{'legal_values'}) {
$self->{'legal_values'} = get_legal_field_values($self->name);
}
return $self->{'legal_values'};
}
=pod =pod
...@@ -305,9 +325,7 @@ sub create_or_update { ...@@ -305,9 +325,7 @@ sub create_or_update {
my $name = $params->{name}; my $name = $params->{name};
my $custom = $params->{custom} ? 1 : 0; my $custom = $params->{custom} ? 1 : 0;
my $in_new_bugmail = $params->{in_new_bugmail} ? 1 : 0; my $in_new_bugmail = $params->{in_new_bugmail} ? 1 : 0;
# Some day we'll allow invocants to specify the field type. my $type = $params->{type} || FIELD_TYPE_UNKNOWN;
# We don't care about $params->{type} yet.
my $type = $custom ? FIELD_TYPE_FREETEXT : FIELD_TYPE_UNKNOWN;
my $field = new Bugzilla::Field({name => $name}); my $field = new Bugzilla::Field({name => $name});
if ($field) { if ($field) {
...@@ -353,6 +371,15 @@ sub create_or_update { ...@@ -353,6 +371,15 @@ sub create_or_update {
$dbh->bz_add_column('bugs', $name, SQL_DEFINITIONS->{$type}); $dbh->bz_add_column('bugs', $name, SQL_DEFINITIONS->{$type});
} }
if ($custom && !$dbh->bz_table_info($name)
&& $type eq FIELD_TYPE_SINGLE_SELECT)
{
# Create the table that holds the legal values for this field.
$dbh->bz_add_field_table($name);
# And insert a default value of "---" into it.
$dbh->do("INSERT INTO $name (value) VALUES ('---')");
}
return new Bugzilla::Field({name => $name}); return new Bugzilla::Field({name => $name});
} }
...@@ -361,19 +388,45 @@ sub create_or_update { ...@@ -361,19 +388,45 @@ sub create_or_update {
=over =over
=item C<match($criteria)> =item C<match>
=over
=item B<Description>
Description: returns a list of fields that match the specified criteria. Returns a list of fields that match the specified criteria.
Params: C<$criteria> - hash reference - the criteria to match against. You should be using L<Bugzilla/get_fields> and
Hash keys represent field properties; hash values represent L<Bugzilla/get_custom_field_names> instead of this function.
their values. All criteria are optional. Valid criteria are
"custom" and "obsolete", and both take boolean values.
Note: Bugzilla->get_fields() and Bugzilla->custom_field_names =item B<Params>
wrap this method for most callers.
Returns: A reference to an array of C<Bugzilla::Field> objects. Takes named parameters in a hashref:
=over
=item C<name> - The name of the field.
=item C<custom> - Boolean. True to only return custom fields. False
to only return non-custom fields.
=item C<obsolete> - Boolean. True to only return obsolete fields.
False to not return obsolete fields.
=item C<type> - The type of the field. A C<FIELD_TYPE> constant from
L<Bugzilla::Constants>.
=item C<enter_bug> - Boolean. True to only return fields that appear
on F<enter_bug.cgi>. False to only return fields that I<don't> appear
on F<enter_bug.cgi>.
=back
=item B<Returns>
A reference to an array of C<Bugzilla::Field> objects.
=back
=back =back
...@@ -395,8 +448,11 @@ sub match { ...@@ -395,8 +448,11 @@ sub match {
if (defined $criteria->{enter_bug}) { if (defined $criteria->{enter_bug}) {
push(@terms, "enter_bug=" . ($criteria->{enter_bug} ? '1' : '0')); push(@terms, "enter_bug=" . ($criteria->{enter_bug} ? '1' : '0'));
} }
if (defined $criteria->{type}) {
push(@terms, "type = " . $criteria->{type});
}
my $where = (scalar(@terms) > 0) ? "WHERE " . join(" AND ", @terms) : ""; my $where = (scalar(@terms) > 0) ? "WHERE " . join(" AND ", @terms) : "";
my $ids = Bugzilla->dbh->selectcol_arrayref( my $ids = Bugzilla->dbh->selectcol_arrayref(
"SELECT id FROM fielddefs $where", {Slice => {}}); "SELECT id FROM fielddefs $where", {Slice => {}});
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
# Everything Solved. All Rights Reserved. # Everything Solved. All Rights Reserved.
# #
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org> # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
# Frédéric Buclin <LpSolit@gmail.com>
use strict; use strict;
...@@ -136,7 +137,7 @@ sub set { ...@@ -136,7 +137,7 @@ sub set {
my $validators = $self->VALIDATORS; my $validators = $self->VALIDATORS;
if (exists $validators->{$field}) { if (exists $validators->{$field}) {
my $validator = $validators->{$field}; my $validator = $validators->{$field};
$value = $self->$validator($value); $value = $self->$validator($value, $field);
} }
$self->{$field} = $value; $self->{$field} = $value;
...@@ -196,7 +197,7 @@ sub run_create_validators { ...@@ -196,7 +197,7 @@ sub run_create_validators {
my $value; my $value;
if (exists $validators->{$field}) { if (exists $validators->{$field}) {
my $validator = $validators->{$field}; my $validator = $validators->{$field};
$value = $class->$validator($params->{$field}); $value = $class->$validator($params->{$field}, $field);
} }
else { else {
$value = $params->{$field}; $value = $params->{$field};
...@@ -328,6 +329,9 @@ a reference to the current object (what we normally call C<$self>). ...@@ -328,6 +329,9 @@ a reference to the current object (what we normally call C<$self>).
The second argument will be the value passed to L</create> or The second argument will be the value passed to L</create> or
L</set>for that field. L</set>for that field.
The third argument will be the name of the field being validated.
This may be required by validators which validate several distinct fields.
These functions should call L<Bugzilla::Error/ThrowUserError> if they fail. These functions should call L<Bugzilla::Error/ThrowUserError> if they fail.
The validator must return the validated value. The validator must return the validated value.
......
...@@ -55,7 +55,7 @@ use constant SUMMARY_RELEVANCE_FACTOR => 7; ...@@ -55,7 +55,7 @@ use constant SUMMARY_RELEVANCE_FACTOR => 7;
# We need to have a list of these fields and what they map to. # We need to have a list of these fields and what they map to.
# Each field points to an array that contains the fields mapped # Each field points to an array that contains the fields mapped
# to, in order. # to, in order.
our %specialorder = ( use constant SPECIAL_ORDER => {
'bugs.target_milestone' => [ 'ms_order.sortkey','ms_order.value' ], 'bugs.target_milestone' => [ 'ms_order.sortkey','ms_order.value' ],
'bugs.bug_status' => [ 'bug_status.sortkey','bug_status.value' ], 'bugs.bug_status' => [ 'bug_status.sortkey','bug_status.value' ],
'bugs.rep_platform' => [ 'rep_platform.sortkey','rep_platform.value' ], 'bugs.rep_platform' => [ 'rep_platform.sortkey','rep_platform.value' ],
...@@ -63,12 +63,12 @@ our %specialorder = ( ...@@ -63,12 +63,12 @@ our %specialorder = (
'bugs.op_sys' => [ 'op_sys.sortkey','op_sys.value' ], 'bugs.op_sys' => [ 'op_sys.sortkey','op_sys.value' ],
'bugs.resolution' => [ 'resolution.sortkey', 'resolution.value' ], 'bugs.resolution' => [ 'resolution.sortkey', 'resolution.value' ],
'bugs.bug_severity' => [ 'bug_severity.sortkey','bug_severity.value' ] 'bugs.bug_severity' => [ 'bug_severity.sortkey','bug_severity.value' ]
); };
# When we add certain fields to the ORDER BY, we need to then add a # When we add certain fields to the ORDER BY, we need to then add a
# table join to the FROM statement. This hash maps input fields to # table join to the FROM statement. This hash maps input fields to
# the join statements that need to be added. # the join statements that need to be added.
our %specialorderjoin = ( use constant SPECIAL_ORDER_JOIN => {
'bugs.target_milestone' => 'LEFT JOIN milestones AS ms_order ON ms_order.value = bugs.target_milestone AND ms_order.product_id = bugs.product_id', 'bugs.target_milestone' => 'LEFT JOIN milestones AS ms_order ON ms_order.value = bugs.target_milestone AND ms_order.product_id = bugs.product_id',
'bugs.bug_status' => 'LEFT JOIN bug_status ON bug_status.value = bugs.bug_status', 'bugs.bug_status' => 'LEFT JOIN bug_status ON bug_status.value = bugs.bug_status',
'bugs.rep_platform' => 'LEFT JOIN rep_platform ON rep_platform.value = bugs.rep_platform', 'bugs.rep_platform' => 'LEFT JOIN rep_platform ON rep_platform.value = bugs.rep_platform',
...@@ -76,7 +76,7 @@ our %specialorderjoin = ( ...@@ -76,7 +76,7 @@ our %specialorderjoin = (
'bugs.op_sys' => 'LEFT JOIN op_sys ON op_sys.value = bugs.op_sys', 'bugs.op_sys' => 'LEFT JOIN op_sys ON op_sys.value = bugs.op_sys',
'bugs.resolution' => 'LEFT JOIN resolution ON resolution.value = bugs.resolution', 'bugs.resolution' => 'LEFT JOIN resolution ON resolution.value = bugs.resolution',
'bugs.bug_severity' => 'LEFT JOIN bug_severity ON bug_severity.value = bugs.bug_severity' 'bugs.bug_severity' => 'LEFT JOIN bug_severity ON bug_severity.value = bugs.bug_severity'
); };
# Create a new Search # Create a new Search
# Note that the param argument may be modified by Bugzilla::Search # Note that the param argument may be modified by Bugzilla::Search
...@@ -117,6 +117,18 @@ sub init { ...@@ -117,6 +117,18 @@ sub init {
my @andlist; my @andlist;
my %chartfields; my %chartfields;
my %special_order = %{SPECIAL_ORDER()};
my %special_order_join = %{SPECIAL_ORDER_JOIN()};
my @select_fields = Bugzilla->get_fields({ type => FIELD_TYPE_SINGLE_SELECT,
obsolete => 0 });
foreach my $field (@select_fields) {
my $name = $field->name;
$special_order{"bugs.$name"} = [ "$name.sortkey", "$name.value" ],
$special_order_join{"bugs.$name"} =
"LEFT JOIN $name ON $name.value = bugs.$name";
}
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
# First, deal with all the old hard-coded non-chart-based poop. # First, deal with all the old hard-coded non-chart-based poop.
...@@ -213,6 +225,9 @@ sub init { ...@@ -213,6 +225,9 @@ sub init {
"assigned_to", "reporter", "component", "classification", "assigned_to", "reporter", "component", "classification",
"target_milestone", "bug_group"); "target_milestone", "bug_group");
# Include custom select fields.
push(@legal_fields, map { $_->name } @select_fields);
foreach my $field ($params->param()) { foreach my $field ($params->param()) {
if (lsearch(\@legal_fields, $field) != -1) { if (lsearch(\@legal_fields, $field) != -1) {
push(@specialchart, [$field, "anyexact", push(@specialchart, [$field, "anyexact",
...@@ -1368,7 +1383,7 @@ sub init { ...@@ -1368,7 +1383,7 @@ sub init {
if ($orderitem =~ /\s+AS\s+(.+)$/i) { if ($orderitem =~ /\s+AS\s+(.+)$/i) {
$orderitem = $1; $orderitem = $1;
} }
BuildOrderBy($orderitem, \@orderby); BuildOrderBy(\%special_order, $orderitem, \@orderby);
} }
# Now JOIN the correct tables in the FROM clause. # Now JOIN the correct tables in the FROM clause.
# This is done separately from the above because it's # This is done separately from the above because it's
...@@ -1376,8 +1391,8 @@ sub init { ...@@ -1376,8 +1391,8 @@ sub init {
foreach my $orderitem (@inputorder) { foreach my $orderitem (@inputorder) {
# Grab the part without ASC or DESC. # Grab the part without ASC or DESC.
my @splitfield = split(/\s+/, $orderitem); my @splitfield = split(/\s+/, $orderitem);
if ($specialorderjoin{$splitfield[0]}) { if ($special_order_join{$splitfield[0]}) {
push(@supptables, $specialorderjoin{$splitfield[0]}); push(@supptables, $special_order_join{$splitfield[0]});
} }
} }
...@@ -1677,7 +1692,7 @@ sub IsValidQueryType ...@@ -1677,7 +1692,7 @@ sub IsValidQueryType
# BuildOrderBy recursively, to let it know that we're "reversing" the # BuildOrderBy recursively, to let it know that we're "reversing" the
# order. That is, that we wanted "A DESC", not "A". # order. That is, that we wanted "A DESC", not "A".
sub BuildOrderBy { sub BuildOrderBy {
my ($orderitem, $stringlist, $reverseorder) = (@_); my ($special_order, $orderitem, $stringlist, $reverseorder) = (@_);
my @twopart = split(/\s+/, $orderitem); my @twopart = split(/\s+/, $orderitem);
my $orderfield = $twopart[0]; my $orderfield = $twopart[0];
...@@ -1695,11 +1710,12 @@ sub BuildOrderBy { ...@@ -1695,11 +1710,12 @@ sub BuildOrderBy {
} }
# Handle fields that have non-standard sort orders, from $specialorder. # Handle fields that have non-standard sort orders, from $specialorder.
if ($specialorder{$orderfield}) { if ($special_order->{$orderfield}) {
foreach my $subitem (@{$specialorder{$orderfield}}) { foreach my $subitem (@{$special_order->{$orderfield}}) {
# DESC on a field with non-standard sort order means # DESC on a field with non-standard sort order means
# "reverse the normal order for each field that we map to." # "reverse the normal order for each field that we map to."
BuildOrderBy($subitem, $stringlist, $orderdirection =~ m/desc/i); BuildOrderBy($special_order, $subitem, $stringlist,
$orderdirection =~ m/desc/i);
} }
return; return;
} }
......
...@@ -41,26 +41,25 @@ print $cgi->header(); ...@@ -41,26 +41,25 @@ print $cgi->header();
# List all existing custom fields if no action is given. # List all existing custom fields if no action is given.
if (!$action) { if (!$action) {
$vars->{'custom_fields'} = [Bugzilla->get_fields({'custom' => 1})];
$template->process('admin/custom_fields/list.html.tmpl', $vars) $template->process('admin/custom_fields/list.html.tmpl', $vars)
|| ThrowTemplateError($template->error()); || ThrowTemplateError($template->error());
} }
# Interface to add a new custom field. # Interface to add a new custom field.
elsif ($action eq 'add') { elsif ($action eq 'add') {
$template->process('admin/custom_fields/create.html.tmpl') $template->process('admin/custom_fields/create.html.tmpl', $vars)
|| ThrowTemplateError($template->error()); || ThrowTemplateError($template->error());
} }
elsif ($action eq 'new') { elsif ($action eq 'new') {
my $name = clean_text($cgi->param('name') || ''); my $name = clean_text($cgi->param('name') || '');
my $desc = clean_text($cgi->param('desc') || ''); my $desc = clean_text($cgi->param('desc') || '');
# For now, there is only one type available for custom fields. my $type = trim($cgi->param('type') || FIELD_TYPE_FREETEXT);
# In the future, we will have to look at $cgi->param('type').
my $type = FIELD_TYPE_FREETEXT;
my $sortkey = $cgi->param('sortkey') || 0; my $sortkey = $cgi->param('sortkey') || 0;
# Validate these fields. # Validate these fields.
$name || ThrowUserError('customfield_missing_name'); $name || ThrowUserError('customfield_missing_name');
# Don't want to allow a name that might mess up SQL.
$name =~ /^\w+$/ || ThrowUserError('customfield_invalid_name',
{ name => $name });
# Prepend cf_ to the custom field name to distinguish it from standard fields. # Prepend cf_ to the custom field name to distinguish it from standard fields.
if ($name !~ /^cf_/) { if ($name !~ /^cf_/) {
$name = 'cf_' . $name; $name = 'cf_' . $name;
...@@ -70,6 +69,11 @@ elsif ($action eq 'new') { ...@@ -70,6 +69,11 @@ elsif ($action eq 'new') {
$desc || ThrowUserError('customfield_missing_description', {'name' => $name}); $desc || ThrowUserError('customfield_missing_description', {'name' => $name});
# We hardcode valid values for $type. This doesn't matter.
my $typ = $type;
(detaint_natural($type) && $type < 3)
|| ThrowCodeError('invalid_customfield_type', {'type' => $typ});
my $skey = $sortkey; my $skey = $sortkey;
detaint_natural($sortkey) detaint_natural($sortkey)
|| ThrowUserError('customfield_invalid_sortkey', {'name' => $name, || ThrowUserError('customfield_invalid_sortkey', {'name' => $name,
...@@ -90,7 +94,6 @@ elsif ($action eq 'new') { ...@@ -90,7 +94,6 @@ elsif ($action eq 'new') {
Bugzilla::Field::create_or_update($vars); Bugzilla::Field::create_or_update($vars);
$vars->{'custom_fields'} = [Bugzilla->get_fields({'custom' => 1})];
$vars->{'message'} = 'custom_field_created'; $vars->{'message'} = 'custom_field_created';
$template->process('admin/custom_fields/list.html.tmpl', $vars) $template->process('admin/custom_fields/list.html.tmpl', $vars)
...@@ -142,7 +145,6 @@ elsif ($action eq 'update') { ...@@ -142,7 +145,6 @@ elsif ($action eq 'update') {
Bugzilla::Field::create_or_update($vars); Bugzilla::Field::create_or_update($vars);
$vars->{'custom_fields'} = [Bugzilla->get_fields({'custom' => 1})];
$vars->{'message'} = 'custom_field_updated'; $vars->{'message'} = 'custom_field_updated';
$template->process('admin/custom_fields/list.html.tmpl', $vars) $template->process('admin/custom_fields/list.html.tmpl', $vars)
......
...@@ -35,6 +35,12 @@ use Bugzilla::Config qw(:admin); ...@@ -35,6 +35,12 @@ use Bugzilla::Config qw(:admin);
our @valid_fields = ('op_sys', 'rep_platform', 'priority', 'bug_severity', our @valid_fields = ('op_sys', 'rep_platform', 'priority', 'bug_severity',
'resolution'); 'resolution');
# Add custom select fields.
my @custom_fields = Bugzilla->get_fields({custom => 1,
type => FIELD_TYPE_SINGLE_SELECT});
push(@valid_fields, map { $_->name } @custom_fields);
###################################################################### ######################################################################
# Subroutines # Subroutines
###################################################################### ######################################################################
...@@ -128,6 +134,7 @@ $defaults{'bug_severity'} = 'defaultseverity'; ...@@ -128,6 +134,7 @@ $defaults{'bug_severity'} = 'defaultseverity';
# In this case, only the sortkey can be altered. # In this case, only the sortkey can be altered.
my %static; my %static;
$static{'resolution'} = ['', 'FIXED', 'MOVED', 'DUPLICATE']; $static{'resolution'} = ['', 'FIXED', 'MOVED', 'DUPLICATE'];
$static{$_->name} = ['---'] foreach (@custom_fields);
# #
# field = '' -> Show nice list of fields # field = '' -> Show nice list of fields
......
...@@ -211,8 +211,16 @@ foreach my $group (@$groups) { ...@@ -211,8 +211,16 @@ foreach my $group (@$groups) {
} }
} }
my @bug_fields = map {$_->name} Bugzilla->get_fields( # Include custom fields editable on bug creation.
{ custom => 1, obsolete => 0, enter_bug => 1}); my @custom_bug_fields = Bugzilla->get_fields(
{ custom => 1, obsolete => 0, enter_bug => 1 });
my @bug_fields = map { $_->name } @custom_bug_fields;
# Custom tables must be locked (required when validating custom fields).
my @custom_tables = grep { $_->type == FIELD_TYPE_SINGLE_SELECT } @custom_bug_fields;
@custom_tables = map { $_->name . ' READ' } @custom_tables;
push(@bug_fields, qw( push(@bug_fields, qw(
product product
component component
...@@ -251,7 +259,7 @@ $dbh->bz_lock_tables('bugs WRITE', 'bug_group_map WRITE', 'longdescs WRITE', ...@@ -251,7 +259,7 @@ $dbh->bz_lock_tables('bugs WRITE', 'bug_group_map WRITE', 'longdescs WRITE',
'products READ', 'versions READ', 'milestones READ', 'products READ', 'versions READ', 'milestones READ',
'components READ', 'profiles READ', 'bug_severity READ', 'components READ', 'profiles READ', 'bug_severity READ',
'op_sys READ', 'priority READ', 'rep_platform READ', 'op_sys READ', 'priority READ', 'rep_platform READ',
'group_control_map READ'); 'group_control_map READ', @custom_tables);
my $bug = Bugzilla::Bug->create(\%bug_params); my $bug = Bugzilla::Bug->create(\%bug_params);
......
...@@ -775,14 +775,16 @@ foreach my $field ("rep_platform", "priority", "bug_severity", ...@@ -775,14 +775,16 @@ foreach my $field ("rep_platform", "priority", "bug_severity",
} }
# Add custom fields data to the query that will update the database. # Add custom fields data to the query that will update the database.
foreach my $field (Bugzilla->custom_field_names) { foreach my $field (Bugzilla->get_fields({custom => 1, obsolete => 0})) {
if (defined $cgi->param($field) my $fname = $field->name;
if (defined $cgi->param($fname)
&& (!$cgi->param('dontchange') && (!$cgi->param('dontchange')
|| $cgi->param($field) ne $cgi->param('dontchange'))) || $cgi->param($fname) ne $cgi->param('dontchange')))
{ {
DoComma(); DoComma();
$::query .= "$field = ?"; $::query .= "$fname = ?";
my $value = $cgi->param($field); my $value = $cgi->param($fname);
check_field($fname, $value) if ($field->type == FIELD_TYPE_SINGLE_SELECT);
trick_taint($value); trick_taint($value);
push(@values, $value); push(@values, $value);
} }
......
...@@ -79,10 +79,11 @@ ...@@ -79,10 +79,11 @@
<tr> <tr>
<th align="right"><label for="type">Type:</label></th> <th align="right"><label for="type">Type:</label></th>
<td> <td>
[%# Only one field type is valid right now. But let's prepare the UI
# for future new types. %]
<select id="type" name="type"> <select id="type" name="type">
<option value="FIELD_TYPE_FREETEXT">Free Text</option> [% FOREACH type = field_types.keys %]
[% NEXT IF type == constants.FIELD_TYPE_UNKNOWN %]
<option value="[% type FILTER html %]">[% field_types.$type FILTER html %]</option>
[% END %]
</select> </select>
</td> </td>
......
...@@ -69,7 +69,7 @@ ...@@ -69,7 +69,7 @@
</tr> </tr>
<tr> <tr>
<th align="right">Type:</th> <th align="right">Type:</th>
<td>Free Text</td> <td>[% field_types.${field.type} FILTER html %]</td>
<th align="right"><label for="obsolete">Is obsolete:</label></th> <th align="right"><label for="obsolete">Is obsolete:</label></th>
<td><input type="checkbox" id="obsolete" name="obsolete" value="1" <td><input type="checkbox" id="obsolete" name="obsolete" value="1"
...@@ -85,6 +85,15 @@ ...@@ -85,6 +85,15 @@
<th>&nbsp;</th> <th>&nbsp;</th>
<td>&nbsp;</td> <td>&nbsp;</td>
</tr> </tr>
[% IF field.type == constants.FIELD_TYPE_SINGLE_SELECT %]
<tr>
<th>&nbsp;</th>
<td colspan="3">
<a href="editvalues.cgi?field=[% field.name FILTER url_quote %]">Edit
legal values for this field</a>.
</td>
</tr>
[% END %]
</table> </table>
<br> <br>
<input type="hidden" name="action" value="update"> <input type="hidden" name="action" value="update">
......
...@@ -37,6 +37,10 @@ ...@@ -37,6 +37,10 @@
heading => "Sortkey" heading => "Sortkey"
}, },
{ {
name => "type_name"
heading => "Type"
},
{
name => "enter_bug" name => "enter_bug"
heading => "Editable on Bug Creation" heading => "Editable on Bug Creation"
}, },
...@@ -51,6 +55,14 @@ ...@@ -51,6 +55,14 @@
] ]
%] %]
[% USE Bugzilla %]
[% custom_fields = Bugzilla.get_fields({ custom => 1 }) %]
[%# We want to display the type name of fields, not their type ID. %]
[% FOREACH cf_field = custom_fields %]
[% cf_field.type_name = field_types.${cf_field.type} %]
[% END %]
[% PROCESS admin/table.html.tmpl [% PROCESS admin/table.html.tmpl
columns = columns columns = columns
data = custom_fields data = custom_fields
......
...@@ -42,6 +42,14 @@ ...@@ -42,6 +42,14 @@
[% SWITCH field.type %] [% SWITCH field.type %]
[% CASE constants.FIELD_TYPE_FREETEXT %] [% CASE constants.FIELD_TYPE_FREETEXT %]
<input name="[% field.name FILTER html %]" value="[% value FILTER html %]" size="60"> <input name="[% field.name FILTER html %]" value="[% value FILTER html %]" size="60">
[% CASE constants.FIELD_TYPE_SINGLE_SELECT %]
<select id="[% field.name FILTER html %]" name="[% field.name FILTER html %]">
[% FOREACH legal_value = field.legal_values %]
<option value="[% legal_value FILTER html %]"
[%- " selected=\"selected\"" IF value == legal_value %]>
[%- legal_value FILTER html %]</option>
[% END %]
</select>
[% END %] [% END %]
[% ELSE %] [% ELSE %]
[% value FILTER html %] [% value FILTER html %]
......
...@@ -170,6 +170,10 @@ ...@@ -170,6 +170,10 @@
The custom sort order specified in your form submission contains an The custom sort order specified in your form submission contains an
invalid column name <em>[% fragment FILTER html %]</em>. invalid column name <em>[% fragment FILTER html %]</em>.
[% ELSIF error == "invalid_customfield_type" %]
[% title = "Invalid Field Type" %]
The type <em>[% type FILTER html %]</em> is not a valid field type.
[% ELSIF error == "invalid_dimensions" %] [% ELSIF error == "invalid_dimensions" %]
[% title = "Invalid Dimensions" %] [% title = "Invalid Dimensions" %]
The width or height specified is not a positive integer. The width or height specified is not a positive integer.
......
...@@ -80,6 +80,10 @@ ...@@ -80,6 +80,10 @@
IF !field_descs.${bz_field.name}.defined %] IF !field_descs.${bz_field.name}.defined %]
[% END %] [% END %]
[% field_types = { ${constants.FIELD_TYPE_UNKNOWN} => "Unknown Type",
${constants.FIELD_TYPE_FREETEXT} => "Free Text",
${constants.FIELD_TYPE_SINGLE_SELECT} => "Drop Down" } %]
[% status_descs = { "UNCONFIRMED" => "UNCONFIRMED", [% status_descs = { "UNCONFIRMED" => "UNCONFIRMED",
"NEW" => "NEW", "NEW" => "NEW",
"ASSIGNED" => "ASSIGNED", "ASSIGNED" => "ASSIGNED",
......
...@@ -314,6 +314,11 @@ ...@@ -314,6 +314,11 @@
The field '[% field.name FILTER html %]' ([% field.description FILTER html %]) The field '[% field.name FILTER html %]' ([% field.description FILTER html %])
already exists. Please choose another name. already exists. Please choose another name.
[% ELSIF error == "customfield_invalid_name" %]
[% title = "Invalid Custom Field Name" %]
'[% name FILTER html %]' is not a valid name for a custom field.
A name may contain only letters, numbers, and the underscore character.
[% ELSIF error == "customfield_nonexistent" %] [% ELSIF error == "customfield_nonexistent" %]
[% title = "Unknown Custom Field" %] [% title = "Unknown Custom Field" %]
There is no custom field with the name '[% name FILTER html %]'. There is no custom field with the name '[% name FILTER html %]'.
......
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