[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
pauamma.
Files modified:
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]](https://www.dreamwidth.org/img/silk/identity/user.png)
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 --------------------------------------------------------------------------------
no subject
no subject