###############################################################################
# Copyright (c) 2003 Jason Remillard. All rights reserved.
#
# This program is free software; you can redistribute it and modify it under
# the terms of the GPL.
# Model object for handling metric data.
package Codestriker::Model::Metrics;
use strict;
use Encode qw(decode_utf8);
use Codestriker::DB::DBI;
sub new {
my ($class, $topicid) = @_;
my $self = {};
$self->{topicmetrics} = undef;
$self->{topicid} = $topicid;
$self->{usermetrics} = {};
$self->{topichistoryrows} = undef;
bless $self, $class;
return $self;
}
# Sets the topic metrics values. The values are passed in as an
# array. The array must be in the same order returned by
# get_topic_metric(). Metrics that are bad are silently not stored.
sub set_topic_metrics {
my ($self,@metric_values) = @_;
my @metrics = $self->get_topic_metrics();
for (my $index = 0; $index < scalar(@metrics); ++$index) {
next if ($metrics[$index]->{enabled} == 0);
die "error: not enough metrics" if (scalar(@metric_values) == 0);
my $value = shift @metric_values;
if ($self->_verify_metric($metrics[$index], $value) eq '') {
$metrics[$index]->{value} = $value;
}
}
}
# Verifies that all of the topic metrics are well formed and valid. It will
# return a non-empty string if a problem is found.
sub verify_topic_metrics {
my ($self,@metric_values) = @_;
my $msg = '';
my @metrics = $self->get_topic_metrics();
for (my $index = 0; $index < scalar(@metrics); ++$index) {
next if ($metrics[$index]->{enabled} == 0);
# Disabled values may be in the database (somebody turned off
# the metrics). However, they are not paramters so the index
# between the paramters and the metrics objects will not
# match.
my $value = shift @metric_values;
$msg .= $self->_verify_metric($metrics[$index], $value);
}
return $msg;
}
# Returns the topic metrics as a collection of references to
# hashs. The hash that is returned has the same keys as the
# metrics_schema hash, plus a value key. If the user has not entered a
# value, it will be set to an empty string.
sub get_topic_metrics {
my $self = shift;
my @topic_metrics;
if (defined($self->{topicmetrics})) {
# The topic metrics have already been loaded from the
# database, just return the cached data.
@topic_metrics = @{$self->{topicmetrics}};
}
else {
my @stored_metrics = ();
if (defined($self->{topicid})) {
# Obtain a database connection.
my $dbh = Codestriker::DB::DBI->get_connection();
my $select_topic_metrics =
$dbh->prepare_cached('SELECT topicmetric.metric_name,
topicmetric.value ' .
'FROM topicmetric ' .
'WHERE topicmetric.topicid = ?');
$select_topic_metrics->execute($self->{topicid});
@stored_metrics = @{$select_topic_metrics->fetchall_arrayref()};
# Close the connection, and check for any database errors.
Codestriker::DB::DBI->release_connection($dbh, 1);
}
# Match the configured metrics to the metrics in the database. If
# the configured metric is found in the database, it is removed
# from the stored_metric list to find any data that is in the
# database, but is not configured.
foreach my $metric_schema (Codestriker::get_metric_schema()) {
if ($metric_schema->{scope} eq 'topic') {
my $metric =
{ # This is the topic metric.
name => $metric_schema->{name},
description => $metric_schema->{description},
value => '',
filter => $metric_schema->{filter},
enabled => $metric_schema->{enabled},
in_database => 0
};
for (my $index = 0; $index < scalar(@stored_metrics); ++$index) {
my $stored_metric = $stored_metrics[$index];
if ($stored_metric->[0] eq $metric_schema->{name}) {
$metric->{value} = $stored_metric->[1];
$metric->{in_database} = 1;
splice @stored_metrics, $index, 1;
last;
}
}
if ($metric_schema->{enabled} || $metric->{in_database}) {
push @topic_metrics, $metric;
}
}
}
# Add in any metrics that are in the database but not
# currently configured. The system should display the
# metrics, but not let the user modify them.
for (my $index = 0; $index < scalar(@stored_metrics); ++$index) {
my $stored_metric = $stored_metrics[$index];
# This is the topic metric.
my $metric =
{
name => $stored_metric->[0],
description => '',
value => $stored_metric->[1],
# User can not change the metric, not configured.
enabled => 0,
in_database => 1
};
push @topic_metrics, $metric;
}
push @topic_metrics, $self->_get_built_in_topic_metrics();
$self->{topicmetrics} = \@topic_metrics;
}
return @topic_metrics;
}
# Get just the list of users that have actually looked at the review. This is
# used on the main page to out users that are not doing the reviews when invited.
sub get_list_of_actual_topic_participants {
my ($self) = @_;
my $dbh = Codestriker::DB::DBI->get_connection();
my $actual_user_list_ref =
$dbh->selectall_arrayref(
'SELECT DISTINCT LOWER(email) FROM topicviewhistory ' .
'WHERE topicid = ?',{}, $self->{topicid});
my @actual_user_list = ();
foreach my $user ( @$actual_user_list_ref ) {
push @actual_user_list,$user->[0] if defined $user->[0] && $user->[0] ne "";
}
# Close the connection, and check for any database errors.
Codestriker::DB::DBI->release_connection($dbh, 1);
return @actual_user_list;
}
# Get a list of users that have metric data for this topic. People can
# look at the topic even if they were not invited, so if somebody touches the
# topic, they will appear in this list. Using this function rather than the
# invite list from the topic will insure that people don't get missed from
# the metric data.
sub get_complete_list_of_topic_participants {
my ($self) = @_;
my $dbh = Codestriker::DB::DBI->get_connection();
my @metric_user_list = @{ $dbh->selectall_arrayref('
SELECT distinct LOWER(email)
from participant WHERE topicid = ?',{}, $self->{topicid})};
push @metric_user_list, @{ $dbh->selectall_arrayref('
SELECT LOWER(author) FROM topic WHERE id = ?',{}, $self->{topicid})};
push @metric_user_list, @{ $dbh->selectall_arrayref('
SELECT DISTINCT LOWER(email) FROM topicusermetric
WHERE topicid = ?',{}, $self->{topicid})};
push @metric_user_list, @{ $dbh->selectall_arrayref(
'SELECT DISTINCT LOWER(author) FROM commentdata, commentstate ' .
'WHERE commentstate.topicid = ? AND
commentstate.id = commentdata.commentstateid ',
{}, $self->{topicid})};
push @metric_user_list, @{ $dbh->selectall_arrayref(
'SELECT DISTINCT LOWER(email) FROM topicviewhistory ' .
'WHERE topicid = ? AND email IS NOT NULL',{}, $self->{topicid})};
# remove the duplicates.
my %metric_user_hash;
foreach my $user (@metric_user_list) {
$metric_user_hash{$user->[0]} = 1;
}
# Need to sort the empty user name last so that the template parameters
# that are done by index don't start at 1, and therefor not allow users
# to save the metrics.
@metric_user_list = sort {
return 1 if ( $a eq "");
return -1 if ( $b eq "");
return $a cmp $b;
} keys %metric_user_hash;
# Close the connection, and check for any database errors.
Codestriker::DB::DBI->release_connection($dbh, 1);
return @metric_user_list;
}
# Get a list of users that are invited for this review.
sub get_list_of_topic_participants {
my ($self) = @_;
my $dbh = Codestriker::DB::DBI->get_connection();
my @metric_user_list = @{ $dbh->selectall_arrayref('
SELECT distinct LOWER(email)
from participant WHERE topicid = ?',{}, $self->{topicid})};
push @metric_user_list, @{ $dbh->selectall_arrayref('
SELECT LOWER(author) FROM topic WHERE id = ?',{}, $self->{topicid})};
# remove the duplicates.
my %metric_user_hash;
foreach my $user (@metric_user_list) {
$metric_user_hash{$user->[0]} = 1;
}
# Need to sort the empty user name last so that the template parameters
# that are done by index don't start at 1, and therefor not allow users
# to save the metrics.
@metric_user_list = sort {
return 1 if ( $a eq "");
return -1 if ( $b eq "");
return $a cmp $b;
} keys %metric_user_hash;
# Close the connection, and check for any database errors.
Codestriker::DB::DBI->release_connection($dbh, 1);
return @metric_user_list;
}
# Sets the metrics for a specific user, both authors and reviewers. cc's don't
# get metrics. The metrics are sent in as an array, that must in the same
# order as the get_user_metric() call returns them. Metrics that are bad are
# silently not stored.
sub set_user_metric {
my ($self, $user, @metric_values) = @_;
my @metrics = $self->get_user_metrics($user);
for (my $index = 0; $index < scalar(@metrics); ++$index) {
next if ($metrics[$index]->{enabled} == 0);
die "error: not enough metrics" if (scalar(@metric_values) == 0);
# Disabled values may be in the database (somebody turned off
# the metrics). However, they are not paramters so the index
# between the paramters and the metrics objects will not
# match.
my $value = shift @metric_values;
if ($self->_verify_metric($metrics[$index], $value) eq '') {
$metrics[$index]->{value} = $value
}
}
}
# Verifies that all of the user metrics are well formed and valid inputs. If a
# problem is found the function will return a non-empty string.
sub verify_user_metrics {
my ($self, $user, @metric_values) = @_;
my $msg = '';
my @metrics = $self->get_user_metrics($user);
for (my $index = 0; $index < scalar(@metrics); ++$index) {
next if ($metrics[$index]->{enabled} == 0);
# Disabled values may be in the database (somebody turned off
# the metrics). However, they are not paramters so the index
# between the paramters and the metrics objects will not
# match.
my $value = shift @metric_values;
$msg .= $self->_verify_metric($metrics[$index], $value);
}
return $msg;
}
# Returns the user metrics as a collection of references to hashs. The
# hash that is returned has the same keys as the metrics_schema hash,
# plus a value key. If the user has not entered a value, it will be
# set to an empty string.
sub get_user_metrics {
my ($self, $username) = @_;
my @user_metrics;
if (exists($self->{usermetrics}->{$username})) {
# If the metrics for this user has already been loaded from
# the database, return the cached result of that load.
@user_metrics = @{$self->{usermetrics}->{$username}};
}
else {
my @stored_metrics = ();
if (defined($self->{topicid})) {
# Obtain a database connection.
my $dbh = Codestriker::DB::DBI->get_connection();
# Get all of the user outputs for this topic regardless of
# the user.
my $selected_all_user_metrics =
$dbh->prepare_cached('SELECT DISTINCT metric_name ' .
'FROM topicusermetric ' .
'WHERE topicid = ? ' .
'ORDER BY metric_name');
$selected_all_user_metrics->execute($self->{topicid});
@stored_metrics =
@{$selected_all_user_metrics->fetchall_arrayref()};
# Get the outputs for this user.
my $select_user_metrics =
$dbh->prepare_cached('SELECT metric_name, value ' .
'FROM topicusermetric ' .
'WHERE topicid = ? AND LOWER(email) = LOWER(?) ' .
'ORDER BY metric_name');
$select_user_metrics->execute($self->{topicid}, $username);
my @user_stored_metrics =
@{$select_user_metrics->fetchall_arrayref()};
# Stuff the complete list with the values from the current
# user list. Handle displaying metrics that are in the
# db, but not enabled for new topics.
foreach my $metric (@stored_metrics) {
my $foundit = 0;
foreach my $user_metric (@user_stored_metrics) {
if ($user_metric->[0] eq $metric->[0]) {
$foundit = 1;
push @$metric, $user_metric->[1];
last;
}
}
if ($foundit == 0) {
push @$metric,'';
}
}
# Close the connection, and check for any database errors.
Codestriker::DB::DBI->release_connection($dbh, 1);
}
foreach my $metric_schema (Codestriker::get_metric_schema()) {
if ($metric_schema->{scope} ne 'topic') {
my $metric =
{
name => $metric_schema->{name},
description => $metric_schema->{description},
value => '',
enabled => $metric_schema->{enabled},
scope => $metric_schema->{scope},
filter => $metric_schema->{filter},
};
for (my $index = 0; $index < scalar(@stored_metrics);
++$index) {
my $stored_metric = $stored_metrics[$index];
if ($stored_metric->[0] eq $metric_schema->{name}) {
$metric->{value} = $stored_metric->[1];
$metric->{in_database} = 1;
splice @stored_metrics, $index,1;
last;
}
}
if ($metric_schema->{enabled} || $metric->{in_database}) {
if ($username eq "") {
# don't let any metrics be set into the db for unknown users.
$metric->{enabled} = 0;
}
push @user_metrics, $metric;
}
}
}
# Clean up any metrics that are in the database but not in the
# schema, we will not let them change them, and we don't have
# the description anymore.
for (my $index = 0; $index < scalar(@stored_metrics); ++$index) {
my $stored_metric = $stored_metrics[$index];
my $metric =
{ # this is the topic metric
name=>$stored_metric->[0],
description=>'',
value=>$stored_metric->[1],
scope=>'participant',
enabled=>0, # user can not change the metric, no schema.
in_database=>1
};
push @user_metrics, $metric;
}
$self->{usermetrics}->{$username} = \@user_metrics;
}
push @user_metrics, $self->_get_built_in_user_metrics($username);
return @user_metrics;
}
# Returns the user metrics as a collection of references to hashs.
sub get_user_metrics_totals {
my ($self,@users) = @_;
my @user_metrics;
if (exists($self->{usermetrics_totals})) {
@user_metrics = @$self->{usermetrics_totals};
}
my @total_metrics;
foreach my $user (@users) {
my @metrics = $self->get_user_metrics($user);
if (scalar(@total_metrics) == 0) {
# Copy the metrics in.
foreach my $metric (@metrics) {
my %temp = %$metric;
push @total_metrics, \%temp;
}
}
else {
# Add them up!
for (my $index = 0; $index < scalar(@total_metrics) ; ++$index) {
if ($metrics[$index]->{value} ne '') {
if ($total_metrics[$index]->{value} eq '') {
$total_metrics[$index]->{value} = 0;
}
$total_metrics[$index]->{value} +=
$metrics[$index]->{value};
}
}
}
}
$self->{usermetrics_totals}= \@total_metrics;
return @total_metrics;
}
# Returns a list of hashes. Each hash is an event. In the hash is stored who
# caused the event, when it happened, and what happened. The hashes are defined
# as:
# email -> the email address of the user who caused the event.
# date -> when the event happened.
# description -> the event description.
#
# The topic must be loaded from the db before this function can be called.
sub get_topic_history {
my ($self) = @_;
my @topic_history = $self->_get_topic_history_rows();
my @event_list;
my $last_history_row;
foreach my $current_history_row (@topic_history) {
if ( !defined($last_history_row) ) {
# The first event is always the topic creation, so lets make
# that now.
my $filteredemail =
Codestriker->filter_email($current_history_row->{author});
my $formatted_time =
Codestriker->format_short_timestamp($current_history_row->{modified_ts});
push @event_list,
{
email=>$filteredemail,
date =>$formatted_time,
description=>'The topic is created.'
};
}
else {
my %event =
(
email=> Codestriker->filter_email(
$current_history_row->{modified_by}),
date => Codestriker->format_short_timestamp(
$current_history_row->{modified_ts}),
description=>''
);
# Look for changes in all of the fields. Several fields could have
# changed at once.
if ($current_history_row->{author} ne $last_history_row->{author}) {
my %new_event = %event;
$new_event{description} =
"Author changed: $last_history_row->{author} to " .
"$current_history_row->{author}.";
push @event_list, \%new_event;
}
if ($current_history_row->{title} ne $last_history_row->{title}) {
my %new_event = %event;
$new_event{description} =
"Title changed to: \"$current_history_row->{title}\".";
push @event_list, \%new_event;
}
if ($current_history_row->{description} ne $last_history_row->{description}) {
my %new_event = %event;
$new_event{description} = "Description changed to: " .
"$current_history_row->{description}.";
push @event_list, \%new_event;
}
if ($current_history_row->{state} ne $last_history_row->{state}) {
my %new_event = %event;
$new_event{description} =
"Topic state changed to: " .
$Codestriker::topic_states[$current_history_row->{state}];
push @event_list, \%new_event;
}
if ($current_history_row->{repository} ne $last_history_row->{repository}) {
my %new_event = %event;
$new_event{description} =
"Repository changed to: $current_history_row->{repository}.";
push @event_list, \%new_event;
}
if ($current_history_row->{project} ne $last_history_row->{project}) {
my %new_event = %event;
$new_event{description} =
"Project changed to: $current_history_row->{project}.";
push @event_list, \%new_event;
}
if ($current_history_row->{reviewers} ne $last_history_row->{reviewers}) {
my %new_event = %event;
# Figure out who was removed, and who was added to the list.
my @reviewers = split /,/,$current_history_row->{reviewers};
my @l_reviewers = split /,/,$last_history_row->{reviewers};
my @new;
my @removed;
Codestriker::set_differences(\@reviewers, \@l_reviewers, \@new, \@removed);
if (@new == 0) {
$new_event{description} =
"Reviewers removed: " . join(',',@removed);;
}
elsif (@removed == 0) {
$new_event{description} =
"Reviewers added: " . join(',',@new);
}
else {
$new_event{description} =
"Reviewers added: " . join(',',@new) .
" and reviewers removed: " . join(',',@removed);
}
push @event_list, \%new_event;
}
if ($current_history_row->{cc} ne $last_history_row->{cc}) {
my %new_event = %event;
$new_event{description} =
"CC changed to $current_history_row->{cc}.";
push @event_list, \%new_event;
}
}
$last_history_row = $current_history_row
}
return @event_list;
}
# Returns the topic metrics as a collection of references to
# hashes. The hash that is returned has the same keys as the
# metrics_schema hash, plus a value key. This private function
# returns "built in" metrics derived from the topic history
# table.
sub _get_built_in_topic_metrics {
my $self = shift;
my @topic_metrics;
my @topic_history = $self->_get_topic_history_rows();
my %state_times;
my $last_history_row;
# Figure out how long the topic has spent in each state.
for ( my $topic_history_index = 0;
$topic_history_index <= scalar(@topic_history);
++$topic_history_index) {
my $current_history_row;
if ($topic_history_index < scalar(@topic_history)) {
$current_history_row = $topic_history[$topic_history_index];
}
if (defined($last_history_row)) {
my $start =
Codestriker->convert_date_timestamp_time(
$last_history_row->{modified_ts});
my $end = 0;
if (defined($current_history_row)) {
$end = Codestriker->convert_date_timestamp_time(
$current_history_row->{modified_ts});
}
else {
$end = time();
}
if (exists($state_times{$last_history_row->{state}})) {
$state_times{$last_history_row->{state}} += $end - $start;
}
else {
$state_times{$last_history_row->{state}} = $end - $start;
}
}
$last_history_row = $current_history_row
}
foreach my $state ( sort keys %state_times) {
my $statename = $Codestriker::topic_states[$state];
my $time_days = sprintf("%1.1f",$state_times{$state} / (60*60*24));
# This is the topic metric.
my $metric =
{
name => 'Time In ' . $statename,
description =>
'Time in days the topic spent in the ' . $statename . ' state.',
value => $time_days,
# User can not change the metric, not configured.
enabled => 0,
in_database => 0,
filter =>"count",
builtin => 1,
};
push @topic_metrics, $metric;
}
return @topic_metrics;
}
# Returns the user topic metrics as a collection of references to
# hashs. The hash that is returned has the same keys as the
# metrics_schema hash, plus a value key. This private function
# returns "built in" metrics derived from the topic history
# table and the topic view history table.
sub _get_built_in_user_metrics {
my ($self,$username) = @_;
my @user_metrics;
my $dbh = Codestriker::DB::DBI->get_connection();
# Setup the prepared statements.
my $select_topic = $dbh->prepare_cached('SELECT creation_ts ' .
'FROM topicviewhistory ' .
'WHERE topicid = ? AND ' .
'LOWER(email) = LOWER(?) ' .
'ORDER BY creation_ts');
$select_topic->execute($self->{topicid}, $username);
# my $total_time = $self->calculate_topic_view_time($select_topic);
my $total_time = 0;
Codestriker::DB::DBI->release_connection($dbh, 1);
if ($total_time == 0) {
$total_time = "";
}
else {
$total_time = sprintf("%1.0f",$total_time / (60));
}
# This is the topic metric.
my $metric =
{
name => 'Codestriker Time',
description =>
'Time in minutes spent in Codestriker looking at this topic.',
value => $total_time,
enabled => 0,
in_database => 0,
filter =>"minutes",
builtin => 1,
scope =>'participant',
};
push @user_metrics, $metric;
return @user_metrics;
}
# Given a DBI statement that returns a sorted collection of timestamps from
# the topicviewhistory table, return the total time.
sub calculate_topic_view_time {
my ($self,$select_topic) = @_;
# The amount of time you give to people after a click assuming no other
# clicks are after it.
my $time_increment = 4*60;
my $total_time = 0;
my $last_time = 0;
while ( my @row_array = $select_topic->fetchrow_array) {
my ($creation_ts) = @row_array;
my $time = Codestriker->convert_date_timestamp_time($creation_ts);
if ($last_time) {
if ($time - $last_time > $time_increment) {
$total_time += $time_increment
}
else {
$total_time += $time - $last_time;
}
}
$last_time = $time;
}
if ($last_time) {
$total_time += $time_increment;
}
return $total_time;
}
# Returns the topichistory rows as an array of hashes. Each element in the
# array is a row, each field in the table is a key. It will only fetch if
# from the db once.
sub _get_topic_history_rows {
my ($self) = @_;
if (defined( $self->{topichistoryrows})) {
return @{$self->{topichistoryrows}};
}
else {
my $dbh = Codestriker::DB::DBI->get_connection();
my @history_list;
# Setup the prepared statements.
my $select_topic = $dbh->prepare_cached('SELECT topichistory.author, ' .
'topichistory.title, ' .
'topichistory.description, ' .
'topichistory.state, ' .
'topichistory.modified_ts, ' .
'topichistory.version, ' .
'topichistory.repository, ' .
'project.name, ' .
'topichistory.reviewers, ' .
'topichistory.cc, ' .
'topichistory.modified_by_user ' .
'FROM topichistory, project ' .
'WHERE topichistory.topicid = ? AND ' .
'topichistory.projectid = project.id ' .
'ORDER BY topichistory.version');
$select_topic->execute($self->{topicid});
while ( my @row_array = $select_topic->fetchrow_array) {
my ($author,$title,$description,$state,$modified_ts, $version,
$repository,$project,$reviewers,$cc, $modified_by) = @row_array;
my %entry = (
author=>$author,
title=>decode_utf8($title),
description=>decode_utf8($description),
state=>$state,
modified_ts=>$modified_ts,
version=>$version,
repository=>$repository,
project=>decode_utf8($project),
reviewers=>lc($reviewers),
cc=>lc($cc),
modified_by=>lc($modified_by)
);
push @history_list, \%entry;
}
Codestriker::DB::DBI->release_connection($dbh, 1);
$self->{topichistoryrows} = \@history_list;
return @history_list;
}
}
# Returns an error message if a number is not a valid value for a given metric.
sub _verify_metric {
my ($self, $metric, $value) = @_;
my $msg = '';
if ($metric->{enabled}) {
my $input_ok = 0;
if ($metric->{filter} eq "hours") {
$input_ok = ($value =~ /(^[\d]+([\.:][\d]*)?$)|(^$)/);
$msg = $metric->{name} .
" must be a valid time in hours. " . HTML::Entities::encode($value) . " was " .
"not saved.
" unless $input_ok;
}
elsif ($metric->{filter} eq "minutes") {
$input_ok = ($value =~ /(^[\d]+)|(^$)/);
$msg = $metric->{name} .
" must be a valid time in minutes. " . HTML::Entities::encode($value) . " was " .
"not saved.
" unless $input_ok;
}
elsif ($metric->{filter} eq "count") {
$input_ok = ($value =~ /(^[\d]+$)|(^$)/);
$msg = $metric->{name} .
" must be a valid count. " . HTML::Entities::encode($value) . " was not " .
"saved.
" unless $input_ok;
}
elsif ($metric->{filter} eq "percent") {
$input_ok = ($value =~ /(^[\d]+(\.[\d]*)?$)|(^$)/);
if ($input_ok && $value ne '') {
$input_ok = 0 unless ($value >= 0.0 && $value <= 100.0);
}
$msg = $metric->{name} .
" must be a valid percent, between 0 and 100. " .
HTML::Entities::encode($value) . " was not saved.
" unless $input_ok;
}
else {
# invalid config.
$input_ok = 0;
$msg = HTML::Entities::encode($metric->{name}) .
" invalid filter type in configuration. Must " .
"be hours, count, or percent.
";
}
}
return $msg;
}
# Stores all of the metrics to the database.
sub store {
my ($self) = @_;
$self->_store_topic_metrics();
$self->_store_user_metrics();
}
# Stores the topic metrics to the database.
sub _store_user_metrics {
my ($self) = @_;
foreach my $user (keys %{$self->{usermetrics}}) {
$self->get_user_metrics($user);
}
# Obtain a database connection.
my $dbh = Codestriker::DB::DBI->get_connection();
# flush out the user metrics from the topic,
my $delete_alluser_metric =
$dbh->prepare_cached('DELETE FROM topicusermetric ' .
'WHERE topicid = ?');
$delete_alluser_metric->execute($self->{topicid});
my $insert_user_metric =
$dbh->prepare_cached('INSERT INTO topicusermetric (topicid,
email,
metric_name,
value) ' .
'VALUES (?, LOWER(?), ?, ? )');
foreach my $user (keys %{$self->{usermetrics}}) {
my @metrics = $self->get_user_metrics($user);
foreach my $metric (@metrics) {
next if ($metric->{builtin});
if ($metric->{value} ne '') {
$insert_user_metric->execute($self->{topicid},
$user,
$metric->{name},
$metric->{value});
}
}
}
# Close the connection, and check for any database errors.
Codestriker::DB::DBI->release_connection($dbh, 1);
}
# Stores the topic metrics to the database.
sub _store_topic_metrics {
my ($self) = @_;
# Obtain a database connection.
my $dbh = Codestriker::DB::DBI->get_connection();
# Store the topic metrics first.
my @topic_metrics = $self->get_topic_metrics();
my $insert_topic_metric =
$dbh->prepare_cached('INSERT INTO topicmetric (topicid,
metric_name,
value) ' .
'VALUES (?, ?, ? )');
my $update_topic_metric =
$dbh->prepare_cached('UPDATE topicmetric SET value = ? ' .
'WHERE topicid = ? and metric_name = ?');
my $delete_topic_metric =
$dbh->prepare_cached('DELETE FROM topicmetric ' .
'WHERE topicid = ? and metric_name = ?');
foreach my $metric (@topic_metrics) {
# don't save built in metrics
next if ($metric->{builtin});
if ($metric->{in_database}) {
if ($metric->{value} ne '') {
$update_topic_metric->execute($metric->{value},
$self->{topicid},
$metric->{name});
}
else {
# Delete the row.
$delete_topic_metric->execute($self->{topicid},
$metric->{name});
$metric->{in_database} = 0;
}
}
else {
# New metric that is not in the datbase.
if ($metric->{value} ne '') {
$insert_topic_metric->execute($self->{topicid},
$metric->{name},
$metric->{value});
$metric->{in_database} = 1;
}
}
$metric->{in_database} = 1;
}
# Close the connection, and check for any database errors.
Codestriker::DB::DBI->release_connection($dbh, 1);
}
1;