]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 489028 - Record last-visited time of bugs when logged in
authorDylan William Hardison <dylan@hardison.net>
Mon, 7 Apr 2014 06:41:11 +0000 (02:41 -0400)
committerDylan William Hardison <dylan@hardison.net>
Tue, 22 Apr 2014 20:37:52 +0000 (16:37 -0400)
r=glob a=justdave

18 files changed:
Bugzilla/Bug.pm
Bugzilla/BugUserLastVisit.pm [new file with mode: 0644]
Bugzilla/Config/Admin.pm
Bugzilla/DB/Schema.pm
Bugzilla/Field.pm
Bugzilla/Install/Filesystem.pm
Bugzilla/Search.pm
Bugzilla/User.pm
Bugzilla/WebService/BugUserLastVisit.pm [new file with mode: 0644]
Bugzilla/WebService/Constants.pm
Bugzilla/WebService/Server/REST.pm
Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm [new file with mode: 0644]
clean-bug-user-last-visit.pl [new file with mode: 0644]
js/bug.js
template/en/default/admin/params/admin.html.tmpl
template/en/default/bug/show-header.html.tmpl
template/en/default/global/field-descs.none.tmpl
template/en/default/global/user-error.html.tmpl

index b53847790b2bca3618948fcc7d5121e92d16c06e..b66ad1e26ea0d2ff0c63a9c23bbf6ebf7d276a34 100644 (file)
@@ -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<Methods>
 
 =over
@@ -4415,6 +4434,11 @@ sub _multi_select_accessor {
 
 Ensures the accessors for custom fields are always created.
 
+=item C<update_user_last_visit($user, $last_visit)>
+
+Creates or updates a L<Bugzilla::BugUserLastVisit> for this bug and the supplied
+$user, the timestamp given as $last_visit.
+
 =back
 
 =head1 B<Methods in need of POD>
diff --git a/Bugzilla/BugUserLastVisit.pm b/Bugzilla/BugUserLastVisit.pm
new file mode 100644 (file)
index 0000000..e8e4834
--- /dev/null
@@ -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<Bugzilla::BugUserLastVisit> is an implementation of L<Bugzilla::Object>, and
+thus provides all the methods of L<Bugzilla::Object> in addition to the methods
+listed below.
+
+=head1 METHODS
+
+=head2 Accessor Methods
+
+=over
+
+=item C<id>
+
+=item C<bug_id>
+
+=item C<user_id>
+
+=item C<last_visit_ts>
+
+=back
index 811f7029e88a278efd5b742e476fc5bcb9a11d8a..97f8ea390de91960f51ed1387d13da0b841e5387 100644 (file)
@@ -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;
 }
index 30ed55b9adcc9eab29c956f8f929613608b11c6c..cf404f4cade805a9f25b0ed61bbaced76f985b40 100644 (file)
@@ -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
index 6289f110aa7f071acbb6ee6e2850ebd9e48c9749..98cf389a11e41517476b9dad57e01433aad21ff6 100644 (file)
@@ -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'},
 );
 
index 0f9df67a8deebf83fcc370b81b6a9c3dcdbc4870..4056d4994e967963cd99fa538314a9a4f10b733a 100644 (file)
@@ -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 },
index 43de327741723bae2e175994516a76ba164d1a7d..1c008bdfb5c78658e5d057f52ef19c1aec8b5063 100644 (file)
@@ -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;
index 5cebe728b716757a9fdfed129ec6305fe28027a5..0b644a3b42ea6641d46234ca28bece590a4dc2a2 100644 (file)
@@ -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<last_visited>
+
+Returns an arrayref L<Bugzilla::BugUserLastVisit> objects.
+
+=item C<is_involved_in_bug($bug)>
+
+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 (file)
index 0000000..71b637f
--- /dev/null
@@ -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<Bugzilla::WebService> for a description of how parameters are passed,
+and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> 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<EXPERIMENTAL>
+
+=over
+
+=item B<Description>
+
+Update the last visit time for the specified bug and current user.
+
+=item B<REST>
+
+To add a single bug id:
+
+    POST /rest/bug_user_last_visit/<bug-id>
+
+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<Params>
+
+=over
+
+=item C<ids> (array) - One or more bug ids to add.
+
+=back
+
+=item B<Returns>
+
+=over
+
+=item C<array> - An array of hashes containing the following:
+
+=over
+
+=item C<id> - (int) The bug id.
+
+=item C<last_visit_ts> - (string) The timestamp the user last visited the bug.
+
+=back
+
+=back
+
+=back
+
+=head2 get
+
+B<EXPERIMENTAL>
+
+=over
+
+=item B<Description>
+
+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<REST>
+
+To return the last visited timestamp for a single bug id:
+
+GET /rest/bug_visit/<bug-id>
+
+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<Params>
+
+=over
+
+=item C<ids> (integer) - One or more optional bug ids to get.
+
+=back
+
+=item B<Returns>
+
+=over
+
+=item C<array> - An array of hashes containing the following:
+
+=over
+
+=item C<id> - (int) The bug id.
+
+=item C<last_visit_ts> - (string) The timestamp the user last visited the bug.
+
+=back
+
+=back
+
+=back
index 4085c2cbd90bfa50f3a76f4cdd1a00660c894573..e18e2b8ecd64c653360724b1c20491805ef15237 100644 (file)
@@ -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;
index 7aac95170e419c0acbf3e380a1c97a451803e39c..ba8304a5fb9fd0d97dc5c523922636fdb5c8266c 100644 (file)
@@ -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 (file)
index 0000000..a434d4b
--- /dev/null
@@ -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<Bugzilla::WebService::BugUserLastVisit> 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 (file)
index 0000000..9884b7c
--- /dev/null
@@ -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);
index fe2e1f53b44c2101d336279c53b509cc2fa73f70..abefbb22d581c0ada3c42155c96437156bda8e61 100644 (file)
--- 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)
+        },
+    };
+})();
index eb023de7520d273f75eef58edecfa36cfbe400eb..dfe513d0557847e69e230eca5e9c64fda488b1fd 100644 (file)
@@ -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."}
 %]
index c340def1739ffc13aa8ff6bfdea5260f5487b554..6dfc19dfb8bd5ea517be12a33defed23d8bd0411 100644 (file)
@@ -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 %]
     }
     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 %]
index cbb6b641baa2b3ca0da725bfd7f9b83bac4c6bfa..f4e17c3f886c699c71f3970676e6d7775cb0c5cc 100644 (file)
@@ -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",
index 0b5d667642361aab0aaa04463a98c1f1502dd2df..dcdf707830e36266574b5a2f997ef3a86da46758 100644 (file)
     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