aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/FixMyStreet/App/Controller
diff options
context:
space:
mode:
Diffstat (limited to 'perllib/FixMyStreet/App/Controller')
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin.pm153
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin/AreaStats.pm225
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin/Stats.pm75
-rw-r--r--perllib/FixMyStreet/App/Controller/Dashboard.pm381
4 files changed, 271 insertions, 563 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm
index 27aeb9e5b..b485ea2dc 100644
--- a/perllib/FixMyStreet/App/Controller/Admin.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin.pm
@@ -73,7 +73,7 @@ sub index : Path : Args(0) {
return $c->cobrand->admin();
}
- $c->forward('stats_by_state');
+ $c->forward('/admin/stats/state');
my @unsent = $c->cobrand->problems->search( {
state => [ FixMyStreet::DB::Result::Problem::open_states() ],
@@ -183,39 +183,6 @@ sub timeline : Path( 'timeline' ) : Args(0) {
return 1;
}
-sub questionnaire : Path('stats/questionnaire') : Args(0) {
- my ( $self, $c ) = @_;
-
- my $questionnaires = $c->model('DB::Questionnaire')->search(
- { whenanswered => { '!=', undef } },
- { group_by => [ 'ever_reported' ],
- select => [ 'ever_reported', { count => 'me.id' } ],
- as => [ qw/reported questionnaire_count/ ] }
- );
-
- my %questionnaire_counts = map {
- ( defined $_->get_column( 'reported' ) ? $_->get_column( 'reported' ) : -1 )
- => $_->get_column( 'questionnaire_count' )
- } $questionnaires->all;
- $questionnaire_counts{1} ||= 0;
- $questionnaire_counts{0} ||= 0;
- $questionnaire_counts{total} = $questionnaire_counts{0} + $questionnaire_counts{1};
- $c->stash->{questionnaires} = \%questionnaire_counts;
-
- $c->stash->{state_changes_count} = $c->model('DB::Questionnaire')->search(
- { whenanswered => \'is not null' }
- )->count;
- $c->stash->{state_changes} = $c->model('DB::Questionnaire')->search(
- { whenanswered => \'is not null' },
- {
- group_by => [ 'old_state', 'new_state' ],
- columns => [ 'old_state', 'new_state', { c => { count => 'id' } } ],
- },
- );
-
- return 1;
-}
-
sub bodies : Path('bodies') : Args(0) {
my ( $self, $c ) = @_;
@@ -504,7 +471,7 @@ sub fetch_contacts : Private {
my $contacts = $c->stash->{body}->contacts->search(undef, { order_by => [ 'category' ] } );
$c->stash->{contacts} = $contacts;
- $c->stash->{live_contacts} = $contacts->search({ state => { '!=' => 'deleted' } });
+ $c->stash->{live_contacts} = $contacts->not_deleted;
$c->stash->{any_not_confirmed} = $contacts->search({ state => 'unconfirmed' })->count;
if ( $c->get_param('text') && $c->get_param('text') eq '1' ) {
@@ -1660,122 +1627,6 @@ sub flagged : Path('flagged') : Args(0) {
return 1;
}
-sub stats_by_state : Path('stats/state') : Args(0) {
- my ( $self, $c ) = @_;
-
- my $problems = $c->cobrand->problems->summary_count;
-
- my %prob_counts =
- map { $_->state => $_->get_column('state_count') } $problems->all;
-
- %prob_counts =
- map { $_ => $prob_counts{$_} || 0 }
- ( FixMyStreet::DB::Result::Problem->all_states() );
- $c->stash->{problems} = \%prob_counts;
- $c->stash->{total_problems_live} += $prob_counts{$_} ? $prob_counts{$_} : 0
- for ( FixMyStreet::DB::Result::Problem->visible_states() );
- $c->stash->{total_problems_users} = $c->cobrand->problems->unique_users;
-
- my $comments = $c->cobrand->updates->summary_count;
-
- my %comment_counts =
- map { $_->state => $_->get_column('state_count') } $comments->all;
-
- $c->stash->{comments} = \%comment_counts;
-}
-
-sub stats_fix_rate : Path('stats/fix-rate') : Args(0) {
- my ( $self, $c ) = @_;
-
- $c->stash->{categories} = $c->cobrand->problems->categories_summary();
-}
-
-sub stats : Path('stats') : Args(0) {
- my ( $self, $c ) = @_;
-
- my $selected_body;
- if ( $c->user->is_superuser ) {
- $c->forward('fetch_all_bodies');
- $selected_body = $c->get_param('body');
- } else {
- $selected_body = $c->user->from_body->id;
- }
-
- if ( $c->cobrand->moniker eq 'zurich' ) {
- return $c->cobrand->admin_stats();
- }
-
- if ( $c->get_param('getcounts') ) {
-
- my ( $start_date, $end_date, @errors );
- my $parser = DateTime::Format::Strptime->new( pattern => '%d/%m/%Y' );
-
- $start_date = $parser-> parse_datetime ( $c->get_param('start_date') );
-
- push @errors, _('Invalid start date') unless defined $start_date;
-
- $end_date = $parser-> parse_datetime ( $c->get_param('end_date') ) ;
-
- push @errors, _('Invalid end date') unless defined $end_date;
-
- $c->stash->{errors} = \@errors;
- $c->stash->{start_date} = $start_date;
- $c->stash->{end_date} = $end_date;
-
- $c->stash->{unconfirmed} = $c->get_param('unconfirmed') eq 'on' ? 1 : 0;
-
- return 1 if @errors;
-
- my $bymonth = $c->get_param('bymonth');
- $c->stash->{bymonth} = $bymonth;
-
- $c->stash->{selected_body} = $selected_body;
-
- my $field = 'confirmed';
-
- $field = 'created' if $c->get_param('unconfirmed');
-
- my $one_day = DateTime::Duration->new( days => 1 );
-
-
- my %select = (
- select => [ 'state', { 'count' => 'me.id' } ],
- as => [qw/state count/],
- group_by => [ 'state' ],
- order_by => [ 'state' ],
- );
-
- if ( $c->get_param('bymonth') ) {
- %select = (
- select => [
- { extract => \"year from $field", -as => 'c_year' },
- { extract => \"month from $field", -as => 'c_month' },
- { 'count' => 'me.id' }
- ],
- as => [qw/c_year c_month count/],
- group_by => [qw/c_year c_month/],
- order_by => [qw/c_year c_month/],
- );
- }
-
- my $p = $c->cobrand->problems->to_body($selected_body)->search(
- {
- -AND => [
- $field => { '>=', $start_date},
- $field => { '<=', $end_date + $one_day },
- ],
- },
- \%select,
- );
-
- # in case the total_report count is 0
- $c->stash->{show_count} = 1;
- $c->stash->{states} = $p;
- }
-
- return 1;
-}
-
=head2 set_allowed_pages
Sets up the allowed_pages stash entry for checking if the current page is
diff --git a/perllib/FixMyStreet/App/Controller/Admin/AreaStats.pm b/perllib/FixMyStreet/App/Controller/Admin/AreaStats.pm
deleted file mode 100644
index 2058ea872..000000000
--- a/perllib/FixMyStreet/App/Controller/Admin/AreaStats.pm
+++ /dev/null
@@ -1,225 +0,0 @@
-package FixMyStreet::App::Controller::Admin::AreaStats;
-use Moose;
-use namespace::autoclean;
-use List::Util qw(sum);
-
-BEGIN { extends 'Catalyst::Controller'; }
-
-sub index : Path : Args(0) {
- my ( $self, $c ) = @_;
-
- my $user = $c->user;
-
- if ($user->is_superuser) {
- $c->forward('/admin/fetch_all_bodies');
- } elsif ( $user->from_body ) {
- $c->forward('load_user_body', [ $user->from_body->id ]);
- $c->stash->{body_id} = $user->from_body->id;
- if ($user->area_id) {
- $c->stash->{area_id} = $user->area_id;
- $c->forward('setup_area');
- $c->visit( 'stats' );
- } else {
- # visit body_stats so we load the list of child areas
- $c->visit( 'body_stats', [ $user->from_body->id], [] );
- }
- } else {
- $c->detach( '/page_error_404_not_found' );
- }
-}
-
-sub check_user : Private {
- my ( $self, $c, $body_id, $area_id ) = @_;
-
- my $user = $c->user;
-
- return if $user->is_superuser;
-
- if ($body_id and $user->from_body->id eq $body_id) {
- if (not $user->area_id) {
- return;
- } elsif ($area_id and $user->area_id eq $area_id) {
- return;
- }
- }
-
- $c->detach( '/page_error_404_not_found' );
-}
-
-sub setup_area : Private {
- my ($self, $c) = @_;
-
- my $area = mySociety::MaPit::call('area', $c->stash->{area_id} );
- $c->detach( '/page_error_404_not_found' ) if $area->{error};
- $c->stash->{area} = $area;
-}
-
-sub body_base : Chained('/') : PathPart('admin/areastats') : CaptureArgs(1) {
- my ($self, $c, $body_id) = @_;
-
- $c->forward('/admin/lookup_body', $body_id);
- $c->stash->{areas} = mySociety::MaPit::call('area/children', [ $c->stash->{body_id} ] );
-}
-
-sub body_stats : Chained('body_base') : PathPart('') : Args(0) {
- my ($self, $c) = @_;
-
- if ($c->get_param('area')) {
- $c->forward('check_user', [$c->stash->{body_id}, $c->get_param('area')]);
- $c->stash->{area_id} = $c->get_param('area');
- $c->forward('setup_area');
- } else {
- $c->forward('check_user', [$c->stash->{body_id}]);
- }
- $c->forward('stats');
-}
-
-sub stats : Private {
- my ($self, $c) = @_;
-
- my $date = DateTime->now->subtract(days => 30);
- # set it to midnight so we get consistent result through the day
- $date->truncate( to => 'day' );
-
- $c->forward('/admin/fetch_contacts');
-
- $c->stash->{template} = 'admin/areastats/area.html';
-
- my $dtf = $c->model('DB')->storage->datetime_parser;
- my $time = $dtf->format_datetime($date);
-
- my $params = {
- 'me.confirmed' => { '>=', $time },
- };
-
- my %area_param = ();
- if ($c->stash->{area}) {
- my $area_id = $c->stash->{area_id};
- $c->stash->{area_name} = $c->stash->{area}->{name};
- $params->{'problem.areas'} = { like => "%,$area_id,%" };
- %area_param = (
- areas => { like => "%,$area_id,%" },
- );
- } else {
- $c->stash->{area_name} = $c->stash->{body}->name;
- }
-
- my %by_category = map { $_->category => {} } $c->stash->{contacts}->all;
- my %recent_by_category = map { $_->category => 0 } $c->stash->{contacts}->all;
-
- my $state_map = {};
-
- $state_map->{$_} = 'open' foreach FixMyStreet::DB::Result::Problem->open_states;
- $state_map->{$_} = 'closed' foreach FixMyStreet::DB::Result::Problem->closed_states;
- $state_map->{$_} = 'fixed' foreach FixMyStreet::DB::Result::Problem->fixed_states;
- $state_map->{$_} = 'scheduled' foreach ('planned', 'action scheduled');
-
- # current problems by category and state
- my $problems = $c->model('DB::Problem')->to_body(
- $c->stash->{body}
- )->search(
- \%area_param,
- {
- group_by => [ 'category', 'state' ],
- select => [ 'category', 'state', { count => 'me.id' } ],
- as => [ qw/category state state_count/ ],
- }
- );
-
- while (my $p = $problems->next) {
- my $meta_state = $state_map->{$p->state};
- $by_category{$p->category}->{$meta_state} += $p->get_column('state_count');
- }
- $c->stash->{by_category} = \%by_category;
-
- # problems this month by state
- $c->stash->{$_} = 0 for values %$state_map;
-
- $c->stash->{open} = $c->model('DB::Problem')->to_body(
- $c->stash->{body}
- )->search(
- {
- %area_param,
- confirmed => { '>=' => $time },
- }
- )->count;
-
- my $comments = $c->model('DB::Comment')->to_body(
- $c->stash->{body}
- )->search(
- {
- %$params,
- 'me.id' => { 'in' => \"(select min(id) from comment where me.problem_id=comment.problem_id and problem_state not in ('', 'confirmed') group by problem_state)" },
- },
- {
- join => 'problem',
- group_by => [ 'problem_state' ],
- select => [ 'problem_state', { count => 'me.id' } ],
- as => [ qw/problem_state state_count/ ],
- }
- );
-
- while (my $comment = $comments->next) {
- my $meta_state = $state_map->{$comment->problem_state};
- $c->stash->{$meta_state} += $comment->get_column('state_count');
- }
-
- $params = {
- %area_param,
- 'me.confirmed' => { '>=', $time },
- };
-
- # problems this month by category
- my $recent_problems = $c->model('DB::Problem')->to_body(
- $c->stash->{body}
- )->search(
- $params,
- {
- group_by => [ 'category' ],
- select => [ 'category', { count => 'me.id' } ],
- as => [ qw/category category_count/ ],
- }
- );
-
- while (my $p = $recent_problems->next) {
- $recent_by_category{$p->category} += $p->get_column('category_count');
- }
- $c->stash->{recent_by_category} = \%recent_by_category;
-
- # average time to state change in last month
- $params = {
- %area_param,
- 'problem.confirmed' => { '>=', $time },
- };
-
- $comments = $c->model('DB::Comment')->to_body(
- $c->stash->{body}
- )->search(
- { %$params,
- 'me.id' => \"= (select min(id) from comment where me.problem_id=comment.problem_id)",
- 'me.problem_state' => { '!=' => 'confirmed' },
- },
- {
- select => [
- { avg => { extract => "epoch from me.confirmed-problem.confirmed" } },
- ],
- as => [ qw/time/ ],
- join => 'problem'
- }
- )->first;
- my $raw_average = $comments->get_column('time');
- if (defined $raw_average) {
- $c->stash->{average} = int( $raw_average / 60 / 60 / 24 + 0.5 );
- } else {
- $c->stash->{average} = -1;
- }
-}
-
-sub load_user_body : Private {
- my ($self, $c, $body_id) = @_;
-
- $c->stash->{body} = $c->model('DB::Body')->find($body_id)
- or $c->detach( '/page_error_404_not_found' );
-}
-
-1;
diff --git a/perllib/FixMyStreet/App/Controller/Admin/Stats.pm b/perllib/FixMyStreet/App/Controller/Admin/Stats.pm
new file mode 100644
index 000000000..2860b3531
--- /dev/null
+++ b/perllib/FixMyStreet/App/Controller/Admin/Stats.pm
@@ -0,0 +1,75 @@
+package FixMyStreet::App::Controller::Admin::Stats;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+sub index : Path : Args(0) {
+ my ( $self, $c ) = @_;
+ return $c->cobrand->admin_stats() if $c->cobrand->moniker eq 'zurich';
+}
+
+sub state : Local : Args(0) {
+ my ( $self, $c ) = @_;
+
+ my $problems = $c->cobrand->problems->summary_count;
+
+ my %prob_counts =
+ map { $_->state => $_->get_column('state_count') } $problems->all;
+
+ %prob_counts =
+ map { $_ => $prob_counts{$_} || 0 }
+ ( FixMyStreet::DB::Result::Problem->all_states() );
+ $c->stash->{problems} = \%prob_counts;
+ $c->stash->{total_problems_live} += $prob_counts{$_} ? $prob_counts{$_} : 0
+ for ( FixMyStreet::DB::Result::Problem->visible_states() );
+ $c->stash->{total_problems_users} = $c->cobrand->problems->unique_users;
+
+ my $comments = $c->cobrand->updates->summary_count;
+
+ my %comment_counts =
+ map { $_->state => $_->get_column('state_count') } $comments->all;
+
+ $c->stash->{comments} = \%comment_counts;
+}
+
+sub fix_rate : Path('fix-rate') : Args(0) {
+ my ( $self, $c ) = @_;
+
+ $c->stash->{categories} = $c->cobrand->problems->categories_summary();
+}
+
+sub questionnaire : Local : Args(0) {
+ my ( $self, $c ) = @_;
+
+ my $questionnaires = $c->model('DB::Questionnaire')->search(
+ { whenanswered => { '!=', undef } },
+ { group_by => [ 'ever_reported' ],
+ select => [ 'ever_reported', { count => 'me.id' } ],
+ as => [ qw/reported questionnaire_count/ ] }
+ );
+
+ my %questionnaire_counts = map {
+ ( defined $_->get_column( 'reported' ) ? $_->get_column( 'reported' ) : -1 )
+ => $_->get_column( 'questionnaire_count' )
+ } $questionnaires->all;
+ $questionnaire_counts{1} ||= 0;
+ $questionnaire_counts{0} ||= 0;
+ $questionnaire_counts{total} = $questionnaire_counts{0} + $questionnaire_counts{1};
+ $c->stash->{questionnaires} = \%questionnaire_counts;
+
+ $c->stash->{state_changes_count} = $c->model('DB::Questionnaire')->search(
+ { whenanswered => \'is not null' }
+ )->count;
+ $c->stash->{state_changes} = $c->model('DB::Questionnaire')->search(
+ { whenanswered => \'is not null' },
+ {
+ group_by => [ 'old_state', 'new_state' ],
+ columns => [ 'old_state', 'new_state', { c => { count => 'id' } } ],
+ },
+ );
+
+ return 1;
+}
+
+1;
diff --git a/perllib/FixMyStreet/App/Controller/Dashboard.pm b/perllib/FixMyStreet/App/Controller/Dashboard.pm
index 264845d40..834d9c8d6 100644
--- a/perllib/FixMyStreet/App/Controller/Dashboard.pm
+++ b/perllib/FixMyStreet/App/Controller/Dashboard.pm
@@ -3,8 +3,9 @@ use Moose;
use namespace::autoclean;
use DateTime;
-use File::Slurp;
use JSON::MaybeXS;
+use Path::Tiny;
+use Time::Piece;
BEGIN { extends 'Catalyst::Controller'; }
@@ -20,43 +21,21 @@ Catalyst Controller.
=cut
+sub auto : Private {
+ my ($self, $c) = @_;
+ $c->stash->{filter_states} = $c->cobrand->state_groups_inspect;
+ return 1;
+}
+
sub example : Local : Args(0) {
my ( $self, $c ) = @_;
$c->stash->{template} = 'dashboard/index.html';
- $c->stash->{filter_states} = $c->cobrand->state_groups_inspect;
-
- $c->stash->{children} = {};
- for my $i (1..3) {
- $c->stash->{children}{$i} = { id => $i, name => "Ward $i" };
- }
-
- # TODO Set up manual version of what the below would do
- #$c->forward( '/report/new/setup_categories_and_bodies' );
-
- # See if we've had anything from the dropdowns - perhaps vary results if so
- $c->stash->{ward} = $c->get_param('ward');
- $c->stash->{category} = $c->get_param('category');
- $c->stash->{q_state} = $c->get_param('state');
+ $c->stash->{group_by} = 'category+state';
eval {
- my $data = File::Slurp::read_file(
- FixMyStreet->path_to( 'data/dashboard.json' )->stringify
- );
- my $j = decode_json($data);
- if ( !$c->stash->{ward} && !$c->stash->{category} ) {
- $c->stash->{problems} = $j->{counts_all};
- } else {
- $c->stash->{problems} = $j->{counts_some};
- }
- $c->stash->{council} = $j->{council};
- $c->stash->{children} = $j->{wards};
- $c->stash->{category_options} = $j->{category_options};
- if ( lc($c->stash->{q_state}) eq 'all' or !$c->stash->{q_state} ) {
- $c->stash->{lists} = $j->{lists}->{all};
- } else {
- $c->stash->{lists} = $j->{lists}->{filtered};
- }
+ my $j = decode_json(path(FixMyStreet->path_to('data/dashboard.json'))->slurp_utf8);
+ $c->stash($j);
};
if ($@) {
my $message = _("There was a problem showing this page. Please try again later.") . ' ' .
@@ -77,14 +56,20 @@ sub check_page_allowed : Private {
$c->detach( '/auth/redirect' ) unless $c->user_exists;
$c->detach( '/page_error_404_not_found' )
- unless $c->user_exists && $c->user->from_body;
+ unless $c->user->from_body || $c->user->is_superuser;
+
+ my $body = $c->user->from_body;
+ if (!$body && $c->get_param('body')) {
+ # Must be a superuser, so allow query parameter if given
+ $body = $c->model('DB::Body')->find({ id => $c->get_param('body') });
+ }
- return $c->user->from_body;
+ return $body;
}
=head2 index
-Show the dashboard table.
+Show the summary statistics table.
=cut
@@ -95,127 +80,224 @@ sub index : Path : Args(0) {
$c->authenticate(undef, "access_token");
}
- my $body = $c->forward('check_page_allowed');
- $c->stash->{body} = $body;
+ my $body = $c->stash->{body} = $c->forward('check_page_allowed');
- # Set up the data for the dropdowns
- $c->stash->{filter_states} = $c->cobrand->state_groups_inspect;
+ if ($body) {
+ $c->stash->{body_name} = $body->name;
- # Just take the first area ID we find
- my $area_id = $body->body_areas->first->area_id;
+ my $area_id = $body->body_areas->first->area_id;
+ my $children = mySociety::MaPit::call('area/children', $area_id,
+ type => $c->cobrand->area_types_children,
+ );
+ $c->stash->{children} = $children;
- my $council_detail = mySociety::MaPit::call('area', $area_id );
- $c->stash->{council} = $council_detail;
+ $c->forward('/admin/fetch_contacts');
+ $c->stash->{contacts} = [ $c->stash->{contacts}->all ];
- my $children = mySociety::MaPit::call('area/children', $area_id,
- type => $c->cobrand->area_types_children,
- );
- $c->stash->{children} = $children;
+ # See if we've had anything from the body dropdowns
+ $c->stash->{category} = $c->get_param('category');
+ $c->stash->{ward} = $c->get_param('ward');
+ if ($c->user->area_id) {
+ $c->stash->{ward} = $c->user->area_id;
+ $c->stash->{body_name} = join "", map { $children->{$_}->{name} } grep { $children->{$_} } $c->user->area_id;
+ }
+ } else {
+ $c->forward('/admin/fetch_all_bodies');
+ }
- $c->stash->{all_areas} = { $area_id => $council_detail };
- $c->forward( '/report/new/setup_categories_and_bodies' );
+ $c->stash->{start_date} = $c->get_param('start_date');
+ $c->stash->{end_date} = $c->get_param('end_date');
+ $c->stash->{q_state} = $c->get_param('state') || '';
- # See if we've had anything from the dropdowns
+ $c->forward('construct_rs_filter');
- $c->stash->{ward} = $c->get_param('ward');
- $c->stash->{category} = $c->get_param('category');
+ if ( $c->get_param('export') ) {
+ $self->export_as_csv($c);
+ } else {
+ $self->generate_data($c);
+ }
+}
- my %where = (
- 'problem.state' => [ FixMyStreet::DB::Result::Problem->visible_states() ],
- );
+sub construct_rs_filter : Private {
+ my ($self, $c) = @_;
+
+ my %where;
$where{areas} = { 'like', '%,' . $c->stash->{ward} . ',%' }
if $c->stash->{ward};
$where{category} = $c->stash->{category}
if $c->stash->{category};
- $c->stash->{where} = \%where;
- my $prob_where = { %where };
- $prob_where->{'me.state'} = $prob_where->{'problem.state'};
- delete $prob_where->{'problem.state'};
- $c->stash->{prob_where} = $prob_where;
-
- my $dtf = $c->model('DB')->storage->datetime_parser;
- my %counts;
- my $now = DateTime->now( time_zone => FixMyStreet->local_time_zone );
- my $t = $now->clone->truncate( to => 'day' );
- $counts{wtd} = $c->forward( 'updates_search',
- [ $dtf->format_datetime( $t->clone->subtract( days => $t->dow - 1 ) ) ] );
- $counts{week} = $c->forward( 'updates_search',
- [ $dtf->format_datetime( $now->clone->subtract( weeks => 1 ) ) ] );
- $counts{weeks} = $c->forward( 'updates_search',
- [ $dtf->format_datetime( $now->clone->subtract( weeks => 4 ) ) ] );
- $counts{ytd} = $c->forward( 'updates_search',
- [ $dtf->format_datetime( $t->clone->set( day => 1, month => 1 ) ) ] );
-
- $c->stash->{problems} = \%counts;
+ my $state = $c->stash->{q_state};
+ if ( FixMyStreet::DB::Result::Problem->fixed_states->{$state} ) { # Probably fixed - council
+ $where{'me.state'} = [ FixMyStreet::DB::Result::Problem->fixed_states() ];
+ } elsif ( $state ) {
+ $where{'me.state'} = $state;
+ } else {
+ $where{'me.state'} = [ FixMyStreet::DB::Result::Problem->visible_states() ];
+ }
- # List of reports underneath summary table
+ my $dtf = $c->model('DB')->storage->datetime_parser;
+ my $date = DateTime->now( time_zone => FixMyStreet->local_time_zone )->subtract(days => 30);
+ $date->truncate( to => 'day' );
- $c->stash->{q_state} = $c->get_param('state') || '';
- if ( $c->stash->{q_state} eq 'fixed - council' ) {
- $prob_where->{'me.state'} = [ FixMyStreet::DB::Result::Problem->fixed_states() ];
- } elsif ( $c->stash->{q_state} ) {
- $prob_where->{'me.state'} = $c->stash->{q_state};
- }
- my $params = {
- %$prob_where,
- 'me.confirmed' => { '>=', $dtf->format_datetime( $now->clone->subtract( days => 30 ) ) },
- };
+ $where{'me.confirmed'} = { '>=', $dtf->format_datetime($date) };
- if ( $c->get_param('start_date') or $c->get_param('end_date') ) {
+ my $start_date = $c->stash->{start_date};
+ my $end_date = $c->stash->{end_date};
+ if ($start_date or $end_date) {
my @parts;
- if ($c->get_param('start_date')) {
- my $date = $dtf->parse_datetime( $c->get_param('start_date') );
+ if ($start_date) {
+ my $date = $dtf->parse_datetime($start_date);
push @parts, { '>=', $dtf->format_datetime( $date ) };
- $c->stash->{start_date} = $c->get_param('start_date');
}
- if ($c->get_param('end_date')) {
+ if ($end_date) {
my $one_day = DateTime::Duration->new( days => 1 );
- my $date = $dtf->parse_datetime( $c->get_param('end_date') );
+ my $date = $dtf->parse_datetime($end_date);
push @parts, { '<', $dtf->format_datetime( $date + $one_day ) };
- $c->stash->{end_date} = $c->get_param('end_date');
}
if (scalar @parts == 2) {
- $params->{'me.confirmed'} = [ -and => $parts[0], $parts[1] ];
+ $where{'me.confirmed'} = [ -and => $parts[0], $parts[1] ];
} else {
- $params->{'me.confirmed'} = $parts[0];
+ $where{'me.confirmed'} = $parts[0];
}
}
- my $problems_rs = $c->cobrand->problems->to_body($body)->search( $params );
- my @problems = $problems_rs->all;
+ $c->stash->{params} = \%where;
+ $c->stash->{problems_rs} = $c->cobrand->problems->to_body($c->stash->{body})->search( \%where );
+}
- my %problems;
- foreach (@problems) {
- if ($_->confirmed >= $now->clone->subtract(days => 7)) {
- push @{$problems{1}}, $_;
- } elsif ($_->confirmed >= $now->clone->subtract(days => 14)) {
- push @{$problems{2}}, $_;
- } else {
- push @{$problems{3}}, $_;
+sub generate_data {
+ my ($self, $c) = @_;
+
+ my $state_map = $c->stash->{state_map} = {};
+ $state_map->{$_} = 'open' foreach FixMyStreet::DB::Result::Problem->open_states;
+ $state_map->{$_} = 'closed' foreach FixMyStreet::DB::Result::Problem->closed_states;
+ $state_map->{$_} = 'fixed' foreach FixMyStreet::DB::Result::Problem->fixed_states;
+
+ $self->generate_grouped_data($c);
+ $self->generate_summary_figures($c);
+}
+
+sub generate_grouped_data {
+ my ($self, $c) = @_;
+ my $state_map = $c->stash->{state_map};
+
+ my $group_by = $c->get_param('group_by') || '';
+ my (%grouped, @groups, %totals);
+ if ($group_by eq 'category') {
+ %grouped = map { $_->category => {} } @{$c->stash->{contacts}};
+ @groups = qw/category/;
+ } elsif ($group_by eq 'state') {
+ @groups = qw/state/;
+ } elsif ($group_by eq 'month') {
+ @groups = (
+ { extract => \"month from confirmed", -as => 'c_month' },
+ { extract => \"year from confirmed", -as => 'c_year' },
+ );
+ } elsif ($group_by eq 'device+site') {
+ @groups = qw/cobrand service/;
+ } else {
+ $group_by = 'category+state';
+ @groups = qw/category state/;
+ %grouped = map { $_->category => {} } @{$c->stash->{contacts}};
+ }
+ my $problems = $c->stash->{problems_rs}->search(undef, {
+ group_by => [ map { ref $_ ? $_->{-as} : $_ } @groups ],
+ select => [ @groups, { count => 'me.id' } ],
+ as => [ @groups == 2 ? qw/key1 key2 count/ : qw/key1 count/ ],
+ } );
+ $c->stash->{group_by} = $group_by;
+
+ my %columns;
+ while (my $p = $problems->next) {
+ my %cols = $p->get_columns;
+ my ($col1, $col2) = ($cols{key1}, $cols{key2});
+ if ($group_by eq 'category+state') {
+ $col2 = $state_map->{$cols{key2}};
+ } elsif ($group_by eq 'month') {
+ $col1 = Time::Piece->strptime("2017-$cols{key1}-01", '%Y-%m-%d')->fullmonth;
}
+ $grouped{$col1}->{$col2} += $cols{count} if defined $col2;
+ $grouped{$col1}->{total} += $cols{count};
+ $totals{$col2} += $cols{count} if defined $col2;
+ $totals{total} += $cols{count};
+ $columns{$col2} = 1 if defined $col2;
}
- $c->stash->{lists} = \%problems;
- if ( $c->get_param('export') ) {
- $self->export_as_csv($c, $problems_rs, $body);
+ my @columns = keys %columns;
+ my @rows = keys %grouped;
+ if ($group_by eq 'month') {
+ my %months;
+ my @months = qw/January February March April May June
+ July August September October November December/;
+ @months{@months} = (0..11);
+ @rows = sort { $months{$a} <=> $months{$b} } @rows;
+ } elsif ($group_by eq 'state') {
+ my $state_map = $c->stash->{state_map};
+ my %map = (confirmed => 0, open => 1, fixed => 2, closed => 3);
+ @rows = sort {
+ my $am = $map{$a} // $map{$state_map->{$a}};
+ my $bm = $map{$b} // $map{$state_map->{$b}};
+ $am <=> $bm;
+ } @rows;
+ } else {
+ @rows = sort @rows;
+ }
+ $c->stash->{rows} = \@rows;
+ $c->stash->{columns} = \@columns;
+
+ $c->stash->{grouped} = \%grouped;
+ $c->stash->{totals} = \%totals;
+}
+
+sub generate_summary_figures {
+ my ($self, $c) = @_;
+ my $state_map = $c->stash->{state_map};
+
+ # problems this month by state
+ $c->stash->{"summary_$_"} = 0 for values %$state_map;
+
+ $c->stash->{summary_open} = $c->stash->{problems_rs}->count;
+
+ my $params = $c->stash->{params};
+ $params = { map { my $n = $_; s/me\./problem\./ unless /me\.confirmed/; $_ => $params->{$n} } keys %$params };
+
+ my $comments = $c->model('DB::Comment')->to_body(
+ $c->stash->{body}
+ )->search(
+ {
+ %$params,
+ 'me.id' => { 'in' => \"(select min(id) from comment where me.problem_id=comment.problem_id and problem_state not in ('', 'confirmed') group by problem_state)" },
+ },
+ {
+ join => 'problem',
+ group_by => [ 'problem_state' ],
+ select => [ 'problem_state', { count => 'me.id' } ],
+ as => [ qw/problem_state count/ ],
+ }
+ );
+
+ while (my $comment = $comments->next) {
+ my $meta_state = $state_map->{$comment->problem_state};
+ next if $meta_state eq 'open';
+ $c->stash->{"summary_$meta_state"} += $comment->get_column('count');
}
}
sub export_as_csv {
- my ($self, $c, $problems_rs, $body) = @_;
+ my ($self, $c) = @_;
require Text::CSV;
- my $problems = $problems_rs->search(
+ my $problems = $c->stash->{problems_rs}->search(
{}, { prefetch => 'comments', order_by => 'me.confirmed' });
my $filename = do {
my %where = (
- body => $body->id,
category => $c->stash->{category},
state => $c->stash->{q_state},
ward => $c->stash->{ward},
);
+ $where{body} = $c->stash->{body}->id if $c->stash->{body};
join '-',
$c->req->uri->host,
map {
@@ -308,88 +390,13 @@ sub export_as_csv {
$c->res->body( join "", @body );
}
-sub updates_search : Private {
- my ( $self, $c, $time ) = @_;
-
- my $body = $c->stash->{body};
-
- my $params = {
- %{$c->stash->{where}},
- 'me.confirmed' => { '>=', $time },
- };
-
- my $comments = $c->model('DB::Comment')->to_body($body)->search(
- $params,
- {
- group_by => [ 'problem_state' ],
- select => [ 'problem_state', { count => 'me.id' } ],
- as => [ qw/state state_count/ ],
- join => 'problem'
- }
- );
-
- my %counts =
- map { ($_->state||'-') => $_->get_column('state_count') } $comments->all;
- %counts =
- map { $_ => $counts{$_} || 0 }
- ('confirmed', 'investigating', 'in progress', 'closed', 'fixed - council',
- 'fixed - user', 'fixed', 'unconfirmed', 'hidden',
- 'partial', 'action scheduled', 'planned');
-
- $counts{'action scheduled'} += $counts{planned} || 0;
-
- for my $vars (
- [ 'time_to_fix', 'fixed - council' ],
- [ 'time_to_mark', 'in progress', 'action scheduled', 'investigating', 'closed' ],
- ) {
- my $col = shift @$vars;
- my $substmt = "select min(id) from comment where me.problem_id=comment.problem_id and problem_state in ('"
- . join("','", @$vars) . "')";
- $comments = $c->model('DB::Comment')->to_body($body)->search(
- { %$params,
- problem_state => $vars,
- 'me.id' => \"= ($substmt)",
- },
- {
- select => [
- { count => 'me.id' },
- { avg => { extract => "epoch from me.confirmed-problem.confirmed" } },
- ],
- as => [ qw/state_count time/ ],
- join => 'problem'
- }
- )->first;
- $counts{$col} = int( ($comments->get_column('time')||0) / 60 / 60 / 24 + 0.5 );
- }
-
- $counts{fixed_user} = $c->model('DB::Comment')->to_body($body)->search(
- { %$params, mark_fixed => 1, problem_state => undef }, { join => 'problem' }
- )->count;
-
- $params = {
- %{$c->stash->{prob_where}},
- 'me.confirmed' => { '>=', $time },
- };
- $counts{total} = $c->cobrand->problems->to_body($body)->search( $params )->count;
-
- $params = {
- %{$c->stash->{prob_where}},
- 'me.confirmed' => { '>=', $time },
- state => 'confirmed',
- '(select min(id) from comment where me.id=problem_id and problem_state is not null)' => undef,
- };
- $counts{not_marked} = $c->cobrand->problems->to_body($body)->search( $params )->count;
-
- return \%counts;
-}
-
=head1 AUTHOR
Matthew Somerville
=head1 LICENSE
-Copyright (c) 2012 UK Citizens Online Democracy. All rights reserved.
+Copyright (c) 2017 UK Citizens Online Democracy. All rights reserved.
Licensed under the Affero GPL.
=cut