fu: Close-up of Fu, bringing a scoop of water to her mouth (Default)
fu ([personal profile] fu) wrote in [site community profile] changelog2010-07-05 02:51 pm

[dw-free] Business statistics

[commit: http://hg.dwscoalition.org/dw-free/rev/4c2d441485d3]

http://bugs.dwscoalition.org/show_bug.cgi?id=124

Convert the old BML pages to use Template Toolkit. Change rounding to the
nearest tenth, rather than nearest whole, and add a message about the
rounding. Change calculation of percentages so they're only done over
personal/community accounts. Some whitespace tweaks.

Patch by [personal profile] pauamma.

Files modified:
  • bin/upgrading/update-db-general.pl
  • cgi-bin/DW/Controller/SiteStats.pm
  • cgi-bin/DW/StatData/ActiveAccounts.pm
  • cgi-bin/LJ/User.pm
  • htdocs/admin/stats.bml
  • htdocs/stats/site.bml
  • htdocs/stats/site.bml.text
  • views/admin/stats.tt
  • views/admin/stats.tt.text
  • views/stats/site.tt
  • views/stats/site.tt.text
--------------------------------------------------------------------------------
diff -r 9f08948db033 -r 4c2d441485d3 bin/upgrading/update-db-general.pl
--- a/bin/upgrading/update-db-general.pl	Mon Jul 05 22:26:30 2010 +0800
+++ b/bin/upgrading/update-db-general.pl	Mon Jul 05 22:57:18 2010 +0800
@@ -341,7 +341,7 @@ CREATE TABLE site_stats (
     -- FIXME: This is good for retrieving data for a single category+key, but
     -- maybe not as good if we want all keys for the category, with a limit on
     -- time (ie, last 5 entries, or last 2 weeks). Do we need an extra index?
-    INDEX (category_id, key_id, insert_time) 
+    INDEX (category_id, key_id, insert_time)
 )
 EOC
 
@@ -1534,6 +1534,7 @@ CREATE TABLE clustertrack2 (
     timeactive INT UNSIGNED NOT NULL,
     clusterid SMALLINT UNSIGNED,
     accountlevel SMALLINT UNSIGNED,
+    journaltype char(1),
 
     INDEX (timeactive, clusterid)
 )
@@ -2985,7 +2986,7 @@ CREATE TABLE content_filter_data (
   data mediumblob NOT NULL,
 
   PRIMARY KEY (userid,filterid)
-) 
+)
 EOC
 
 register_tablecreate('sitekeywords', <<'EOC');
@@ -3846,6 +3847,11 @@ EOF
                   q{ALTER TABLE clustertrack2 ADD COLUMN accountlevel SMALLINT UNSIGNED AFTER clusterid} );
     }
 
