From b84db39863cd55166bff584265d255a0f882e64a Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Thu, 25 Jul 2013 14:41:32 +0800 Subject: Bug 750742: Create new BMO extension called TrackingFlags to move current tracking flags away from custom fields --- Bugzilla/Bug.pm | 19 +- Bugzilla/Constants.pm | 9 +- Bugzilla/Object.pm | 3 + Bugzilla/Search/Quicksearch.pm | 19 +- extensions/BMO/Extension.pm | 1 + extensions/BMO/lib/Reports/ReleaseTracking.pm | 25 +- extensions/BMO/lib/Util.pm | 17 +- .../en/default/bug/create/create-winqual.html.tmpl | 93 ++++-- .../BMO/template/en/default/email/bugmail.txt.tmpl | 9 +- .../hook/bug/edit-after_custom_fields.html.tmpl | 128 -------- extensions/BMO/web/js/edit_bug.js | 32 -- extensions/TrackingFlags/Extension.pm | 324 +++++++++++++++++++++ post_bug.cgi | 4 +- skins/custom/create_bug.css | 4 +- template/en/default/bug/create/create.html.tmpl | 67 +++-- template/en/default/bug/edit.html.tmpl | 1 + template/en/default/bug/field.html.tmpl | 4 + template/en/default/global/field-descs.none.tmpl | 1 + 18 files changed, 508 insertions(+), 252 deletions(-) delete mode 100644 extensions/BMO/template/en/default/hook/bug/edit-after_custom_fields.html.tmpl diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm index 15272e542..d974d8aa0 100644 --- a/Bugzilla/Bug.pm +++ b/Bugzilla/Bug.pm @@ -80,7 +80,8 @@ use constant AUDIT_UPDATES => 0; # This is a sub because it needs to call other subroutines. sub DB_COLUMNS { my $dbh = Bugzilla->dbh; - my @custom = grep {$_->type != FIELD_TYPE_MULTI_SELECT} + my @custom = grep {$_->type != FIELD_TYPE_MULTI_SELECT + && $_->type != FIELD_TYPE_EXTENSION} Bugzilla->active_custom_fields; my @custom_names = map {$_->name} @custom; @@ -114,9 +115,9 @@ sub DB_COLUMNS { $dbh->sql_date_format('creation_ts', '%Y.%m.%d %H:%i') . ' AS creation_ts', $dbh->sql_date_format('deadline', '%Y-%m-%d') . ' AS deadline', @custom_names); - + Bugzilla::Hook::process("bug_columns", { columns => \@columns }); - + return @columns; } @@ -214,7 +215,8 @@ sub VALIDATOR_DEPENDENCIES { }; sub UPDATE_COLUMNS { - my @custom = grep {$_->type != FIELD_TYPE_MULTI_SELECT} + my @custom = grep {$_->type != FIELD_TYPE_MULTI_SELECT + && $_->type != FIELD_TYPE_EXTENSION} Bugzilla->active_custom_fields; my @custom_names = map {$_->name} @custom; my @columns = qw( @@ -2334,7 +2336,8 @@ sub set_all { $self->_add_remove($params, 'see_also'); # And set custom fields. - my @custom_fields = Bugzilla->active_custom_fields; + my @custom_fields = grep { $_->type != FIELD_TYPE_EXTENSION } + Bugzilla->active_custom_fields; foreach my $field (@custom_fields) { my $fname = $field->name; if (exists $params->{$fname}) { @@ -3830,6 +3833,9 @@ sub editable_bug_fields { # Ensure field exists before attempting to remove it. splice(@fields, $location, 1) if ($location > -1); } + + Bugzilla::Hook::process('bug_editable_bug_fields', { fields => \@fields }); + # Sorted because the old @::log_columns variable, which this replaces, # was sorted. return sort(@fields); @@ -4344,6 +4350,7 @@ sub _create_cf_accessors { my $fields = Bugzilla->fields({ custom => 1 }); foreach my $field (@$fields) { + next if $field->type == FIELD_TYPE_EXTENSION; my $accessor = $class->_accessor_for($field); my $name = "${class}::" . $field->name; { @@ -4353,6 +4360,8 @@ sub _create_cf_accessors { } } + Bugzilla::Hook::process('bug_create_cf_accessors'); + Bugzilla->request_cache->{"${class}_cf_accessors_created"} = 1; } diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm index b5d3aab61..1f712f25d 100644 --- a/Bugzilla/Constants.pm +++ b/Bugzilla/Constants.pm @@ -129,6 +129,8 @@ use Memoize; FIELD_TYPE_BUG_ID FIELD_TYPE_BUG_URLS FIELD_TYPE_KEYWORDS + FIELD_TYPE_EXTENSION + FIELD_TYPE_HIGHEST_PLUS_ONE EMPTY_DATETIME_REGEX @@ -397,7 +399,8 @@ use constant SENDMAIL_PATH => '/usr/lib:/usr/sbin:/usr/ucblib'; # only storage but also logic. For example, we might add a "user" field type # whose values are stored in an integer column in the database but for which # we do more than we would do for a standard integer type (f.e. we might -# display a user picker). +# display a user picker). Fields of type FIELD_TYPE_EXTENSION should generally +# be ignored by the core code and is used primary by extensions. use constant FIELD_TYPE_UNKNOWN => 0; use constant FIELD_TYPE_FREETEXT => 1; @@ -409,9 +412,11 @@ use constant FIELD_TYPE_BUG_ID => 6; use constant FIELD_TYPE_BUG_URLS => 7; use constant FIELD_TYPE_KEYWORDS => 8; use constant FIELD_TYPE_DATE => 9; +use constant FIELD_TYPE_EXTENSION => 99; + # Add new field types above this line, and change the below value in the # obvious fashion -use constant FIELD_TYPE_HIGHEST_PLUS_ONE => 10; +use constant FIELD_TYPE_HIGHEST_PLUS_ONE => 100; use constant EMPTY_DATETIME_REGEX => qr/^[0\-:\sA-Za-z]+$/; diff --git a/Bugzilla/Object.pm b/Bugzilla/Object.pm index 96651d191..34134a69f 100644 --- a/Bugzilla/Object.pm +++ b/Bugzilla/Object.pm @@ -61,6 +61,9 @@ sub new { my $class = ref($invocant) || $invocant; my $object = $class->_init(@_); bless($object, $class) if $object; + + Bugzilla::Hook::process('object_end_of_new', { object => $object }); + return $object; } diff --git a/Bugzilla/Search/Quicksearch.pm b/Bugzilla/Search/Quicksearch.pm index 48eaff8d1..c3c11b728 100644 --- a/Bugzilla/Search/Quicksearch.pm +++ b/Bugzilla/Search/Quicksearch.pm @@ -384,15 +384,18 @@ sub _handle_field_names { # Flag and requestee shortcut if ($or_operand =~ /^(?:flag:)?([^\?]+\?)([^\?]*)$/) { - my ($flagtype, $requestee) = ($1, $2); - addChart('flagtypes.name', 'substring', $flagtype, $negate); - if ($requestee) { - # AND - $chart++; - $and = $or = 0; - addChart('requestees.login_name', 'substring', $requestee, $negate); + # BMO: Do not treat custom fields as flags if value is ? + if ($1 !~ /^cf_/) { + my ($flagtype, $requestee) = ($1, $2); + addChart('flagtypes.name', 'substring', $flagtype, $negate); + if ($requestee) { + # AND + $chart++; + $and = $or = 0; + addChart('requestees.login_name', 'substring', $requestee, $negate); + } + return 1; } - return 1; } # Generic field1,field2,field3:value1,value2 notation. diff --git a/extensions/BMO/Extension.pm b/extensions/BMO/Extension.pm index 4f4e4e82d..2129e675d 100644 --- a/extensions/BMO/Extension.pm +++ b/extensions/BMO/Extension.pm @@ -889,6 +889,7 @@ sub mailer_before_send { my @set_values = (); foreach my $field (@fields) { + next if $field->type == FIELD_TYPE_EXTENSION; my $field_name = $field->name; next if cf_flag_disabled($field_name, $bug); next if !$bug->$field_name || $bug->$field_name eq '---'; diff --git a/extensions/BMO/lib/Reports/ReleaseTracking.pm b/extensions/BMO/lib/Reports/ReleaseTracking.pm index e307da192..f9eabc340 100644 --- a/extensions/BMO/lib/Reports/ReleaseTracking.pm +++ b/extensions/BMO/lib/Reports/ReleaseTracking.pm @@ -9,6 +9,7 @@ package Bugzilla::Extension::BMO::Reports::ReleaseTracking; use strict; use warnings; +use Bugzilla::Constants; use Bugzilla::Error; use Bugzilla::Extension::BMO::Util; use Bugzilla::Field; @@ -121,7 +122,7 @@ sub report { my @unlink_products; foreach my $product (@usable_products) { my @fields = - grep { is_active_status_field($_->name) } + grep { is_active_status_field($_) } Bugzilla->active_custom_fields({ product => $product }); my @field_ids = map { $_->id } @fields; if (!scalar @fields) { @@ -244,6 +245,11 @@ sub report { $query .= "INNER JOIN bugs_activity a ON a.bug_id = b.bug_id "; } + if (grep($_ == FIELD_TYPE_EXTENSION, map { $_->{type} } @{ $q->{fields} })) { + $query .= "LEFT JOIN tracking_flags_bugs AS tfb ON tfb.bug_id = b.bug_id " . + "LEFT JOIN tracking_flags AS tf ON tfb.tracking_flag_id = tf.id "; + } + $query .= "WHERE "; if ($q->{start_date}) { @@ -273,11 +279,15 @@ sub report { if (scalar @{$q->{fields}}) { my @fields; foreach my $field (@{$q->{fields}}) { - push @fields, - "(" . - ($field->{value} eq '+' ? '' : '!') . - "(b.".$field->{name}." IN ('fixed','verified'))" . - ") "; + my $field_sql = "(" . ($field->{value} eq '+' ? '' : '!') . "("; + if ($field->{type} == FIELD_TYPE_EXTENSION) { + $field_sql .= "tf.name = " . $dbh->quote($field->{name}) . " AND tfb.value"; + } + else { + $field_sql .= "b." . $field->{name}; + } + $field_sql .= " IN ('fixed','verified')))"; + push(@fields, $field_sql); } my $join = uc $q->{join}; push @where, '(' . join(" $join ", @fields) . ')'; @@ -383,7 +393,8 @@ sub _parse_query { my ($id, $value) = ($1, $2); my $field_obj = Bugzilla::Field->new($id) or ThrowUserError('report_invalid_parameter', { name => 'field_id' }); - push @fields, { id => $id, value => $value, name => $field_obj->name }; + push @fields, { id => $id, value => $value, + name => $field_obj->name, type => $field_obj->type }; } $query->{fields} = \@fields; diff --git a/extensions/BMO/lib/Util.pm b/extensions/BMO/lib/Util.pm index ccb25758b..b25215f61 100644 --- a/extensions/BMO/lib/Util.pm +++ b/extensions/BMO/lib/Util.pm @@ -9,6 +9,7 @@ package Bugzilla::Extension::BMO::Util; use strict; use warnings; +use Bugzilla::Constants; use Bugzilla::Error; use Bugzilla::Extension::BMO::Data qw($cf_disabled_flags); use Date::Parse; @@ -74,10 +75,20 @@ sub parse_date { } sub is_active_status_field { - my ($field_name) = @_; - if ($field_name =~ /^cf_status/) { - return !grep { $field_name eq $_ } @$cf_disabled_flags + my ($field) = @_; + if ($field->type != FIELD_TYPE_EXTENSION + && $field->name =~ /^cf_status/) + { + return !grep { $field->name eq $_ } @$cf_disabled_flags } + + if ($field->type == FIELD_TYPE_EXTENSION + && $field->can('flag_type') + && $field->flag_type eq 'status') + { + return 1; + } + return 0; } diff --git a/extensions/BMO/template/en/default/bug/create/create-winqual.html.tmpl b/extensions/BMO/template/en/default/bug/create/create-winqual.html.tmpl index d14cca810..fd21ed4ed 100644 --- a/extensions/BMO/template/en/default/bug/create/create-winqual.html.tmpl +++ b/extensions/BMO/template/en/default/bug/create/create-winqual.html.tmpl @@ -611,23 +611,25 @@ TUI_hide_default('expert_fields'); [%# non-tracking flags custom fields %] -[% FOREACH field = Bugzilla.active_custom_fields %] +[% FOREACH field = Bugzilla.active_custom_fields(product=>product,type=>1) %] + [% NEXT IF field.type == constants.FIELD_TYPE_EXTENSION %] [% NEXT UNLESS field.enter_bug %] - [% NEXT IF cf_hidden_in_product(field.name, product.name, component.name, 1) %] [%# crash-signature gets custom handling %] - [% NEXT IF field.name == 'cf_crash_signature' %] - + [% IF field.name == 'cf_crash_signature' %] + [% show_crash_signature = 1 %] + [% NEXT %] + [% END %] [% SET value = ${field.name}.defined ? ${field.name} : "" %] - [% INCLUDE bug/field.html.tmpl - bug = default, field = field, value = value, editable = 1, + [% INCLUDE bug/field.html.tmpl + bug = default, field = field, value = value, editable = 1, value_span = 3 %] [% END %] [%# crash-signature handling %] -[% UNLESS cf_hidden_in_product('cf_crash_signature', product.name, component.name, 1) %] +[% IF show_crash_signature %] @@ -647,23 +649,27 @@ TUI_hide_default('expert_fields'); [% END %] -[% display_bug_flags = 0 %] -[% FOREACH field = Bugzilla.active_custom_fields %] +[% old_tracking_flags = [] %] +[% old_project_flags = [] %] +[% FOREACH field = Bugzilla.active_custom_fields(product=>product,type=>2) %] + [% NEXT IF field.type == constants.FIELD_TYPE_EXTENSION %] [% NEXT UNLESS field.enter_bug %] - [% NEXT IF cf_hidden_in_product(field.name, product.name, component.name, 2) %] - [% display_bug_flags = 1 %] - [% LAST %] + [% IF cf_is_project_flag(field.name) %] + [% old_project_flags.push(field) %] + [% ELSE %] + [% old_tracking_flags.push(field) %] + [% END %] [% END %] [% display_flags = 0 %] [% any_flags_requesteeble = 0 %] -[% FOREACH flag_type = product.flag_types(is_active=>1).bug %] +[% FOREACH flag_type = product.flag_types.bug %] [% display_flags = 1 %] [% SET any_flags_requesteeble = 1 IF flag_type.is_requestable && flag_type.is_requesteeble %] [% LAST IF display_flags && any_flags_requesteeable %] [% END %] -[% IF display_bug_flags || display_flags %] +[% IF old_project_flags.size || old_tracking_flags.size || display_flags %] Flags: @@ -680,38 +686,63 @@ TUI_hide_default('expert_fields'); Set [% terms.bug %] flags - - [% IF display_bug_flags %] - - - -[% END %] - -[% IF tracking_flags.size %] - - - - -[% END %] diff --git a/extensions/BMO/web/js/edit_bug.js b/extensions/BMO/web/js/edit_bug.js index e630eb995..16bef9890 100644 --- a/extensions/BMO/web/js/edit_bug.js +++ b/extensions/BMO/web/js/edit_bug.js @@ -23,38 +23,6 @@ * ***** END LICENSE BLOCK ***** */ -// --- custom flags -var Dom = YAHOO.util.Dom; - -function bmo_hide_tracking_flags() { - for (var field in bmo_custom_flags) { - var el = Dom.get(field); - var value = el ? el.value : bmo_custom_flags[field]; - if (el && (value != bmo_custom_flags[field])) { - bmo_show_tracking_flags(); - return; - } - if (value == '---') { - Dom.addClass('row_' + field, 'bz_hidden'); - } else { - Dom.addClass(field, 'bz_hidden'); - Dom.removeClass('ro_' + field, 'bz_hidden'); - } - } -} - -function bmo_show_tracking_flags() { - Dom.addClass('edit_tracking_fields_action', 'bz_hidden'); - for (var field in bmo_custom_flags) { - if (Dom.get(field).value == '---') { - Dom.removeClass('row_' + field, 'bz_hidden'); - } else { - Dom.removeClass(field, 'bz_hidden'); - Dom.addClass('ro_' + field, 'bz_hidden'); - } - } -} - function init_clone_bug_menu(el, bug_id, product, component) { var diff_url = 'enter_bug.cgi?cloned_bug_id=' + bug_id; var cur_url = diff_url + diff --git a/extensions/TrackingFlags/Extension.pm b/extensions/TrackingFlags/Extension.pm index a7cfe97c9..7aa3f5af3 100644 --- a/extensions/TrackingFlags/Extension.pm +++ b/extensions/TrackingFlags/Extension.pm @@ -11,8 +11,80 @@ use strict; use base qw(Bugzilla::Extension); +use Bugzilla::Extension::TrackingFlags::Constants; +use Bugzilla::Extension::TrackingFlags::Flag; +use Bugzilla::Extension::TrackingFlags::Flag::Bug; +use Bugzilla::Extension::TrackingFlags::Admin; + +use Bugzilla::Bug; +use Bugzilla::Constants; +use Bugzilla::Field; +use Bugzilla::Product; +use Bugzilla::Component; +use Bugzilla::Error; +use Bugzilla::Extension::BMO::Data; + our $VERSION = '1'; +sub page_before_template { + my ($self, $args) = @_; + my $page = $args->{'page_id'}; + my $vars = $args->{'vars'}; + + if ($page eq 'tracking_flags_admin_list.html') { + Bugzilla->user->in_group('admin') + || ThrowUserError('auth_failure', + { group => 'admin', + action => 'access', + object => 'administrative_pages' }); + admin_list($vars); + + } elsif ($page eq 'tracking_flags_admin_edit.html') { + Bugzilla->user->in_group('admin') + || ThrowUserError('auth_failure', + { group => 'admin', + action => 'access', + object => 'administrative_pages' }); + admin_edit($vars); + } +} + +sub template_before_process { + my ($self, $args) = @_; + my $file = $args->{'file'}; + my $vars = $args->{'vars'}; + + if ($file eq 'bug/create/create.html.tmpl' + || $file eq 'bug/create/create-winqual.html.tmpl') + { + $vars->{'tracking_flags'} = Bugzilla::Extension::TrackingFlags::Flag->match({ + product => $vars->{'product'}->name, + is_active => 1, + }); + + $vars->{'tracking_flag_types'} = FLAG_TYPES; + } + elsif ($file eq 'bug/edit.html.tmpl'|| $file eq 'bug/show.xml.tmpl') { + # note: bug/edit.html.tmpl doesn't support multiple bugs + my $bug = exists $vars->{'bugs'} ? $vars->{'bugs'}[0] : $vars->{'bug'}; + + $vars->{'tracking_flags'} = Bugzilla::Extension::TrackingFlags::Flag->match({ + product => $bug->product, + component => $bug->component, + bug_id => $bug->id, + is_active => 1, + }); + + $vars->{'tracking_flag_types'} = FLAG_TYPES; + } + elsif ($file eq 'list/edit-multiple.html.tmpl' && $vars->{'one_product'}) { + $vars->{'tracking_flags'} = Bugzilla::Extension::TrackingFlags::Flag->match({ + product => $vars->{'one_product'}->name, + is_active => 1 + }); + } +} + sub db_schema_abstract_schema { my ($self, $args) = @_; $args->{'schema'}->{'tracking_flags'} = { @@ -188,4 +260,256 @@ sub db_schema_abstract_schema { }; } +sub active_custom_fields { + my ($self, $args) = @_; + my $fields = $args->{'fields'}; + my $params = $args->{'params'}; + my $product = $params->{'product'}; + my $component = $params->{'component'}; + + # Create a hash of current fields based on field names + my %field_hash = map { $_->name => $_ } @$$fields; + + my @tracking_flags; + if ($product) { + my $params = { product_id => $product->id }; + $params->{'component_id'} = $component->id if $component; + @tracking_flags = @{ Bugzilla::Extension::TrackingFlags::Flag->match($params) }; + } + else { + @tracking_flags = Bugzilla::Extension::TrackingFlags::Flag->get_all; + } + + # Add tracking flags to fields hash replacing if already exists for our + # flag object instead of the usual Field.pm object + foreach my $flag (@tracking_flags) { + $field_hash{$flag->name} = $flag; + } + + @$$fields = values %field_hash; +} + +sub buglist_columns { + my ($self, $args) = @_; + my $columns = $args->{columns}; + my $dbh = Bugzilla->dbh; + my @tracking_flags = Bugzilla::Extension::TrackingFlags::Flag->get_all; + foreach my $flag (@tracking_flags) { + $columns->{$flag->name} = { + name => "COALESCE(map_" . $flag->name . ".value, '---')", + title => $flag->description + }; + } +} + +sub buglist_column_joins { + my ($self, $args) = @_; + my $column_joins = $args->{'column_joins'}; + my @tracking_flags = Bugzilla::Extension::TrackingFlags::Flag->get_all; + foreach my $flag (@tracking_flags) { + $column_joins->{$flag->name} = { + as => 'map_' . $flag->name, + table => 'tracking_flags_bugs', + extra => [ 'map_' . $flag->name . '.tracking_flag_id = ' . $flag->flag_id ] + }; + } +} + +sub bug_create_cf_accessors { + my ($self, $args) = @_; + # Create the custom accessors for the flag values + my @tracking_flags = Bugzilla::Extension::TrackingFlags::Flag->get_all; + foreach my $flag (@tracking_flags) { + my $flag_name = $flag->name; + my $accessor = sub { + my $self = shift; + return $self->{$flag_name} if defined $self->{$flag_name}; + if (!exists $self->{'_tf_bug_values_preloaded'}) { + # preload all values currently set for this bug + my $bug_values + = Bugzilla::Extension::TrackingFlags::Flag::Bug->match({ bug_id => $self->id }); + foreach my $value (@$bug_values) { + $self->{$value->tracking_flag->name} = $value->value; + } + $self->{'_tf_bug_values_preloaded'} = 1; + } + return $self->{$flag_name} ||= '---'; + }; + my $name = "Bugzilla::Bug::$flag_name"; + if (!Bugzilla::Bug->can($flag_name)) { + no strict 'refs'; + *{$name} = $accessor; + } + } +} + +sub bug_editable_bug_fields { + my ($self, $args) = @_; + my $fields = $args->{'fields'}; + my @tracking_flags = Bugzilla::Extension::TrackingFlags::Flag->get_all; + foreach my $flag (@tracking_flags) { + push(@$fields, $flag->name); + } +} + +sub search_operator_field_override { + my ($self, $args) = @_; + my $operators = $args->{'operators'}; + my @tracking_flags = Bugzilla::Extension::TrackingFlags::Flag->get_all; + foreach my $flag (@tracking_flags) { + $operators->{$flag->name} = { + _non_changed => sub { + _tracking_flags_search_nonchanged($flag->flag_id, @_) + } + }; + } +} + +sub _tracking_flags_search_nonchanged { + my ($flag_id, $search, $args) = @_; + my ($bugs_table, $chart_id, $joins, $value, $operator) = + @$args{qw(bugs_table chart_id joins value operator)}; + my $dbh = Bugzilla->dbh; + + return if ($operator =~ m/^changed/); + + my $bugs_alias = "tracking_flags_bugs_$chart_id"; + my $flags_alias = "tracking_flags_$chart_id"; + + my $bugs_join = { + table => 'tracking_flags_bugs', + as => $bugs_alias, + from => $bugs_table . ".bug_id", + to => "bug_id", + extra => [$bugs_alias . ".tracking_flag_id = $flag_id"] + }; + + push(@$joins, $bugs_join); + + $args->{'full_field'} = "$bugs_alias.value"; +} + +sub bug_end_of_create { + my ($self, $args) = @_; + my $bug = $args->{'bug'}; + my $timestamp = $args->{'timestamp'}; + my $params = Bugzilla->input_params; + my $user = Bugzilla->user; + + my $tracking_flags = Bugzilla::Extension::TrackingFlags::Flag->match({ + product => $bug->product, + component => $bug->component, + is_active => 1, + }); + + foreach my $flag (@$tracking_flags) { + next if !$params->{$flag->name}; + foreach my $value (@{$flag->values}) { + next if $value->value ne $params->{$flag->name}; + next if $value->value eq '---'; # do not insert if value is '---', same as empty + if (!$flag->can_set_value($value->value)) { + ThrowUserError('tracking_flags_change_denied', + { flag => $flag, value => $value }); + } + Bugzilla::Extension::TrackingFlags::Flag::Bug->create({ + tracking_flag_id => $flag->flag_id, + bug_id => $bug->id, + value => $value->value, + }); + # Add the name/value pair to the bug object + $bug->{$flag->name} = $value->value; + } + } +} + +sub bug_end_of_update { + my ($self, $args) = @_; + my $bug = $args->{'bug'}; + my $timestamp = $args->{'timestamp'}; + my $changes = $args->{'changes'}; + my $params = Bugzilla->input_params; + my $user = Bugzilla->user; + + # Do not filter by product/component as we may be changing those + my $tracking_flags = Bugzilla::Extension::TrackingFlags::Flag->match({ + bug_id => $bug->id, + is_active => 1, + }); + + my (@flag_changes); + foreach my $flag (@$tracking_flags) { + my $new_value = $params->{$flag->name} || '---'; + my $old_value = $flag->bug_flag->value; + + next if $new_value eq $old_value; + + if ($new_value ne $old_value) { + # Do not allow if the user cannot set the old value or the new value + if (!$flag->can_set_value($new_value)) { + ThrowUserError('tracking_flags_change_denied', + { flag => $flag, value => $new_value }); + } + push(@flag_changes, { flag => $flag, + added => $new_value, + removed => $old_value }); + } + } + + foreach my $change (@flag_changes) { + my $flag = $change->{'flag'}; + my $added = $change->{'added'}; + my $removed = $change->{'removed'}; + + if ($added eq '---') { + $flag->bug_flag->remove_from_db(); + } + elsif ($removed eq '---') { + Bugzilla::Extension::TrackingFlags::Flag::Bug->create({ + tracking_flag_id => $flag->flag_id, + bug_id => $bug->id, + value => $added, + }); + } + else { + $flag->bug_flag->set_value($added); + $flag->bug_flag->update($timestamp); + } + + $changes->{$flag->name} = [ $removed, $added ]; + LogActivityEntry($bug->id, $flag->name, $removed, $added, $user->id, $timestamp); + + # Update the name/value pair in the bug object + $bug->{$flag->name} = $added; + } +} + +sub mailer_before_send { + my ($self, $args) = @_; + my $email = $args->{email}; + + # Add X-Bugzilla-Tracking header or add to it + # if already exists + if ($email->header('X-Bugzilla-ID')) { + my $bug_id = $email->header('X-Bugzilla-ID'); + + my $tracking_flags + = Bugzilla::Extension::TrackingFlags::Flag->match({ bug_id => $bug_id }); + + my @set_values = (); + foreach my $flag (@$tracking_flags) { + next if $flag->bug_flag->value eq '---'; + push(@set_values, $flag->description . ":" . $flag->bug_flag->value); + } + + if (@set_values) { + my $set_values_string = join(' ', @set_values); + if ($email->header('X-Bugzilla-Tracking')) { + $set_values_string = $email->header('X-Bugzilla-Tracking') . + " " . $set_values_string; + } + $email->header_set('X-Bugzilla-Tracking' => $set_values_string); + } + } +} + __PACKAGE__->NAME; diff --git a/post_bug.cgi b/post_bug.cgi index 839751b58..a201393c1 100755 --- a/post_bug.cgi +++ b/post_bug.cgi @@ -98,7 +98,9 @@ $template->process($format->{'template'}, $vars, \$comment) || ThrowTemplateError($template->error()); # Include custom fields editable on bug creation. -my @custom_bug_fields = grep {$_->type != FIELD_TYPE_MULTI_SELECT && $_->enter_bug} +my @custom_bug_fields = grep {$_->type != FIELD_TYPE_MULTI_SELECT + && $_->type != FIELD_TYPE_EXTENSION + && $_->enter_bug} Bugzilla->active_custom_fields; # Undefined custom fields are ignored to ensure they will get their default diff --git a/skins/custom/create_bug.css b/skins/custom/create_bug.css index b1164aa75..b3088048d 100644 --- a/skins/custom/create_bug.css +++ b/skins/custom/create_bug.css @@ -1,7 +1,7 @@ -#bug_project_flags .field_label, -#bug_tracking_flags .field_label { +.tracking_flags .field_label a { font-weight: normal !important; + color: #000; } #guided { diff --git a/template/en/default/bug/create/create.html.tmpl b/template/en/default/bug/create/create.html.tmpl index eeb4539d2..3eed01124 100644 --- a/template/en/default/bug/create/create.html.tmpl +++ b/template/en/default/bug/create/create.html.tmpl @@ -635,6 +635,7 @@ TUI_hide_default('attachment_text_field'); [%# non-tracking flags custom fields %] [% FOREACH field = Bugzilla.active_custom_fields(product=>product,type=>1) %] + [% NEXT IF field.type == constants.FIELD_TYPE_EXTENSION %] [% NEXT UNLESS field.enter_bug %] [%# crash-signature gets custom handling %] [% IF field.name == 'cf_crash_signature' %] @@ -671,14 +672,15 @@ TUI_hide_default('attachment_text_field'); [% END %] -[% tracking_flags = [] %] -[% project_flags = [] %] +[% old_tracking_flags = [] %] +[% old_project_flags = [] %] [% FOREACH field = Bugzilla.active_custom_fields(product=>product,type=>2) %] + [% NEXT IF field.type == constants.FIELD_TYPE_EXTENSION %] [% NEXT UNLESS field.enter_bug %] [% IF cf_is_project_flag(field.name) %] - [% project_flags.push(field) %] + [% old_project_flags.push(field) %] [% ELSE %] - [% tracking_flags.push(field) %] + [% old_tracking_flags.push(field) %] [% END %] [% END %] @@ -690,7 +692,7 @@ TUI_hide_default('attachment_text_field'); [% LAST IF display_flags && any_flags_requesteeable %] [% END %] -[% IF project_flags.size || tracking_flags.size || display_flags %] +[% IF old_project_flags.size || old_tracking_flags.size || display_flags %] @@ -708,41 +710,47 @@ TUI_hide_default('attachment_text_field');
- + + [% IF old_tracking_flags.size %] + + [% END %] + [% IF old_project_flags.size %] + + + [% END %] [% IF display_flags %] [% END %] - + + [% Hook.process('bug_flags_end') %]
+ + [% FOREACH field = old_tracking_flags %] + [% SET value = ${field.name}.defined ? ${field.name} : "" %] + + [% INCLUDE bug/field.html.tmpl + bug = default + field = field + value = value + editable = 1 + value_span = 3 + %] + + [% END %] + [% Hook.process('tracking_flags_end') %] +
Tracking Flags:
+
+ - [% FOREACH field = Bugzilla.active_custom_fields %] - [% NEXT UNLESS field.enter_bug %] - [% NEXT IF cf_hidden_in_product(field.name, product.name, component.name, 2) %] - - [% SET value = ${field.name}.defined ? ${field.name} : "" %] - - [% INCLUDE bug/field.html.tmpl - bug = default, field = field, value = value, editable = 1, - value_span = 3 %] - - [% END %] + + [% FOREACH field = old_project_flags %] + [% SET value = ${field.name}.defined ? ${field.name} : "" %] + + [% INCLUDE bug/field.html.tmpl + bug = default + field = field + value = value + editable = 1 + value_span = 3 + %] + + [% END %] + [% Hook.process('project_flags_end') %]
Project Flags:
- [% PROCESS "flag/list.html.tmpl" flag_types = product.flag_types(is_active=>1).bug + [% PROCESS "flag/list.html.tmpl" flag_types = product.flag_types.bug any_flags_requesteeble = any_flags_requesteeble flag_table_id = "bug_flags" %]
diff --git a/extensions/BMO/template/en/default/email/bugmail.txt.tmpl b/extensions/BMO/template/en/default/email/bugmail.txt.tmpl index 76fa492ee..771157dd1 100644 --- a/extensions/BMO/template/en/default/email/bugmail.txt.tmpl +++ b/extensions/BMO/template/en/default/email/bugmail.txt.tmpl @@ -40,15 +40,16 @@ Configure [% terms.bug %]mail: [% urlbase %]userprefs.cgi?tab=email Product/Component: [%+ bug.product +%] :: [%+ bug.component %] [% USE Bugzilla %] -[% tracking_flags = [] %] +[% show_tracking_flags = [] %] [% FOREACH field = Bugzilla.active_custom_fields(product => bug.product_obj, component => bug.component_obj, type => 2) %] [% NEXT IF cf_flag_disabled(field.name, bug) %] [% NEXT IF bug.${field.name} == "---" %] - [% tracking_flags.push(field) %] + [% show_tracking_flags.push(field) %] [% END %] -[% IF tracking_flags.size %] + +[% IF show_tracking_flags.size %] ------- Tracking Flags: ------- -[% FOREACH field = tracking_flags %] +[% FOREACH field = show_tracking_flags %] [%+ field.description %]:[% bug.${field.name} %] [% END %] [% END %] diff --git a/extensions/BMO/template/en/default/hook/bug/edit-after_custom_fields.html.tmpl b/extensions/BMO/template/en/default/hook/bug/edit-after_custom_fields.html.tmpl deleted file mode 100644 index f72267246..000000000 --- a/extensions/BMO/template/en/default/hook/bug/edit-after_custom_fields.html.tmpl +++ /dev/null @@ -1,128 +0,0 @@ -[%# ***** BEGIN LICENSE BLOCK ***** - # Version: MPL 1.1 - # - # The contents of this file are subject to the Mozilla Public License Version - # 1.1 (the "License"); you may not use this file except in compliance with - # the License. You may obtain a copy of the License at - # http://www.mozilla.org/MPL/ - # - # Software distributed under the License is distributed on an "AS IS" basis, - # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - # for the specific language governing rights and limitations under the - # License. - # - # The Original Code is the BMO Bugzilla Extension; - # - # The Initial Developer of the Original Code is the Mozilla Foundation. - # Portions created by the Initial Developer are Copyright (C) 2011 the - # Initial Developer. All Rights Reserved. - # - # Contributor(s): - # Byron Jones - # - # ***** END LICENSE BLOCK ***** - #%] - -[% tracking_flags = [] %] -[% project_flags = [] %] -[% FOREACH field = Bugzilla.active_custom_fields(product=>bug.product_obj,component=>bug.component_obj,type=>2) %] - [% NEXT IF NOT user.id AND bug.${field.name} == "---" %] - [% NEXT IF cf_flag_disabled(field.name, bug) %] - [% IF cf_is_project_flag(field.name) %] - [% project_flags.push(field) %] - [% ELSE %] - [% tracking_flags.push(field) %] - [% END %] -[% END %] - -[% IF project_flags.size %] -
- - - - [% IF bug.check_can_change_field('flagtypes.name', 0, 1) %] - - [% FOREACH field = project_flags %] - [% NEXT IF NOT user.id AND field.value == "---" %] - - - - - - [% END %] -
  - - - [% PROCESS bug/field.html.tmpl value = bug.${field.name} - editable = user.id - no_tds = 1 %] - [% IF user.id %] - - [% bug.${field.name} FILTER html %] - - [% END %] -
- [% ELSE %] - [% FOREACH field = project_flags %] - [% NEXT IF bug.${field.name} == "---" %] - [% field_descs.${field.name} FILTER html %]: [% bug.${field.name} FILTER html %]
- [% END %] - [% END %] -
- - - - [% IF bug.check_can_change_field('flagtypes.name', 0, 1) %] - [% IF user.id %] - - (edit) - - [% END %] - - [% FOREACH field = tracking_flags %] - [% NEXT IF NOT user.id AND field.value == "---" %] - - - - - - [% END %] -
  - - - [% PROCESS bug/field.html.tmpl value = bug.${field.name} - editable = user.id - no_tds = 1 %] - [% IF user.id %] - - [% bug.${field.name} FILTER html %] - - [% END %] -
- [% ELSE %] - [% FOREACH field = tracking_flags %] - [% NEXT IF bug.${field.name} == "---" %] - [% field_descs.${field.name} FILTER html %]: [% bug.${field.name} FILTER html %]
- [% END %] - [% END %] -
Flags:
- [% IF tracking_flags.size %] + [% IF old_tracking_flags.size %] [% END %] - [% IF project_flags.size %] + [% IF old_project_flags.size %] @@ -757,6 +765,7 @@ TUI_hide_default('attachment_text_field'); [% END %] + [% Hook.process('bug_flags_end') %]
- +
- - [% FOREACH field = tracking_flags %] - [% SET value = ${field.name}.defined ? ${field.name} : "" %] - - [% INCLUDE bug/field.html.tmpl - bug = default, field = field, value = value, editable = 1, - value_span = 3 %] - - [% END %] - + [% FOREACH field = old_tracking_flags %] + [% SET value = ${field.name}.defined ? ${field.name} : "" %] + + [% INCLUDE bug/field.html.tmpl + bug = default + field = field + value = value + editable = 1 + value_span = 3 + %] + + [% END %] + [% Hook.process('tracking_flags_end') %]
Tracking Flags:
- +
- - [% FOREACH field = project_flags %] - [% SET value = ${field.name}.defined ? ${field.name} : "" %] - - [% INCLUDE bug/field.html.tmpl - bug = default, field = field, value = value, editable = 1, - value_span = 3 %] - - [% END %] - + [% FOREACH field = old_project_flags %] + [% SET value = ${field.name}.defined ? ${field.name} : "" %] + + [% INCLUDE bug/field.html.tmpl + bug = default + field = field + value = value + editable = 1 + value_span = 3 + %] + + [% END %] + [% Hook.process('project_flags_end') %]
Project Flags:
diff --git a/template/en/default/bug/edit.html.tmpl b/template/en/default/bug/edit.html.tmpl index e641cee93..8c2d09872 100644 --- a/template/en/default/bug/edit.html.tmpl +++ b/template/en/default/bug/edit.html.tmpl @@ -962,6 +962,7 @@ [%# *** Custom Fields *** %] [% USE Bugzilla %] [% FOREACH field = Bugzilla.active_custom_fields(product=>bug.product_obj,component=>bug.component_obj,type=>1) %] + [% NEXT IF field.type == constants.FIELD_TYPE_EXTENSION %] [% NEXT IF NOT user.id AND field.value == "---" %] [% Hook.process('custom_field', 'bug/edit.html.tmpl') %] [% NEXT IF field.hidden %] diff --git a/template/en/default/bug/field.html.tmpl b/template/en/default/bug/field.html.tmpl index 74f632c54..73131225d 100644 --- a/template/en/default/bug/field.html.tmpl +++ b/template/en/default/bug/field.html.tmpl @@ -246,6 +246,8 @@ YAHOO.bugzilla.keywordAutocomplete.init('[% field.name FILTER js %]', 'keyword_autocomplete'); + [% CASE constants.FIELD_TYPE_EXTENSION %] + [% Hook.process('editable') %] [% END %] [% ELSE %] [% SWITCH field.type %] @@ -269,6 +271,8 @@ [% END %] [% '' IF value.size %] + [% CASE constants.FIELD_TYPE_EXTENSION %] + [% Hook.process('non_editable') %] [% CASE %] [% value.join(', ') FILTER html %] [% END %] diff --git a/template/en/default/global/field-descs.none.tmpl b/template/en/default/global/field-descs.none.tmpl index 1596f3884..731ba37ef 100644 --- a/template/en/default/global/field-descs.none.tmpl +++ b/template/en/default/global/field-descs.none.tmpl @@ -62,6 +62,7 @@ ${constants.FIELD_TYPE_DATETIME} => "Date/Time", ${constants.FIELD_TYPE_DATE} => "Date", ${constants.FIELD_TYPE_BUG_ID} => "$terms.Bug ID", + ${constants.FIELD_TYPE_EXTENSION} => "Extension", } %] [% IF in_template_var %] -- cgit v1.2.3-24-g4f1b