From 3e6d936f502e89cd0ff6872282a654500ec6db49 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Wed, 23 Oct 2024 17:26:42 -0400 Subject: [PATCH 1/2] Bug 1926081 - Add modification_time datetime column to profiles table denoting when the profile was last updated --- Bugzilla/DB/Schema.pm | 10 +++-- Bugzilla/Install/DB.pm | 38 ++++++++++++++++++- Bugzilla/User.pm | 15 +++++++- .../en/default/admin/users/edit.html.tmpl | 7 +++- 4 files changed, 63 insertions(+), 7 deletions(-) diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm index 31715f07df..47119cf315 100644 --- a/Bugzilla/DB/Schema.pm +++ b/Bugzilla/DB/Schema.pm @@ -1002,12 +1002,14 @@ use constant ABSTRACT_SCHEMA => { mfa_required_date => {TYPE => 'DATETIME'}, forget_after_date => {TYPE => 'DATETIME'}, bounce_count => {TYPE => 'INT1', NOTNULL => 1, DEFAULT => 0}, + modification_ts => {TYPE => 'DATETIME', NOTNULL => 1}, ], INDEXES => [ - profiles_login_name_idx => {FIELDS => ['login_name'], TYPE => 'UNIQUE'}, - profiles_extern_id_idx => {FIELDS => ['extern_id'], TYPE => 'UNIQUE'}, - profiles_nickname_idx => ['nickname'], - profiles_realname_ft_idx => {FIELDS => ['realname'], TYPE => 'FULLTEXT'}, + profiles_login_name_idx => {FIELDS => ['login_name'], TYPE => 'UNIQUE'}, + profiles_extern_id_idx => {FIELDS => ['extern_id'], TYPE => 'UNIQUE'}, + profiles_modification_ts_idx => ['modification_ts'], + profiles_nickname_idx => ['nickname'], + profiles_realname_ft_idx => {FIELDS => ['realname'], TYPE => 'FULLTEXT'}, ], }, diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm index 3d804d0eb3..29c941adce 100644 --- a/Bugzilla/Install/DB.pm +++ b/Bugzilla/Install/DB.pm @@ -26,7 +26,7 @@ use Bugzilla::Field; use Date::Parse; use Date::Format; use IO::File; -use List::MoreUtils qw(uniq); +use List::Util qw(max uniq); use URI; use URI::QueryParam; @@ -838,6 +838,9 @@ sub update_table_definitions { # Bug 1803658 - dkl@mozilla.com $dbh->bz_alter_column('ts_error', 'message', {TYPE => 'TEXT', NOTNULL => 1}); + # Bug 1926081 - dkl@mozilla.com + _migrate_profiles_modification_ts(); + ################################################################ # New --TABLE-- changes should go *** A B O V E *** this point # ################################################################ @@ -4437,6 +4440,39 @@ sub _update_see_also_any_url { } } +sub _migrate_profiles_modification_ts { + my $dbh = Bugzilla->dbh; + + return if $dbh->bz_column_info('profiles', 'modification_ts'); + + $dbh->bz_add_column('profiles', 'modification_ts', {TYPE => 'DATETIME'}); + + my $sth = $dbh->prepare( + 'UPDATE profiles SET modification_ts = FROM_UNIXTIME(?) WHERE userid = ?'); + + my $user_ids + = $dbh->selectall_arrayref('SELECT userid FROM profiles ORDER BY userid'); + foreach my $user_id (@{$user_ids}) { + my ($audit_log_when) = $dbh->selectrow_array( + 'SELECT UNIX_TIMESTAMP(at_time) FROM audit_log + WHERE class = \'Bugzilla::User\' AND object_id = ? ORDER BY at_time DESC ' + . $dbh->sql_limit(1), undef, $user_id + ); + my ($profiles_act_when) = $dbh->selectrow_array( + 'SELECT UNIX_TIMESTAMP(profiles_when) FROM profiles_activity + WHERE userid = ? ORDER BY profiles_when DESC ' + . $dbh->sql_limit(1), undef, $user_id + ); + + # We use unix timestamps to make value comparison easier + my $modification_ts = max($audit_log_when, $profiles_act_when); + $sth->execute($modification_ts, $user_id); + } + + $dbh->bz_alter_column('profiles', 'modification_ts', + {TYPE => 'DATETIME', NOTNULL => 1}); +} + 1; __END__ diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm index 1e1cc804b4..c5ff64bf1c 100644 --- a/Bugzilla/User.pm +++ b/Bugzilla/User.pm @@ -85,7 +85,8 @@ sub DB_COLUMNS { 'profiles.mfa', 'profiles.mfa_required_date', 'profiles.nickname', - 'profiles.bounce_count' + 'profiles.bounce_count', + $dbh->sql_date_format('modification_ts', '%Y-%m-%d %H:%i:%s') . ' AS modification_ts', ), ; } @@ -107,6 +108,7 @@ use constant VALIDATORS => { password_change_reason => \&_check_password_change_reason, mfa => \&_check_mfa, bounce_count => \&_check_numeric, + modification_ts => \&_check_timestamp, }; sub UPDATE_COLUMNS { @@ -124,6 +126,7 @@ sub UPDATE_COLUMNS { mfa_required_date nickname bounce_count + modification_ts ); push(@cols, 'cryptpassword') if exists $self->{cryptpassword}; return @cols; @@ -337,6 +340,11 @@ sub update { || exists $changes->{cryptpassword}) ); + # Update modification_ts if any changes were made + if (keys %{$changes}) { + $dbh->do('UPDATE profiles set modification_ts = NOW() WHERE userid = ?', undef, $self->id) + } + # XXX Can update profiles_activity here as soon as it understands # field names like login_name. @@ -448,6 +456,10 @@ sub _check_numeric { return $value; } +sub _check_timestamp { + return Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)'); +} + ################################################################################ # Mutators ################################################################################ @@ -669,6 +681,7 @@ sub showmybugslink { $_[0]->{showmybugslink}; } sub email_disabled { $_[0]->{disable_mail} || !$_[0]->{is_enabled}; } sub email_enabled { !$_[0]->email_disabled; } sub last_seen_date { $_[0]->{last_seen_date}; } +sub modification_ts { $_[0]->{modification_ts}; } sub password_change_required { $_[0]->{password_change_required}; } sub password_change_reason { $_[0]->{password_change_reason}; } diff --git a/template/en/default/admin/users/edit.html.tmpl b/template/en/default/admin/users/edit.html.tmpl index 689e870e65..4571ea1f44 100644 --- a/template/en/default/admin/users/edit.html.tmpl +++ b/template/en/default/admin/users/edit.html.tmpl @@ -153,7 +153,12 @@ $(function() { [% END %] - + + Last Modified: + + [% otheruser.modification_ts FILTER html %] + + Last Login: From 273e25f875afc42d211c46297bd3fd9464c76ef5 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Thu, 31 Oct 2024 10:08:14 -0400 Subject: [PATCH 2/2] Updated migration code and fixup script added --- Bugzilla/Install/DB.pm | 44 +++++++++++++++++++++--- scripts/bug1926081.pl | 78 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 scripts/bug1926081.pl diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm index b2250d411f..29db402402 100644 --- a/Bugzilla/Install/DB.pm +++ b/Bugzilla/Install/DB.pm @@ -4447,25 +4447,59 @@ sub _migrate_profiles_modification_ts { $dbh->bz_add_column('profiles', 'modification_ts', {TYPE => 'DATETIME'}); + # A fresh DB will not have this column yet as it is added later by an extension + # so we will need to ignore it later in this case. + my $has_creation_ts = $dbh->bz_column_info('profiles', 'creation_ts'); + my $sth = $dbh->prepare( 'UPDATE profiles SET modification_ts = FROM_UNIXTIME(?) WHERE userid = ?'); + # Todays timestamp + my $now_when + = $dbh->selectrow_array('SELECT UNIX_TIMESTAMP(LOCALTIMESTAMP(0))'); + my $user_ids - = $dbh->selectall_arrayref('SELECT userid FROM profiles ORDER BY userid'); + = $dbh->selectcol_arrayref('SELECT userid FROM profiles ORDER BY userid'); + + my $count = 1; + my $total = scalar @{$user_ids}; + foreach my $user_id (@{$user_ids}) { - my ($audit_log_when) = $dbh->selectrow_array( + indicate_progress({total => $total, current => $count++, every => 25}); + + my $audit_log_when = $dbh->selectrow_array( 'SELECT UNIX_TIMESTAMP(at_time) FROM audit_log WHERE class = \'Bugzilla::User\' AND object_id = ? ORDER BY at_time DESC ' . $dbh->sql_limit(1), undef, $user_id ); - my ($profiles_act_when) = $dbh->selectrow_array( + my $profiles_act_when = $dbh->selectrow_array( 'SELECT UNIX_TIMESTAMP(profiles_when) FROM profiles_activity WHERE userid = ? ORDER BY profiles_when DESC ' . $dbh->sql_limit(1), undef, $user_id ); - # We use unix timestamps to make value comparison easier - my $modification_ts = max($audit_log_when, $profiles_act_when); + $audit_log_when ||= 0; + $profiles_act_when ||= 0; + + my $creation_when = 0; + if ($has_creation_ts) { + $creation_when + = $dbh->selectrow_array( + 'SELECT UNIX_TIMESTAMP(creation_ts) FROM profiles WHERE userid = ?', + undef, $user_id); + $creation_when ||= 0; + } + + my $modification_ts = 0; + # IF we could not find anything then use todays date + if (!$audit_log_when && !$profiles_act_when && !$creation_when) { + $modification_ts = $now_when; + } + # We used unix timestamps to make value comparison easier without using DateTime instance of each. + else { + $modification_ts = max($audit_log_when, $profiles_act_when, $creation_when); + } + $sth->execute($modification_ts, $user_id); } diff --git a/scripts/bug1926081.pl b/scripts/bug1926081.pl new file mode 100644 index 0000000000..7377626f33 --- /dev/null +++ b/scripts/bug1926081.pl @@ -0,0 +1,78 @@ +#!/usr/bin/env perl +# 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. + +use 5.10.1; +use strict; +use warnings; +use lib qw(. lib local/lib/perl5); + +use Bugzilla; +use Bugzilla::Constants; +use Bugzilla::Install::Util qw(indicate_progress); + +use List::Util qw(max); + +Bugzilla->usage_mode(USAGE_MODE_CMDLINE); + +my $dbh = Bugzilla->dbh; + +my $sth = $dbh->prepare( + 'UPDATE profiles SET modification_ts = FROM_UNIXTIME(?) WHERE userid = ?'); + +# Todays timestamp +my $now_when + = $dbh->selectrow_array('SELECT UNIX_TIMESTAMP(LOCALTIMESTAMP(0))'); + +my $user_ids + = $dbh->selectcol_arrayref('SELECT userid FROM profiles ORDER BY userid'); + +my $count = 1; +my $total = scalar @{$user_ids}; + +foreach my $user_id (@{$user_ids}) { + indicate_progress({total => $total, current => $count++, every => 25}); + + my $audit_log_when = $dbh->selectrow_array( + 'SELECT UNIX_TIMESTAMP(at_time) FROM audit_log + WHERE class = \'Bugzilla::User\' AND object_id = ? ORDER BY at_time DESC ' + . $dbh->sql_limit(1), undef, $user_id + ); + my $profiles_act_when = $dbh->selectrow_array( + 'SELECT UNIX_TIMESTAMP(profiles_when) FROM profiles_activity + WHERE userid = ? ORDER BY profiles_when DESC ' + . $dbh->sql_limit(1), undef, $user_id + ); + + my $creation_when + = $dbh->selectrow_array( + 'SELECT UNIX_TIMESTAMP(creation_ts) FROM profiles WHERE userid = ?', + undef, $user_id); + + $creation_when ||= 0; + $audit_log_when ||= 0; + $profiles_act_when ||= 0; + + my $modification_ts = 0; + + # IF we could not find anything then use todays date + if (!$audit_log_when && !$profiles_act_when && !$creation_when) { + $modification_ts = $now_when; + } + +# We used unix timestamps to make value comparison easier without using DateTime instance of each. + else { + $modification_ts = max($audit_log_when, $profiles_act_when, $creation_when); + } + + $sth->execute($modification_ts, $user_id); +} + +$dbh->bz_alter_column('profiles', 'modification_ts', + {TYPE => 'DATETIME', NOTNULL => 1}); + +1;