From eab44b1aad3f243dd69b1d30519b73a1e537fda2 Mon Sep 17 00:00:00 2001 From: Dylan William Hardison Date: Mon, 7 Apr 2014 02:41:11 -0400 Subject: Bug 489028 - Record last-visited time of bugs when logged in r=glob a=justdave --- Bugzilla/Bug.pm | 24 +++ Bugzilla/BugUserLastVisit.pm | 76 ++++++++ Bugzilla/Config/Admin.pm | 7 + Bugzilla/DB/Schema.pm | 19 ++ Bugzilla/Field.pm | 2 + Bugzilla/Install/Filesystem.pm | 1 + Bugzilla/Search.pm | 48 ++++- Bugzilla/User.pm | 53 ++++++ Bugzilla/WebService/BugUserLastVisit.pm | 208 +++++++++++++++++++++ Bugzilla/WebService/Constants.pm | 13 +- Bugzilla/WebService/Server/REST.pm | 1 + .../Server/REST/Resources/BugUserLastVisit.pm | 52 ++++++ clean-bug-user-last-visit.pl | 38 ++++ js/bug.js | 46 +++++ template/en/default/admin/params/admin.html.tmpl | 5 +- template/en/default/bug/show-header.html.tmpl | 5 + template/en/default/global/field-descs.none.tmpl | 1 + template/en/default/global/user-error.html.tmpl | 5 + 18 files changed, 596 insertions(+), 8 deletions(-) create mode 100644 Bugzilla/BugUserLastVisit.pm create mode 100644 Bugzilla/WebService/BugUserLastVisit.pm create mode 100644 Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm create mode 100644 clean-bug-user-last-visit.pl diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm index b53847790..b66ad1e26 100644 --- a/Bugzilla/Bug.pm +++ b/Bugzilla/Bug.pm @@ -28,6 +28,7 @@ use Bugzilla::Group; use Bugzilla::Status; use Bugzilla::Comment; use Bugzilla::BugUrl; +use Bugzilla::BugUserLastVisit; use List::MoreUtils qw(firstidx uniq part); use List::Util qw(min max first); @@ -4081,6 +4082,23 @@ sub LogActivityEntry { } } +# Update bug_user_last_visit table +sub update_user_last_visit { + my ($self, $user, $last_visit_ts) = @_; + my $lv = Bugzilla::BugUserLastVisit->match({ bug_id => $self->id, + user_id => $user->id })->[0]; + + if ($lv) { + $lv->set(last_visit_ts => $last_visit_ts); + $lv->update; + } + else { + Bugzilla::BugUserLastVisit->create({ bug_id => $self->id, + user_id => $user->id, + last_visit_ts => $last_visit_ts }); + } +} + # Convert WebService API and email_in.pl field names to internal DB field # names. sub map_fields { @@ -4407,6 +4425,7 @@ sub _multi_select_accessor { 1; +__END__ =head1 B =over @@ -4415,6 +4434,11 @@ sub _multi_select_accessor { Ensures the accessors for custom fields are always created. +=item C + +Creates or updates a L for this bug and the supplied +$user, the timestamp given as $last_visit. + =back =head1 B diff --git a/Bugzilla/BugUserLastVisit.pm b/Bugzilla/BugUserLastVisit.pm new file mode 100644 index 000000000..e8e483405 --- /dev/null +++ b/Bugzilla/BugUserLastVisit.pm @@ -0,0 +1,76 @@ +package Bugzilla::BugUserLastVisit; + +use 5.10.1; +use strict; + +use parent qw(Bugzilla::Object); + +##################################################################### +# Overriden Constants that are used as methods +##################################################################### + +use constant DB_TABLE => 'bug_user_last_visit'; +use constant DB_COLUMNS => qw( id user_id bug_id last_visit_ts ); +use constant UPDATE_COLUMNS => qw( last_visit_ts ); +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 last_visit_ts { return $_[0]->{last_visit_ts} } + +1; +__END__ + +=head1 NAME + +Bugzilla::BugUserLastVisit - Model for BugUserLastVisit bug search data + +=head1 SYNOPSIS + + use Bugzilla::BugUserLastVisit; + + my $lv = Bugzilla::BugUserLastVisit->new($id); + + # Class Functions + $user = Bugzilla::BugUserLastVisit->create({ + bug_id => $bug_id, + user_id => $user_id, + last_visit_ts => $last_visit_ts + }); + +=head1 DESCRIPTION + +This package handles Bugzilla BugUserLastVisit. + +C is an implementation of L, and +thus provides all the methods of L in addition to the methods +listed below. + +=head1 METHODS + +=head2 Accessor Methods + +=over + +=item C + +=item C + +=item C + +=item C + +=back diff --git a/Bugzilla/Config/Admin.pm b/Bugzilla/Config/Admin.pm index 811f7029e..97f8ea390 100644 --- a/Bugzilla/Config/Admin.pm +++ b/Bugzilla/Config/Admin.pm @@ -33,6 +33,13 @@ sub get_param_list { name => 'allowuserdeletion', type => 'b', default => 0 + }, + + { + name => 'last_visit_keep_days', + type => 't', + default => 10, + checker => \&check_numeric }); return @param_list; } diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm index 30ed55b9a..cf404f4ca 100644 --- a/Bugzilla/DB/Schema.pm +++ b/Bugzilla/DB/Schema.pm @@ -1713,6 +1713,25 @@ use constant ABSTRACT_SCHEMA => { ], }, + bug_user_last_visit => { + FIELDS => [ + id => {TYPE => 'INTSERIAL', NOTNULL => 1, + PRIMARYKEY => 1}, + user_id => {TYPE => 'INT3', NOTNULL => 1, + REFERENCES => {TABLE => 'profiles', + COLUMN => 'userid', + DELETE => 'CASCADE'}}, + bug_id => {TYPE => 'INT3', NOTNULL => 1, + REFERENCES => {TABLE => 'bugs', + COLUMN => 'bug_id', + DELETE => 'CASCADE'}}, + last_visit_ts => {TYPE => 'DATETIME', NOTNULL => 1}, + ], + INDEXES => [ + bug_user_last_visit_idx => {FIELDS => ['user_id', 'bug_id'], + TYPE => 'UNIQUE'} + ], + }, }; # Foreign Keys are added in Bugzilla::DB::bz_add_field_tables diff --git a/Bugzilla/Field.pm b/Bugzilla/Field.pm index 6289f110a..98cf389a1 100644 --- a/Bugzilla/Field.pm +++ b/Bugzilla/Field.pm @@ -258,6 +258,8 @@ use constant DEFAULT_FIELDS => ( type => FIELD_TYPE_BUG_URLS}, {name => 'tag', desc => 'Personal Tags', buglist => 1, type => FIELD_TYPE_KEYWORDS}, + {name => 'last_visit_ts', desc => 'Last Visit', buglist => 1, + type => FIELD_TYPE_DATETIME}, {name => 'comment_tag', desc => 'Comment Tag'}, ); diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm index 0f9df67a8..4056d4994 100644 --- a/Bugzilla/Install/Filesystem.pm +++ b/Bugzilla/Install/Filesystem.pm @@ -152,6 +152,7 @@ sub FILESYSTEM { 'jobqueue.pl' => { perms => OWNER_EXECUTE }, 'migrate.pl' => { perms => OWNER_EXECUTE }, 'install-module.pl' => { perms => OWNER_EXECUTE }, + 'clean-bug-user-last-visit.pl' => { perms => WS_EXECUTE }, 'Bugzilla.pm' => { perms => CGI_READ }, "$localconfig*" => { perms => CGI_READ }, diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm index 43de32774..1c008bdfb 100644 --- a/Bugzilla/Search.pm +++ b/Bugzilla/Search.pm @@ -320,6 +320,10 @@ use constant OPERATOR_FIELD_OVERRIDE => { changedafter => \&_work_time_changedbefore_after, _default => \&_work_time, }, + last_visit_ts => { + _non_changed => \&_last_visit_ts, + _default => \&_last_visit_ts_invalid_operator, + }, # Custom Fields FIELD_TYPE_FREETEXT, { _non_changed => \&_nullable }, @@ -349,6 +353,10 @@ sub SPECIAL_PARSING { creation_ts => \&_datetime_translate, deadline => \&_date_translate, delta_ts => \&_datetime_translate, + + # last_visit field that accept both a 1d, 1w, 1m, 1y format and the + # %last_changed% pronoun. + last_visit_ts => \&_last_visit_datetime, }; foreach my $field (Bugzilla->active_custom_fields) { if ($field->type == FIELD_TYPE_DATETIME) { @@ -514,7 +522,14 @@ sub COLUMN_JOINS { from => 'map_bug_tag.tag_id', to => 'id', }, - } + }, + last_visit_ts => { + as => 'bug_user_last_visit', + table => 'bug_user_last_visit', + extra => ['bug_user_last_visit.user_id = ' . $user->id], + from => 'bug_id', + to => 'bug_id', + }, }; return $joins; }; @@ -587,6 +602,7 @@ sub COLUMNS { 'longdescs.count' => 'COUNT(DISTINCT map_longdescs_count.comment_id)', tag => $dbh->sql_group_concat('DISTINCT map_tag.name'), + last_visit_ts => 'bug_user_last_visit.last_visit_ts', ); # Backward-compatibility for old field names. Goes new_name => old_name. @@ -2141,6 +2157,21 @@ sub _datetime_translate { return shift->_timestamp_translate(0, @_); } +sub _last_visit_datetime { + my ($self, $args) = @_; + my $value = $args->{value}; + + $self->_datetime_translate($args); + if ($value eq $args->{value}) { + # Failed to translate a datetime. let's try the pronoun expando. + if ($value eq '%last_changed%') { + $self->_add_extra_column('changeddate'); + $args->{value} = $args->{quoted} = 'bugs.delta_ts'; + } + } +} + + sub _date_translate { return shift->_timestamp_translate(1, @_); } @@ -2622,6 +2653,21 @@ sub _percentage_complete { $self->_add_extra_column('actual_time'); } +sub _last_visit_ts { + my ($self, $args) = @_; + + $args->{full_field} = $self->COLUMNS->{last_visit_ts}->{name}; + $self->_add_extra_column('last_visit_ts'); +} + +sub _last_visit_ts_invalid_operator { + my ($self, $args) = @_; + + ThrowUserError('search_field_operator_invalid', + { field => $args->{field}, + operator => $args->{operator} }); +} + sub _days_elapsed { my ($self, $args) = @_; my $dbh = Bugzilla->dbh; diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm index 5cebe728b..0b644a3b4 100644 --- a/Bugzilla/User.pm +++ b/Bugzilla/User.pm @@ -19,9 +19,11 @@ use Bugzilla::Product; use Bugzilla::Classification; use Bugzilla::Field; use Bugzilla::Group; +use Bugzilla::BugUserLastVisit; use DateTime::TimeZone; use List::Util qw(max); +use List::MoreUtils qw(any); use Scalar::Util qw(blessed); use URI; use URI::QueryParam; @@ -729,6 +731,28 @@ sub groups { return $self->{groups}; } +sub last_visited { + my ($self) = @_; + + return Bugzilla::BugUserLastVisit->match({ user_id => $self->id }); +} + +sub is_involved_in_bug { + my ($self, $bug) = @_; + my $user_id = $self->id; + my $user_login = $self->login; + + return unless $user_id; + return 1 if $user_id == $bug->assigned_to->id; + return 1 if $user_id == $bug->reporter->id; + + if (Bugzilla->params->{'useqacontact'} and $bug->qa_contact) { + return 1 if $user_id == $bug->qa_contact->id; + } + + return any { $user_login eq $_ } @{ $bug->cc }; +} + # It turns out that calling ->id on objects a few hundred thousand # times is pretty slow. (It showed up as a significant time contributor # when profiling xt/search.t.) So we cache the group ids separately from @@ -2767,6 +2791,35 @@ Returns true if the user can attach tags to comments. i.e. if the 'comment_taggers_group' parameter is set and the user belongs to this group. +=item C + +Returns an arrayref L objects. + +=item C + +Returns true if any of the following conditions are met, false otherwise. + +=over + +=item * + +User is the assignee of the bug + +=item * + +User is the reporter of the bug + +=item * + +User is the QA contact of the bug (if Bugzilla is configured to use a QA +contact) + +=item * + +User is in the cc list for the bug. + +=back + =back =head1 CLASS FUNCTIONS diff --git a/Bugzilla/WebService/BugUserLastVisit.pm b/Bugzilla/WebService/BugUserLastVisit.pm new file mode 100644 index 000000000..71b637fef --- /dev/null +++ b/Bugzilla/WebService/BugUserLastVisit.pm @@ -0,0 +1,208 @@ +# 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::WebService::BugUserLastVisit; + +use 5.10.1; +use strict; + +use parent qw(Bugzilla::WebService); + +use Bugzilla::Bug; +use Bugzilla::Error; +use Bugzilla::WebService::Util qw( validate filter ); +use Bugzilla::Constants; + +sub update { + my ($self, $params) = validate(@_, 'ids'); + my $user = Bugzilla->user; + my $dbh = Bugzilla->dbh; + + $user->login(LOGIN_REQUIRED); + + my $ids = $params->{ids} // []; + ThrowCodeError('param_required', { param => 'ids' }) unless @$ids; + + # Cache permissions for bugs. This highly reduces the number of calls to the + # DB. visible_bugs() is only able to handle bug IDs, so we have to skip + # aliases. + $user->visible_bugs([grep /^[0-9]$/, @$ids]); + + $dbh->bz_start_transaction(); + my @results; + my $last_visit_ts = $dbh->selectrow_array('SELECT NOW()'); + foreach my $bug_id (@$ids) { + my $bug = Bugzilla::Bug->check({ id => $bug_id, cache => 1 }); + + ThrowUserError('user_not_involved', { bug_id => $bug->id }) + unless $user->is_involved_in_bug($bug); + + $bug->update_user_last_visit($user, $last_visit_ts); + + push( + @results, + $self->_bug_user_last_visit_to_hash( + $bug, $last_visit_ts, $params + )); + } + $dbh->bz_commit_transaction(); + + return \@results; +} + +sub get { + my ($self, $params) = validate(@_, 'ids'); + my $user = Bugzilla->user; + my $ids = $params->{ids}; + + $user->login(LOGIN_REQUIRED); + + if ($ids) { + # Cache permissions for bugs. This highly reduces the number of calls to + # the DB. visible_bugs() is only able to handle bug IDs, so we have to + # skip aliases. + $user->visible_bugs([grep /^[0-9]$/, @$ids]); + } + + my @last_visits = @{ $user->last_visits }; + + if ($ids) { + # remove bugs that we arn't interested in if ids is passed in. + my %id_set = map { ($_ => 1) } @$ids; + @last_visits = grep { $id_set{ $_->bug_id } } @last_visits; + } + + return [ + map { + $self->_bug_user_last_visit_to_hash($_->bug_id, $_->last_visit_ts, + $params) + } @last_visits + ]; +} + +sub _bug_user_last_visit_to_hash { + my ($self, $bug_id, $last_visit_ts, $params) = @_; + + my %result = (id => $self->type('int', $bug_id), + last_visit_ts => $self->type('dateTime', $last_visit_ts)); + + return filter($params, \%result); +} + +1; + +__END__ +=head1 NAME + +Bugzilla::WebService::BugUserLastVisit - Find and Store the last time a user +visited a bug. + +=head1 METHODS + +See L for a description of how parameters are passed, +and what B, B, and B mean. + +Although the data input and output is the same for JSONRPC, XMLRPC and REST, +the directions for how to access the data via REST is noted in each method +where applicable. + +=head2 update + +B + +=over + +=item B + +Update the last visit time for the specified bug and current user. + +=item B + +To add a single bug id: + + POST /rest/bug_user_last_visit/ + +Tp add one or more bug ids at once: + + POST /rest/bug_user_last_visit + +The returned data format is the same as below. + +=item B + +=over + +=item C (array) - One or more bug ids to add. + +=back + +=item B + +=over + +=item C - An array of hashes containing the following: + +=over + +=item C - (int) The bug id. + +=item C - (string) The timestamp the user last visited the bug. + +=back + +=back + +=back + +=head2 get + +B + +=over + +=item B + +Get the last visited timestamp for one or more specified bug ids or get a +list of the last 20 visited bugs and their timestamps. + +=item B + +To return the last visited timestamp for a single bug id: + +GET /rest/bug_visit/ + +To return more than one bug timestamp or the last 20: + +GET /rest/bug_visit + +The returned data format is the same as below. + +=item B + +=over + +=item C (integer) - One or more optional bug ids to get. + +=back + +=item B + +=over + +=item C - An array of hashes containing the following: + +=over + +=item C - (int) The bug id. + +=item C - (string) The timestamp the user last visited the bug. + +=back + +=back + +=back diff --git a/Bugzilla/WebService/Constants.pm b/Bugzilla/WebService/Constants.pm index 4085c2cbd..e18e2b8ec 100644 --- a/Bugzilla/WebService/Constants.pm +++ b/Bugzilla/WebService/Constants.pm @@ -266,12 +266,13 @@ sub WS_DISPATCH { Bugzilla::Hook::process('webservice', { dispatch => \%hook_dispatch }); my $dispatch = { - 'Bugzilla' => 'Bugzilla::WebService::Bugzilla', - 'Bug' => 'Bugzilla::WebService::Bug', - 'Classification' => 'Bugzilla::WebService::Classification', - 'Group' => 'Bugzilla::WebService::Group', - 'Product' => 'Bugzilla::WebService::Product', - 'User' => 'Bugzilla::WebService::User', + 'Bugzilla' => 'Bugzilla::WebService::Bugzilla', + 'Bug' => 'Bugzilla::WebService::Bug', + 'Classification' => 'Bugzilla::WebService::Classification', + 'Group' => 'Bugzilla::WebService::Group', + 'Product' => 'Bugzilla::WebService::Product', + 'User' => 'Bugzilla::WebService::User', + 'BugUserLastVisit' => 'Bugzilla::WebService::BugUserLastVisit', %hook_dispatch }; return $dispatch; diff --git a/Bugzilla/WebService/Server/REST.pm b/Bugzilla/WebService/Server/REST.pm index 7aac95170..ba8304a5f 100644 --- a/Bugzilla/WebService/Server/REST.pm +++ b/Bugzilla/WebService/Server/REST.pm @@ -27,6 +27,7 @@ use Bugzilla::WebService::Server::REST::Resources::Classification; use Bugzilla::WebService::Server::REST::Resources::Group; use Bugzilla::WebService::Server::REST::Resources::Product; use Bugzilla::WebService::Server::REST::Resources::User; +use Bugzilla::WebService::Server::REST::Resources::BugUserLastVisit;; use Scalar::Util qw(blessed reftype); use MIME::Base64 qw(decode_base64); diff --git a/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm b/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm new file mode 100644 index 000000000..a434d4bef --- /dev/null +++ b/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm @@ -0,0 +1,52 @@ +# 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::WebService::Server::REST::Resources::BugUserLastVisit; + +use 5.10.1; +use strict; +use warnings; + +BEGIN { + *Bugzilla::WebService::BugUserLastVisit::rest_resources = \&_rest_resources; +} + +sub _rest_resources { + return [ + # bug-id + qr{^/bug_user_last_visit/(\d+)$}, { + GET => { + method => 'get', + params => sub { + return { ids => $_[0] }; + }, + }, + POST => { + method => 'update', + params => sub { + return { ids => $_[0] }; + }, + }, + }, + ]; +} + +1; +__END__ + +=head1 NAME + +Bugzilla::Webservice::Server::REST::Resources::BugUserLastVisit - The +BugUserLastVisit REST API + +=head1 DESCRIPTION + +This part of the Bugzilla REST API allows you to lookup and update the last time +a user visited a bug. + +See L for more details on how to use +this part of the REST API. diff --git a/clean-bug-user-last-visit.pl b/clean-bug-user-last-visit.pl new file mode 100644 index 000000000..9884b7c48 --- /dev/null +++ b/clean-bug-user-last-visit.pl @@ -0,0 +1,38 @@ +#!/usr/bin/perl -wT +# 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. + +=head1 NAME + +clean-bug-user-last-visit.pl + +=head1 DESCRIPTION + +This utility script cleans out entries from the bug_user_last_visit table that +are older than (a configurable) number of days. + +It takes no arguments and produces no output except in the case of errors. + +=cut + +use 5.10.1; +use strict; +use warnings; +use lib qw(. lib); + +use Bugzilla; +use Bugzilla::Constants; + +Bugzilla->usage_mode(USAGE_MODE_CMDLINE); + +my $dbh = Bugzilla->dbh; +my $sql = 'DELETE FROM bug_user_last_visit WHERE last_visit_ts < ' + . $dbh->sql_date_math('NOW()', + '-', + Bugzilla->params->{last_visit_keep_days}, + 'DAY'); +$dbh->do($sql); diff --git a/js/bug.js b/js/bug.js index fe2e1f53b..abefbb22d 100644 --- a/js/bug.js +++ b/js/bug.js @@ -189,3 +189,49 @@ function set_assign_to(use_qa_contact) { } } } + +(function(){ + 'use strict'; + var JSON = YAHOO.lang.JSON; + + YAHOO.bugzilla.bugUserLastVisit = { + update: function(bug_id) { + var args = JSON.stringify({ + version: "1.1", + method: 'BugUserLastVisit.update', + params: { ids: bug_id }, + }); + var callbacks = { + failure: function(res) { + if (console) + console.log("failed to update last visited: " + + res.responseText); + }, + }; + + YAHOO.util.Connect.setDefaultPostHeader('application/json', true); + YAHOO.util.Connect.asyncRequest('POST', 'jsonrpc.cgi', callbacks, + args) + }, + + get: function(done) { + var args = JSON.stringify({ + version: "1.1", + method: 'BugUserLastVisit.get', + params: { }, + }); + var callbacks = { + success: function(res) { done(JSON.parse(res.responseText)) }, + failure: function(res) { + if (console) + console.log("failed to get last visited: " + + res.responseText); + }, + }; + + YAHOO.util.Connect.setDefaultPostHeader('application/json', true); + YAHOO.util.Connect.asyncRequest('POST', 'jsonrpc.cgi', callbacks, + args) + }, + }; +})(); diff --git a/template/en/default/admin/params/admin.html.tmpl b/template/en/default/admin/params/admin.html.tmpl index eb023de75..dfe513d05 100644 --- a/template/en/default/admin/params/admin.html.tmpl +++ b/template/en/default/admin/params/admin.html.tmpl @@ -24,5 +24,8 @@ "Bugzilla will issue a warning in case you'd run into inconsistencies " _ "when you're about to do so, but such deletions remain kinda scary. " _ "So, you have to turn on this option before any such deletions " _ - "will ever happen." } + "will ever happen." + + last_visit_keep_days => "This option controls how many days Bugzilla will " _ + "remember when users visit specific bugs."} %] diff --git a/template/en/default/bug/show-header.html.tmpl b/template/en/default/bug/show-header.html.tmpl index c340def17..6dfc19dfb 100644 --- a/template/en/default/bug/show-header.html.tmpl +++ b/template/en/default/bug/show-header.html.tmpl @@ -26,6 +26,7 @@ [% yui = ['autocomplete', 'calendar'] %] [% yui.push('container') IF user.can_tag_comments %] [% javascript_urls = [ "js/util.js", "js/field.js" ] %] +[% javascript_urls.push("js/bug.js") IF user.id %] [% javascript_urls.push('js/comment-tagging.js') IF user.id && Param('comment_taggers_group') %] [% IF bug.defined %] @@ -52,6 +53,10 @@ } YAHOO.util.Event.onDOMReady(function() { initDirtyFieldTracking(); + + [% IF user.id AND user.is_involved_in_bug(bug) %] + YAHOO.bugzilla.bugUserLastVisit.update([% bug.bug_id FILTER none %]); + [% END %] }); [% javascript FILTER none %] [% END %] diff --git a/template/en/default/global/field-descs.none.tmpl b/template/en/default/global/field-descs.none.tmpl index cbb6b641b..f4e17c3f8 100644 --- a/template/en/default/global/field-descs.none.tmpl +++ b/template/en/default/global/field-descs.none.tmpl @@ -96,6 +96,7 @@ "everconfirmed" => "Ever confirmed", "flagtypes.name" => "Flags", "keywords" => "Keywords", + "last_visit_ts" => "Last Visit", "longdesc" => "Comment", "longdescs.count" => "Number of Comments", "longdescs.isprivate" => "Comment is private", diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl index 0b5d66764..dcdf70783 100644 --- a/template/en/default/global/user-error.html.tmpl +++ b/template/en/default/global/user-error.html.tmpl @@ -1871,6 +1871,11 @@ Sorry, but you are not allowed to (un)mark comments or attachments as private. + [% ELSIF error == "user_not_involved" %] + [% title = "User Not Involved with $terms.Bug" %] + Sorry, but you are not involved with [% terms.Bug %] [%+ + bug_id FILTER bug_link(bug_id) FILTER none %]. + [% ELSIF error == "webdot_too_large" %] [% title = "Dependency Graph Too Large" %] The dependency graph contains too many [% terms.bugs %] to display (more -- cgit v1.2.3-24-g4f1b