From 40f5156e2e047efb2d863181ae7afb02c460d313 Mon Sep 17 00:00:00 2001 From: Byron Jones Date: Wed, 7 Nov 2012 15:40:53 +0800 Subject: Bug 801713: implement "bug shadowing" feature --- extensions/InlineHistory/Extension.pm | 8 +- extensions/ShadowBugs/Config.pm | 15 ++++ extensions/ShadowBugs/Extension.pm | 99 ++++++++++++++++++++++ .../hook/bug/comments-aftercomments.html.tmpl | 70 +++++++++++++++ .../hook/bug/edit-after_comment_textarea.html.tmpl | 13 +++ .../hook/bug/edit-after_custom_fields.html.tmpl | 27 ++++++ .../default/hook/bug/edit-custom_field.html.tmpl | 9 ++ .../en/default/hook/bug/show-header-end.html.tmpl | 12 +++ .../hook/global/user-error-errors.html.tmpl | 14 +++ extensions/ShadowBugs/web/shadow-bugs.js | 51 +++++++++++ extensions/ShadowBugs/web/style.css | 10 +++ 11 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 extensions/ShadowBugs/Config.pm create mode 100644 extensions/ShadowBugs/Extension.pm create mode 100644 extensions/ShadowBugs/template/en/default/hook/bug/comments-aftercomments.html.tmpl create mode 100644 extensions/ShadowBugs/template/en/default/hook/bug/edit-after_comment_textarea.html.tmpl create mode 100644 extensions/ShadowBugs/template/en/default/hook/bug/edit-after_custom_fields.html.tmpl create mode 100644 extensions/ShadowBugs/template/en/default/hook/bug/edit-custom_field.html.tmpl create mode 100644 extensions/ShadowBugs/template/en/default/hook/bug/show-header-end.html.tmpl create mode 100644 extensions/ShadowBugs/template/en/default/hook/global/user-error-errors.html.tmpl create mode 100644 extensions/ShadowBugs/web/shadow-bugs.js create mode 100644 extensions/ShadowBugs/web/style.css (limited to 'extensions') diff --git a/extensions/InlineHistory/Extension.pm b/extensions/InlineHistory/Extension.pm index 61a263f61..2e388994a 100644 --- a/extensions/InlineHistory/Extension.pm +++ b/extensions/InlineHistory/Extension.pm @@ -90,10 +90,16 @@ sub template_before_process { $change->{added} = $change->{added} ? 'true' : 'false'; } + my $field_obj; + if ($change->{fieldname} =~ /^cf_/) { + $field_obj = Bugzilla::Field->new({ name => $change->{fieldname} }); + } + # identify buglist changes if ($change->{fieldname} eq 'blocked' || $change->{fieldname} eq 'dependson' || - $change->{fieldname} eq 'dupe' + $change->{fieldname} eq 'dupe' || + ($field_obj && $field_obj->type == FIELD_TYPE_BUG_ID) ) { $change->{buglist} = 1; foreach my $what (qw(removed added)) { diff --git a/extensions/ShadowBugs/Config.pm b/extensions/ShadowBugs/Config.pm new file mode 100644 index 000000000..6999edaf3 --- /dev/null +++ b/extensions/ShadowBugs/Config.pm @@ -0,0 +1,15 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Extension::ShadowBugs; +use strict; + +use constant NAME => 'ShadowBugs'; +use constant REQUIRED_MODULES => []; +use constant OPTIONAL_MODULES => []; + +__PACKAGE__->NAME; diff --git a/extensions/ShadowBugs/Extension.pm b/extensions/ShadowBugs/Extension.pm new file mode 100644 index 000000000..b015a6164 --- /dev/null +++ b/extensions/ShadowBugs/Extension.pm @@ -0,0 +1,99 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Extension::ShadowBugs; + +use strict; + +use base qw(Bugzilla::Extension); + +use Bugzilla::Bug; +use Bugzilla::Error; +use Bugzilla::Field; +use Bugzilla::User; + +our $VERSION = '1'; + +BEGIN { + *Bugzilla::is_cf_shadow_bug_hidden = \&_is_cf_shadow_bug_hidden; + *Bugzilla::Bug::cf_shadow_bug_obj = \&_cf_shadow_bug_obj; +} + +# Determine if the shadow-bug / shadowed-by fields are visibile on the +# specified bug. +sub _is_cf_shadow_bug_hidden { + my ($self, $bug) = @_; + + # completely hide unless you're a member of the right group + return 1 unless Bugzilla->user->in_group('can_shadow_bugs'); + + my $is_public = Bugzilla::User->new()->can_see_bug($bug->id); + if ($is_public) { + # hide on public bugs, unless it's shadowed + my $related = $bug->related_bugs(Bugzilla->process_cache->{shadow_bug_field}); + return 1 if !@$related; + } +} + +sub _cf_shadow_bug_obj { + my ($self) = @_; + return unless $self->cf_shadow_bug; + return $self->{cf_shadow_bug_obj} ||= Bugzilla::Bug->new($self->cf_shadow_bug); +} + +sub template_before_process { + my ($self, $args) = @_; + my $file = $args->{'file'}; + my $vars = $args->{'vars'}; + + Bugzilla->process_cache->{shadow_bug_field} ||= Bugzilla::Field->new({ name => 'cf_shadow_bug' }); + + return unless Bugzilla->user->in_group('can_shadow_bugs'); + return unless + $file eq 'bug/edit.html.tmpl' + || $file eq 'bug/show.html.tmpl' + || $file eq 'bug/show-header.html.tmpl'; + my $bug = exists $vars->{'bugs'} ? $vars->{'bugs'}[0] : $vars->{'bug'}; + return unless $bug->cf_shadow_bug; + $vars->{is_shadow_bug} = 1; + + if ($file eq 'bug/edit.html.tmpl') { + # load comments from other bug + $vars->{shadow_comments} = $bug->cf_shadow_bug_obj->comments; + } +} + +sub bug_end_of_update { + my ($self, $args) = @_; + + # don't allow shadowing non-public bugs + if (exists $args->{changes}->{cf_shadow_bug}) { + my ($old_id, $new_id) = @{ $args->{changes}->{cf_shadow_bug} }; + if ($new_id) { + if (!Bugzilla::User->new()->can_see_bug($new_id)) { + ThrowUserError('illegal_shadow_bug_public', { id => $new_id }); + } + } + } + + # if a shadow bug is made public, clear the shadow_bug field + if (exists $args->{changes}->{bug_group}) { + my $bug = $args->{bug}; + return unless my $shadow_id = $bug->cf_shadow_bug; + my $is_public = Bugzilla::User->new()->can_see_bug($bug->id); + if ($is_public) { + Bugzilla->dbh->do( + "UPDATE bugs SET cf_shadow_bug=NULL WHERE bug_id=?", + undef, $bug->id); + LogActivityEntry($bug->id, 'cf_shadow_bug', $shadow_id, '', + Bugzilla->user->id, $args->{timestamp}); + + } + } +} + +__PACKAGE__->NAME; diff --git a/extensions/ShadowBugs/template/en/default/hook/bug/comments-aftercomments.html.tmpl b/extensions/ShadowBugs/template/en/default/hook/bug/comments-aftercomments.html.tmpl new file mode 100644 index 000000000..d8dae521a --- /dev/null +++ b/extensions/ShadowBugs/template/en/default/hook/bug/comments-aftercomments.html.tmpl @@ -0,0 +1,70 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[% RETURN UNLESS is_shadow_bug %] + +[% public_bug = bug.cf_shadow_bug_obj %] +[% count = 0 %] +[% FOREACH comment = shadow_comments %] + [% IF count >= start_at %] + [% PROCESS a_comment %] + [% END %] + [% count = count + increment %] +[% END %] + +[% BLOCK a_comment %] + [% RETURN IF comment.is_private AND NOT (user.is_insider || user.id == comment.author.id) %] + [% comment_text = comment.body_full %] + [% RETURN IF comment_text == '' %] + +
+ [% IF count == description %] + [% class_name = "bz_first_comment_head" %] + [% comment_label = "Public Description" %] + [% ELSE %] + [% class_name = "bz_comment_head" %] + [% comment_label = "Public Comment " _ count %] + [% END %] + +
+ + + [%- comment_label FILTER html %] + + + + [% commenter_id = comment.author.id %] + [% UNLESS user_cache.$commenter_id %] + [% user_cache.$commenter_id = BLOCK %] + [% INCLUDE global/user.html.tmpl who = comment.author %] + [% END %] + [% END %] + [% user_cache.$commenter_id FILTER none %] + [% Hook.process('user', 'bug/comments.html.tmpl') %] + + + + [% FOREACH group = comment.author.groups_with_icon %] + [% group.name FILTER html %] + [% END %] + + + + [%+ comment.creation_ts FILTER time %] + +
+ +
+  [%- comment_text FILTER quoteUrls(public_bug, comment) -%]
+
+
+[% END %] diff --git a/extensions/ShadowBugs/template/en/default/hook/bug/edit-after_comment_textarea.html.tmpl b/extensions/ShadowBugs/template/en/default/hook/bug/edit-after_comment_textarea.html.tmpl new file mode 100644 index 000000000..9873ea3d7 --- /dev/null +++ b/extensions/ShadowBugs/template/en/default/hook/bug/edit-after_comment_textarea.html.tmpl @@ -0,0 +1,13 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[% RETURN UNLESS is_shadow_bug %] + +
+Add public comment + diff --git a/extensions/ShadowBugs/template/en/default/hook/bug/edit-after_custom_fields.html.tmpl b/extensions/ShadowBugs/template/en/default/hook/bug/edit-after_custom_fields.html.tmpl new file mode 100644 index 000000000..8e8327ef2 --- /dev/null +++ b/extensions/ShadowBugs/template/en/default/hook/bug/edit-after_custom_fields.html.tmpl @@ -0,0 +1,27 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[% RETURN IF Bugzilla.is_cf_shadow_bug_hidden(bug) %] +[% field = Bugzilla.process_cache.shadow_bug_field %] +[% shadowed_by = bug.related_bugs(field).pop %] + + [% IF shadowed_by && user.can_see_bug(shadowed_by) %] + + [% field.reverse_desc FILTER html %]: + + + [% shadowed_by.id FILTER bug_link(shadowed_by, use_alias => 1) FILTER none %][% " " %] + + [% ELSE %] + [% PROCESS bug/field.html.tmpl + value = bug.cf_shadow_bug + editable = bug.check_can_change_field(field.name, 0, 1) + no_tds = false + value_span = 2 %] + [% END %] + diff --git a/extensions/ShadowBugs/template/en/default/hook/bug/edit-custom_field.html.tmpl b/extensions/ShadowBugs/template/en/default/hook/bug/edit-custom_field.html.tmpl new file mode 100644 index 000000000..4389b27ad --- /dev/null +++ b/extensions/ShadowBugs/template/en/default/hook/bug/edit-custom_field.html.tmpl @@ -0,0 +1,9 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[% field.hidden = field.name == 'cf_shadow_bug' %] diff --git a/extensions/ShadowBugs/template/en/default/hook/bug/show-header-end.html.tmpl b/extensions/ShadowBugs/template/en/default/hook/bug/show-header-end.html.tmpl new file mode 100644 index 000000000..5786b3df6 --- /dev/null +++ b/extensions/ShadowBugs/template/en/default/hook/bug/show-header-end.html.tmpl @@ -0,0 +1,12 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[% IF is_shadow_bug %] + [% style_urls.push('extensions/ShadowBugs/web/style.css') %] + [% javascript_urls.push('extensions/ShadowBugs/web/shadow-bugs.js') %] +[% END %] diff --git a/extensions/ShadowBugs/template/en/default/hook/global/user-error-errors.html.tmpl b/extensions/ShadowBugs/template/en/default/hook/global/user-error-errors.html.tmpl new file mode 100644 index 000000000..775a7721e --- /dev/null +++ b/extensions/ShadowBugs/template/en/default/hook/global/user-error-errors.html.tmpl @@ -0,0 +1,14 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[% IF error == 'illegal_shadow_bug_public' %] + [% title = "Invalid Shadow " _ terms.Bug %] + You cannot shadow [% terms.bug %] [%+ id FILTER html %] because it is not a + public [% terms.bug %]. +[% END %] + diff --git a/extensions/ShadowBugs/web/shadow-bugs.js b/extensions/ShadowBugs/web/shadow-bugs.js new file mode 100644 index 000000000..ff320e117 --- /dev/null +++ b/extensions/ShadowBugs/web/shadow-bugs.js @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This Source Code Form is "Incompatible With Secondary Licenses", as + * defined by the Mozilla Public License, v. 2.0. */ + +var shadow_bug = { + init: function() { + var Dom = YAHOO.util.Dom; + var comment_divs = Dom.getElementsByClassName('bz_comment', 'div', 'comments'); + var comments = new Array(); + for (var i = 0, l = comment_divs.length; i < l; i++) { + var time_spans = Dom.getElementsByClassName('bz_comment_time', 'span', comment_divs[i]); + if (!time_spans.length) continue; + var date = this.parse_date(time_spans[0].innerHTML); + if (!date) continue; + + var comment = {}; + comment.div = comment_divs[i]; + comment.date = date; + comment.shadow = Dom.hasClass(comment.div, 'shadow_bug_comment'); + comments.push(comment); + } + + for (var i = 0, l = comments.length; i < l; i++) { + if (!comments[i].shadow) continue; + for (var j = 0, jl = comments.length; j < jl; j++) { + if (comments[j].shadow) continue; + if (comments[j].date > comments[i].date) { + comments[j].div.parentNode.insertBefore(comments[i].div, comments[j].div); + break; + } + } + Dom.removeClass(comments[i].div, 'bz_default_hidden'); + } + + Dom.get('comment').placeholder = 'Add non-public comment'; + }, + + parse_date: function(date) { + var matches = date.match(/^\s*(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/); + if (!matches) return; + return (matches[1] + matches[2] + matches[3] + matches[4] + matches[5] + matches[6]) + 0; + } +}; + + +YAHOO.util.Event.onDOMReady(function() { + shadow_bug.init(); +}); diff --git a/extensions/ShadowBugs/web/style.css b/extensions/ShadowBugs/web/style.css new file mode 100644 index 000000000..0c104130f --- /dev/null +++ b/extensions/ShadowBugs/web/style.css @@ -0,0 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This Source Code Form is "Incompatible With Secondary Licenses", as + * defined by the Mozilla Public License, v. 2.0. */ + +.shadow_bug_comment { + background: transparent !important; +} -- cgit v1.2.3-24-g4f1b