summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDylan William Hardison <dylan@hardison.net>2014-10-10 19:58:41 +0200
committerDylan William Hardison <dylan@hardison.net>2014-10-10 19:58:41 +0200
commitccea670dcba24ff2ac0233437aa549b22edb390c (patch)
treed59168e74f767e4665f2736130b4e97254b83418
parent1f84551e1414eeba886e04e0e9e2a8e61d568fc1 (diff)
downloadbugzilla-ccea670dcba24ff2ac0233437aa549b22edb390c.tar.gz
bugzilla-ccea670dcba24ff2ac0233437aa549b22edb390c.tar.xz
Bug 1074586 - New Feature: Bugs of Interest
r=dkl
-rw-r--r--Bugzilla/Field.pm2
-rw-r--r--Bugzilla/Search.pm25
-rw-r--r--extensions/MyDashboard/Extension.pm112
-rw-r--r--extensions/MyDashboard/lib/BugInterest.pm70
-rw-r--r--extensions/MyDashboard/lib/Queries.pm17
-rw-r--r--extensions/MyDashboard/lib/WebService.pm27
-rw-r--r--extensions/MyDashboard/template/en/default/pages/mydashboard.html.tmpl4
-rw-r--r--extensions/MyDashboard/web/js/query.js25
-rw-r--r--extensions/MyDashboard/web/styles/mydashboard.css3
-rw-r--r--js/bug.js21
10 files changed, 291 insertions, 15 deletions
diff --git a/Bugzilla/Field.pm b/Bugzilla/Field.pm
index d5f8e33f9..86811d9bc 100644
--- a/Bugzilla/Field.pm
+++ b/Bugzilla/Field.pm
@@ -270,6 +270,8 @@ use constant DEFAULT_FIELDS => (
{name => 'tag', desc => 'Tags'},
{name => 'last_visit_ts', desc => 'Last Visit', buglist => 1,
type => FIELD_TYPE_DATETIME},
+ {name => 'bug_interest_ts', desc => 'Bug Interest', buglist => 1,
+ type => FIELD_TYPE_DATETIME},
{name => 'comment_tag', desc => 'Comment Tag'},
);
diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm
index 86e6764ff..7f27e96f8 100644
--- a/Bugzilla/Search.pm
+++ b/Bugzilla/Search.pm
@@ -354,9 +354,12 @@ use constant OPERATOR_FIELD_OVERRIDE => {
},
last_visit_ts => {
_non_changed => \&_last_visit_ts,
- _default => \&_last_visit_ts_invalid_operator,
+ _default => \&_invalid_operator,
+ },
+ bug_interest_ts => {
+ _non_changed => \&_bug_interest_ts,
+ _default => \&_invalid_operator,
},
-
# Custom Fields
FIELD_TYPE_FREETEXT, { _non_changed => \&_nullable },
FIELD_TYPE_BUG_ID, { _non_changed => \&_nullable_int },
@@ -387,6 +390,7 @@ sub SPECIAL_PARSING {
# last_visit field that accept both a 1d, 1w, 1m, 1y format and the
# %last_changed% pronoun.
last_visit_ts => \&_last_visit_datetime,
+ bug_interest_ts => \&_last_visit_datetime,
# BMO - Add ability to use pronoun for bug mentors field
bug_mentor => \&_commenter_pronoun,
@@ -563,6 +567,13 @@ sub COLUMN_JOINS {
from => 'bug_id',
to => 'bug_id',
},
+ bug_interest_ts => {
+ as => 'bug_interest',
+ table => 'bug_interest',
+ extra => ['bug_interest.user_id = ' . $user->id],
+ from => 'bug_id',
+ to => 'bug_id',
+ },
};
return $joins;
};
@@ -634,6 +645,7 @@ sub COLUMNS {
'longdescs.count' => 'COUNT(DISTINCT map_longdescs_count.comment_id)',
last_visit_ts => 'bug_user_last_visit.last_visit_ts',
+ bug_interest_ts => 'bug_interest.modification_time',
assignee_last_login => 'assignee.last_seen_date',
);
@@ -2788,7 +2800,14 @@ sub _last_visit_ts {
$self->_add_extra_column('last_visit_ts');
}
-sub _last_visit_ts_invalid_operator {
+sub _bug_interest_ts {
+ my ($self, $args) = @_;
+
+ $args->{full_field} = $self->COLUMNS->{bug_interest_ts}->{name};
+ $self->_add_extra_column('bug_interest_ts');
+}
+
+sub _invalid_operator {
my ($self, $args) = @_;
ThrowUserError('search_field_operator_invalid',
diff --git a/extensions/MyDashboard/Extension.pm b/extensions/MyDashboard/Extension.pm
index 082f1c562..51e774b5b 100644
--- a/extensions/MyDashboard/Extension.pm
+++ b/extensions/MyDashboard/Extension.pm
@@ -12,10 +12,13 @@ use strict;
use base qw(Bugzilla::Extension);
use Bugzilla;
+use Bugzilla::Status 'is_open_state';
+
use Bugzilla::Constants;
use Bugzilla::Search::Saved;
use Bugzilla::Extension::MyDashboard::Queries qw(QUERY_DEFS);
+use Bugzilla::Extension::MyDashboard::BugInterest;
our $VERSION = BUGZILLA_VERSION;
@@ -45,6 +48,33 @@ sub db_schema_abstract_schema {
mydashboard_user_id_idx => ['user_id'],
],
};
+
+ $schema->{'bug_interest'} = {
+ FIELDS => [
+ id => { TYPE => 'MEDIUMSERIAL',
+ NOTNULL => 1,
+ PRIMARYKEY => 1 },
+
+ bug_id => { TYPE => 'INT3',
+ NOTNULL => 1,
+ REFERENCES => { TABLE => 'bugs',
+ COLUMN => 'bug_id',
+ DELETE => 'CASCADE' } },
+
+ user_id => { TYPE => 'INT3',
+ NOTNOLL => 1,
+ REFERENCES => { TABLE => 'profiles',
+ COLUMN => 'userid' } },
+
+ modification_time => { TYPE => 'DATETIME',
+ NOTNULL => 1 }
+ ],
+ INDEXES => [
+ bug_interest_idx => { FIELDS => [qw(bug_id user_id)],
+ TYPE => 'UNIQUE' },
+ bug_interest_user_id_idx => ['user_id']
+ ],
+ };
}
###########
@@ -53,6 +83,7 @@ sub db_schema_abstract_schema {
BEGIN {
*Bugzilla::Search::Saved::in_mydashboard = \&_in_mydashboard;
+ *Bugzilla::Component::watcher_ids = \&_component_watcher_ids;
}
sub _in_mydashboard {
@@ -65,6 +96,22 @@ sub _in_mydashboard {
return $self->{'in_mydashboard'};
}
+sub _component_watcher_ids {
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $query = "SELECT user_id FROM component_watch
+ WHERE product_id = ?
+ AND (component_id = ?
+ OR component_id IS NULL
+ OR ? LIKE CONCAT(component_prefix, '%'))";
+
+ $self->{watcher_ids} ||= $dbh->selectcol_arrayref($query, undef,
+ $self->product_id, $self->id, $self->name);
+
+ return $self->{watcher_ids};
+}
+
#############
# Templates #
#############
@@ -123,4 +170,69 @@ sub webservice {
$dispatch->{MyDashboard} = "Bugzilla::Extension::MyDashboard::WebService";
}
+sub bug_end_of_create {
+ my ($self, $args) = @_;
+ my ($bug, $params, $timestamp) = @$args{qw(bug params timestamp)};
+ my $user = Bugzilla->user;
+
+ # Anyone added to the CC list of a bug is now interested in that bug.
+ foreach my $cc_user (@{ $bug->cc_users }) {
+ next $user->id == $cc_user->id;
+ Bugzilla::Extension::MyDashboard::BugInterest->mark($cc_user->id, $bug->id, $timestamp);
+ }
+
+ # Anyone that is watching a component is interested when a bug is filed into the component.
+ foreach my $watcher_id (@{ $bug->component_obj->watcher_ids }) {
+ Bugzilla::Extension::MyDashboard::BugInterest->mark($watcher_id, $bug->id, $timestamp);
+ }
+}
+
+sub bug_end_of_update {
+ my ($self, $args) = @_;
+ my ($bug, $old_bug, $changes, $timestamp) = @$args{qw(bug old_bug changes timestamp)};
+ my $user = Bugzilla->user;
+
+ # Anyone added to the CC list of a bug is now interested in that bug.
+ my %old_cc = map { $_->id => $_ } grep { defined } @{ $old_bug->cc_users };
+ my @added = grep { not $old_cc{ $_->id } } grep { defined } @{ $bug->cc_users };
+ foreach my $cc_user (@added) {
+ next $user->id == $cc_user->id;
+ Bugzilla::Extension::MyDashboard::BugInterest->mark($cc_user->id, $bug->id, $timestamp);
+ }
+
+ # Anyone that is watching a component is interested when a bug is filed into the component.
+ if ($changes->{product} or $changes->{component}) {
+ # All of the watchers would be interested in this bug update
+ foreach my $watcher_id (@{ $bug->component_obj->watcher_ids }) {
+ Bugzilla::Extension::MyDashboard::BugInterest->mark($watcher_id, $bug->id, $timestamp);
+ }
+ }
+
+ if ($changes->{bug_status}) {
+ my ($old_status, $new_status) = @{ $changes->{bug_status} };
+ if (is_open_state($old_status) && !is_open_state($new_status)) {
+ my @related_bugs = (@{ $bug->blocks_obj }, @{ $bug->depends_on_obj });
+ my %involved;
+
+ foreach my $related_bug (@related_bugs) {
+ my @users = grep { defined } $related_bug->assigned_to,
+ $related_bug->reporter,
+ $related_bug->qa_contact,
+ @{ $related_bug->cc_users };
+
+ foreach my $involved_user (@users) {
+ $involved{ $involved_user->id }{ $related_bug->id } = 1;
+ }
+ }
+ foreach my $involved_user_id (keys %involved) {
+ foreach my $related_bug_id (keys %{$involved{$involved_user_id}}) {
+ Bugzilla::Extension::MyDashboard::BugInterest->mark($involved_user_id,
+ $related_bug_id,
+ $timestamp);
+ }
+ }
+ }
+ }
+}
+
__PACKAGE__->NAME;
diff --git a/extensions/MyDashboard/lib/BugInterest.pm b/extensions/MyDashboard/lib/BugInterest.pm
new file mode 100644
index 000000000..4f14eb4fd
--- /dev/null
+++ b/extensions/MyDashboard/lib/BugInterest.pm
@@ -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.
+
+package Bugzilla::Extension::MyDashboard::BugInterest;
+
+use 5.10.1;
+use strict;
+
+use parent qw(Bugzilla::Object);
+
+#####################################################################
+# Overriden Constants that are used as methods
+#####################################################################
+
+use constant DB_TABLE => 'bug_interest';
+use constant DB_COLUMNS => qw( id bug_id user_id modification_time );
+use constant UPDATE_COLUMNS => qw( modification_time );
+use constant VALIDATORS => {};
+use constant LIST_ORDER => 'id';
+use constant NAME_FIELD => 'id';
+
+# turn off auditing and exclude these objects from memcached
+use constant { AUDIT_CREATES => 0,
+ AUDIT_UPDATES => 0,
+ AUDIT_REMOVES => 0,
+ USE_MEMCACHED => 0 };
+
+#####################################################################
+# Provide accessors for our columns
+#####################################################################
+
+sub id { return $_[0]->{id} }
+sub bug_id { return $_[0]->{bug_id} }
+sub user_id { return $_[0]->{user_id} }
+sub modification_time { return $_[0]->{modification_time} }
+
+sub mark {
+ my ($class, $user_id, $bug_id, $timestamp) = @_;
+
+ my ($interest) = @{ $class->match({ user_id => $user_id,
+ bug_id => $bug_id }) };
+ if ($interest) {
+ $interest->set(modification_time => $timestamp);
+ $interest->update();
+ return $interest;
+ }
+ else {
+ return $class->create({
+ user_id => $user_id,
+ bug_id => $bug_id,
+ modification_time => $timestamp,
+ });
+ }
+}
+
+sub unmark {
+ my ($class, $user_id, $bug_id) = @_;
+
+ my ($interest) = @{ $class->match({ user_id => $user_id,
+ bug_id => $bug_id }) };
+ if ($interest) {
+ $interest->remove_from_db();
+ }
+}
+
+1;
diff --git a/extensions/MyDashboard/lib/Queries.pm b/extensions/MyDashboard/lib/Queries.pm
index 9dff5abe4..34c63e07b 100644
--- a/extensions/MyDashboard/lib/Queries.pm
+++ b/extensions/MyDashboard/lib/Queries.pm
@@ -106,6 +106,7 @@ sub QUERY_DEFS {
name => 'lastvisitedbugs',
heading => 'Updated Since Last Visit',
description => 'Bugs updated since last visited',
+ mark_read => 'Mark Visited',
params => {
o1 => 'lessthan',
v1 => '%last_changed%',
@@ -113,9 +114,25 @@ sub QUERY_DEFS {
},
},
{
+ name => 'interestingbugs',
+ heading => 'Interesting Bugs',
+ description => 'Bugs that you may find interesting',
+ mark_read => 'Remove Interest',
+ params => {
+ j_top => 'OR',
+ f1 => 'bug_interest_ts',
+ o1 => 'isnotempty',
+
+ f2 => 'last_visit_ts',
+ o2 => 'lessthan',
+ v2 => '%last_changed%',
+ }
+ },
+ {
name => 'nevervisitbugs',
heading => 'Involved with and Never Visited',
description => "Bugs you've never visited, but are involved with",
+ mark_read => 'Mark Visited',
params => {
query_format => "advanced",
bug_status => ['__open__'],,
diff --git a/extensions/MyDashboard/lib/WebService.pm b/extensions/MyDashboard/lib/WebService.pm
index 87061eabe..9e9de42be 100644
--- a/extensions/MyDashboard/lib/WebService.pm
+++ b/extensions/MyDashboard/lib/WebService.pm
@@ -17,6 +17,7 @@ use Bugzilla::Util qw(detaint_natural trick_taint template_var datetime_from);
use Bugzilla::WebService::Util qw(validate);
use Bugzilla::Extension::MyDashboard::Queries qw(QUERY_DEFS query_bugs query_flags);
+use Bugzilla::Extension::MyDashboard::BugInterest;
use constant READ_ONLY => qw(
run_bug_query
@@ -127,6 +128,32 @@ sub run_flag_query {
return { result => { $type => $results }};
}
+sub bug_interest_unmark {
+ my ($self, $params) = @_;
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+ ThrowCodeError('param_required', { function => 'MyDashboard.bug_interest_unmark', param => 'bug_ids' })
+ unless $params->{bug_ids};
+
+ my @bug_ids = ref($params->{bug_ids}) ? @{$params->{bug_ids}} : ( $params->{bug_ids} );
+
+ Bugzilla->dbh->bz_start_transaction();
+ foreach my $bug_id (@bug_ids) {
+ Bugzilla::Extension::MyDashboard::BugInterest->unmark($user->id, $bug_id);
+ }
+ Bugzilla->dbh->bz_commit_transaction();
+}
+
+sub rest_resources {
+ return [
+ qr{^/bug_interest_unmark$}, {
+ PUT => {
+ method => 'bug_interest_unmark'
+ }
+ }
+ ];
+}
+
1;
__END__
diff --git a/extensions/MyDashboard/template/en/default/pages/mydashboard.html.tmpl b/extensions/MyDashboard/template/en/default/pages/mydashboard.html.tmpl
index 16f363f49..350c8d0aa 100644
--- a/extensions/MyDashboard/template/en/default/pages/mydashboard.html.tmpl
+++ b/extensions/MyDashboard/template/en/default/pages/mydashboard.html.tmpl
@@ -107,8 +107,8 @@
<span id="query_count_refresh" class="bz_default_hidden">
<span class="items_found" id="query_bugs_found">0 [% terms.bugs %] found</span>
| <a class="refresh" href="javascript:void(0);" id="query_refresh">Refresh</a>
- | <a class="markvisited" href="javascript:void(0);" id="query_markvisited">Mark Visited</a>
- <span class="markvisited bz_default_hidden" id="query_markvisited_text">Mark Visited</span>
+ <span id="bar_markread">|</span> <a class="markread" href="javascript:void(0);" id="query_markread">none</a>
+ <span class="markread bz_default_hidden" id="query_markread_text">none</span>
| <a class="buglist" href="javascript:void(0);" id="query_buglist">Buglist</a>
</span>
<div id="query_pagination_top"></div>
diff --git a/extensions/MyDashboard/web/js/query.js b/extensions/MyDashboard/web/js/query.js
index 4a6b64157..42dfca561 100644
--- a/extensions/MyDashboard/web/js/query.js
+++ b/extensions/MyDashboard/web/js/query.js
@@ -57,7 +57,8 @@ YUI({
metaFields: {
description: "result.result.description",
heading: "result.result.heading",
- buffer: "result.result.buffer"
+ buffer: "result.result.buffer",
+ mark_read: "result.result.mark_read"
}
}
});
@@ -82,6 +83,19 @@ YUI({
'<a href="buglist.cgi?' + e.response.meta.buffer +
'" target="_blank">' + e.response.results.length + ' bugs found</a>');
bugQueryTable.set('data', e.response.results);
+
+ var mark_read = e.response.meta.mark_read;
+ if (mark_read) {
+ Y.one('#query_markread').setHTML( mark_read );
+ Y.one('#bar_markread').removeClass('bz_default_hidden');
+ Y.one('#query_markread_text').setHTML( mark_read );
+ Y.one('#query_markread').removeClass('bz_default_hidden');
+ }
+ else {
+ Y.one('#bar_markread').addClass('bz_default_hidden');
+ Y.one('#query_markread').addClass('bz_default_hidden');
+ }
+ Y.one('#query_markread_text').addClass('bz_default_hidden');
}
},
failure: function(o) {
@@ -99,8 +113,6 @@ YUI({
counter = counter + 1;
lastChangesCache = {};
- Y.one('#query_markvisited').removeClass('bz_default_hidden');
- Y.one('#query_markvisited_text').addClass('bz_default_hidden');
Y.one('#query_count_refresh').addClass('bz_default_hidden');
bugQueryTable.set('data', []);
bugQueryTable.render("#query_table");
@@ -240,17 +252,18 @@ YUI({
updateQueryTable(selected_value);
});
- Y.one('#query_markvisited').on('click', function(e) {
+ Y.one('#query_markread').on('click', function(e) {
var data = bugQueryTable.data;
var bug_ids = [];
- Y.one('#query_markvisited').addClass('bz_default_hidden');
- Y.one('#query_markvisited_text').removeClass('bz_default_hidden');
+ Y.one('#query_markread').addClass('bz_default_hidden');
+ Y.one('#query_markread_text').removeClass('bz_default_hidden');
for (var i = 0, l = data.size(); i < l; i++) {
bug_ids.push(data.item(i).get('bug_id'));
}
YAHOO.bugzilla.bugUserLastVisit.update(bug_ids);
+ YAHOO.bugzilla.bugInterest.unmark(bug_ids);
});
Y.one('#query_buglist').on('click', function(e) {
diff --git a/extensions/MyDashboard/web/styles/mydashboard.css b/extensions/MyDashboard/web/styles/mydashboard.css
index 2ce19d96b..d7deadcad 100644
--- a/extensions/MyDashboard/web/styles/mydashboard.css
+++ b/extensions/MyDashboard/web/styles/mydashboard.css
@@ -47,8 +47,7 @@
width: 40%;
}
-.items_found, .refresh, .buglist, .markvisited {
-
+.items_found, .refresh, .buglist, .markread {
font-size: 80%;
}
diff --git a/js/bug.js b/js/bug.js
index 9237f7241..97a330af1 100644
--- a/js/bug.js
+++ b/js/bug.js
@@ -168,10 +168,27 @@ YAHOO.bugzilla.dupTable = {
+ res.responseText);
},
};
+ YAHOO.util.Connect.setDefaultPostHeader('application/json', true);
+ YAHOO.util.Connect.asyncRequest('POST', 'jsonrpc.cgi', callbacks, args)
+ },
+ };
+ YAHOO.bugzilla.bugInterest = {
+ unmark: function(bug_ids) {
+ var args = JSON.stringify({
+ version: "1.1",
+ method: 'MyDashboard.bug_interest_unmark',
+ params: { bug_ids: bug_ids },
+ });
+ var callbacks = {
+ failure: function(res) {
+ if (console)
+ console.log("failed to unmark interest: "
+ + res.responseText);
+ },
+ };
YAHOO.util.Connect.setDefaultPostHeader('application/json', true);
- YAHOO.util.Connect.asyncRequest('POST', 'jsonrpc.cgi', callbacks,
- args)
+ YAHOO.util.Connect.asyncRequest('POST', 'jsonrpc.cgi', callbacks, args)
},
};
})();