Rev 1293 | Blame | Last modification | View Log | RSS feed
################################################################################ 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 topicusermetricWHERE topicid = ?',{}, $self->{topicid})};push @metric_user_list, @{ $dbh->selectall_arrayref('SELECT DISTINCT LOWER(author) FROM commentdata, commentstate ' .'WHERE commentstate.topicid = ? ANDcommentstate.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 metricname=>$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.<BR>" 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.<BR>" 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.<BR>" 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.<BR>" 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.<BR>";}}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 metricsnext 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;