summaryrefslogtreecommitdiffstats
path: root/extensions/BugModal
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/BugModal')
-rw-r--r--extensions/BugModal/Config.pm6
-rw-r--r--extensions/BugModal/Extension.pm551
-rw-r--r--extensions/BugModal/lib/ActivityStream.pm567
-rw-r--r--extensions/BugModal/lib/MonkeyPatches.pm21
-rw-r--r--extensions/BugModal/lib/Util.pm28
-rw-r--r--extensions/BugModal/lib/WebService.pm618
6 files changed, 907 insertions, 884 deletions
diff --git a/extensions/BugModal/Config.pm b/extensions/BugModal/Config.pm
index 25a864e6e..bd15c075c 100644
--- a/extensions/BugModal/Config.pm
+++ b/extensions/BugModal/Config.pm
@@ -10,8 +10,8 @@ use 5.10.1;
use strict;
use warnings;
-use constant NAME => 'BugModal';
-use constant REQUIRED_MODULES => [ ];
-use constant OPTIONAL_MODULES => [ ];
+use constant NAME => 'BugModal';
+use constant REQUIRED_MODULES => [];
+use constant OPTIONAL_MODULES => [];
__PACKAGE__->NAME;
diff --git a/extensions/BugModal/Extension.pm b/extensions/BugModal/Extension.pm
index ef9c93a37..8b72bb757 100644
--- a/extensions/BugModal/Extension.pm
+++ b/extensions/BugModal/Extension.pm
@@ -26,322 +26,321 @@ use JSON::XS qw(encode_json);
our $VERSION = '1';
use constant READABLE_BUG_STATUS_PRODUCTS => (
- 'Core',
- 'Toolkit',
- 'Firefox',
- 'Firefox for Android',
- 'Firefox for iOS',
- 'Bugzilla',
- 'bugzilla.mozilla.org'
+ 'Core', 'Toolkit',
+ 'Firefox', 'Firefox for Android',
+ 'Firefox for iOS', 'Bugzilla',
+ 'bugzilla.mozilla.org'
);
sub show_bug_format {
- my ($self, $args) = @_;
- $args->{format} = _alternative_show_bug_format();
+ my ($self, $args) = @_;
+ $args->{format} = _alternative_show_bug_format();
}
sub edit_bug_format {
- my ($self, $args) = @_;
- $args->{format} = _alternative_show_bug_format();
+ my ($self, $args) = @_;
+ $args->{format} = _alternative_show_bug_format();
}
sub _alternative_show_bug_format {
- my $cgi = Bugzilla->cgi;
- my $user = Bugzilla->user;
- if (my $ctype = $cgi->param('ctype')) {
- return '' if $ctype ne 'html';
- }
- if (my $format = $cgi->param('format')) {
- return ($format eq '__default__' || $format eq 'default') ? '' : $format;
- }
- return $user->setting('ui_experiments') eq 'on' ? 'modal' : '';
+ my $cgi = Bugzilla->cgi;
+ my $user = Bugzilla->user;
+ if (my $ctype = $cgi->param('ctype')) {
+ return '' if $ctype ne 'html';
+ }
+ if (my $format = $cgi->param('format')) {
+ return ($format eq '__default__' || $format eq 'default') ? '' : $format;
+ }
+ return $user->setting('ui_experiments') eq 'on' ? 'modal' : '';
}
sub template_after_create {
- my ($self, $args) = @_;
- my $context = $args->{template}->context;
-
- # wrapper around time_ago()
- $context->define_filter(
- time_duration => sub {
- my ($context) = @_;
- return sub {
- my ($timestamp) = @_;
- my $datetime = datetime_from($timestamp)
- // return $timestamp;
- return time_ago($datetime);
- };
- }, 1
- );
-
- # morph a string into one which is suitable to use as an element's id
- $context->define_filter(
- id => sub {
- my ($context) = @_;
- return sub {
- my ($id) = @_;
- $id //= '';
- $id = lc($id);
- while ($id ne '' && $id !~ /^[a-z]/) {
- $id = substr($id, 1);
- }
- $id =~ tr/ /-/;
- $id =~ s/[^a-z\d\-_:\.]/_/g;
- return $id;
- };
- }, 1
- );
-
- # parse date string and output epoch
- $context->define_filter(
- epoch => sub {
- my ($context) = @_;
- return sub {
- my ($date_str) = @_;
- return date_str_to_time($date_str);
- };
- }, 1
- );
-
- # flatten a list of hashrefs to a list of values
- # eg. logins = users.pluck("login")
- $context->define_vmethod(
- list => pluck => sub {
- my ($list, $field) = @_;
- return [ map { $_->$field } @$list ];
- }
- );
-
- # returns array where the value in $field does not equal $value
- # opposite of "only"
- # eg. not_byron = users.skip("name", "Byron")
- $context->define_vmethod(
- list => skip => sub {
- my ($list, $field, $value) = @_;
- return [ grep { $_->$field ne $value } @$list ];
- }
- );
-
- # returns array where the value in $field equals $value
- # opposite of "skip"
- # eg. byrons_only = users.only("name", "Byron")
- $context->define_vmethod(
- list => only => sub {
- my ($list, $field, $value) = @_;
- return [ grep { $_->$field eq $value } @$list ];
- }
- );
-
- # returns boolean indicating if the value exists in the list
- # eg. has_byron = user_names.exists("byron")
- $context->define_vmethod(
- list => exists => sub {
- my ($list, $value) = @_;
- return any { $_ eq $value } @$list;
- }
- );
-
- # ucfirst is only available in new template::toolkit versions
- $context->define_vmethod(
- item => ucfirst => sub {
- my ($text) = @_;
- return ucfirst($text);
- }
- );
-}
-
-sub template_before_process {
- my ($self, $args) = @_;
- my $file = $args->{file};
- my $vars = $args->{vars};
-
- if ($file eq 'bug/process/header.html.tmpl'
- || $file eq 'bug/create/created.html.tmpl'
- || $file eq 'attachment/created.html.tmpl'
- || $file eq 'attachment/updated.html.tmpl')
- {
- if (_alternative_show_bug_format() eq 'modal') {
- $vars->{alt_ui_header} = 'bug_modal/header.html.tmpl';
- $vars->{alt_ui_show} = 'bug/show-modal.html.tmpl';
- $vars->{alt_ui_edit} = 'bug_modal/edit.html.tmpl';
+ my ($self, $args) = @_;
+ my $context = $args->{template}->context;
+
+ # wrapper around time_ago()
+ $context->define_filter(
+ time_duration => sub {
+ my ($context) = @_;
+ return sub {
+ my ($timestamp) = @_;
+ my $datetime = datetime_from($timestamp) // return $timestamp;
+ return time_ago($datetime);
+ };
+ },
+ 1
+ );
+
+ # morph a string into one which is suitable to use as an element's id
+ $context->define_filter(
+ id => sub {
+ my ($context) = @_;
+ return sub {
+ my ($id) = @_;
+ $id //= '';
+ $id = lc($id);
+ while ($id ne '' && $id !~ /^[a-z]/) {
+ $id = substr($id, 1);
}
- return;
+ $id =~ tr/ /-/;
+ $id =~ s/[^a-z\d\-_:\.]/_/g;
+ return $id;
+ };
+ },
+ 1
+ );
+
+ # parse date string and output epoch
+ $context->define_filter(
+ epoch => sub {
+ my ($context) = @_;
+ return sub {
+ my ($date_str) = @_;
+ return date_str_to_time($date_str);
+ };
+ },
+ 1
+ );
+
+ # flatten a list of hashrefs to a list of values
+ # eg. logins = users.pluck("login")
+ $context->define_vmethod(
+ list => pluck => sub {
+ my ($list, $field) = @_;
+ return [map { $_->$field } @$list];
}
-
- if ($file =~ m#^bug/show-([^\.]+)\.html\.tmpl$#) {
- my $format = $1;
- return unless _alternative_show_bug_format() eq $format;
+ );
+
+ # returns array where the value in $field does not equal $value
+ # opposite of "only"
+ # eg. not_byron = users.skip("name", "Byron")
+ $context->define_vmethod(
+ list => skip => sub {
+ my ($list, $field, $value) = @_;
+ return [grep { $_->$field ne $value } @$list];
}
- elsif ($file ne 'bug_modal/edit.html.tmpl') {
- return;
+ );
+
+ # returns array where the value in $field equals $value
+ # opposite of "skip"
+ # eg. byrons_only = users.only("name", "Byron")
+ $context->define_vmethod(
+ list => only => sub {
+ my ($list, $field, $value) = @_;
+ return [grep { $_->$field eq $value } @$list];
}
-
- if ($vars->{bug} && !$vars->{bugs}) {
- $vars->{bugs} = [$vars->{bug}];
+ );
+
+ # returns boolean indicating if the value exists in the list
+ # eg. has_byron = user_names.exists("byron")
+ $context->define_vmethod(
+ list => exists => sub {
+ my ($list, $value) = @_;
+ return any { $_ eq $value } @$list;
}
+ );
- return unless
- $vars->{bugs}
- && ref($vars->{bugs}) eq 'ARRAY'
- && scalar(@{ $vars->{bugs} }) == 1;
- my $bug = $vars->{bugs}->[0];
- return if exists $bug->{error};
-
- # trigger loading of tracking flags
- if (Bugzilla->has_extension('TrackingFlags')) {
- Bugzilla::Extension::TrackingFlags->template_before_process({
- file => 'bug/edit.html.tmpl',
- vars => $vars,
- });
+ # ucfirst is only available in new template::toolkit versions
+ $context->define_vmethod(
+ item => ucfirst => sub {
+ my ($text) = @_;
+ return ucfirst($text);
}
+ );
+}
- if (any { $bug->product eq $_ } READABLE_BUG_STATUS_PRODUCTS) {
- my @flags = map { { name => $_->name, status => $_->status } } @{$bug->flags};
- $vars->{readable_bug_status_json} = encode_json({
- dupe_of => $bug->dup_id,
- id => $bug->id,
- keywords => [ map { $_->name } @{$bug->keyword_objects} ],
- priority => $bug->priority,
- resolution => $bug->resolution,
- status => $bug->bug_status,
- flags => \@flags,
- target_milestone => $bug->target_milestone,
- map { $_->name => $_->bug_flag($bug->id)->value } @{$vars->{tracking_flags}},
- });
- # HTML4 attributes cannot be longer than this, so just skip it in this case.
- if (length($vars->{readable_bug_status_json}) > 65536) {
- delete $vars->{readable_bug_status_json};
- }
+sub template_before_process {
+ my ($self, $args) = @_;
+ my $file = $args->{file};
+ my $vars = $args->{vars};
+
+ if ( $file eq 'bug/process/header.html.tmpl'
+ || $file eq 'bug/create/created.html.tmpl'
+ || $file eq 'attachment/created.html.tmpl'
+ || $file eq 'attachment/updated.html.tmpl')
+ {
+ if (_alternative_show_bug_format() eq 'modal') {
+ $vars->{alt_ui_header} = 'bug_modal/header.html.tmpl';
+ $vars->{alt_ui_show} = 'bug/show-modal.html.tmpl';
+ $vars->{alt_ui_edit} = 'bug_modal/edit.html.tmpl';
}
+ return;
+ }
+
+ if ($file =~ m#^bug/show-([^\.]+)\.html\.tmpl$#) {
+ my $format = $1;
+ return unless _alternative_show_bug_format() eq $format;
+ }
+ elsif ($file ne 'bug_modal/edit.html.tmpl') {
+ return;
+ }
+
+ if ($vars->{bug} && !$vars->{bugs}) {
+ $vars->{bugs} = [$vars->{bug}];
+ }
+
+ return
+ unless $vars->{bugs}
+ && ref($vars->{bugs}) eq 'ARRAY'
+ && scalar(@{$vars->{bugs}}) == 1;
+ my $bug = $vars->{bugs}->[0];
+ return if exists $bug->{error};
+
+ # trigger loading of tracking flags
+ if (Bugzilla->has_extension('TrackingFlags')) {
+ Bugzilla::Extension::TrackingFlags->template_before_process({
+ file => 'bug/edit.html.tmpl', vars => $vars,
+ });
+ }
+
+ if (any { $bug->product eq $_ } READABLE_BUG_STATUS_PRODUCTS) {
+ my @flags = map { {name => $_->name, status => $_->status} } @{$bug->flags};
+ $vars->{readable_bug_status_json} = encode_json({
+ dupe_of => $bug->dup_id,
+ id => $bug->id,
+ keywords => [map { $_->name } @{$bug->keyword_objects}],
+ priority => $bug->priority,
+ resolution => $bug->resolution,
+ status => $bug->bug_status,
+ flags => \@flags,
+ target_milestone => $bug->target_milestone,
+ map { $_->name => $_->bug_flag($bug->id)->value } @{$vars->{tracking_flags}},
+ });
- # bug->choices loads a lot of data that we want to lazy-load
- # just load the status and resolutions and perform extra checks here
- # upstream does these checks in the bug/fields template
- my $perms = $bug->user;
- my @resolutions;
- foreach my $r (@{ Bugzilla::Field->new({ name => 'resolution', cache => 1 })->legal_values }) {
- my $resolution = $r->name;
- next unless $resolution;
-
- # always allow the current value
- if ($resolution eq $bug->resolution) {
- push @resolutions, $r;
- next;
- }
-
- # never allow inactive values
- next unless $r->is_active;
+ # HTML4 attributes cannot be longer than this, so just skip it in this case.
+ if (length($vars->{readable_bug_status_json}) > 65536) {
+ delete $vars->{readable_bug_status_json};
+ }
+ }
+
+ # bug->choices loads a lot of data that we want to lazy-load
+ # just load the status and resolutions and perform extra checks here
+ # upstream does these checks in the bug/fields template
+ my $perms = $bug->user;
+ my @resolutions;
+ foreach my $r (
+ @{Bugzilla::Field->new({name => 'resolution', cache => 1})->legal_values})
+ {
+ my $resolution = $r->name;
+ next unless $resolution;
+
+ # always allow the current value
+ if ($resolution eq $bug->resolution) {
+ push @resolutions, $r;
+ next;
+ }
- # ensure the user has basic rights to change this field
- next unless $bug->check_can_change_field('resolution', '---', $resolution);
+ # never allow inactive values
+ next unless $r->is_active;
- # canconfirm users can only set the resolution to WFM, INCOMPLETE or DUPE
- if ($perms->{canconfirm}
- && !($perms->{canedit} || $perms->{isreporter}))
- {
- next if
- $resolution ne 'WORKSFORME'
- && $resolution ne 'INCOMPLETE'
- && $resolution ne 'DUPLICATE';
- }
+ # ensure the user has basic rights to change this field
+ next unless $bug->check_can_change_field('resolution', '---', $resolution);
- # reporters can set it to anything, except INCOMPLETE
- if ($perms->{isreporter}
- && !($perms->{canconfirm} || $perms->{canedit}))
- {
- next if $resolution eq 'INCOMPLETE';
- }
+ # canconfirm users can only set the resolution to WFM, INCOMPLETE or DUPE
+ if ($perms->{canconfirm} && !($perms->{canedit} || $perms->{isreporter})) {
+ next
+ if $resolution ne 'WORKSFORME'
+ && $resolution ne 'INCOMPLETE'
+ && $resolution ne 'DUPLICATE';
+ }
- # expired has, uh, expired
- next if $resolution eq 'EXPIRED';
+ # reporters can set it to anything, except INCOMPLETE
+ if ($perms->{isreporter} && !($perms->{canconfirm} || $perms->{canedit})) {
+ next if $resolution eq 'INCOMPLETE';
+ }
- push @resolutions, $r;
+ # expired has, uh, expired
+ next if $resolution eq 'EXPIRED';
+
+ push @resolutions, $r;
+ }
+ $bug->{choices} = {
+ bug_status => [
+ grep { $_->is_active || $_->name eq $bug->bug_status }
+ @{$bug->statuses_available}
+ ],
+ resolution => \@resolutions,
+ };
+
+ # group tracking flags by version to allow for a better tabular output
+ my @tracking_table;
+ my $tracking_flags = $vars->{tracking_flags};
+ foreach my $flag (@$tracking_flags) {
+ my $flag_type = $flag->flag_type;
+ my $type = 'status';
+ my $name = $flag->description;
+ if ($flag_type eq 'tracking' && $name =~ /^(tracking|status)-(.+)/) {
+ ($type, $name) = ($1, $2);
}
- $bug->{choices} = {
- bug_status => [
- grep { $_->is_active || $_->name eq $bug->bug_status }
- @{ $bug->statuses_available }
- ],
- resolution => \@resolutions,
- };
-
- # group tracking flags by version to allow for a better tabular output
- my @tracking_table;
- my $tracking_flags = $vars->{tracking_flags};
- foreach my $flag (@$tracking_flags) {
- my $flag_type = $flag->flag_type;
- my $type = 'status';
- my $name = $flag->description;
- if ($flag_type eq 'tracking' && $name =~ /^(tracking|status)-(.+)/) {
- ($type, $name) = ($1, $2);
- }
- my ($existing) = grep { $_->{type} eq $flag_type && $_->{name} eq $name } @tracking_table;
- if ($existing) {
- $existing->{$type} = $flag;
- }
- else {
- push @tracking_table, {
- $type => $flag,
- name => $name,
- type => $flag_type,
- };
- }
+ my ($existing)
+ = grep { $_->{type} eq $flag_type && $_->{name} eq $name } @tracking_table;
+ if ($existing) {
+ $existing->{$type} = $flag;
}
- $vars->{tracking_flags_table} = \@tracking_table;
-
- # for the "view -> hide treeherder comments" menu item
- my $treeherder_id = Bugzilla->treeherder_user->id;
- foreach my $change_set (@{ $bug->activity_stream }) {
- if ($change_set->{comment} && $change_set->{comment}->author->id == $treeherder_id) {
- $vars->{treeherder} = Bugzilla->treeherder_user;
- last;
- }
+ else {
+ push @tracking_table, {$type => $flag, name => $name, type => $flag_type,};
+ }
+ }
+ $vars->{tracking_flags_table} = \@tracking_table;
+
+ # for the "view -> hide treeherder comments" menu item
+ my $treeherder_id = Bugzilla->treeherder_user->id;
+ foreach my $change_set (@{$bug->activity_stream}) {
+ if ( $change_set->{comment}
+ && $change_set->{comment}->author->id == $treeherder_id)
+ {
+ $vars->{treeherder} = Bugzilla->treeherder_user;
+ last;
}
+ }
}
sub bug_start_of_set_all {
- my ($self, $args) = @_;
- my $bug = $args->{bug};
- my $params = $args->{params};
-
- # reset to the component defaults if not supplied
- if (exists $params->{assigned_to} && (!defined $params->{assigned_to} || $params->{assigned_to} eq '')) {
- $params->{assigned_to} = $bug->component_obj->default_assignee->login;
- }
- if (exists $params->{qa_contact} && (!defined $params->{qa_contact} || $params->{qa_contact} eq '')
- && $bug->component_obj->default_qa_contact->id)
- {
- $params->{qa_contact} = $bug->component_obj->default_qa_contact->login;
- }
+ my ($self, $args) = @_;
+ my $bug = $args->{bug};
+ my $params = $args->{params};
+
+ # reset to the component defaults if not supplied
+ if (exists $params->{assigned_to}
+ && (!defined $params->{assigned_to} || $params->{assigned_to} eq ''))
+ {
+ $params->{assigned_to} = $bug->component_obj->default_assignee->login;
+ }
+ if ( exists $params->{qa_contact}
+ && (!defined $params->{qa_contact} || $params->{qa_contact} eq '')
+ && $bug->component_obj->default_qa_contact->id)
+ {
+ $params->{qa_contact} = $bug->component_obj->default_qa_contact->login;
+ }
}
sub webservice {
- my ($self, $args) = @_;
- my $dispatch = $args->{dispatch};
- $dispatch->{bug_modal} = 'Bugzilla::Extension::BugModal::WebService';
+ my ($self, $args) = @_;
+ my $dispatch = $args->{dispatch};
+ $dispatch->{bug_modal} = 'Bugzilla::Extension::BugModal::WebService';
}
sub install_before_final_checks {
- my ($self, $args) = @_;
- add_setting({
- name => 'ui_experiments',
- options => ['on', 'off'],
- default => 'on',
- category => 'User Interface'
- });
- add_setting({
- name => 'ui_remember_collapsed',
- options => ['on', 'off'],
- default => 'off',
- category => 'User Interface'
- });
- add_setting({
- name => 'ui_use_absolute_time',
- options => ['on', 'off'],
- default => 'off',
- category => 'User Interface',
- });
+ my ($self, $args) = @_;
+ add_setting({
+ name => 'ui_experiments',
+ options => ['on', 'off'],
+ default => 'on',
+ category => 'User Interface'
+ });
+ add_setting({
+ name => 'ui_remember_collapsed',
+ options => ['on', 'off'],
+ default => 'off',
+ category => 'User Interface'
+ });
+ add_setting({
+ name => 'ui_use_absolute_time',
+ options => ['on', 'off'],
+ default => 'off',
+ category => 'User Interface',
+ });
}
__PACKAGE__->NAME;
diff --git a/extensions/BugModal/lib/ActivityStream.pm b/extensions/BugModal/lib/ActivityStream.pm
index 098c5df33..a7983e85c 100644
--- a/extensions/BugModal/lib/ActivityStream.pm
+++ b/extensions/BugModal/lib/ActivityStream.pm
@@ -49,310 +49,324 @@ use Bugzilla::Constants;
# ]
sub activity_stream {
- my ($self) = @_;
- if (!$self->{activity_stream}) {
- my $stream = [];
- _add_comments_to_stream($self, $stream);
- _add_activities_to_stream($self, $stream);
- _add_duplicates_to_stream($self, $stream);
-
- my $base_time = date_str_to_time($self->creation_ts);
- foreach my $change_set (@$stream) {
- $change_set->{id} = $change_set->{comment}
- ? 'c' . $change_set->{comment}->count
- : 'a' . ($change_set->{time} - $base_time) . '_' . $change_set->{user_id};
- foreach my $activity (@{ $change_set->{activity} }) {
- $activity->{changes} = [
- sort { $a->{fieldname} cmp $b->{fieldname} }
- @{ $activity->{changes} }
- ];
- }
- }
- my $order = Bugzilla->user->setting('comment_sort_order');
- if ($order eq 'oldest_to_newest') {
- $self->{activity_stream} = [ sort { $a->{time} <=> $b->{time} } @$stream ];
- }
- elsif ($order eq 'newest_to_oldest') {
- $self->{activity_stream} = [ sort { $b->{time} <=> $a->{time} } @$stream ];
- }
- elsif ($order eq 'newest_to_oldest_desc_first') {
- my $desc = shift @$stream;
- $self->{activity_stream} = [ $desc, sort { $b->{time} <=> $a->{time} } @$stream ];
- }
+ my ($self) = @_;
+ if (!$self->{activity_stream}) {
+ my $stream = [];
+ _add_comments_to_stream($self, $stream);
+ _add_activities_to_stream($self, $stream);
+ _add_duplicates_to_stream($self, $stream);
+
+ my $base_time = date_str_to_time($self->creation_ts);
+ foreach my $change_set (@$stream) {
+ $change_set->{id}
+ = $change_set->{comment}
+ ? 'c' . $change_set->{comment}->count
+ : 'a' . ($change_set->{time} - $base_time) . '_' . $change_set->{user_id};
+ foreach my $activity (@{$change_set->{activity}}) {
+ $activity->{changes}
+ = [sort { $a->{fieldname} cmp $b->{fieldname} } @{$activity->{changes}}];
+ }
+ }
+ my $order = Bugzilla->user->setting('comment_sort_order');
+ if ($order eq 'oldest_to_newest') {
+ $self->{activity_stream} = [sort { $a->{time} <=> $b->{time} } @$stream];
}
- return $self->{activity_stream};
+ elsif ($order eq 'newest_to_oldest') {
+ $self->{activity_stream} = [sort { $b->{time} <=> $a->{time} } @$stream];
+ }
+ elsif ($order eq 'newest_to_oldest_desc_first') {
+ my $desc = shift @$stream;
+ $self->{activity_stream} = [$desc, sort { $b->{time} <=> $a->{time} } @$stream];
+ }
+ }
+ return $self->{activity_stream};
}
sub find_activity_id_for_attachment {
- my ($self, $attachment) = @_;
- my $attach_id = $attachment->id;
- my $stream = $self->activity_stream;
- foreach my $change_set (@$stream) {
- next unless exists $change_set->{attach_id};
- return $change_set->{id} if $change_set->{attach_id} == $attach_id;
- }
- return undef;
+ my ($self, $attachment) = @_;
+ my $attach_id = $attachment->id;
+ my $stream = $self->activity_stream;
+ foreach my $change_set (@$stream) {
+ next unless exists $change_set->{attach_id};
+ return $change_set->{id} if $change_set->{attach_id} == $attach_id;
+ }
+ return undef;
}
sub find_activity_id_for_flag {
- my ($self, $flag) = @_;
- my $flagtype_name = $flag->type->name;
- my $date = $flag->modification_date;
- my $setter_id = $flag->setter->id;
- my $stream = $self->activity_stream;
-
- # unfortunately bugs_activity treats all flag changes as the same field, so
- # we don't have an object_id to match on
-
- if (!exists $self->{activity_cache}->{flag}->{$flag->id}) {
- foreach my $change_set (reverse @$stream) {
- foreach my $activity (@{ $change_set->{activity} }) {
- # match by user, timestamp, and flag-type name
- next unless
- $activity->{who}->id == $setter_id
- && $activity->{when} eq $date;
- foreach my $change (@{ $activity->{changes} }) {
- next unless
- $change->{fieldname} eq 'flagtypes.name'
- && $change->{flagtype_name} eq $flagtype_name;
- $self->{activity_cache}->{flag}->{$flag->id} = $change_set->{id};
- return $change_set->{id};
- }
- }
+ my ($self, $flag) = @_;
+ my $flagtype_name = $flag->type->name;
+ my $date = $flag->modification_date;
+ my $setter_id = $flag->setter->id;
+ my $stream = $self->activity_stream;
+
+ # unfortunately bugs_activity treats all flag changes as the same field, so
+ # we don't have an object_id to match on
+
+ if (!exists $self->{activity_cache}->{flag}->{$flag->id}) {
+ foreach my $change_set (reverse @$stream) {
+ foreach my $activity (@{$change_set->{activity}}) {
+
+ # match by user, timestamp, and flag-type name
+ next unless $activity->{who}->id == $setter_id && $activity->{when} eq $date;
+ foreach my $change (@{$activity->{changes}}) {
+ next
+ unless $change->{fieldname} eq 'flagtypes.name'
+ && $change->{flagtype_name} eq $flagtype_name;
+ $self->{activity_cache}->{flag}->{$flag->id} = $change_set->{id};
+ return $change_set->{id};
}
- # if we couldn't find the flag in bugs_activity it means it was set
- # during bug creation
- $self->{activity_cache}->{flag}->{$flag->id} = 'c0';
+ }
}
- return $self->{activity_cache}->{flag}->{$flag->id};
+
+ # if we couldn't find the flag in bugs_activity it means it was set
+ # during bug creation
+ $self->{activity_cache}->{flag}->{$flag->id} = 'c0';
+ }
+ return $self->{activity_cache}->{flag}->{$flag->id};
}
# comments are processed first, so there's no need to merge into existing entries
sub _add_comment_to_stream {
- my ($stream, $time, $user_id, $comment) = @_;
- my $rh = {
- time => $time,
- user_id => $user_id,
- comment => $comment,
- activity => [],
- };
- if ($comment->type == CMT_ATTACHMENT_CREATED || $comment->type == CMT_ATTACHMENT_UPDATED) {
- $rh->{attach_id} = $comment->extra_data;
- }
- push @$stream, $rh;
+ my ($stream, $time, $user_id, $comment) = @_;
+ my $rh
+ = {time => $time, user_id => $user_id, comment => $comment, activity => [],};
+ if ( $comment->type == CMT_ATTACHMENT_CREATED
+ || $comment->type == CMT_ATTACHMENT_UPDATED)
+ {
+ $rh->{attach_id} = $comment->extra_data;
+ }
+ push @$stream, $rh;
}
sub _add_activity_to_stream {
- my ($stream, $time, $user_id, $data) = @_;
- foreach my $entry (@$stream) {
- next unless $entry->{time} == $time && $entry->{user_id} == $user_id;
- $entry->{cc_only} = $entry->{cc_only} && $data->{cc_only};
- push @{ $entry->{activity} }, $data;
- return;
- }
- push @$stream, {
- time => $time,
- user_id => $user_id,
- comment => undef,
- cc_only => $data->{cc_only},
- activity => [ $data ],
+ my ($stream, $time, $user_id, $data) = @_;
+ foreach my $entry (@$stream) {
+ next unless $entry->{time} == $time && $entry->{user_id} == $user_id;
+ $entry->{cc_only} = $entry->{cc_only} && $data->{cc_only};
+ push @{$entry->{activity}}, $data;
+ return;
+ }
+ push @$stream,
+ {
+ time => $time,
+ user_id => $user_id,
+ comment => undef,
+ cc_only => $data->{cc_only},
+ activity => [$data],
};
}
sub _add_comments_to_stream {
- my ($bug, $stream) = @_;
- my $user = Bugzilla->user;
- my $treeherder_id = Bugzilla->treeherder_user->id;
-
- my $raw_comments = $bug->comments();
- foreach my $comment (@$raw_comments) {
- next if $comment->type == CMT_HAS_DUPE;
- my $author_id = $comment->author->id;
- next if $comment->is_private && !($user->is_insider || $user->id == $author_id);
- next if $comment->body eq '' && ($comment->work_time - 0) != 0 && $user->is_timetracker;
-
- # treeherder is so spammy we hide its comments by default
- if ($author_id == $treeherder_id) {
- $comment->{collapsed} = 1;
- $comment->{collapsed_reason} = $comment->author->name;
- }
- if ($comment->type != CMT_ATTACHMENT_CREATED && $comment->count == 0 && length($comment->body) == 0) {
- $comment->{collapsed} = 1;
- $comment->{collapsed_reason} = 'empty';
- }
- # If comment type is resolved as duplicate, do not add '...marked as duplicate...' string to comment body
- if ($comment->type == CMT_DUPE_OF) {
- $comment->set_type(0);
- # Skip if user did not supply comment also
- next if $comment->body eq '';
- }
-
- _add_comment_to_stream($stream, date_str_to_time($comment->creation_ts), $comment->author->id, $comment);
+ my ($bug, $stream) = @_;
+ my $user = Bugzilla->user;
+ my $treeherder_id = Bugzilla->treeherder_user->id;
+
+ my $raw_comments = $bug->comments();
+ foreach my $comment (@$raw_comments) {
+ next if $comment->type == CMT_HAS_DUPE;
+ my $author_id = $comment->author->id;
+ next if $comment->is_private && !($user->is_insider || $user->id == $author_id);
+ next
+ if $comment->body eq ''
+ && ($comment->work_time - 0) != 0
+ && $user->is_timetracker;
+
+ # treeherder is so spammy we hide its comments by default
+ if ($author_id == $treeherder_id) {
+ $comment->{collapsed} = 1;
+ $comment->{collapsed_reason} = $comment->author->name;
}
-}
-
-sub _add_activities_to_stream {
- my ($bug, $stream) = @_;
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
-
- # build bug activity
- my ($raw_activity) = $bug->can('get_activity')
- ? $bug->get_activity()
- : Bugzilla::Bug::GetBugActivity($bug->id);
-
- # allow other extensions to alter history
- Bugzilla::Hook::process('inline_history_activitiy', { activity => $raw_activity });
-
- my %attachment_cache;
- foreach my $attachment (@{$bug->attachments}) {
- $attachment_cache{$attachment->id} = $attachment;
+ if ( $comment->type != CMT_ATTACHMENT_CREATED
+ && $comment->count == 0
+ && length($comment->body) == 0)
+ {
+ $comment->{collapsed} = 1;
+ $comment->{collapsed_reason} = 'empty';
}
- # build a list of bugs we need to check visibility of, so we can check with a single query
- my %visible_bug_ids;
+# If comment type is resolved as duplicate, do not add '...marked as duplicate...' string to comment body
+ if ($comment->type == CMT_DUPE_OF) {
+ $comment->set_type(0);
- # envelope, augment and tweak
- foreach my $operation (@$raw_activity) {
-
- # make operation.who an object
- $operation->{who} = Bugzilla::User->new({ name => $operation->{who}, cache => 1 });
-
- # we need to track operations which are just cc changes
- $operation->{cc_only} = 1;
-
- for (my $i = 0; $i < scalar(@{$operation->{changes}}); $i++) {
- my $change = $operation->{changes}->[$i];
-
- # make an attachment object
- if ($change->{attachid}) {
- $change->{attach} = $attachment_cache{$change->{attachid}};
- }
-
- # empty resolutions are displayed as --- by default
- # make it explicit here to enable correct display of the change
- if ($change->{fieldname} eq 'resolution') {
- $change->{removed} = '---' if $change->{removed} eq '';
- $change->{added} = '---' if $change->{added} eq '';
- }
-
- # make boolean fields true/false instead of 1/0
- my ($table, $field) = ('bugs', $change->{fieldname});
- if ($field =~ /^([^\.]+)\.(.+)$/) {
- ($table, $field) = ($1, $2);
- }
- my $column = $dbh->bz_column_info($table, $field);
- if ($column && $column->{TYPE} eq 'BOOLEAN') {
- $change->{removed} = '';
- $change->{added} = $change->{added} ? 'true' : 'false';
- }
-
- # load field object (only required for custom fields), and set the
- # field type for custom fields
- my $field_obj;
- if ($change->{fieldname} =~ /^cf_/) {
- $field_obj = Bugzilla::Field->new({ name => $change->{fieldname}, cache => 1 });
- $change->{fieldtype} = $field_obj->type;
- }
+ # Skip if user did not supply comment also
+ next if $comment->body eq '';
+ }
- # identify buglist changes
- if ($change->{fieldname} eq 'blocked' ||
- $change->{fieldname} eq 'dependson' ||
- $change->{fieldname} eq 'dupe' ||
- ($field_obj && $field_obj->type == FIELD_TYPE_BUG_ID)
- ) {
- $change->{buglist} = 1;
- foreach my $what (qw(removed added)) {
- my @buglist = split(/[\s,]+/, $change->{$what});
- foreach my $id (@buglist) {
- if ($id && $id =~ /^\d+$/) {
- $visible_bug_ids{$id} = 1;
- }
- }
- }
- }
+ _add_comment_to_stream($stream, date_str_to_time($comment->creation_ts),
+ $comment->author->id, $comment);
+ }
+}
- # split see-also
- if ($change->{fieldname} eq 'see_also') {
- my $url_base = Bugzilla->localconfig->{urlbase};
- foreach my $f (qw( added removed )) {
- my @values;
- foreach my $value (split(/, /, $change->{$f})) {
- my ($bug_id) = substr($value, 0, length($url_base)) eq $url_base
- ? $value =~ /id=(\d+)$/
- : undef;
- push @values, {
- url => $value,
- bug_id => $bug_id,
- };
- }
- $change->{$f} = \@values;
- }
+sub _add_activities_to_stream {
+ my ($bug, $stream) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+
+ # build bug activity
+ my ($raw_activity)
+ = $bug->can('get_activity')
+ ? $bug->get_activity()
+ : Bugzilla::Bug::GetBugActivity($bug->id);
+
+ # allow other extensions to alter history
+ Bugzilla::Hook::process('inline_history_activitiy',
+ {activity => $raw_activity});
+
+ my %attachment_cache;
+ foreach my $attachment (@{$bug->attachments}) {
+ $attachment_cache{$attachment->id} = $attachment;
+ }
+
+# build a list of bugs we need to check visibility of, so we can check with a single query
+ my %visible_bug_ids;
+
+ # envelope, augment and tweak
+ foreach my $operation (@$raw_activity) {
+
+ # make operation.who an object
+ $operation->{who}
+ = Bugzilla::User->new({name => $operation->{who}, cache => 1});
+
+ # we need to track operations which are just cc changes
+ $operation->{cc_only} = 1;
+
+ for (my $i = 0; $i < scalar(@{$operation->{changes}}); $i++) {
+ my $change = $operation->{changes}->[$i];
+
+ # make an attachment object
+ if ($change->{attachid}) {
+ $change->{attach} = $attachment_cache{$change->{attachid}};
+ }
+
+ # empty resolutions are displayed as --- by default
+ # make it explicit here to enable correct display of the change
+ if ($change->{fieldname} eq 'resolution') {
+ $change->{removed} = '---' if $change->{removed} eq '';
+ $change->{added} = '---' if $change->{added} eq '';
+ }
+
+ # make boolean fields true/false instead of 1/0
+ my ($table, $field) = ('bugs', $change->{fieldname});
+ if ($field =~ /^([^\.]+)\.(.+)$/) {
+ ($table, $field) = ($1, $2);
+ }
+ my $column = $dbh->bz_column_info($table, $field);
+ if ($column && $column->{TYPE} eq 'BOOLEAN') {
+ $change->{removed} = '';
+ $change->{added} = $change->{added} ? 'true' : 'false';
+ }
+
+ # load field object (only required for custom fields), and set the
+ # field type for custom fields
+ my $field_obj;
+ if ($change->{fieldname} =~ /^cf_/) {
+ $field_obj = Bugzilla::Field->new({name => $change->{fieldname}, cache => 1});
+ $change->{fieldtype} = $field_obj->type;
+ }
+
+ # identify buglist changes
+ if ( $change->{fieldname} eq 'blocked'
+ || $change->{fieldname} eq 'dependson'
+ || $change->{fieldname} eq 'dupe'
+ || ($field_obj && $field_obj->type == FIELD_TYPE_BUG_ID))
+ {
+ $change->{buglist} = 1;
+ foreach my $what (qw(removed added)) {
+ my @buglist = split(/[\s,]+/, $change->{$what});
+ foreach my $id (@buglist) {
+ if ($id && $id =~ /^\d+$/) {
+ $visible_bug_ids{$id} = 1;
}
+ }
+ }
+ }
+
+ # split see-also
+ if ($change->{fieldname} eq 'see_also') {
+ my $url_base = Bugzilla->localconfig->{urlbase};
+ foreach my $f (qw( added removed )) {
+ my @values;
+ foreach my $value (split(/, /, $change->{$f})) {
+ my ($bug_id)
+ = substr($value, 0, length($url_base)) eq $url_base
+ ? $value =~ /id=(\d+)$/
+ : undef;
+ push @values, {url => $value, bug_id => $bug_id,};
+ }
+ $change->{$f} = \@values;
+ }
+ }
+
+ # track cc-only
+ if ($change->{fieldname} ne 'cc') {
+ $operation->{cc_only} = 0;
+ }
+
+ # split multiple flag changes (must be processed last)
+ # set $change->{flagtype_name} to make searching the activity
+ # stream for flag changes easier and quicker
+ if ($change->{fieldname} eq 'flagtypes.name') {
+ my @added = split(/, /, $change->{added});
+ my @removed = split(/, /, $change->{removed});
+ if (scalar(@added) <= 1 && scalar(@removed) <= 1) {
+ $change->{flagtype_name} = _extract_flagtype($added[0] || $removed[0]);
+ next;
+ }
- # track cc-only
- if ($change->{fieldname} ne 'cc') {
- $operation->{cc_only} = 0;
- }
+ # remove current change
+ splice(@{$operation->{changes}}, $i, 1);
- # split multiple flag changes (must be processed last)
- # set $change->{flagtype_name} to make searching the activity
- # stream for flag changes easier and quicker
- if ($change->{fieldname} eq 'flagtypes.name') {
- my @added = split(/, /, $change->{added});
- my @removed = split(/, /, $change->{removed});
- if (scalar(@added) <= 1 && scalar(@removed) <= 1) {
- $change->{flagtype_name} = _extract_flagtype($added[0] || $removed[0]);
- next;
- }
- # remove current change
- splice(@{$operation->{changes}}, $i, 1);
- # restructure into added/removed for each flag
- my %flags;
- foreach my $flag (@added) {
- $flags{$flag}{added} = $flag;
- $flags{$flag}{removed} = '';
- }
- foreach my $flag (@removed) {
- $flags{$flag}{added} = '';
- $flags{$flag}{removed} = $flag;
- }
- # clone current change, modify and insert
- foreach my $flag (sort keys %flags) {
- my $flag_change = {};
- foreach my $key (keys %$change) {
- $flag_change->{$key} = $change->{$key};
- }
- $flag_change->{removed} = $flags{$flag}{removed};
- $flag_change->{added} = $flags{$flag}{added};
- $flag_change->{flagtype_name} = _extract_flagtype($flag);
- splice(@{$operation->{changes}}, $i, 0, $flag_change);
- }
- $i--;
- }
+ # restructure into added/removed for each flag
+ my %flags;
+ foreach my $flag (@added) {
+ $flags{$flag}{added} = $flag;
+ $flags{$flag}{removed} = '';
+ }
+ foreach my $flag (@removed) {
+ $flags{$flag}{added} = '';
+ $flags{$flag}{removed} = $flag;
}
- _add_activity_to_stream($stream, date_str_to_time($operation->{when}), $operation->{who}->id, $operation);
+ # clone current change, modify and insert
+ foreach my $flag (sort keys %flags) {
+ my $flag_change = {};
+ foreach my $key (keys %$change) {
+ $flag_change->{$key} = $change->{$key};
+ }
+ $flag_change->{removed} = $flags{$flag}{removed};
+ $flag_change->{added} = $flags{$flag}{added};
+ $flag_change->{flagtype_name} = _extract_flagtype($flag);
+ splice(@{$operation->{changes}}, $i, 0, $flag_change);
+ }
+ $i--;
+ }
}
- # prime the visible-bugs cache
- $user->visible_bugs([keys %visible_bug_ids]);
+ _add_activity_to_stream(
+ $stream,
+ date_str_to_time($operation->{when}),
+ $operation->{who}->id, $operation
+ );
+ }
+
+ # prime the visible-bugs cache
+ $user->visible_bugs([keys %visible_bug_ids]);
}
sub _extract_flagtype {
- my ($value) = @_;
- return $value =~ /^(.+)[\?\-\+]/ ? $1 : undef;
+ my ($value) = @_;
+ return $value =~ /^(.+)[\?\-\+]/ ? $1 : undef;
}
# display 'duplicate of this bug' as an activity entry, not a comment
sub _add_duplicates_to_stream {
- my ($bug, $stream) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($bug, $stream) = @_;
+ my $dbh = Bugzilla->dbh;
- my $sth = $dbh->prepare("
+ my $sth = $dbh->prepare("
SELECT longdescs.who,
- UNIX_TIMESTAMP(bug_when), " .
- $dbh->sql_date_format('bug_when') . ",
+ UNIX_TIMESTAMP(bug_when), " . $dbh->sql_date_format('bug_when') . ",
type,
extra_data
FROM longdescs
@@ -360,19 +374,22 @@ sub _add_duplicates_to_stream {
WHERE bug_id = ? AND (type = ? OR type = ?)
ORDER BY bug_when
");
- $sth->execute($bug->id, CMT_HAS_DUPE, CMT_DUPE_OF);
-
- while (my($who, $time, $when, $type, $dupe_id) = $sth->fetchrow_array) {
- _add_activity_to_stream($stream, $time, $who, {
- who => Bugzilla::User->new({ id => $who, cache => 1 }),
- when => $when,
- changes => [{
- fieldname => ($type == CMT_HAS_DUPE ? 'has_dupe' : 'dupe_of'),
- added => $dupe_id,
- buglist => 1,
- }],
- });
- }
+ $sth->execute($bug->id, CMT_HAS_DUPE, CMT_DUPE_OF);
+
+ while (my ($who, $time, $when, $type, $dupe_id) = $sth->fetchrow_array) {
+ _add_activity_to_stream(
+ $stream, $time, $who,
+ {
+ who => Bugzilla::User->new({id => $who, cache => 1}),
+ when => $when,
+ changes => [{
+ fieldname => ($type == CMT_HAS_DUPE ? 'has_dupe' : 'dupe_of'),
+ added => $dupe_id,
+ buglist => 1,
+ }],
+ }
+ );
+ }
}
1;
diff --git a/extensions/BugModal/lib/MonkeyPatches.pm b/extensions/BugModal/lib/MonkeyPatches.pm
index 54bd6e560..042dabc38 100644
--- a/extensions/BugModal/lib/MonkeyPatches.pm
+++ b/extensions/BugModal/lib/MonkeyPatches.pm
@@ -17,10 +17,10 @@ use warnings;
use Bugzilla::User;
sub treeherder_user {
- return Bugzilla->process_cache->{treeherder_user} //=
- Bugzilla::User->new({ name => 'tbplbot@gmail.com', cache => 1 })
- || Bugzilla::User->new({ name => 'orangefactor@bots.tld', cache => 1 })
- || Bugzilla::User->new();
+ return Bugzilla->process_cache->{treeherder_user}
+ //= Bugzilla::User->new({name => 'tbplbot@gmail.com', cache => 1})
+ || Bugzilla::User->new({name => 'orangefactor@bots.tld', cache => 1})
+ || Bugzilla::User->new();
}
package Bugzilla::Bug;
@@ -32,10 +32,11 @@ use warnings;
use Bugzilla::Attachment;
sub active_attachments {
- my ($self) = @_;
- return [] if $self->{error};
- return $self->{active_attachments} //= Bugzilla::Attachment->get_attachments_by_bug(
- $self, { exclude_obsolete => 1, preload => 1 });
+ my ($self) = @_;
+ return [] if $self->{error};
+ return $self->{active_attachments}
+ //= Bugzilla::Attachment->get_attachments_by_bug($self,
+ {exclude_obsolete => 1, preload => 1});
}
1;
@@ -47,8 +48,8 @@ use strict;
use warnings;
sub is_image {
- my ($self) = @_;
- return substr($self->contenttype, 0, 6) eq 'image/';
+ my ($self) = @_;
+ return substr($self->contenttype, 0, 6) eq 'image/';
}
1;
diff --git a/extensions/BugModal/lib/Util.pm b/extensions/BugModal/lib/Util.pm
index 6a453159e..b1d7068d8 100644
--- a/extensions/BugModal/lib/Util.pm
+++ b/extensions/BugModal/lib/Util.pm
@@ -21,19 +21,21 @@ use DateTime::TimeZone;
use Time::Local qw(timelocal);
sub date_str_to_time {
- my ($date) = @_;
- # avoid creating a DateTime object
- if ($date =~ /^(\d{4})[\.\-](\d{2})[\.\-](\d{2}) (\d{2}):(\d{2}):(\d{2})$/) {
- return timelocal($6, $5, $4, $3, $2 - 1, $1 - 1900);
- }
- state $tz //= DateTime::TimeZone->new( name => 'local' );
- my $dt = datetime_from($date, $tz);
- if (!$dt) {
- # this should never happen
- warn("invalid datetime '$date'");
- return undef;
- }
- return $dt->epoch;
+ my ($date) = @_;
+
+ # avoid creating a DateTime object
+ if ($date =~ /^(\d{4})[\.\-](\d{2})[\.\-](\d{2}) (\d{2}):(\d{2}):(\d{2})$/) {
+ return timelocal($6, $5, $4, $3, $2 - 1, $1 - 1900);
+ }
+ state $tz //= DateTime::TimeZone->new(name => 'local');
+ my $dt = datetime_from($date, $tz);
+ if (!$dt) {
+
+ # this should never happen
+ warn("invalid datetime '$date'");
+ return undef;
+ }
+ return $dt->epoch;
}
1;
diff --git a/extensions/BugModal/lib/WebService.pm b/extensions/BugModal/lib/WebService.pm
index b69d609dd..5f3308327 100644
--- a/extensions/BugModal/lib/WebService.pm
+++ b/extensions/BugModal/lib/WebService.pm
@@ -27,353 +27,357 @@ use Taint::Util qw(untaint);
# these methods are much lighter than our public API calls
sub rest_resources {
- return [
- # return all the products accessible by the user.
- # required by new-bug
- qr{^/bug_modal/initial_field_values}, {
- GET => {
- method => 'initial_field_values'
- },
+ return [
+ # return all the products accessible by the user.
+ # required by new-bug
+ qr{^/bug_modal/initial_field_values},
+ {GET => {method => 'initial_field_values'},},
+
+ # return all the components pertaining to the product.
+ # required by new-bug
+ qr{^/bug_modal/product_info},
+ {
+ GET => {
+ method => 'product_info',
+ params => sub {
+ return {product_name => Bugzilla->input_params->{product}};
},
+ },
+ },
- # return all the components pertaining to the product.
- # required by new-bug
- qr{^/bug_modal/product_info}, {
- GET => {
- method => 'product_info',
- params => sub {
- return { product_name => Bugzilla->input_params->{product} }
- },
- },
+ # return all the lazy-loaded data; kept in sync with the UI's
+ # requirements.
+ qr{^/bug_modal/edit/(\d+)$},
+ {
+ GET => {
+ method => 'edit',
+ params => sub {
+ return {id => $_[0]};
},
+ },
+ },
- # return all the lazy-loaded data; kept in sync with the UI's
- # requirements.
- qr{^/bug_modal/edit/(\d+)$}, {
- GET => {
- method => 'edit',
- params => sub {
- return { id => $_[0] }
- },
- },
+ # returns pre-formatted html, enabling reuse of the user template
+ qr{^/bug_modal/cc/(\d+)$},
+ {
+ GET => {
+ method => 'cc',
+ params => sub {
+ return {id => $_[0]};
},
+ },
+ },
- # returns pre-formatted html, enabling reuse of the user template
- qr{^/bug_modal/cc/(\d+)$}, {
- GET => {
- method => 'cc',
- params => sub {
- return { id => $_[0] }
- },
- },
- },
+ # returns fields that require touching when the product is changed
+ qw{^/bug_modal/new_product/(\d+)$},
+ {
+ GET => {
+ method => 'new_product',
+ params => sub {
- # returns fields that require touching when the product is changed
- qw{^/bug_modal/new_product/(\d+)$}, {
- GET => {
- method => 'new_product',
- params => sub {
- # products with slashes in their name means we have to grab
- # the product from the query-string instead of the path
- return { id => $_[0], product_name => Bugzilla->input_params->{product} }
- },
- },
+ # products with slashes in their name means we have to grab
+ # the product from the query-string instead of the path
+ return {id => $_[0], product_name => Bugzilla->input_params->{product}};
},
- ]
+ },
+ },
+ ];
}
sub initial_field_values {
- my $user = Bugzilla->user;
- return {
- products => _name($user->get_enterable_products),
- keywords => _name([Bugzilla::Keyword->get_all()]),
- };
+ my $user = Bugzilla->user;
+ return {
+ products => _name($user->get_enterable_products),
+ keywords => _name([Bugzilla::Keyword->get_all()]),
+ };
}
sub product_info {
- my ( $self, $params ) = @_;
- if ( !ref $params->{product_name} ) {
- untaint( $params->{product_name} );
- }
- else {
- ThrowCodeError( 'params_required', { function => 'BugModal.components', params => ['product'] } );
- }
- my $product = Bugzilla::Product->check( { name => $params->{product_name}, cache => 1 } );
- $product = Bugzilla->user->can_enter_product( $product, 1 );
- my @components = map {
- {
- name => $_->name,
- description => $_->description,
- }
- } @{ $product->components };
- return {
- components => \@components,
- versions => _name($product->versions),
- };
+ my ($self, $params) = @_;
+ if (!ref $params->{product_name}) {
+ untaint($params->{product_name});
+ }
+ else {
+ ThrowCodeError('params_required',
+ {function => 'BugModal.components', params => ['product']});
+ }
+ my $product
+ = Bugzilla::Product->check({name => $params->{product_name}, cache => 1});
+ $product = Bugzilla->user->can_enter_product($product, 1);
+ my @components = map { {name => $_->name, description => $_->description,} }
+ @{$product->components};
+ return {components => \@components, versions => _name($product->versions),};
}
# everything we need for edit mode in a single call, returning just the fields
# that the ui requires.
sub edit {
- my ($self, $params) = @_;
- my $user = Bugzilla->user;
- my $bug = Bugzilla::Bug->check({ id => $params->{id} });
-
- # the keys of the options hash must match the field id in the ui
- my %options;
-
- my @products = @{ $user->get_enterable_products };
- unless (grep { $_->id == $bug->product_id } @products) {
- unshift @products, $bug->product_obj;
- }
- $options{product} = [ map { { name => $_->name } } @products ];
-
- $options{component} = _name($bug->product_obj->components, $bug->component);
- $options{version} = _name($bug->product_obj->versions, $bug->version);
- $options{target_milestone} = _name($bug->product_obj->milestones, $bug->target_milestone);
- $options{priority} = _name('priority', $bug->priority);
- $options{bug_severity} = _name('bug_severity', $bug->bug_severity);
- $options{rep_platform} = _name('rep_platform', $bug->rep_platform);
- $options{op_sys} = _name('op_sys', $bug->op_sys);
-
- # custom select fields
- my @custom_fields =
- grep { $_->type == FIELD_TYPE_SINGLE_SELECT || $_->type == FIELD_TYPE_MULTI_SELECT }
- Bugzilla->active_custom_fields({ product => $bug->product_obj, component => $bug->component_obj });
- foreach my $field (@custom_fields) {
- my $field_name = $field->name;
- my @values = map { { name => $_->name } }
- grep { $bug->$field_name eq $_->name
- || ($_->is_active
- && $bug->check_can_change_field($field_name, $bug->$field_name, $_->name)) }
- @{ $field->legal_values };
- $options{$field_name} = \@values;
- }
-
- # keywords
- my @keywords = grep { $_->is_active } Bugzilla::Keyword->get_all();
-
- # results
- return {
- options => \%options,
- keywords => [ map { $_->name } @keywords ],
- };
+ my ($self, $params) = @_;
+ my $user = Bugzilla->user;
+ my $bug = Bugzilla::Bug->check({id => $params->{id}});
+
+ # the keys of the options hash must match the field id in the ui
+ my %options;
+
+ my @products = @{$user->get_enterable_products};
+ unless (grep { $_->id == $bug->product_id } @products) {
+ unshift @products, $bug->product_obj;
+ }
+ $options{product} = [map { {name => $_->name} } @products];
+
+ $options{component} = _name($bug->product_obj->components, $bug->component);
+ $options{version} = _name($bug->product_obj->versions, $bug->version);
+ $options{target_milestone}
+ = _name($bug->product_obj->milestones, $bug->target_milestone);
+ $options{priority} = _name('priority', $bug->priority);
+ $options{bug_severity} = _name('bug_severity', $bug->bug_severity);
+ $options{rep_platform} = _name('rep_platform', $bug->rep_platform);
+ $options{op_sys} = _name('op_sys', $bug->op_sys);
+
+ # custom select fields
+ my @custom_fields = grep {
+ $_->type == FIELD_TYPE_SINGLE_SELECT
+ || $_->type == FIELD_TYPE_MULTI_SELECT
+ } Bugzilla->active_custom_fields(
+ {product => $bug->product_obj, component => $bug->component_obj});
+ foreach my $field (@custom_fields) {
+ my $field_name = $field->name;
+ my @values = map { {name => $_->name} } grep {
+ $bug->$field_name eq $_->name
+ || ($_->is_active
+ && $bug->check_can_change_field($field_name, $bug->$field_name, $_->name))
+ } @{$field->legal_values};
+ $options{$field_name} = \@values;
+ }
+
+ # keywords
+ my @keywords = grep { $_->is_active } Bugzilla::Keyword->get_all();
+
+ # results
+ return {options => \%options, keywords => [map { $_->name } @keywords],};
}
sub _name {
- my ($values, $current) = @_;
- # values can either be an array-ref of values, or a field name, which
- # result in that field's legal-values being used.
- if (!ref($values)) {
- $values = Bugzilla::Field->new({ name => $values, cache => 1 })->legal_values;
- }
- return [
- map { { name => $_->name } }
- grep { (defined $current && $_->name eq $current) || $_->is_active }
- @$values
- ];
+ my ($values, $current) = @_;
+
+ # values can either be an array-ref of values, or a field name, which
+ # result in that field's legal-values being used.
+ if (!ref($values)) {
+ $values = Bugzilla::Field->new({name => $values, cache => 1})->legal_values;
+ }
+ return [map { {name => $_->name} }
+ grep { (defined $current && $_->name eq $current) || $_->is_active }
+ @$values];
}
sub cc {
- my ($self, $params) = @_;
- my $template = Bugzilla->template;
- my $bug = Bugzilla::Bug->check({ id => $params->{id} });
- my $vars = {
- bug => $bug,
- cc_list => [
- sort { lc($a->identity) cmp lc($b->identity) }
- @{ $bug->cc_users }
- ]
- };
-
- my $html = '';
- $template->process('bug_modal/cc_list.html.tmpl', $vars, \$html)
- || ThrowTemplateError($template->error);
- return { html => $html };
+ my ($self, $params) = @_;
+ my $template = Bugzilla->template;
+ my $bug = Bugzilla::Bug->check({id => $params->{id}});
+ my $vars = {
+ bug => $bug,
+ cc_list => [sort { lc($a->identity) cmp lc($b->identity) } @{$bug->cc_users}]
+ };
+
+ my $html = '';
+ $template->process('bug_modal/cc_list.html.tmpl', $vars, \$html)
+ || ThrowTemplateError($template->error);
+ return {html => $html};
}
sub new_product {
- my ($self, $params) = @_;
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
- my $bug = Bugzilla::Bug->check({ id => $params->{id} });
- my $product = Bugzilla::Product->check({ name => $params->{product_name}, cache => 1 });
- my $true = $self->type('boolean', 1);
- my %result;
-
- # components
-
- my $components = _name($product->components);
- my $current_component = $bug->component;
- if (my $component = first_value { $_->{name} eq $current_component} @$components) {
- # identical component in both products
- $component->{selected} = $true;
- }
- else {
- # default to a blank value
- unshift @$components, {
- name => '',
- selected => $true,
- };
- }
- $result{component} = $components;
-
- # milestones
-
- my $milestones = _name($product->milestones);
- my $current_milestone = $bug->target_milestone;
- if ($bug->check_can_change_field('target_milestone', 0, 1)
- && (my $milestone = first_value { $_->{name} eq $current_milestone} @$milestones))
- {
- # identical milestone in both products
- $milestone->{selected} = $true;
- }
- else {
- # use default milestone
- my $default_milestone = $product->default_milestone;
- my $milestone = first_value { $_->{name} eq $default_milestone } @$milestones;
- $milestone->{selected} = $true;
+ my ($self, $params) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+ my $bug = Bugzilla::Bug->check({id => $params->{id}});
+ my $product
+ = Bugzilla::Product->check({name => $params->{product_name}, cache => 1});
+ my $true = $self->type('boolean', 1);
+ my %result;
+
+ # components
+
+ my $components = _name($product->components);
+ my $current_component = $bug->component;
+ if (my $component
+ = first_value { $_->{name} eq $current_component } @$components)
+ {
+ # identical component in both products
+ $component->{selected} = $true;
+ }
+ else {
+ # default to a blank value
+ unshift @$components, {name => '', selected => $true,};
+ }
+ $result{component} = $components;
+
+ # milestones
+
+ my $milestones = _name($product->milestones);
+ my $current_milestone = $bug->target_milestone;
+ if ($bug->check_can_change_field('target_milestone', 0, 1)
+ && (my $milestone
+ = first_value { $_->{name} eq $current_milestone } @$milestones))
+ {
+ # identical milestone in both products
+ $milestone->{selected} = $true;
+ }
+ else {
+ # use default milestone
+ my $default_milestone = $product->default_milestone;
+ my $milestone = first_value { $_->{name} eq $default_milestone } @$milestones;
+ $milestone->{selected} = $true;
+ }
+ $result{target_milestone} = $milestones;
+
+ # versions
+
+ my $versions = _name($product->versions);
+ my $current_version = $bug->version;
+ my $selected_version;
+ if (my $version = first_value { $_->{name} eq $current_version } @$versions) {
+
+ # identical version in both products
+ $version->{selected} = $true;
+ $selected_version = $version;
+ }
+ elsif ($current_version =~ /^(\d+) Branch$/
+ || $current_version =~ /^Firefox (\d+)$/
+ || $current_version =~ /^(\d+)$/)
+ {
+ # firefox, with its three version naming schemes
+ my $branch = $1;
+ foreach my $test_version ("$branch Branch", "Firefox $branch", $branch) {
+ if (my $version = first_value { $_->{name} eq $test_version } @$versions) {
+ $version->{selected} = $true;
+ $selected_version = $version;
+ last;
+ }
}
- $result{target_milestone} = $milestones;
-
- # versions
+ }
+ if (!$selected_version) {
- my $versions = _name($product->versions);
- my $current_version = $bug->version;
- my $selected_version;
- if (my $version = first_value { $_->{name} eq $current_version } @$versions) {
- # identical version in both products
+ # "unspecified", "other"
+ foreach my $test_version ("unspecified", "other") {
+ if (my $version = first_value { lc($_->{name}) eq $test_version } @$versions) {
$version->{selected} = $true;
$selected_version = $version;
+ last;
+ }
}
- elsif (
- $current_version =~ /^(\d+) Branch$/
- || $current_version =~ /^Firefox (\d+)$/
- || $current_version =~ /^(\d+)$/)
+ }
+ if (!$selected_version) {
+
+ # default to a blank value
+ unshift @$versions, {name => '', selected => $true,};
+ }
+ $result{version} = $versions;
+
+ # groups
+
+ my @groups;
+
+ # find invalid groups
+ push @groups,
+ map { {type => 'invalid', group => $_, checked => 0,} }
+ @{Bugzilla::Bug->get_invalid_groups(
+ {bug_ids => [$bug->id], product => $product})};
+
+ # logic lifted from bug/process/verify-new-product.html.tmpl
+ my $current_groups = $bug->groups_in;
+ my $group_controls = $product->group_controls;
+ foreach my $group_id (keys %$group_controls) {
+ my $group_control = $group_controls->{$group_id};
+ if (
+ $group_control->{membercontrol} == CONTROLMAPMANDATORY
+ || ($group_control->{othercontrol} == CONTROLMAPMANDATORY
+ && !$user->in_group($group_control->{name}))
+ )
{
- # firefox, with its three version naming schemes
- my $branch = $1;
- foreach my $test_version ("$branch Branch", "Firefox $branch", $branch) {
- if (my $version = first_value { $_->{name} eq $test_version } @$versions) {
- $version->{selected} = $true;
- $selected_version = $version;
- last;
- }
- }
- }
- if (!$selected_version) {
- # "unspecified", "other"
- foreach my $test_version ("unspecified", "other") {
- if (my $version = first_value { lc($_->{name}) eq $test_version } @$versions) {
- $version->{selected} = $true;
- $selected_version = $version;
- last;
- }
- }
- }
- if (!$selected_version) {
- # default to a blank value
- unshift @$versions, {
- name => '',
- selected => $true,
- };
- }
- $result{version} = $versions;
-
- # groups
-
- my @groups;
-
- # find invalid groups
- push @groups,
- map {{
- type => 'invalid',
- group => $_,
- checked => 0,
- }}
- @{ Bugzilla::Bug->get_invalid_groups({ bug_ids => [ $bug->id ], product => $product }) };
-
- # logic lifted from bug/process/verify-new-product.html.tmpl
- my $current_groups = $bug->groups_in;
- my $group_controls = $product->group_controls;
- foreach my $group_id (keys %$group_controls) {
- my $group_control = $group_controls->{$group_id};
- if ($group_control->{membercontrol} == CONTROLMAPMANDATORY
- || ($group_control->{othercontrol} == CONTROLMAPMANDATORY && !$user->in_group($group_control->{name})))
- {
- # mandatory, always checked
- push @groups, {
- type => 'mandatory',
- group => $group_control->{group},
- checked => 1,
- };
- }
- elsif (
- ($group_control->{membercontrol} != CONTROLMAPNA && $user->in_group($group_control->{name}))
- || $group_control->{othercontrol} != CONTROLMAPNA)
- {
- # optional, checked if..
- my $group = $group_control->{group};
- my $checked =
- # same group as current product
- (any { $_->id == $group->id } @$current_groups)
- # member default
- || $group_control->{membercontrol} == CONTROLMAPDEFAULT && $user->in_group($group_control->{name})
- # or other default
- || $group_control->{othercontrol} == CONTROLMAPDEFAULT && !$user->in_group($group_control->{name})
- ;
- push @groups, {
- type => 'optional',
- group => $group_control->{group},
- checked => $checked || 0,
- };
- }
+ # mandatory, always checked
+ push @groups,
+ {type => 'mandatory', group => $group_control->{group}, checked => 1,};
}
+ elsif (
+ (
+ $group_control->{membercontrol} != CONTROLMAPNA
+ && $user->in_group($group_control->{name})
+ )
+ || $group_control->{othercontrol} != CONTROLMAPNA
+ )
+ {
+ # optional, checked if..
+ my $group = $group_control->{group};
+ my $checked =
- my $default_group_name = $product->default_security_group;
- if (my $default_group = first_value { $_->{group}->name eq $default_group_name } @groups) {
- # because we always allow the default product group to be selected, it's never invalid
- $default_group->{type} = 'optional' if $default_group->{type} eq 'invalid';
- }
- else {
- # add the product's default group if it's missing
- unshift @groups, {
- type => 'optional',
- group => $product->default_security_group_obj,
- checked => 0,
- };
- }
+ # same group as current product
+ (any { $_->id == $group->id } @$current_groups)
- # if the bug is currently in a group, ensure a group is checked by default
- # by checking the product's default group if no other groups apply
- if (@$current_groups && !any { $_->{checked} } @groups) {
- foreach my $g (@groups) {
- next unless $g->{group}->name eq $default_group_name;
- $g->{checked} = 1;
- last;
- }
- }
+ # member default
+ || $group_control->{membercontrol} == CONTROLMAPDEFAULT
+ && $user->in_group($group_control->{name})
- # group by type and flatten
- my $vars = {
- product => $product,
- groups => { invalid => [], mandatory => [], optional => [] },
- };
- foreach my $g (@groups) {
- push @{ $vars->{groups}->{$g->{type}} }, {
- id => $g->{group}->id,
- name => $g->{group}->name,
- description => $g->{group}->description,
- checked => $g->{checked},
+ # or other default
+ || $group_control->{othercontrol} == CONTROLMAPDEFAULT
+ && !$user->in_group($group_control->{name});
+ push @groups,
+ {
+ type => 'optional',
+ group => $group_control->{group},
+ checked => $checked || 0,
};
}
-
- # build group selection html
- my $template = Bugzilla->template;
- $template->process('bug_modal/new_product_groups.html.tmpl', $vars, \$result{groups})
- || ThrowTemplateError($template->error);
-
- return \%result;
+ }
+
+ my $default_group_name = $product->default_security_group;
+ if (my $default_group
+ = first_value { $_->{group}->name eq $default_group_name } @groups)
+ {
+# because we always allow the default product group to be selected, it's never invalid
+ $default_group->{type} = 'optional' if $default_group->{type} eq 'invalid';
+ }
+ else {
+ # add the product's default group if it's missing
+ unshift @groups,
+ {
+ type => 'optional',
+ group => $product->default_security_group_obj,
+ checked => 0,
+ };
+ }
+
+ # if the bug is currently in a group, ensure a group is checked by default
+ # by checking the product's default group if no other groups apply
+ if (@$current_groups && !any { $_->{checked} } @groups) {
+ foreach my $g (@groups) {
+ next unless $g->{group}->name eq $default_group_name;
+ $g->{checked} = 1;
+ last;
+ }
+ }
+
+ # group by type and flatten
+ my $vars = {
+ product => $product,
+ groups => {invalid => [], mandatory => [], optional => []},
+ };
+ foreach my $g (@groups) {
+ push @{$vars->{groups}->{$g->{type}}},
+ {
+ id => $g->{group}->id,
+ name => $g->{group}->name,
+ description => $g->{group}->description,
+ checked => $g->{checked},
+ };
+ }
+
+ # build group selection html
+ my $template = Bugzilla->template;
+ $template->process('bug_modal/new_product_groups.html.tmpl',
+ $vars, \$result{groups})
+ || ThrowTemplateError($template->error);
+
+ return \%result;
}
1;