# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # # This Source Code Form is "Incompatible With Secondary Licenses", as # defined by the Mozilla Public License, v. 2.0. package Bugzilla::Extension::UserProfile::Util; use 5.10.1; use strict; use warnings; use base qw(Exporter); our @EXPORT = qw( update_statistics_by_user tag_for_recount_from_bug last_user_activity ); use Bugzilla; sub update_statistics_by_user { my ($user_id) = @_; # run all our queries on the slaves my $dbh = Bugzilla->switch_to_shadow_db(); my $now = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)'); # grab the current values my $last_statistics_ts = _get_last_statistics_ts($user_id); my $statistics = _get_stats($user_id, 'profiles_statistics', 'name'); my $by_status = _get_stats($user_id, 'profiles_statistics_status', 'status'); my $by_product = _get_stats($user_id, 'profiles_statistics_products', 'product'); # bugs filed _update_statistics($statistics, 'bugs_filed', [ $user_id ], <switch_to_main_db(); $dbh->bz_start_transaction(); # commit updated statistics _set_stats($statistics, $user_id, 'profiles_statistics', 'name') if _has_dirty($statistics); _set_stats($by_status, $user_id, 'profiles_statistics_status', 'status') if _has_dirty($by_status); _set_stats($by_product, $user_id, 'profiles_statistics_products', 'product') if _has_dirty($by_product); # update the user's last_statistics_ts _set_last_statistics_ts($user_id, $now); $dbh->bz_commit_transaction(); } sub tag_for_recount_from_bug { my ($bug_id) = @_; my $dbh = Bugzilla->dbh; # get a list of all users associated with this bug my $user_ids = $dbh->selectcol_arrayref(< ? UNION ALL SELECT DISTINCT who AS user_id FROM longdescs WHERE bug_id = ? ) tmp EOF # clear last_statistics_ts $dbh->do( "UPDATE profiles SET last_statistics_ts=NULL WHERE " . $dbh->sql_in('userid', $user_ids) ); foreach my $id (@$user_ids) { Bugzilla->memcached->clear({ table => 'profiles', id => $id }); } return scalar(@$user_ids); } sub last_user_activity { # last comment, or change to a bug (excluding CC changes) my ($user_id) = @_; return Bugzilla->dbh->selectrow_array(< ? ) tmp EOF } # for performance reasons hit the db directly rather than using the user object sub _get_last_statistics_ts { my ($user_id) = @_; return Bugzilla->dbh->selectrow_array( "SELECT last_statistics_ts FROM profiles WHERE userid = ?", undef, $user_id ); } sub _set_last_statistics_ts { my ($user_id, $timestamp) = @_; Bugzilla->dbh->do( "UPDATE profiles SET last_statistics_ts = ? WHERE userid = ?", undef, $timestamp, $user_id, ); Bugzilla->memcached->clear({ table => 'profiles', id => $user_id }); } sub _update_statistics { my ($statistics, $name, $values, $sql) = @_; my ($count) = Bugzilla->dbh->selectrow_array($sql, undef, @$values); if (!exists $statistics->{$name}) { $statistics->{$name} = { id => 0, count => $count, dirty => 1, }; } elsif ($statistics->{$name}->{count} != $count) { $statistics->{$name}->{count} = $count; $statistics->{$name}->{dirty} = 1; }; } sub _activity_by_status { my ($by_status, $user_id) = @_; my $dbh = Bugzilla->dbh; # we actually track both status and resolution changes as statuses my @values = ($user_id, _field_id('bug_status'), $user_id, _field_id('resolution')); my $rows = $dbh->selectall_arrayref(< {} }, @values); SELECT added AS status, COUNT(*) AS count FROM bugs_activity WHERE who = ? AND fieldid = ? GROUP BY added UNION ALL SELECT CONCAT('RESOLVED/', added) AS status, COUNT(*) AS count FROM bugs_activity WHERE who = ? AND fieldid = ? AND added != '' GROUP BY added EOF foreach my $row (@$rows) { my $status = $row->{status}; if (!exists $by_status->{$status}) { $by_status->{$status} = { id => 0, count => $row->{count}, dirty => 1, }; } elsif ($by_status->{$status}->{count} != $row->{count}) { $by_status->{$status}->{count} = $row->{count}; $by_status->{$status}->{dirty} = 1; } } } sub _activity_by_product { my ($by_product, $user_id) = @_; my $dbh = Bugzilla->dbh; my %products; # changes my $rows = $dbh->selectall_arrayref(< {} }, $user_id); SELECT products.name AS product, count(*) AS count FROM bugs_activity INNER JOIN bugs ON bugs.bug_id = bugs_activity.bug_id INNER JOIN products ON products.id = bugs.product_id WHERE who = ? GROUP BY bugs.product_id EOF map { $products{$_->{product}} += $_->{count} } @$rows; # comments $rows = $dbh->selectall_arrayref(< {} }, $user_id); SELECT products.name AS product, count(*) AS count FROM longdescs INNER JOIN bugs ON bugs.bug_id = longdescs.bug_id INNER JOIN products ON products.id = bugs.product_id WHERE who = ? GROUP BY bugs.product_id EOF map { $products{$_->{product}} += $_->{count} } @$rows; # store only the top 10 and 'other' (which is an empty string) my @sorted = sort { $products{$b} <=> $products{$a} } keys %products; my @other; @other = splice(@sorted, 10) if scalar(@sorted) > 10; map { $products{''} += $products{$_} } @other; push @sorted, '' if $products{''}; # update by_product foreach my $product (@sorted) { if (!exists $by_product->{$product}) { $by_product->{$product} = { id => 0, count => $products{$product}, dirty => 1, }; } elsif ($by_product->{$product}->{count} != $products{$product}) { $by_product->{$product}->{count} = $products{$product}; $by_product->{$product}->{dirty} = 1; } } foreach my $product (keys %$by_product) { if (!grep { $_ eq $product } @sorted) { delete $by_product->{$product}; } } } our $_field_id_cache; sub _field_id { my ($name) = @_; if (!$_field_id_cache) { my $rows = Bugzilla->dbh->selectall_arrayref("SELECT id, name FROM fielddefs"); foreach my $row (@$rows) { $_field_id_cache->{$row->[1]} = $row->[0]; } } return $_field_id_cache->{$name}; } sub _get_stats { my ($user_id, $table, $name_field) = @_; my $result = {}; my $rows = Bugzilla->dbh->selectall_arrayref( "SELECT * FROM $table WHERE user_id = ?", { Slice => {} }, $user_id, ); foreach my $row (@$rows) { unless (defined $row->{$name_field}) { print "$user_id $table $name_field\n"; die; } $result->{$row->{$name_field}} = { id => $row->{id}, count => $row->{count}, dirty => 0, } } return $result; } sub _set_stats { my ($statistics, $user_id, $table, $name_field) = @_; my $dbh = Bugzilla->dbh; foreach my $name (keys %$statistics) { next unless $statistics->{$name}->{dirty}; if ($statistics->{$name}->{id}) { $dbh->do( "UPDATE $table SET count = ? WHERE user_id = ? AND $name_field = ?", undef, $statistics->{$name}->{count}, $user_id, $name, ); } else { $dbh->do( "INSERT INTO $table(user_id, $name_field, count) VALUES (?, ?, ?)", undef, $user_id, $name, $statistics->{$name}->{count}, ); } } } sub _has_dirty { my ($statistics) = @_; foreach my $name (keys %$statistics) { return 1 if $statistics->{$name}->{dirty}; } return 0; } 1;