diff options
Diffstat (limited to 'perllib/FixMyStreet/App/Controller/Reports.pm')
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Reports.pm | 270 |
1 files changed, 213 insertions, 57 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Reports.pm b/perllib/FixMyStreet/App/Controller/Reports.pm index 8f8205719..ec7a192b3 100644 --- a/perllib/FixMyStreet/App/Controller/Reports.pm +++ b/perllib/FixMyStreet/App/Controller/Reports.pm @@ -2,9 +2,9 @@ package FixMyStreet::App::Controller::Reports; use Moose; use namespace::autoclean; -use File::Slurp; use JSON::MaybeXS; use List::MoreUtils qw(any); +use Path::Tiny; use POSIX qw(strcoll); use RABX; use mySociety::MaPit; @@ -69,34 +69,16 @@ sub index : Path : Args(0) { } } - # Fetch all bodies - my @bodies = $c->model('DB::Body')->search({ - deleted => 0, - }, { - '+select' => [ { count => 'area_id' } ], - '+as' => [ 'area_count' ], - join => 'body_areas', - distinct => 1, - })->all; - @bodies = sort { strcoll($a->name, $b->name) } @bodies; - $c->stash->{bodies} = \@bodies; - $c->stash->{any_empty_bodies} = any { $_->get_column('area_count') == 0 } @bodies; + my $dashboard = $c->forward('load_dashboard_data'); - my $dashboard = eval { - my $data = File::Slurp::read_file( - FixMyStreet->path_to( '../data/all-reports-dashboard.json' )->stringify - ); - $c->stash(decode_json($data)); - return 1; - }; - my $table = eval { - my $data = File::Slurp::read_file( - FixMyStreet->path_to( '../data/all-reports.json' )->stringify - ); + my $table = !$c->stash->{body} && eval { + my $data = path(FixMyStreet->path_to('../data/all-reports.json'))->slurp_utf8; $c->stash(decode_json($data)); return 1; }; if (!$dashboard && !$table) { + $c->detach('/page_error_404_not_found') if $c->stash->{body}; + my $message = _("There was a problem showing the All Reports page. Please try again later."); if ($c->config->{STAGING_SITE}) { $message .= '</p><p>Perhaps the bin/update-all-reports script needs running. Use: bin/update-all-reports</p><p>' @@ -105,6 +87,26 @@ sub index : Path : Args(0) { $c->detach('/page_error_500_internal_error', [ $message ]); } + if ($c->stash->{body}) { + my $children = $c->stash->{body}->first_area_children; + unless ($children->{error}) { + $c->stash->{children} = $children; + } + } else { + # Fetch all bodies + my @bodies = $c->model('DB::Body')->search({ + deleted => 0, + }, { + '+select' => [ { count => 'area_id' } ], + '+as' => [ 'area_count' ], + join => 'body_areas', + distinct => 1, + })->all; + @bodies = sort { strcoll($a->name, $b->name) } @bodies; + $c->stash->{bodies} = \@bodies; + $c->stash->{any_empty_bodies} = any { $_->get_column('area_count') == 0 } @bodies; + } + # Down here so that error pages aren't cached. $c->response->header('Cache-Control' => 'max-age=3600'); } @@ -131,9 +133,24 @@ sub ward : Path : Args(2) { $c->forward('/auth/get_csrf_token'); + my @wards = split /\|/, $ward || ""; $c->forward( 'body_check', [ $body ] ); - $c->forward( 'ward_check', [ $ward ] ) - if $ward; + + my $body_short = $c->cobrand->short_name( $c->stash->{body} ); + $c->stash->{body_url} = '/reports/' . $body_short; + + if ($ward && $ward eq 'summary') { + if (my $actual_ward = $c->get_param('ward')) { + $ward = $c->cobrand->short_name({ name => $actual_ward }); + $c->res->redirect($ward); + $c->detach; + } + $c->cobrand->call_hook('council_dashboard_hook'); + $c->go('index'); + } + + $c->forward( 'ward_check', [ @wards ] ) + if @wards; $c->forward( 'check_canonical_url', [ $body ] ); $c->forward( 'stash_report_filter_status' ); $c->forward( 'load_and_group_problems' ); @@ -142,13 +159,10 @@ sub ward : Path : Args(2) { $c->detach('ajax', [ 'reports/_problem-list.html' ]); } - my $body_short = $c->cobrand->short_name( $c->stash->{body} ); $c->stash->{rss_url} = '/rss/reports/' . $body_short; $c->stash->{rss_url} .= '/' . $c->cobrand->short_name( $c->stash->{ward} ) if $c->stash->{ward}; - $c->stash->{body_url} = '/reports/' . $body_short; - $c->stash->{stats} = $c->cobrand->get_report_stats(); my @categories = $c->stash->{body}->contacts->not_deleted->search( undef, { @@ -166,7 +180,7 @@ sub ward : Path : Args(2) { my %map_params = ( latitude => @$pins ? $pins->[0]{latitude} : 0, longitude => @$pins ? $pins->[0]{longitude} : 0, - area => $c->stash->{ward} ? $c->stash->{ward}->{id} : [ keys %{$c->stash->{body}->areas} ], + area => [ $c->stash->{wards} ? map { $_->{id} } @{$c->stash->{wards}} : keys %{$c->stash->{body}->areas} ], any_zoom => 1, ); FixMyStreet::Map::display_map( @@ -176,10 +190,8 @@ sub ward : Path : Args(2) { $c->cobrand->tweak_all_reports_map( $c ); # List of wards - if ( !$c->stash->{ward} && $c->stash->{body}->id && $c->stash->{body}->body_areas->first ) { - my $children = mySociety::MaPit::call('area/children', [ $c->stash->{body}->body_areas->first->area_id ], - type => $c->cobrand->area_types_children, - ); + if ( !$c->stash->{wards} && $c->stash->{body}->id && $c->stash->{body}->body_areas->first ) { + my $children = $c->stash->{body}->first_area_children; unless ($children->{error}) { foreach (values %$children) { $_->{url} = $c->uri_for( $c->stash->{body_url} @@ -309,17 +321,35 @@ sub body_check : Private { # Oslo/ kommunes sharing a name in Norway return if $c->cobrand->reports_body_check( $c, $q_body ); + my $body = $c->forward('body_find', [ $q_body ]); + if ($body) { + $c->stash->{body} = $body; + return; + } + + # No result, bad body name. + $c->detach( 'redirect_index' ); +} + +=head2 + +Given a string, try and find a body starting with/matching that string. +Returns the matching body object if found. + +=cut + +sub body_find : Private { + my ($self, $c, $q_body) = @_; + # We must now have a string to check my @bodies = $c->model('DB::Body')->search( { name => { -like => "$q_body%" } } )->all; if (@bodies == 1) { - $c->stash->{body} = $bodies[0]; - return; + return $bodies[0]; } else { foreach (@bodies) { if (lc($_->name) eq lc($q_body) || $_->name =~ /^\Q$q_body\E (Borough|City|District|County) Council$/i) { - $c->stash->{body} = $_; - return; + return $_; } } } @@ -332,29 +362,27 @@ sub body_check : Private { if (@translations == 1) { if ( my $body = $c->model('DB::Body')->find( { id => $translations[0]->object_id } ) ) { - $c->stash->{body} = $body; - return; + return $body; } } - - # No result, bad body name. - $c->detach( 'redirect_index' ); } =head2 ward_check -This action checks the ward name from a URI exists and is part of the right +This action checks the ward names from a URI exists and are part of the right parent, already found with body_check. It either stores the ward Area if okay, or redirects to the body page if bad. =cut sub ward_check : Private { - my ( $self, $c, $ward ) = @_; + my ( $self, $c, @wards ) = @_; - $ward =~ s/\+/ /g; - $ward =~ s/\.html//; - $ward =~ s{_}{/}g; + foreach (@wards) { + s/\+/ /g; + s/\.html//; + s{_}{/}g; + } # Could be from RSS area, or body... my $parent_id; @@ -366,21 +394,125 @@ sub ward_check : Private { $parent_id = $c->stash->{area}->{id}; } - my $qw = mySociety::MaPit::call('areas', $ward, + my $qw = mySociety::MaPit::call('area/children', [ $parent_id ], type => $c->cobrand->area_types_children, ); + my %names = map { $_ => 1 } @wards; + my @areas; foreach my $area (sort { $a->{name} cmp $b->{name} } values %$qw) { - if ($area->{parent_area} == $parent_id) { - $c->stash->{ward} = $area; - return; - } + push @areas, $area if $names{$area->{name}}; + } + if (@areas) { + $c->stash->{ward} = $areas[0] if @areas == 1; + $c->stash->{wards} = \@areas; + return; } + # Given a false ward name $c->stash->{body} = $c->stash->{area} unless $c->stash->{body}; $c->detach( 'redirect_body' ); } +=head2 summary + +This is the summary page used on fixmystreet.com + +=cut + +sub summary : Private { + my ($self, $c) = @_; + my $dashboard = $c->forward('load_dashboard_data'); + + eval { + my $data = path(FixMyStreet->path_to('../data/all-reports-dashboard.json'))->slurp_utf8; + $data = decode_json($data); + $c->stash( + top_five_bodies => $data->{top_five_bodies}, + average => $data->{average}, + ); + }; + + my $dtf = $c->model('DB')->storage->datetime_parser; + my $period = $c->stash->{period} = $c->get_param('period') || ''; + my $start_date; + if ($period eq 'ever') { + $start_date = DateTime->new(year => 2007); + } elsif ($period eq 'year') { + $start_date = DateTime->now->subtract(years => 1); + } elsif ($period eq '3months') { + $start_date = DateTime->now->subtract(months => 3); + } elsif ($period eq 'week') { + $start_date = DateTime->now->subtract(weeks => 1); + } else { + $c->stash->{period} = 'month'; + $start_date = DateTime->now->subtract(months => 1); + } + + # required to stop errors in generate_grouped_data + $c->stash->{q_state} = ''; + $c->stash->{ward} = $c->get_param('ward'); + $c->stash->{start_date} = $dtf->format_date($start_date); + $c->stash->{end_date} = $c->get_param('end_date'); + + $c->stash->{group_by_default} = 'category'; + + my $area_id = $c->stash->{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; + + $c->forward('/admin/fetch_contacts'); + $c->stash->{contacts} = [ $c->stash->{contacts}->all ]; + + $c->forward('/dashboard/construct_rs_filter'); + + if ( $c->get_param('csv') ) { + $c->detach('export_summary_csv'); + } + + $c->forward('/dashboard/generate_grouped_data'); + $c->forward('/dashboard/generate_body_response_time'); + + $c->stash->{template} = 'reports/summary.html'; +} + +sub export_summary_csv : Private { + my ( $self, $c ) = @_; + + $c->stash->{csv} = { + problems => $c->stash->{problems_rs}->search_rs({}, { + rows => 100, + order_by => { '-desc' => 'me.confirmed' }, + }), + headers => [ + 'Report ID', + 'Title', + 'Category', + 'Created', + 'Confirmed', + 'Status', + 'Latitude', 'Longitude', + 'Query', + 'Report URL', + ], + columns => [ + 'id', + 'title', + 'category', + 'created_pp', + 'confirmed_pp', + 'state', + 'latitude', 'longitude', + 'postcode', + 'url', + ], + filename => 'fixmystreet-data.csv', + }; + $c->forward('/dashboard/generate_csv'); +} + =head2 check_canonical_url Given an already found (case-insensitively) body, check what URL @@ -397,6 +529,25 @@ sub check_canonical_url : Private { $c->detach( 'redirect_body' ) unless $body_short eq $url_short; } +sub load_dashboard_data : Private { + my ($self, $c) = @_; + my $dashboard = eval { + my $data = FixMyStreet->config('TEST_DASHBOARD_DATA'); + # uncoverable branch true + unless ($data) { + my $fn = '../data/all-reports-dashboard'; + if ($c->stash->{body}) { + $fn .= '-' . $c->stash->{body}->id; + } + $data = decode_json(path(FixMyStreet->path_to($fn . '.json'))->slurp_utf8); + } + $c->stash($data); + return 1; + }; + + return $dashboard; +} + sub load_and_group_problems : Private { my ( $self, $c ) = @_; @@ -448,8 +599,10 @@ sub load_and_group_problems : Private { my $problems = $c->cobrand->problems; - if ($c->stash->{ward}) { - $where->{areas} = { 'like', '%,' . $c->stash->{ward}->{id} . ',%' }; + if ($c->stash->{wards}) { + $where->{areas} = [ + map { { 'like', '%,' . $_->{id} . ',%' } } @{$c->stash->{wards}} + ]; $problems = $problems->to_body($c->stash->{body}); } elsif ($c->stash->{body}) { $problems = $problems->to_body($c->stash->{body}); @@ -510,8 +663,8 @@ sub redirect_body : Private { $url .= "/rss" if $c->stash->{rss}; $url .= '/reports'; $url .= '/' . $c->cobrand->short_name( $c->stash->{body} ); - $url .= '/' . $c->cobrand->short_name( $c->stash->{ward} ) - if $c->stash->{ward}; + $url .= '/' . join('|', map { $c->cobrand->short_name($_) } @{$c->stash->{wards}}) + if $c->stash->{wards}; $c->res->redirect( $c->uri_for($url, $c->req->params ) ); } @@ -529,16 +682,19 @@ sub stash_report_filter_status : Private { my $s = FixMyStreet::DB::Result::Problem->open_states(); %filter_problem_states = (%filter_problem_states, %$s); $filter_status{open} = 1; + $filter_status{$_} = 1 for keys %$s; } if ($status{closed}) { my $s = FixMyStreet::DB::Result::Problem->closed_states(); %filter_problem_states = (%filter_problem_states, %$s); $filter_status{closed} = 1; + $filter_status{$_} = 1 for keys %$s; } if ($status{fixed}) { my $s = FixMyStreet::DB::Result::Problem->fixed_states(); %filter_problem_states = (%filter_problem_states, %$s); $filter_status{fixed} = 1; + $filter_status{$_} = 1 for keys %$s; } if ($status{all}) { |