+    unless ( column_type( 'clustertrack2', 'journaltype' ) ) {
+        do_alter( 'clustertrack2',
+                  q{ALTER TABLE clustertrack2 ADD COLUMN journaltype char(1) AFTER accountlevel} );
+    }
+
     unless ( column_type( 'acctcode_promo', 'suggest_journalid' ) ) {
         do_alter( 'acctcode_promo',
                   q{ALTER TABLE acctcode_promo ADD COLUMN suggest_journalid INT UNSIGNED} );
diff -r 9f08948db033 -r 4c2d441485d3 cgi-bin/DW/Controller/SiteStats.pm
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cgi-bin/DW/Controller/SiteStats.pm	Mon Jul 05 22:57:18 2010 +0800
@@ -0,0 +1,188 @@
+#!/usr/bin/perl
+#
+# DW::Controller::SiteStats
+#
+# Controller module for new DW stats (public and restricted)
+#
+# Authors:
+#      Afuna <coder.dw@afunamatata.com>
+#      Pau Amma <pauamma@cpan.org>
+#
+# Copyright (c) 2009-2010 by Dreamwidth Studios, LLC.
+#
+# This program is free software; you may redistribute it and/or modify it under
+# the same terms as Perl itself. For a copy of the license, please reference
+# 'perldoc perlartistic' or 'perldoc perlgpl'.
+
+=head1 NAME
+
+DW::Controller::SiteStats -- Controller for new DW stats (public and restricted)
+
+=head1 SYNOPSIS
+
+  use DW::Controller::SiteStats; # That's all there is to it.
+
+=cut
+
+use strict;
+use warnings;
+
+package DW::Controller::Sitestats;
+
+use DW::Controller;
+use DW::Routing;
+use DW::Template;
+use DW::StatStore;
+use DW::StatData;
+
+LJ::ModuleLoader::autouse_subclasses( 'DW::StatData' );
+
+DW::Routing->register_string( '/stats/site', \&stats_page, app => 1,
+                              args => [ 'stats/site.tt', \&public_data, 1 ] );
+
+DW::Routing->register_string( '/admin/stats', \&stats_page, app => 1,
+                              args => [ 'admin/stats.tt', \&admin_data, 0,
+                                        'payments' ] );
+
+=head1 Internals
+
+=head2 C<< DW::Controller::SiteStats::stats_page( $opts ) >>
+
+C<< $opts->args >> is C<< [ $template, $source, $anon_ok, $privs ] >>, where
+
+=over
+
+=item $template -- filename of template, relative to views/
+
+=item $source -- subref to retrieve stat data passed to template
+
+=item $anon_ok -- true if anonymous not-logged-in access to the page is allowed
+
+=item $privs -- (optional) privs needed, in the privcheck => controller format
+
+=back
+
+=cut
+
+sub stats_page {
+    my ( $template, $source, $anon_ok, $privs ) = @{ $_[0]->args };
+
+    my ( $ok, $rv ) = controller( anonymous => $anon_ok, privcheck => $privs );
+
+    return $rv unless $ok;
+
+    my %vars = ( %$rv, %{ $source->() } );
+
+    return DW::Template->render_template( $template, \%vars );
+}
+
+=head2 C<< _make_a_number( $value ) >>
+
+Internal - return $value forced to a number
+
+=cut
+
+sub _make_a_number {
+    return defined( $_[0] ) ? $_[0] + 0 : 0;
+}
+
+=head2 C<< _dashes_to_underlines( $string ) >>
+
+Internal - returns $string with - characters changed to _
+
+=cut
+
+sub _dashes_to_underlines {
+    my $undashed = $_[0];
+    $undashed =~ tr/\-/_/;
+    return $undashed;
+}
+
+=head2 C<< DW::Controller::SiteStats::public_data( ) >>
+
+Public stats data
+
+Returns hashref of variables to pass to the template. Note: doesn't check
+or care whether user should have access. That's for the caller to do.
+
+=cut
+
+sub public_data {
+    my $vars = {};
+
+    # Accounts by type
+    my $accounts_by_type = DW::StatData::AccountsByType->load_latest( DW::StatStore->get( "accounts" ) );
+    $vars->{accounts_by_type}
+        = { map { _dashes_to_underlines( $_ )
+                  => _make_a_number( $accounts_by_type->value( $_ ) ) }
+                @{$accounts_by_type->keylist} }
+        if defined $accounts_by_type;
+
+    # Active accounts by time since last active, level, and type
+    my $active_accounts = DW::StatData::ActiveAccounts->load_latest( DW::StatStore->get( "active" ) );
+    $vars->{active_accounts}
+        = { map { _dashes_to_underlines( $_ )
+                  => _make_a_number( $active_accounts->value( $_ ) ) }
+                @{$active_accounts->keylist} }
+        if defined $active_accounts;
+
+    # Paid accounts by level
+    my $paid = DW::StatData::PaidAccounts->load_latest( DW::StatStore->get( "paid" ) );
+    $vars->{paid}
+        = { map { _dashes_to_underlines( $_ )
+                  => _make_a_number( $paid->value( $_ ) ) }
+                @{$paid->keylist} }
+        if defined $paid;
+
+    return $vars;
+}
+
+=head2 C<< DW::Controller::SiteStats::admin_data( ) >>
+
+Admin stats data
+
+Returns hashref of variables to pass to the template. Note: doesn't check or
+or care whether user should have access. That's for the caller to do.
+
+=cut
+
+sub admin_data {
+    my $vars = public_data( @_ ); # Just in case it gets arguments someday.
+
+<<COMMENT;
+
+FIXME: remove this when you have implemented them all 
+
+* Number of accounts, total (done)
+* Number of accounts active (done)
+* Number of paid accounts (by payment level) (done)
+  -- as a percentage of total accounts (done)
+  -- as a percentage of active accounts (done)
+  -- number of active paid accounts (done)
+  -- number of inactive paid accounts (done)
+* Number of payments in last 1d/2d/5d/7d/1m/3m/1y
+  -- broken down by which payment level/payment item chosen
+  -- and divided into new payments vs. renewals
+  -- and expressed as a dollar amount taken in during that time
+* Number of lapsed paid accounts in last 1d/2d/5d/7d/1m/3m/1y
+  -- and renewed within 7d/14d/1m
+  -- and not renewed within 7d/14d/1m
+  -- and as a percentage of total paid accounts
+* Percent churn over last 7d/1m/3m/1y
+ -- (churn formula: total lapsed paid accounts that don't renew within 7d/total
+paid accounts * 100)
+* Number of paid accounts that were created via payment (no code)
+* Number of paid accounts that were created via code, then paid
+  -- within 1d/2d/5d/7d/1m/3m/1y of creation
+* Total refunds issued within last 7d/1m/3m/1y
+  -- with dollar amount refunded
+  -- with fees added to dollar amount refunded
+* Total chargebacks/PayPal refunds within last 7d/1m/3m/1y
+  -- with dollar amount charged back
+  -- with fees added to dollar amount charged back
+COMMENT
+
+    return $vars;
+}
+
+1;
diff -r 9f08948db033 -r 4c2d441485d3 cgi-bin/DW/StatData/ActiveAccounts.pm
--- a/cgi-bin/DW/StatData/ActiveAccounts.pm	Mon Jul 05 22:26:30 2010 +0800
+++ b/cgi-bin/DW/StatData/ActiveAccounts.pm	Mon Jul 05 22:57:18 2010 +0800
@@ -7,7 +7,7 @@
 #      Pau Amma <pauamma@cpan.org>
 #      Some code based off bin/maint/stats.pl
 #
-# Copyright (c) 2009 by Dreamwidth Studios, LLC.
+# Copyright (c) 2009-2010 by Dreamwidth Studios, LLC.
 #
 # This program is free software; you may redistribute it and/or modify it under
 # the same terms as Perl itself. For a copy of the license, please reference
@@ -29,13 +29,14 @@ An account is counted as active when it 
 An account is counted as active when it logs in, when it posts an entry (when
 posting to a community, both the poster and the community are marked active),
 or when it posts or edits a comment.
- 
+
 =cut
 
 use strict;
 use warnings;
 
 use base 'DW::StatData';
+use DW::Pay;
 
 sub category { "active" }
 sub name     { "Active Accounts" }
@@ -45,7 +46,8 @@ sub keylist {
     my @levels = ( 'unknown', values %{DW::Pay::all_shortnames()} );
     my @keys = ();
     foreach my $k ( keys %key_to_days ) {
-        push @keys, $k, map "$k-$_", @levels;
+        push @keys, $k, map { +"$k-$_", "$k-$_-P", "$k-$_-C", "$k-$_-I" }
+                            @levels;
     }
     return \@keys;
 }
@@ -58,25 +60,29 @@ Collects data for the following keys:
 
 =over
 
-=item active_1d, active_1d-I<< level name >>
+=item active_1d, active_1d-I<< level name >>, active_1d-I<< level name >>-I<< type letter >>
 
-Number of accounts active in the last 24 hours (total and for each account
-level)
+Number of accounts active in the last 24 hours (total, for each account
+level, and for each account level and type - for personal, community, and
+identity accounts only)
 
-=item active_7d, active_7d-I<< level name >>
+=item active_7d, active_7d-I<< level name >>, active_7d-I<< level name >>-I<< type letter >>
 
-Number of accounts active in the last 168 (7*24) hours (total and for each
-account level)
+Number of accounts active in the last 168 (7*24) hours (total, for each
+account level, and for each account level and type - for personal, community,
+and identity accounts only)
 
-=item active_30d, active_30d-I<< level name >>
+=item active_30d, active_30d-I<< level name >>, active_30d-I<< level name >>-I<< type letter >>
 
-Number of accounts active in the last 720 (30*24) hours (total and for each
-account level)
+Number of accounts active in the last 720 (30*24) hours (total, for each
+account level, and for each account level and type - for personal, community,
+and identity accounts only)
 
 =back
 
 In the above, I<< level name >> is any of the account level names returned by
-C<< DW::Pay::all_shortnames >> or "unknown".
+C<< DW::Pay::all_shortnames >> or "unknown", and I<< type letter >> is P for
+personal, C for community, or I for identity (OpenID, etc).
 
 =cut
 
@@ -88,12 +94,14 @@ sub collect {
     my @levels = ( '', 'unknown', values %$shortnames );
 
     foreach my $k ( @keys ) {
-        my ( $keyprefix, $keylevel ) = split( '-', $k );
+        my ( $keyprefix, $keylevel, $keytype ) = split( '-', $k );
         $keylevel ||= '';
+        $keytype ||= '';
 
         die "Unknown statkey $k for $class"
             unless exists $key_to_days{$keyprefix}
-                   and grep { $_ eq $keylevel } @levels;
+                   and grep { $_ eq $keylevel } @levels
+                   and $keytype =~ /^[PCI]?$/;
 
         $max_days = $key_to_days{$keyprefix}
             if $max_days < $key_to_days{$keyprefix};
@@ -105,22 +113,25 @@ sub collect {
 
         my $sth = $dbr->prepare( qq{
             SELECT FLOOR((UNIX_TIMESTAMP()-timeactive)/86400) as days,
-                   accountlevel, COUNT(*)
+                   accountlevel, journaltype, COUNT(*)
             FROM clustertrack2
             WHERE timeactive > UNIX_TIMESTAMP()-?
-            GROUP BY days, accountlevel } );
+            GROUP BY days, accountlevel, journaltype } );
         $sth->execute( $max_days*86400 );
 
-        while ( my ( $days, $level, $active ) = $sth->fetchrow_array ) {
+        while ( my ( $days, $level, $type, $active ) = $sth->fetchrow_array ) {
             $level = ( defined $level ) ? $shortnames->{$level} : 'unknown';
+            $type ||= '';
 
             # which day interval(s) does this fall in?
             # -- in last day, in last 7, in last 30?
             foreach my $k ( @keys ) {
-                my ( $keyprefix, $keylevel ) = split( '-', $k );
+                my ( $keyprefix, $keylevel, $keytype ) = split( '-', $k );
                 $keylevel ||= '';
+                $keytype ||= '';
                 if ( $days < $key_to_days{$keyprefix}
-                     && ( $keylevel eq $level || $keylevel eq '' ) ) {
+                     && ( $keylevel eq $level || $keylevel eq '' )
+                     && ( $keytype eq $type || $keytype eq '' ) ) {
                     $data{$k} += $active;
                 }
             }
@@ -132,7 +143,9 @@ sub collect {
 
 =head1 BUGS
 
-Bound to be some.
+Because not all account types are collected separately, only P/C/I, but the
+per-level stats count all types, the numbers don't add up. This is arguably
+a bug in the design.
 
 =head1 AUTHORS
 
@@ -140,7 +153,7 @@ Pau Amma <pauamma@cpan.org>, with some c
 
 =head1 COPYRIGHT AND LICENSE
 
-Copyright (c) 2009 by Dreamwidth Studios, LLC.
+Copyright (c) 2009-2010 by Dreamwidth Studios, LLC.
 
 This program is free software; you may redistribute it and/or modify it under
 the same terms as Perl itself. For a copy of the license, please reference
diff -r 9f08948db033 -r 4c2d441485d3 cgi-bin/LJ/User.pm
--- a/cgi-bin/LJ/User.pm	Mon Jul 05 22:26:30 2010 +0800
+++ b/cgi-bin/LJ/User.pm	Mon Jul 05 22:57:18 2010 +0800
@@ -238,7 +238,7 @@ sub create_personal {
         }
     }
     # if we have initial friends for new accounts, add them.
-    # TODO(mark): INITIAL_FRIENDS should be moved/renamed.
+    # FIXME(mark): INITIAL_FRIENDS should be moved/renamed.
     foreach my $friend ( @LJ::INITIAL_FRIENDS ) {
         my $friendid = LJ::get_userid( $friend )
             or next;
@@ -271,8 +271,8 @@ sub create_personal {
     }
 
     # populate some default friends groups
-    # TODO(mark): this should probably be removed or refactored, especially since
-    #             editfriendgroups is dying/dead
+    # FIXME(mark): this should probably be removed or refactored, especially
+    # since editfriendgroups is dying/dead
 #    LJ::do_request(
 #                   {
 #                       'mode'           => 'editfriendgroups',
@@ -331,8 +331,8 @@ sub create_syndicated {
 
 sub delete_and_purge_completely {
     my $u = shift;
-    # TODO: delete from user tables
-    # TODO: delete from global tables
+    # FIXME: delete from user tables
+    # FIXME: delete from global tables
     my $dbh = LJ::get_db_writer();
 
     my @tables = qw(user useridmap reluser priv_map infohistory email password);
@@ -388,7 +388,7 @@ sub who_invited {
 
 sub get_previous_statusvis {
     my $u = shift;
-    
+
     my $extra = $u->selectcol_arrayref(
         "SELECT extra FROM userlog WHERE userid=? AND action='accountstatus' ORDER BY logtime DESC",
         undef, $u->userid );
@@ -723,7 +723,7 @@ sub last_updated {
     my $ago_text = LJ::ago_text( $secondsold );
 
     if ( $u->timeupdate ) {
-        return LJ::Lang::ml( 'lastupdated.ago', 
+        return LJ::Lang::ml( 'lastupdated.ago',
             { timestamp => $lastupdated, agotext => $ago_text });
     } else {
         return LJ::Lang::ml ( 'lastupdated.never' );
@@ -1037,7 +1037,7 @@ sub get_timeactive {
     my $memkey = [$u->userid, "timeactive:" . $u->userid];
     my $active;
     unless (defined($active = LJ::MemCache::get($memkey))) {
-        # TODO: die if unable to get handle? This was left verbatim from
+        # FIXME: die if unable to get handle? This was left verbatim from
         # refactored code.
         my $dbcr = LJ::get_cluster_def_reader($u) or return 0;
         $active = $dbcr->selectrow_array("SELECT timeactive FROM clustertrack2 ".
@@ -1164,7 +1164,7 @@ sub make_login_session {
     unless ( $fake_login ) {
         # activity for cluster usage tracking
         LJ::mark_user_active($u, 'login');
-    
+
         # activity for global account number tracking
         $u->note_activity('A');
     }
@@ -2807,7 +2807,7 @@ sub sticky_entry {
         unless ( $input ) {
             $u->set_prop( sticky_entry => '' );
             return 1;
-        } 
+        }
         #also takes URL
         my $ditemid;
         if ( $input =~ m!/(\d+)\.html! ) {
@@ -2817,11 +2817,11 @@ sub sticky_entry {
         } else {
             return 0;
         }
-        
+
         # Validate the entry
         my $item = LJ::Entry->new( $u, ditemid => $ditemid );
         return 0 unless $item && $item->valid;
-        
+
         $u->set_prop( sticky_entry => $ditemid );
         return 1;
     }
@@ -4137,7 +4137,7 @@ sub emails_visible {
         push @emails, $u->email_raw;
     } elsif ( $u->prop( 'opt_whatemailshow' ) eq "D" || $u->prop( 'opt_whatemailshow' ) eq "V" ) {
         push @emails, $u->prop( 'opt_profileemail' );
-    } 
+    }
 
     if ($LJ::USER_EMAIL && $useremail_cap) {
         if ($whatemail eq "B" || $whatemail eq "V" || $whatemail eq "L") {
@@ -4292,7 +4292,7 @@ sub draft_text {
 #           'start_date' - UTC date after which to look for match
 #           'end_date' - UTC date before which to look for match
 #           'return' - if 'count' just return the count
-#           TODO: Add caching?
+#           FIXME: Add caching?
 # </LJFUNC>
 sub get_post_ids {
     my ($u, %opts) = @_;
@@ -4488,8 +4488,8 @@ sub set_draft_text {
         push @methods, [ "append", $appending, 40 + length $new ];
     }
 
-    # TODO: prepending/middle insertion (the former being just the latter), as well
-    # appending, wihch we could then get rid of
+    # FIXME: prepending/middle insertion (the former being just the latter), as
+    # well as appending, wihch we could then get rid of
 
     # try the methods in increasing order
     foreach my $m (sort { $a->[2] <=> $b->[2] } @methods) {
@@ -6505,6 +6505,9 @@ sub mark_user_active {
     return 0 unless $u;   # do not auto-vivify $u
     my $uid = $u->userid;
     return 0 unless $uid && $u->clusterid;
+    # FIXME: return 1 instead? Callers don't use the return value, so I'm not
+    # sure whether 0 means "some error happened" or just "nothing done"
+    return 0 unless $u->is_personal || $u->is_community || $u->is_identity;
 
     # Update the clustertrack2 table, but not if we've done it for this
     # user in the last hour.  if no memcache servers are configured
@@ -6514,9 +6517,12 @@ sub mark_user_active {
 
         return 0 unless $u->writer;
         my $active = time();
-        $u->do( "REPLACE INTO clustertrack2 SET ".
-                "userid=?, timeactive=?, clusterid=?, accountlevel=?", undef,
-                $uid, $active, $u->clusterid, DW::Pay::get_current_account_status( $uid ) ) or return 0;
+        $u->do( qq{ REPLACE INTO clustertrack2
+                        SET userid=?, timeactive=?, clusterid=?,
+                        accountlevel=?, journaltype=? },
+                undef, $uid, $active, $u->clusterid,
+                DW::Pay::get_current_account_status( $uid ), $u->journaltype )
+            or return 0;
 
         my $memkey = [$u->userid, "timeactive:" . $u->userid];
         LJ::MemCache::set($memkey, $active, 86400);
@@ -7586,7 +7592,7 @@ sub user_search_display {
         # when the site is overloaded we don't always load the users
         # we request.
         next unless LJ::isu($u);
-        
+
         $ret .= "<div class='user-search-display'>";
         $ret .= "<table style='height: 105px'><tr>";
 
@@ -7735,8 +7741,8 @@ sub rate_check {
 
     # would this transaction go over the limit?
     if ($sum + $count > $opp) {
-        # TODO: optionally log to rateabuse, unless caller is doing it themselves
-        # somehow, like with the "loginstall" table.
+        # FIXME: optionally log to rateabuse, unless caller is doing it
+        # themselves somehow, like with the "loginstall" table.
         return 0;
     }
 
@@ -8275,7 +8281,7 @@ sub journal_base
             }
         }
     }
- 
+
     if ( $LJ::ONLY_USER_VHOSTS ) {
         my $rule = $u ? $LJ::SUBDOMAIN_RULES->{$u->journaltype} : undef;
         $rule ||= $LJ::SUBDOMAIN_RULES->{P};
diff -r 9f08948db033 -r 4c2d441485d3 htdocs/admin/stats.bml
--- a/htdocs/admin/stats.bml	Mon Jul 05 22:26:30 2010 +0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,142 +0,0 @@
-<?_c
-#
-# admin/stats.bml
-#
-# Admin-level statistics
-#
-# Authors:
-#      Afuna <coder.dw@afunamatata.com>
-#
-# Copyright (c) 2009 by Dreamwidth Studios, LLC.
-#
-# This program is free software; you may redistribute it and/or modify it under
-# the same terms as Perl itself. For a copy of the license, please reference
-# 'perldoc perlartistic' or 'perldoc perlgpl'.
-#
-_c?><?page
-body<=
-<?_code
-{
-    use strict;
-    use warnings;
-    use vars qw/ $title /;
-
-    # translated/custom page title can go here
-    $title = $ML{'/admin/index.bml.admin.stats.link'};
-
-    my $remote = LJ::get_remote();
-
-    return BML::ml( "admin.noprivserror", { numprivs => "1", needprivs => "<b>payments</b>"} )
-        unless $LJ::IS_DEV_SERVER || ( $remote && $remote->has_priv( "payments" ) );
-
-    my $ret;
-    
-    use DW::StatStore;
-    use DW::StatData;
-    LJ::ModuleLoader::autouse_subclasses( 'DW::StatData' );
-
-    # FIXME: refactor stuff common with stats/site.bml into... something. (A LJ::Widget?)
-    # FIXME: finish stripping
-
-    # number of accounts, total
-    my $accounts_by_type = DW::StatData::AccountsByType->load_latest( DW::StatStore->get( "accounts" ) );
-    my $total; # Used in paid account stats below
-    if ( defined $accounts_by_type ) {
-        $ret .= "<h2>Number of accounts</h2>";
-        $ret .= "<ul>";
-        $ret .= "<li><label>" . $ML{"/stats/site.bml.accounts.bytype.$_"} . "</label> "
-                    . $accounts_by_type->value( $_ ) . "</li>"
-            foreach qw/ total personal identity /;
-        $ret .= "</ul>";
-        $total = $accounts_by_type->value( 'total' );
-    }
-
-    # number of accounts, active
-    my $active_accounts = DW::StatData::ActiveAccounts->load_latest( DW::StatStore->get( "active" ) );
-    my ( $active, $active_allpaid ); # Used in paid account stats below
-    $ret .= "<h2>$ML{'/stats/site.bml.active.title'}</h2><p>$ML{'/stats/site.bml.active.desc'}</p>";
- 
-    if ( defined $active_accounts ) {
-        $ret .= "<ul>";
-        $ret .= "<li>" . $ML{"/stats/site.bml.active.bytime.$_"} . " "
-                . $active_accounts->value( $_ ) . "</li>"
-            foreach qw/ active_1d active_7d active_30d /;
-        $ret .= "</ul>";
-        $active = $active_accounts->value( 'active_30d' );
-        $active_allpaid = $active_accounts->value( 'active_30d-paid' )
-                          + $active_accounts->value( 'active_30d-premium' )
-                          + $active_accounts->value( 'active_30d-seed' );
-    } else {
-        $ret .= $ML{'/stats/site.bml.error.notavailable'};
-    }
-    
-    # Paid accounts (by level), with % of total and active
-    my $paid = DW::StatData::PaidAccounts->load_latest( DW::StatStore->get( "paid" ) );
-
-    $ret .= "<h2>$ML{'/stats/site.bml.paid.title'}</h2>";
-    if ( defined $paid ) {
-        $ret .= "<table><tr>";
-        $ret .= "<th>" . $ML{"/stats/site.bml.paid.colhdr.$_"} . "</th>"
-            foreach qw/ level number pct_total pct_active /;
-        $ret .= "</tr>\n";
-
-        foreach my $level ( qw( paid premium seed ) ) {
-            $ret .= "<tr><th>" . $ML{"/stats/site.bml.paid.rowhdr.$level"} . "</th>";
-            my $n = $paid->value( $level ) || 0;
-            $ret .= "<td class='stats'>$n</td>";
-            $ret .= "<td class='stats'>"
-                    . ( defined $total ? int( 100 * $n / $total ) : "" )
-                    . "</td>";
-            $ret .= "<td class='stats'>"
-                    . ( defined $active ? int( 100 * $n / $active ) : "" )
-                    . "</td></tr>\n";
-        }
-        $ret .= "<tr><th>$ML{'/stats/site.bml.paid.rowhdr.activepaid'}</th><td class='stats'>";
-        $ret .= $active_allpaid
-            if defined $active_allpaid;
-        $ret .= "</td></tr><tr><th>$ML{'/stats/site.bml.paid.rowhdr.inactivepaid'}</th><td class='stats'>";
-
-        $ret .= $paid->value( 'total' ) - $active_allpaid
-            if defined $active_allpaid;
-        $ret .= "</td></tr></table>";
-    } else {
-        $ret .= $ML{'/stats/site.bml.error.notavailable'};
-    }
-<<COMMENT;
-
-FIXME: remove this when you have implemented them all 
-
-* Number of accounts, total (done)
-* Number of accounts active (done)
-* Number of paid accounts (by payment level) (done)
-  -- as a percentage of total accounts (done)
-  -- as a percentage of active accounts (done)
-  -- number of active paid accounts (done)
-  -- number of inactive paid accounts (done)
-* Number of payments in last 1d/2d/5d/7d/1m/3m/1y
-  -- broken down by which payment level/payment item chosen
-  -- and divided into new payments vs. renewals
-  -- and expressed as a dollar amount taken in during that time
-* Number of lapsed paid accounts in last 1d/2d/5d/7d/1m/3m/1y
-  -- and renewed within 7d/14d/1m
-  -- and not renewed within 7d/14d/1m
-  -- and as a percentage of total paid accounts
-* Percent churn over last 7d/1m/3m/1y
- -- (churn formula: total lapsed paid accounts that don't renew within 7d/total
-paid accounts * 100)
-* Number of paid accounts that were created via payment (no code)
-* Number of paid accounts that were created via code, then paid
-  -- within 1d/2d/5d/7d/1m/3m/1y of creation
-* Total refunds issued within last 7d/1m/3m/1y
-  -- with dollar amount refunded
-  -- with fees added to dollar amount refunded
-* Total chargebacks/PayPal refunds within last 7d/1m/3m/1y
-  -- with dollar amount charged back
-  -- with fees added to dollar amount charged back
-COMMENT
-    return $ret;
-}
-_code?>
-<=body
-title=><?_code return $title; _code?>
-page?>
diff -r 9f08948db033 -r 4c2d441485d3 htdocs/stats/site.bml
--- a/htdocs/stats/site.bml	Mon Jul 05 22:26:30 2010 +0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +0,0 @@
-<?_c
-#
-# stats/site.bml
-#
-# New public statistics
-#
-# Authors:
-#      Afuna <coder.dw@afunamatata.com>
-#      Pau Amma <pauamma@cpan.org>
-#
-# Copyright (c) 2009 by Dreamwidth Studios, LLC.
-#
-# This program is free software; you may redistribute it and/or modify it under
-# the same terms as Perl itself. For a copy of the license, please reference
-# 'perldoc perlartistic' or 'perldoc perlgpl'.
-#
-_c?><?page
-body<=
-
-<?_code
-{
-    use strict;
-    use warnings;
-    use vars qw/ $title /;
-
-    use DW::StatStore;
-    use DW::StatData;
-
-    LJ::ModuleLoader::autouse_subclasses( 'DW::StatData' );
-
-    LJ::need_res( 'stc/sitestats.css' );
-    LJ::set_active_crumb( 'sitestats' );
-
-    $title = BML::ml( '.title', { sitenameshort => $LJ::SITENAMESHORT } );
-
-    # number of accounts (total+by type)
-    my $accounts_by_type = DW::StatData::AccountsByType->load_latest( DW::StatStore->get( "accounts" ) );
-    my $total; # Used in paid account stats below
-    my $ret = "<h2>$ML{'.accounts.title'}</h2>";
- 
-    if ( defined $accounts_by_type ) {
-        $ret .= "<ul>";
-        $ret .= "<li>" . $ML{".accounts.bytype.$_"} . " "
-                . $accounts_by_type->value( $_ ) . "</li>"
-            foreach qw/ total personal identity /;
-        $ret .= "</ul>";
-        $total = $accounts_by_type->value( 'total' );
-    } else {
-        $ret .= $ML{'.error.notavailable'};
-    }
-    
-    # number of active accounts (by time since last active)
-    my $active_accounts = DW::StatData::ActiveAccounts->load_latest( DW::StatStore->get( "active" ) );
-    my ( $active, $active_allpaid ); # Used in paid account stats below
-    $ret .= "<h2>$ML{'.active.title'}</h2><p>$ML{'.active.desc'}</p>";
- 
-    if ( defined $active_accounts ) {
-        $ret .= "<ul>";
-        $ret .= "<li>" . $ML{".active.bytime.$_"} . " "
-                . $active_accounts->value( $_ ) . "</li>"
-            foreach qw/ active_1d active_7d active_30d /;
-        $ret .= "</ul>";
-        $active = $active_accounts->value( 'active_30d' );
-        $active_allpaid = $active_accounts->value( 'active_30d-paid' )
-                          + $active_accounts->value( 'active_30d-premium' )
-                          + $active_accounts->value( 'active_30d-seed' );
-    } else {
-        $ret .= $ML{'.error.notavailable'};
-    }
- 
-    # Paid accounts (by level), with % of total and active
-    my $paid = DW::StatData::PaidAccounts->load_latest( DW::StatStore->get( "paid" ) );
-    $ret .= "<h2>$ML{'.paid.title'}</h2>";
- 
-    if ( defined $paid ) {
-        $ret .= "<table class='stats-matrix'><tr>";
-        $ret .= "<th>" . $ML{".paid.colhdr.$_"} . "</th>"
-            foreach qw/ level number pct_total pct_active /;
-        $ret .= "</tr>\n";
-        foreach my $level ( qw( paid premium seed ) ) {
-            $ret .= "<tr><th>" . $ML{".paid.rowhdr.$level"} . "</th>";
-            my $n = $paid->value( $level ) || 0;
-            $ret .= "<td class='stats'>$n</td>";
-            $ret .= "<td class='stats'>"
-                    . ( defined $total ? int( 100 * $n / $total ) : "" )
-                    . "</td>";
-            $ret .= "<td class='stats'>"
-                    . ( defined $active ? int( 100 * $n / $active ) : "" )
-                    . "</td></tr>\n";
-        }
-        $ret .= "<tr><th>$ML{'.paid.rowhdr.activepaid'}</th><td class='stats'>";
-        $ret .= $active_allpaid
-            if defined $active_allpaid;
-        $ret .= "</td></tr><tr><th>$ML{'.paid.rowhdr.inactivepaid'}</th><td class='stats'>";
-        $ret .= $paid->value( 'total' ) - $active_allpaid
-            if defined $active_allpaid;
-        $ret .= "</td></tr></table>";
-    } else {
-        $ret .= $ML{'.error.notavailable'};
-    }
-    
-    return $ret;
-}
-_code?>
-
-<=body
-title=><?_code return $title; _code?>
-page?>
diff -r 9f08948db033 -r 4c2d441485d3 htdocs/stats/site.bml.text
--- a/htdocs/stats/site.bml.text	Mon Jul 05 22:26:30 2010 +0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-;; -*- coding: utf-8 -*-
-
-.accounts.bytype.total=Total:
-
-.accounts.bytype.personal=Personal (users):
-
-.accounts.bytype.identity=External identity (OpenID, etc.):
-
-.accounts.title=Number of accounts
-
-.active.bytime.active_1d=Last 24 hours:
-
-.active.bytime.active_7d=Last 7 days:
-
-.active.bytime.active_30d=Last 30 days:
-
-.active.desc=These are accounts who logged in, posted an entry, commented, or edited a comment during the period indicated. For entries posted to communities, both the poster and the community are counted.
-
-.active.title=Active accounts
-
-.error.notavailable=(Sorry, those statistics aren't available right now.)
-
-.paid.colhdr.level=Level
-
-.paid.colhdr.number=Number
-
-.paid.colhdr.pct_total=% of total accounts
-
-.paid.colhdr.pct_active=% of active accounts
-
-.paid.rowhdr.activepaid=Active paid/premium/seed
-
-.paid.rowhdr.inactivepaid=Inactive paid/premium/seed
-
-.paid.rowhdr.paid=Paid
-
-.paid.rowhdr.premium=Premium
-
-.paid.rowhdr.seed=Seed
-
-.paid.title=Paid accounts
-
-.title=[[sitenameshort]] Site Statistics
diff -r 9f08948db033 -r 4c2d441485d3 views/admin/stats.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/admin/stats.tt	Mon Jul 05 22:57:18 2010 +0800
@@ -0,0 +1,53 @@
+[%#
+admin/stats.tt
+
+Admin-level statistics
+
+Authors:
+     Afuna <coder.dw@afunamatata.com>
+     Pau Amma <pauamma@cpan.org>
+
+Copyright (c) 2009-2010 by Dreamwidth Studios, LLC.
+
+This program is free software; you may redistribute it and/or modify it under
+the same terms as Perl itself. For a copy of the license, please reference
+'perldoc perlartistic' or 'perldoc perlgpl'.
+%]
+
+[% dw.need_res( 'stc/sitestats.css' ) %]
+[% scope = dw.ml_scope( ); CALL dw.ml_scope( '/stats/site.tt' );
+   INCLUDE stats/site.tt; CALL dw.ml_scope( scope ); %]
+[% sections.title = '.title' | ml( sitenameshort => site.nameshort ) %]
+
+[%#
+
+FIXME: remove this when you have implemented them all 
+
+* Number of accounts, total (done)
+* Number of accounts active (done)
+* Number of paid accounts (by payment level) (done)
+  -- as a percentage of total accounts (done)
+  -- as a percentage of active accounts (done)
+  -- number of active paid accounts (done)
+  -- number of inactive paid accounts (done)
+* Number of payments in last 1d/2d/5d/7d/1m/3m/1y
+  -- broken down by which payment level/payment item chosen
+  -- and divided into new payments vs. renewals
+  -- and expressed as a dollar amount taken in during that time
+* Number of lapsed paid accounts in last 1d/2d/5d/7d/1m/3m/1y
+  -- and renewed within 7d/14d/1m
+  -- and not renewed within 7d/14d/1m
+  -- and as a percentage of total paid accounts
+* Percent churn over last 7d/1m/3m/1y
+ -- (churn formula: total lapsed paid accounts that don't renew within 7d/total
+paid accounts * 100)
+* Number of paid accounts that were created via payment (no code)
+* Number of paid accounts that were created via code, then paid
+  -- within 1d/2d/5d/7d/1m/3m/1y of creation
+* Total refunds issued within last 7d/1m/3m/1y
+  -- with dollar amount refunded
+  -- with fees added to dollar amount refunded
+* Total chargebacks/PayPal refunds within last 7d/1m/3m/1y
+  -- with dollar amount charged back
+  -- with fees added to dollar amount charged back
+%]
diff -r 9f08948db033 -r 4c2d441485d3 views/admin/stats.tt.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/admin/stats.tt.text	Mon Jul 05 22:57:18 2010 +0800
@@ -0,0 +1,3 @@
+;; -*- coding: utf-8 -*-
+
+.title=Business Statistics
diff -r 9f08948db033 -r 4c2d441485d3 views/stats/site.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/stats/site.tt	Mon Jul 05 22:57:18 2010 +0800
@@ -0,0 +1,87 @@
+[%#
+stats/site.tt
+
+New public statistics
+
+Authors:
+     Afuna <coder.dw@afunamatata.com>
+     Pau Amma <pauamma@cpan.org>
+
+Copyright (c) 2009-2010 by Dreamwidth Studios, LLC.
+
+This program is free software; you may redistribute it and/or modify it under
+the same terms as Perl itself. For a copy of the license, please reference
+'perldoc perlartistic' or 'perldoc perlgpl'.
+%]
+
+[% dw.need_res( 'stc/sitestats.css' ) %]
+[% sections.title = '.title' | ml( sitenameshort => site.nameshort ) %]
+
+<p>[% '.note.roundedtonearesttenthofpct' | ml %]</p>
+
+<h2>[% '.accounts.title' | ml %]</h2>
+
+[%# number of accounts (total+by type) %]
+[% IF accounts_by_type.defined %]
+    <ul>
+    [% FOREACH t = [ 'total' 'personal' 'identity' ] %]
+        <li>[% ".accounts.bytype.$t" | ml %] [% accounts_by_type.$t %]</li>
+    [% END %]
+    </ul>
+    [% total_pc = accounts_by_type.personal + accounts_by_type.community %]
+[% ELSE %]
+    [% '.error.notavailable' | ml %]
+[% END %]
+
+[%# number of active accounts (by time since last active) %]
+<h2>[% '.active.title' | ml %]</h2><p>[% '.active.desc' | ml %]</p>
+[% IF active_accounts.defined %]
+    <ul>
+    [% FOREACH t = [ 'active_1d' 'active_7d' 'active_30d' ] %]
+        <li>[% ".active.bytime.$t" | ml %] [% active_accounts.$t %]</li>
+    [% END %]
+    </ul>
+    [% active_pc = active_accounts.active_30d_free_P
+                   + active_accounts.active_30d_paid_P
+                   + active_accounts.active_30d_premium_P
+                   + active_accounts.active_30d_seed_P
+                   + active_accounts.active_30d_free_C
+                   + active_accounts.active_30d_paid_C
+                   + active_accounts.active_30d_premium_C
+                   + active_accounts.active_30d_seed_C %]
+    [% active_allpaid = active_accounts.active_30d_paid
+                        + active_accounts.active_30d_premium
+                        + active_accounts.active_30d_seed %]
+[% ELSE %]
+    [% '.error.notavailable' | ml %]
+[% END %]
+
+[%# Paid accounts (by level), with % of total (P+C) and active %]
+<h2>[% '.paid.title' | ml %]</h2>
+[% IF paid.defined %]
+    <table class='stats-matrix'><tr>
+    [% FOREACH h = [ 'level' 'number' 'pct_total' 'pct_active' ] %]
+        <th>[% ".paid.colhdr.$h" | ml %]</th>
+    [% END %]
+    </tr>
+    [% allpaid = 0 %]
+    [% FOREACH level = [ 'paid' 'premium' 'seed' ] %]
+        <tr><th>[% ".paid.rowhdr.$level" | ml %]</th>
+        [% n = paid.$level.defined ? paid.$level : 0 %]
+        [% allpaid = allpaid + n %]
+        <td class='stats'>[% n %]</td>
+        <td class='stats'>
+        [%- 100 * n / total_pc | format "%.1f" IF total_pc != 0 %]</td>
+        <td class='stats'>
+        [%- 100 * n / active_pc | format "%.1f" IF active_pc != 0 %]</td>
+        </tr>
+    [% END %]
+    <tr><th>[% '.paid.rowhdr.activepaid' | ml %]</th><td class='stats'>
+    [% active_allpaid IF active_allpaid.defined %]
+    </td></tr><tr><th>[% '.paid.rowhdr.inactivepaid' | ml %]</th>
+    <td class='stats'>
+    [% allpaid - active_allpaid IF active_allpaid.defined %]
+    </td></tr></table>
+[% ELSE %]
+    [% '.error.notavailable' | ml %]
+[% END %]
diff -r 9f08948db033 -r 4c2d441485d3 views/stats/site.tt.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/stats/site.tt.text	Mon Jul 05 22:57:18 2010 +0800
@@ -0,0 +1,45 @@
+;; -*- coding: utf-8 -*-
+
+.accounts.bytype.total=Total:
+
+.accounts.bytype.personal=Personal (users):
+
+.accounts.bytype.identity=External identity (OpenID, etc.):
+
+.accounts.title=Number of accounts
+
+.active.bytime.active_1d=Last 24 hours:
+
+.active.bytime.active_7d=Last 7 days:
+
+.active.bytime.active_30d=Last 30 days:
+
+.active.desc=These are accounts who logged in, posted an entry, commented, or edited a comment during the period indicated. For entries posted to communities, both the poster and the community are counted.
+
+.active.title=Active accounts
+
+.error.notavailable=(Sorry, those statistics aren't available right now.)
+
+.note.roundedtonearesttenthofpct=Note: all percentages are rounded (up or down) to the nearest 0.1%.
+
+.paid.colhdr.level=Level
+
+.paid.colhdr.number=Number
+
+.paid.colhdr.pct_total=% of total accounts
+
+.paid.colhdr.pct_active=% of active accounts
+
+.paid.rowhdr.activepaid=Active paid/premium/seed
+
+.paid.rowhdr.inactivepaid=Inactive paid/premium/seed
+
+.paid.rowhdr.paid=Paid
+
+.paid.rowhdr.premium=Premium
+
+.paid.rowhdr.seed=Seed
+
+.paid.title=Paid accounts
+
+.title=[[sitenameshort]] Site Statistics
--------------------------------------------------------------------------------
kareila: (Default)

[personal profile] kareila 2010-07-06 07:17 pm (UTC)(link)
Is htdocs/stats.bml obsolete? (I just noticed it has a reference to an image - htdocs/stats/newbyday.png - that's not on the site.)