From 038e6854b32ae3155018188d80f306599a1e9644 Mon Sep 17 00:00:00 2001 From: Byron Jones Date: Wed, 28 May 2014 15:21:14 +0800 Subject: Bug 993939: Bugzilla::User::Setting::groups() should use memcached --- Bugzilla/Memcached.pm | 20 ++++++--- Bugzilla/User.pm | 116 ++++++++++++++++++++++++++++---------------------- 2 files changed, 80 insertions(+), 56 deletions(-) (limited to 'Bugzilla') diff --git a/Bugzilla/Memcached.pm b/Bugzilla/Memcached.pm index 7a9734375..28934e4f5 100644 --- a/Bugzilla/Memcached.pm +++ b/Bugzilla/Memcached.pm @@ -106,7 +106,7 @@ sub set_config { return unless $self->{memcached}; if (exists $args->{key}) { - return $self->_set($self->_config_prefix . ':' . $args->{key}, $args->{data}); + return $self->_set($self->_config_prefix . '.' . $args->{key}, $args->{data}); } else { ThrowCodeError('params_required', { function => "Bugzilla::Memcached::set_config", @@ -119,7 +119,7 @@ sub get_config { return unless $self->{memcached}; if (exists $args->{key}) { - return $self->_get($self->_config_prefix . ':' . $args->{key}); + return $self->_get($self->_config_prefix . '.' . $args->{key}); } else { ThrowCodeError('params_required', { function => "Bugzilla::Memcached::get_config", @@ -167,9 +167,14 @@ sub clear_all { } sub clear_config { - my ($self) = @_; + my ($self, $args) = @_; return unless $self->{memcached}; - $self->_inc_prefix("config"); + if ($args && exists $args->{key}) { + $self->_delete($self->_config_prefix . '.' . $args->{key}); + } + else { + $self->_inc_prefix("config"); + } } # in order to clear all our keys, we add a prefix to all our keys. when we @@ -221,7 +226,7 @@ sub _config_prefix { sub _encode_key { my ($self, $key) = @_; - $key = $self->_global_prefix . ':' . uri_escape_utf8($key); + $key = $self->_global_prefix . '.' . uri_escape_utf8($key); return length($self->{namespace} . $key) > MAX_KEY_LENGTH ? undef : $key; @@ -426,6 +431,11 @@ corresponding C and C entry. Removes C with the specified C
and C, as well as the corresponding C
and C entry. +=item C $key })> + +Remove C with the specified C from the configuration cache. See +C for more information. + =item C Removes all configuration related values from the cache. See C for diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm index 4c0b18250..d9c3756b0 100644 --- a/Bugzilla/User.pm +++ b/Bugzilla/User.pm @@ -677,63 +677,75 @@ sub groups { return $self->{groups} if defined $self->{groups}; return [] unless $self->id; - my $dbh = Bugzilla->dbh; - my $groups_to_check = $dbh->selectcol_arrayref( - q{SELECT DISTINCT group_id - FROM user_group_map - WHERE user_id = ? AND isbless = 0}, undef, $self->id); - - my $cache_key = 'group_grant_type_' . GROUP_MEMBERSHIP; - my $membership_rows = Bugzilla->memcached->get_config({ - key => $cache_key, + my $user_groups_key = "user_groups." . $self->id; + my $groups = Bugzilla->memcached->get_config({ + key => $user_groups_key }); - if (!$membership_rows) { - $membership_rows = $dbh->selectall_arrayref( - "SELECT DISTINCT grantor_id, member_id - FROM group_group_map - WHERE grant_type = " . GROUP_MEMBERSHIP); - Bugzilla->memcached->set_config({ - key => $cache_key, - data => $membership_rows, + + if (!$groups) { + my $dbh = Bugzilla->dbh; + my $groups_to_check = $dbh->selectcol_arrayref( + "SELECT DISTINCT group_id + FROM user_group_map + WHERE user_id = ? AND isbless = 0", undef, $self->id); + + my $grant_type_key = 'group_grant_type_' . GROUP_MEMBERSHIP; + my $membership_rows = Bugzilla->memcached->get_config({ + key => $grant_type_key, }); - } + if (!$membership_rows) { + $membership_rows = $dbh->selectall_arrayref( + "SELECT DISTINCT grantor_id, member_id + FROM group_group_map + WHERE grant_type = " . GROUP_MEMBERSHIP); + Bugzilla->memcached->set_config({ + key => $grant_type_key, + data => $membership_rows, + }); + } - my %group_membership; - foreach my $row (@$membership_rows) { - my ($grantor_id, $member_id) = @$row; - push (@{ $group_membership{$member_id} }, $grantor_id); - } - - # Let's walk the groups hierarchy tree (using FIFO) - # On the first iteration it's pre-filled with direct groups - # membership. Later on, each group can add its own members into the - # FIFO. Circular dependencies are eliminated by checking - # $checked_groups{$member_id} hash values. - # As a result, %groups will have all the groups we are the member of. - my %checked_groups; - my %groups; - while (scalar(@$groups_to_check) > 0) { - # Pop the head group from FIFO - my $member_id = shift @$groups_to_check; - - # Skip the group if we have already checked it - if (!$checked_groups{$member_id}) { - # Mark group as checked - $checked_groups{$member_id} = 1; - - # Add all its members to the FIFO check list - # %group_membership contains arrays of group members - # for all groups. Accessible by group number. - my $members = $group_membership{$member_id}; - my @new_to_check = grep(!$checked_groups{$_}, @$members); - push(@$groups_to_check, @new_to_check); - - $groups{$member_id} = 1; + my %group_membership; + foreach my $row (@$membership_rows) { + my ($grantor_id, $member_id) = @$row; + push (@{ $group_membership{$member_id} }, $grantor_id); } - } - $self->{groups} = Bugzilla::Group->new_from_list([keys %groups]); + # Let's walk the groups hierarchy tree (using FIFO) + # On the first iteration it's pre-filled with direct groups + # membership. Later on, each group can add its own members into the + # FIFO. Circular dependencies are eliminated by checking + # $checked_groups{$member_id} hash values. + # As a result, %groups will have all the groups we are the member of. + my %checked_groups; + my %groups; + while (scalar(@$groups_to_check) > 0) { + # Pop the head group from FIFO + my $member_id = shift @$groups_to_check; + + # Skip the group if we have already checked it + if (!$checked_groups{$member_id}) { + # Mark group as checked + $checked_groups{$member_id} = 1; + + # Add all its members to the FIFO check list + # %group_membership contains arrays of group members + # for all groups. Accessible by group number. + my $members = $group_membership{$member_id}; + my @new_to_check = grep(!$checked_groups{$_}, @$members); + push(@$groups_to_check, @new_to_check); + + $groups{$member_id} = 1; + } + } + $groups = [ keys %groups ]; + + Bugzilla->memcached->set_config({ + key => $user_groups_key, + data => $groups, + }); + } + $self->{groups} = Bugzilla::Group->new_from_list($groups); return $self->{groups}; } @@ -1343,6 +1355,8 @@ sub derive_regexp_groups { $group_delete->execute($id, $group, GRANT_REGEXP) if $present; } } + + Bugzilla->memcached->clear_config({ key => "user_groups.$id" }); } sub product_responsibilities { -- cgit v1.2.3-24-g4f1b From b6b83df873a1509797235738e00f9e6307eca876 Mon Sep 17 00:00:00 2001 From: Dylan William Hardison Date: Tue, 20 May 2014 02:19:51 -0400 Subject: Bug 1000917 - Backport upstream bug 489028 to bmo/4.2 to allow user last visit searching --- Bugzilla/Bug.pm | 18 ++ 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 | 189 ++++++++++++------- Bugzilla/User.pm | 53 ++++++ Bugzilla/WebService/BugUserLastVisit.pm | 208 +++++++++++++++++++++ Bugzilla/WebService/Constants.pm | 1 + Bugzilla/WebService/Server/REST.pm | 1 + .../Server/REST/Resources/BugUserLastVisit.pm | 52 ++++++ 12 files changed, 558 insertions(+), 69 deletions(-) create mode 100644 Bugzilla/BugUserLastVisit.pm create mode 100644 Bugzilla/WebService/BugUserLastVisit.pm create mode 100644 Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm (limited to 'Bugzilla') diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm index 344939333..fe259be27 100644 --- a/Bugzilla/Bug.pm +++ b/Bugzilla/Bug.pm @@ -50,6 +50,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); @@ -4143,6 +4144,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 { 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 e6141cf9e..769e3170b 100644 --- a/Bugzilla/Config/Admin.pm +++ b/Bugzilla/Config/Admin.pm @@ -56,6 +56,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 d8f3e175a..cbcb5b26c 100644 --- a/Bugzilla/DB/Schema.pm +++ b/Bugzilla/DB/Schema.pm @@ -1714,6 +1714,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 3e69d152d..0a26b9320 100644 --- a/Bugzilla/Field.pm +++ b/Bugzilla/Field.pm @@ -266,6 +266,8 @@ use constant DEFAULT_FIELDS => ( {name => 'see_also', desc => "See Also", type => FIELD_TYPE_BUG_URLS}, {name => 'tag', desc => 'Tags'}, + {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 fc05aaacd..9c2d3026f 100644 --- a/Bugzilla/Install/Filesystem.pm +++ b/Bugzilla/Install/Filesystem.pm @@ -163,6 +163,7 @@ sub FILESYSTEM { 'sentry.pl' => { perms => WS_EXECUTE }, 'metrics.pl' => { perms => WS_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 0665fbd96..288d4677b 100644 --- a/Bugzilla/Search.pm +++ b/Bugzilla/Search.pm @@ -349,6 +349,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 }, @@ -376,6 +380,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) { @@ -454,79 +462,91 @@ use constant COLUMN_DEPENDS => { # certain columns in the buglist. For the most part, Search.pm uses # DB::Schema to figure out what needs to be joined, but for some # fields it needs a little help. -use constant COLUMN_JOINS => { - actual_time => { - table => '(SELECT bug_id, SUM(work_time) AS total' - . ' FROM longdescs GROUP BY bug_id)', - join => 'INNER', - }, - assigned_to => { - from => 'assigned_to', - to => 'userid', - table => 'profiles', - join => 'INNER', - }, - reporter => { - from => 'reporter', - to => 'userid', - table => 'profiles', - join => 'INNER', - }, - qa_contact => { - from => 'qa_contact', - to => 'userid', - table => 'profiles', - }, - component => { - from => 'component_id', - to => 'id', - table => 'components', - join => 'INNER', - }, - product => { - from => 'product_id', - to => 'id', - table => 'products', - join => 'INNER', - }, - classification => { - table => 'classifications', - from => 'map_product.classification_id', - to => 'id', - join => 'INNER', - }, - 'flagtypes.name' => { - as => 'map_flags', - table => 'flags', - extra => ['map_flags.attach_id IS NULL'], - then_to => { - as => 'map_flagtypes', - table => 'flagtypes', - from => 'map_flags.type_id', +sub COLUMN_JOINS { + my $user = Bugzilla->user; + + my $joins = { + actual_time => { + table => '(SELECT bug_id, SUM(work_time) AS total' + . ' FROM longdescs GROUP BY bug_id)', + join => 'INNER', + }, + assigned_to => { + from => 'assigned_to', + to => 'userid', + table => 'profiles', + join => 'INNER', + }, + reporter => { + from => 'reporter', + to => 'userid', + table => 'profiles', + join => 'INNER', + }, + qa_contact => { + from => 'qa_contact', + to => 'userid', + table => 'profiles', + }, + component => { + from => 'component_id', to => 'id', + table => 'components', + join => 'INNER', }, - }, - keywords => { - table => 'keywords', - then_to => { - as => 'map_keyworddefs', - table => 'keyworddefs', - from => 'map_keywords.keywordid', + product => { + from => 'product_id', to => 'id', + table => 'products', + join => 'INNER', }, - }, - blocked => { - table => 'dependencies', - to => 'dependson', - }, - dependson => { - table => 'dependencies', - to => 'blocked', - }, - 'longdescs.count' => { - table => 'longdescs', - join => 'INNER', - }, + classification => { + table => 'classifications', + from => 'map_product.classification_id', + to => 'id', + join => 'INNER', + }, + 'flagtypes.name' => { + as => 'map_flags', + table => 'flags', + extra => ['map_flags.attach_id IS NULL'], + then_to => { + as => 'map_flagtypes', + table => 'flagtypes', + from => 'map_flags.type_id', + to => 'id', + }, + }, + keywords => { + table => 'keywords', + then_to => { + as => 'map_keyworddefs', + table => 'keyworddefs', + from => 'map_keywords.keywordid', + to => 'id', + }, + }, + blocked => { + table => 'dependencies', + to => 'dependson', + }, + dependson => { + table => 'dependencies', + to => 'blocked', + }, + 'longdescs.count' => { + table => 'longdescs', + join => 'INNER', + }, + 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; }; # This constant defines the columns that can be selected in a query @@ -595,6 +615,7 @@ sub COLUMNS { dependson => $dbh->sql_group_concat('DISTINCT map_dependson.dependson'), 'longdescs.count' => 'COUNT(DISTINCT map_longdescs_count.comment_id)', + last_visit_ts => 'bug_user_last_visit.last_visit_ts', ); # Backward-compatibility for old field names. Goes new_name => old_name. @@ -2202,6 +2223,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, @_); } @@ -2726,6 +2762,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 d9c3756b0..a796ee765 100644 --- a/Bugzilla/User.pm +++ b/Bugzilla/User.pm @@ -51,9 +51,11 @@ use Bugzilla::Classification; use Bugzilla::Field; use Bugzilla::Group; use Bugzilla::Hook; +use Bugzilla::BugUserLastVisit; use DateTime::TimeZone; use List::Util qw(max); +use List::MoreUtils qw(any); use Scalar::Util qw(blessed); use Storable qw(dclone); use URI; @@ -749,6 +751,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 @@ -2694,6 +2718,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 c2a6d855c..c6bbeb9bd 100644 --- a/Bugzilla/WebService/Constants.pm +++ b/Bugzilla/WebService/Constants.pm @@ -277,6 +277,7 @@ sub WS_DISPATCH { 'User' => 'Bugzilla::WebService::User', 'Product' => 'Bugzilla::WebService::Product', 'Group' => 'Bugzilla::WebService::Group', + 'BugUserLastVisit' => 'Bugzilla::WebService::BugUserLastVisit', %hook_dispatch }; return $dispatch; diff --git a/Bugzilla/WebService/Server/REST.pm b/Bugzilla/WebService/Server/REST.pm index 5457b41db..96e4b3179 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. -- cgit v1.2.3-24-g4f1b From 7ea36c899372e8e113935ff036e992a819d786b6 Mon Sep 17 00:00:00 2001 From: Dylan William Hardison Date: Wed, 28 May 2014 11:04:37 -0400 Subject: Revert previous commit to re-apply with the schema change in a separate commit. This reverts commit b6b83df873a1509797235738e00f9e6307eca876. --- Bugzilla/Bug.pm | 18 -- 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 | 189 +++++++------------ Bugzilla/User.pm | 53 ------ Bugzilla/WebService/BugUserLastVisit.pm | 208 --------------------- Bugzilla/WebService/Constants.pm | 1 - Bugzilla/WebService/Server/REST.pm | 1 - .../Server/REST/Resources/BugUserLastVisit.pm | 52 ------ 12 files changed, 69 insertions(+), 558 deletions(-) delete mode 100644 Bugzilla/BugUserLastVisit.pm delete mode 100644 Bugzilla/WebService/BugUserLastVisit.pm delete mode 100644 Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm (limited to 'Bugzilla') diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm index fe259be27..344939333 100644 --- a/Bugzilla/Bug.pm +++ b/Bugzilla/Bug.pm @@ -50,7 +50,6 @@ 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); @@ -4144,23 +4143,6 @@ 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 { diff --git a/Bugzilla/BugUserLastVisit.pm b/Bugzilla/BugUserLastVisit.pm deleted file mode 100644 index e8e483405..000000000 --- a/Bugzilla/BugUserLastVisit.pm +++ /dev/null @@ -1,76 +0,0 @@ -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 769e3170b..e6141cf9e 100644 --- a/Bugzilla/Config/Admin.pm +++ b/Bugzilla/Config/Admin.pm @@ -56,13 +56,6 @@ 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 cbcb5b26c..d8f3e175a 100644 --- a/Bugzilla/DB/Schema.pm +++ b/Bugzilla/DB/Schema.pm @@ -1714,25 +1714,6 @@ 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 0a26b9320..3e69d152d 100644 --- a/Bugzilla/Field.pm +++ b/Bugzilla/Field.pm @@ -266,8 +266,6 @@ use constant DEFAULT_FIELDS => ( {name => 'see_also', desc => "See Also", type => FIELD_TYPE_BUG_URLS}, {name => 'tag', desc => 'Tags'}, - {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 9c2d3026f..fc05aaacd 100644 --- a/Bugzilla/Install/Filesystem.pm +++ b/Bugzilla/Install/Filesystem.pm @@ -163,7 +163,6 @@ sub FILESYSTEM { 'sentry.pl' => { perms => WS_EXECUTE }, 'metrics.pl' => { perms => WS_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 288d4677b..0665fbd96 100644 --- a/Bugzilla/Search.pm +++ b/Bugzilla/Search.pm @@ -349,10 +349,6 @@ 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 }, @@ -380,10 +376,6 @@ 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) { @@ -462,91 +454,79 @@ use constant COLUMN_DEPENDS => { # certain columns in the buglist. For the most part, Search.pm uses # DB::Schema to figure out what needs to be joined, but for some # fields it needs a little help. -sub COLUMN_JOINS { - my $user = Bugzilla->user; - - my $joins = { - actual_time => { - table => '(SELECT bug_id, SUM(work_time) AS total' - . ' FROM longdescs GROUP BY bug_id)', - join => 'INNER', - }, - assigned_to => { - from => 'assigned_to', - to => 'userid', - table => 'profiles', - join => 'INNER', - }, - reporter => { - from => 'reporter', - to => 'userid', - table => 'profiles', - join => 'INNER', - }, - qa_contact => { - from => 'qa_contact', - to => 'userid', - table => 'profiles', - }, - component => { - from => 'component_id', +use constant COLUMN_JOINS => { + actual_time => { + table => '(SELECT bug_id, SUM(work_time) AS total' + . ' FROM longdescs GROUP BY bug_id)', + join => 'INNER', + }, + assigned_to => { + from => 'assigned_to', + to => 'userid', + table => 'profiles', + join => 'INNER', + }, + reporter => { + from => 'reporter', + to => 'userid', + table => 'profiles', + join => 'INNER', + }, + qa_contact => { + from => 'qa_contact', + to => 'userid', + table => 'profiles', + }, + component => { + from => 'component_id', + to => 'id', + table => 'components', + join => 'INNER', + }, + product => { + from => 'product_id', + to => 'id', + table => 'products', + join => 'INNER', + }, + classification => { + table => 'classifications', + from => 'map_product.classification_id', + to => 'id', + join => 'INNER', + }, + 'flagtypes.name' => { + as => 'map_flags', + table => 'flags', + extra => ['map_flags.attach_id IS NULL'], + then_to => { + as => 'map_flagtypes', + table => 'flagtypes', + from => 'map_flags.type_id', to => 'id', - table => 'components', - join => 'INNER', }, - product => { - from => 'product_id', + }, + keywords => { + table => 'keywords', + then_to => { + as => 'map_keyworddefs', + table => 'keyworddefs', + from => 'map_keywords.keywordid', to => 'id', - table => 'products', - join => 'INNER', }, - classification => { - table => 'classifications', - from => 'map_product.classification_id', - to => 'id', - join => 'INNER', - }, - 'flagtypes.name' => { - as => 'map_flags', - table => 'flags', - extra => ['map_flags.attach_id IS NULL'], - then_to => { - as => 'map_flagtypes', - table => 'flagtypes', - from => 'map_flags.type_id', - to => 'id', - }, - }, - keywords => { - table => 'keywords', - then_to => { - as => 'map_keyworddefs', - table => 'keyworddefs', - from => 'map_keywords.keywordid', - to => 'id', - }, - }, - blocked => { - table => 'dependencies', - to => 'dependson', - }, - dependson => { - table => 'dependencies', - to => 'blocked', - }, - 'longdescs.count' => { - table => 'longdescs', - join => 'INNER', - }, - 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; + }, + blocked => { + table => 'dependencies', + to => 'dependson', + }, + dependson => { + table => 'dependencies', + to => 'blocked', + }, + 'longdescs.count' => { + table => 'longdescs', + join => 'INNER', + }, }; # This constant defines the columns that can be selected in a query @@ -615,7 +595,6 @@ sub COLUMNS { dependson => $dbh->sql_group_concat('DISTINCT map_dependson.dependson'), 'longdescs.count' => 'COUNT(DISTINCT map_longdescs_count.comment_id)', - last_visit_ts => 'bug_user_last_visit.last_visit_ts', ); # Backward-compatibility for old field names. Goes new_name => old_name. @@ -2223,21 +2202,6 @@ 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, @_); } @@ -2762,21 +2726,6 @@ 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 a796ee765..d9c3756b0 100644 --- a/Bugzilla/User.pm +++ b/Bugzilla/User.pm @@ -51,11 +51,9 @@ use Bugzilla::Classification; use Bugzilla::Field; use Bugzilla::Group; use Bugzilla::Hook; -use Bugzilla::BugUserLastVisit; use DateTime::TimeZone; use List::Util qw(max); -use List::MoreUtils qw(any); use Scalar::Util qw(blessed); use Storable qw(dclone); use URI; @@ -751,28 +749,6 @@ 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 @@ -2718,35 +2694,6 @@ 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 deleted file mode 100644 index 71b637fef..000000000 --- a/Bugzilla/WebService/BugUserLastVisit.pm +++ /dev/null @@ -1,208 +0,0 @@ -# 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 c6bbeb9bd..c2a6d855c 100644 --- a/Bugzilla/WebService/Constants.pm +++ b/Bugzilla/WebService/Constants.pm @@ -277,7 +277,6 @@ sub WS_DISPATCH { 'User' => 'Bugzilla::WebService::User', 'Product' => 'Bugzilla::WebService::Product', 'Group' => 'Bugzilla::WebService::Group', - 'BugUserLastVisit' => 'Bugzilla::WebService::BugUserLastVisit', %hook_dispatch }; return $dispatch; diff --git a/Bugzilla/WebService/Server/REST.pm b/Bugzilla/WebService/Server/REST.pm index 96e4b3179..5457b41db 100644 --- a/Bugzilla/WebService/Server/REST.pm +++ b/Bugzilla/WebService/Server/REST.pm @@ -27,7 +27,6 @@ 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 deleted file mode 100644 index a434d4bef..000000000 --- a/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm +++ /dev/null @@ -1,52 +0,0 @@ -# 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. -- cgit v1.2.3-24-g4f1b From 24a16ae497460834719d6214a7e73ecc7982f9fa Mon Sep 17 00:00:00 2001 From: Dylan William Hardison Date: Wed, 28 May 2014 11:30:47 -0400 Subject: Bug 1000917 - allow user last visit searching (schema only) --- Bugzilla/DB/Schema.pm | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'Bugzilla') diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm index d8f3e175a..cbcb5b26c 100644 --- a/Bugzilla/DB/Schema.pm +++ b/Bugzilla/DB/Schema.pm @@ -1714,6 +1714,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 -- cgit v1.2.3-24-g4f1b