summaryrefslogtreecommitdiffstats
path: root/extensions/ShadowBugs
diff options
context:
space:
mode:
authorByron Jones <bjones@mozilla.com>2012-11-07 08:40:53 +0100
committerByron Jones <bjones@mozilla.com>2012-11-07 08:40:53 +0100
commit40f5156e2e047efb2d863181ae7afb02c460d313 (patch)
tree2c754cf75f59bbbadb81f43e5f031a17509b5a47 /extensions/ShadowBugs
parent7cd9fd570d61e316e2398a8b9db274d200d73bc4 (diff)
downloadbugzilla-40f5156e2e047efb2d863181ae7afb02c460d313.tar.gz
bugzilla-40f5156e2e047efb2d863181ae7afb02c460d313.tar.xz
Bug 801713: implement "bug shadowing" feature
Diffstat (limited to 'extensions/ShadowBugs')
-rw-r--r--extensions/ShadowBugs/Config.pm15
-rw-r--r--extensions/ShadowBugs/Extension.pm99
-rw-r--r--extensions/ShadowBugs/template/en/default/hook/bug/comments-aftercomments.html.tmpl70
-rw-r--r--extensions/ShadowBugs/template/en/default/hook/bug/edit-after_comment_textarea.html.tmpl13
-rw-r--r--extensions/ShadowBugs/template/en/default/hook/bug/edit-after_custom_fields.html.tmpl27
-rw-r--r--extensions/ShadowBugs/template/en/default/hook/bug/edit-custom_field.html.tmpl9
-rw-r--r--extensions/ShadowBugs/template/en/default/hook/bug/show-header-end.html.tmpl12
-rw-r--r--extensions/ShadowBugs/template/en/default/hook/global/user-error-errors.html.tmpl14
-rw-r--r--extensions/ShadowBugs/web/shadow-bugs.js51
-rw-r--r--extensions/ShadowBugs/web/style.css10
10 files changed, 320 insertions, 0 deletions
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 == '' %]
+
+ <div id="pc[% count %]" class="bz_comment[% " bz_private" IF comment.is_private %]
+ shadow_bug_comment bz_default_hidden
+ [% " bz_first_comment" IF count == description %]">
+ [% 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 %]
+
+ <div class="[% class_name FILTER html %]">
+ <span class="bz_comment_number">
+ <a href="show_bug.cgi?id=[% public_bug.bug_id FILTER none %]#c[% count %]">
+ [%- comment_label FILTER html %]</a>
+ </span>
+
+ <span class="bz_comment_user">
+ [% 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') %]
+ </span>
+
+ <span class="bz_comment_user_images">
+ [% FOREACH group = comment.author.groups_with_icon %]
+ <img src="[% group.icon_url FILTER html %]"
+ alt="[% group.name FILTER html %]"
+ title="[% group.name FILTER html %] - [% group.description FILTER html %]">
+ [% END %]
+ </span>
+
+ <span class="bz_comment_time">
+ [%+ comment.creation_ts FILTER time %]
+ </span>
+ </div>
+
+<pre class="bz_comment_text">
+ [%- comment_text FILTER quoteUrls(public_bug, comment) -%]
+</pre>
+ </div>
+[% 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 %]
+
+<br>
+<a href="show_bug.cgi?id=[% bug.cf_shadow_bug FILTER none %]#comment">Add public comment</a>
+
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 %]
+<tr>
+ [% IF shadowed_by && user.can_see_bug(shadowed_by) %]
+ <th class="field_label">
+ [% field.reverse_desc FILTER html %]:
+ </th>
+ <td>
+ [% shadowed_by.id FILTER bug_link(shadowed_by, use_alias => 1) FILTER none %][% " " %]
+ </td>
+ [% 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 %]
+</tr>
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;
+}