diff options
author | Matthew Somerville <matthew-github@dracos.co.uk> | 2017-10-20 17:02:09 +0100 |
---|---|---|
committer | Matthew Somerville <matthew-github@dracos.co.uk> | 2017-10-20 17:02:09 +0100 |
commit | 8e5853830f0fb65985881272b3b0178b37ac947b (patch) | |
tree | 3be466a2381a89c819002f91f350f341bdd14554 | |
parent | 235502e48f2a94ac90c425a04cda09cb22ad78d2 (diff) | |
parent | 6e1d005093e6a97f2f8bd90def4aa794b2ca7cc3 (diff) |
Merge branch 'reports-dashboard-councils'
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rwxr-xr-x | bin/update-all-reports | 32 | ||||
-rwxr-xr-x | perllib/FixMyStreet/App/Controller/About.pm | 1 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Reports.pm | 110 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/FixMyStreet.pm | 98 | ||||
-rw-r--r-- | perllib/FixMyStreet/DB/Result/Body.pm | 5 | ||||
-rwxr-xr-x | perllib/FixMyStreet/Script/UpdateAllReports.pm | 157 | ||||
-rw-r--r-- | t/app/controller/reports.t | 19 | ||||
-rw-r--r-- | t/cobrand/fixmystreet.t | 47 | ||||
-rwxr-xr-x | templates/web/base/reports/index.html | 39 | ||||
-rw-r--r-- | templates/web/fixmystreet.com/about/council-dashboard.html | 60 | ||||
-rw-r--r-- | web/cobrands/fixmystreet.com/fmsforcouncils.scss | 2 |
12 files changed, 456 insertions, 115 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index a2dad6f80..b4a57c9c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Allow multiple wards to be shown on reports page - Don't cover whole map with pin loading indicator. - Add Expand map toggle to more mobile maps. + - Add functionality to have per-body /reports page. - Bugfixes - Shortlist menu item always remains a link #1855 - Fix encoded entities in RSS output. #1859 diff --git a/bin/update-all-reports b/bin/update-all-reports index 4c0e96d31..0f9231f87 100755 --- a/bin/update-all-reports +++ b/bin/update-all-reports @@ -16,19 +16,45 @@ BEGIN { require "$d/../setenv.pl"; } +use FixMyStreet::DB; use FixMyStreet::Script::UpdateAllReports; +use Path::Tiny; use Getopt::Long::Descriptive; +use JSON::MaybeXS; my ($opt, $usage) = describe_options( '%c %o', [ 'table', "Output JSON for old table-style page." ], - [ 'areas', "Include area IDs in output JSON." ], + [ 'body=i', "Restrict results to a particular body (dashboard-style)." ], + [ 'all-bodies', "Generate set of results for all bodies." ], + [ 'areas', "Include area IDs in output JSON (table-style)." ], [ 'help', "print usage message and exit", { shortcircuit => 1 } ], ); print($usage->text), exit if $opt->help; +my ($data, $filename); if ($opt->table) { - FixMyStreet::Script::UpdateAllReports::generate($opt->areas); + $data = FixMyStreet::Script::UpdateAllReports::generate($opt->areas); + output('all-reports', $data); +} elsif ($opt->all_bodies) { + my $bodies = FixMyStreet::DB->resultset("Body")->search({ deleted => 0 }); + while (my $body = $bodies->next) { + my $data = FixMyStreet::Script::UpdateAllReports::generate_dashboard($body->id); + output("all-reports-dashboard-" . $body->id, $data); + } +} elsif (my $body_id = $opt->body) { + my $body = FixMyStreet::DB->resultset("Body")->find({ id => $body_id }); + die "Could not find body $body_id" unless $body; + $data = FixMyStreet::Script::UpdateAllReports::generate_dashboard($body); + output("all-reports-dashboard-$body_id", $data); } else { - FixMyStreet::Script::UpdateAllReports::generate_dashboard(); + $data = FixMyStreet::Script::UpdateAllReports::generate_dashboard(); + output("all-reports-dashboard", $data); +} + +sub output { + my ($filename, $data) = @_; + my $json = encode_json($data); + path(FixMyStreet->path_to('../data/'))->mkpath; + path(FixMyStreet->path_to("../data/$filename.json"))->spew_utf8($json); } diff --git a/perllib/FixMyStreet/App/Controller/About.pm b/perllib/FixMyStreet/App/Controller/About.pm index 233da25d3..48a5dfffd 100755 --- a/perllib/FixMyStreet/App/Controller/About.pm +++ b/perllib/FixMyStreet/App/Controller/About.pm @@ -23,6 +23,7 @@ sub page : Path("/about") : Args(1) { my $template = $c->forward('find_template'); $c->detach('/page_error_404_not_found', []) unless $template; $c->stash->{template} = $template; + $c->cobrand->call_hook('about_hook'); } sub index : Path("/about") : Args(0) { diff --git a/perllib/FixMyStreet/App/Controller/Reports.pm b/perllib/FixMyStreet/App/Controller/Reports.pm index 8beb2c091..b6281f0ca 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,27 @@ 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 = eval { - my $data = File::Slurp::read_file( - FixMyStreet->path_to( '../data/all-reports-dashboard.json' )->stringify - ); - $c->stash(decode_json($data)); + 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; }; - 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 +98,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'); } @@ -133,6 +146,20 @@ sub ward : Path : Args(2) { my @wards = split /\|/, $ward || ""; $c->forward( 'body_check', [ $body ] ); + + 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 ] ); @@ -143,13 +170,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, { @@ -178,9 +202,7 @@ sub ward : Path : Args(2) { # List of wards if ( !$c->stash->{wards} && $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, - ); + my $children = $c->stash->{body}->first_area_children; unless ($children->{error}) { foreach (values %$children) { $_->{url} = $c->uri_for( $c->stash->{body_url} @@ -310,17 +332,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 $_; } } } @@ -333,13 +373,9 @@ 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 diff --git a/perllib/FixMyStreet/Cobrand/FixMyStreet.pm b/perllib/FixMyStreet/Cobrand/FixMyStreet.pm index c50721334..a50a22ff9 100644 --- a/perllib/FixMyStreet/Cobrand/FixMyStreet.pm +++ b/perllib/FixMyStreet/Cobrand/FixMyStreet.pm @@ -1,6 +1,9 @@ package FixMyStreet::Cobrand::FixMyStreet; use base 'FixMyStreet::Cobrand::UK'; +use strict; +use warnings; + use mySociety::Random; use constant COUNCIL_ID_BROMLEY => 2482; @@ -62,5 +65,98 @@ sub extra_contact_validation { return %errors; } -1; +=head2 council_dashboard_hook + +This is for council-specific dashboard pages, which can only be seen by +superusers and logged-in users with an email domain matching a body name. + +=cut + +sub council_dashboard_hook { + my $self = shift; + my $c = $self->{c}; + + unless ( $c->user_exists ) { + $c->res->redirect('/about/council-dashboard'); + $c->detach; + } + + return if $c->user->is_superuser; + + my $body = $c->user->from_body || _user_to_body($c); + if ($body) { + # Matching URL and user's email body + return if $body->id eq $c->stash->{body}->id; + + # Matched /a/ body, redirect to its summary page + $c->stash->{body} = $body; + $c->stash->{wards} = [ { name => 'summary' } ]; + $c->detach('/reports/redirect_body'); + } + + $c->res->redirect('/about/council-dashboard'); +} +sub _user_to_body { + my $c = shift; + my $email = lc $c->user->email; + return _email_to_body($c, $email); +} + +sub _email_to_body { + my ($c, $email) = @_; + my ($domain) = $email =~ m{ @ (.*) \z }x; + + my @data = eval { FixMyStreet->path_to('../data/fixmystreet-councils.csv')->slurp }; + my $body; + foreach (@data) { + chomp; + my ($d, $b) = split /\|/; + if ($d eq $domain) { + $body = $b; + last; + } + } + # If we didn't find a lookup entry, default to the first part of the domain + unless ($body) { + $domain =~ s/\.gov\.uk$//; + $body = ucfirst $domain; + } + + $body = $c->forward('/reports/body_find', [ $body ]); + return $body; +} + +sub about_hook { + my $self = shift; + my $c = $self->{c}; + + if ($c->stash->{template} eq 'about/council-dashboard.html') { + $c->stash->{form_name} = $c->get_param('name') || ''; + $c->stash->{email} = $c->get_param('username') || ''; + if ($c->user_exists) { + my $body = _user_to_body($c); + if ($body) { + $c->stash->{body} = $body; + $c->stash->{wards} = [ { name => 'summary' } ]; + $c->detach('/reports/redirect_body'); + } + } + if (my $email = $c->get_param('username')) { + $email = lc $email; + $email =~ s/\s+//g; + my $body = _email_to_body($c, $email); + if ($body) { + # Send confirmation email (hopefully) + $c->stash->{template} = 'auth/general.html'; + $c->detach('/auth/general'); + } else { + $c->stash->{no_body_found} = 1; + $c->set_param('em', $email); # What the contact form wants + $c->detach('/contact/submit'); + } + } + } +} + +1; diff --git a/perllib/FixMyStreet/DB/Result/Body.pm b/perllib/FixMyStreet/DB/Result/Body.pm index 6481d5cfc..da5c38168 100644 --- a/perllib/FixMyStreet/DB/Result/Body.pm +++ b/perllib/FixMyStreet/DB/Result/Body.pm @@ -156,12 +156,13 @@ sub areas { } sub first_area_children { - my ( $self, $c ) = @_; + my ( $self ) = @_; my $area_id = $self->body_areas->first->area_id; + my $cobrand = $self->result_source->schema->cobrand; my $children = mySociety::MaPit::call('area/children', $area_id, - type => $c->cobrand->area_types_children, + type => $cobrand->area_types_children, ); return $children; diff --git a/perllib/FixMyStreet/Script/UpdateAllReports.pm b/perllib/FixMyStreet/Script/UpdateAllReports.pm index 1bd069ee8..f4f444d5b 100755 --- a/perllib/FixMyStreet/Script/UpdateAllReports.pm +++ b/perllib/FixMyStreet/Script/UpdateAllReports.pm @@ -4,11 +4,9 @@ use strict; use warnings; use FixMyStreet; +use FixMyStreet::Cobrand; use FixMyStreet::DB; -use File::Path (); -use File::Slurp; -use JSON::MaybeXS; use List::MoreUtils qw(zip); use List::Util qw(sum); @@ -21,6 +19,11 @@ if ( FixMyStreet->config('BASE_URL') =~ /zurich|zueri/ ) { $age_column = 'created'; } +my $dtf = FixMyStreet::DB->schema->storage->datetime_parser; + +my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker('default')->new; +FixMyStreet::DB->schema->cobrand($cobrand); + sub generate { my $include_areas = shift; @@ -81,13 +84,10 @@ sub generate { } } - my $body = encode_json( { + return { fixed => \%fixed, open => \%open, - } ); - - File::Path::mkpath( FixMyStreet->path_to( '../data/' )->stringify ); - File::Slurp::write_file( FixMyStreet->path_to( '../data/all-reports.json' )->stringify, \$body ); + }; } sub end_period { @@ -107,10 +107,18 @@ sub loop_period { } sub generate_dashboard { + my $body = shift; + my %data; + my $rs = FixMyStreet::DB->resultset('Problem'); + $rs = $rs->to_body($body) if $body; + + my $rs_c = FixMyStreet::DB->resultset('Comment'); + $rs_c = $rs_c->to_body($body) if $body; + my $end_today = end_period('day'); - my $min_confirmed = FixMyStreet::DB->resultset('Problem')->search({ + my $min_confirmed = $rs->search({ state => [ FixMyStreet::DB::Result::Problem->visible_states() ], }, { select => [ { min => 'confirmed' } ], @@ -134,11 +142,11 @@ sub generate_dashboard { my @problem_periods = loop_period($min_confirmed, $group_by, $extra); my %problems_reported_by_period = stuff_by_day_or_year( - $group_by, 'Problem', + $group_by, $rs, state => [ FixMyStreet::DB::Result::Problem->visible_states() ], ); my %problems_fixed_by_period = stuff_by_day_or_year( - $group_by, 'Problem', + $group_by, $rs, state => [ FixMyStreet::DB::Result::Problem->fixed_states() ], ); @@ -158,24 +166,23 @@ sub generate_dashboard { ); $data{last_seven_days} = \%last_seven_days; - my $dtf = FixMyStreet::DB->schema->storage->datetime_parser; my $eight_ago = $dtf->format_datetime(DateTime->now->subtract(days => 8)); %problems_reported_by_period = stuff_by_day_or_year('day', - 'Problem', + $rs, state => [ FixMyStreet::DB::Result::Problem->visible_states() ], - confirmed => { '>=', $eight_ago }, + 'me.confirmed' => { '>=', $eight_ago }, ); %problems_fixed_by_period = stuff_by_day_or_year('day', - 'Comment', - confirmed => { '>=', $eight_ago }, + $rs_c, + 'me.confirmed' => { '>=', $eight_ago }, -or => [ problem_state => [ FixMyStreet::DB::Result::Problem->fixed_states() ], mark_fixed => 1, ], ); my %problems_updated_by_period = stuff_by_day_or_year('day', - 'Comment', - confirmed => { '>=', $eight_ago }, + $rs_c, + 'me.confirmed' => { '>=', $eight_ago }, ); my $date = DateTime->today->subtract(days => 7); @@ -189,47 +196,17 @@ sub generate_dashboard { $last_seven_days{fixed_total} = sum @{$last_seven_days{fixed}}; $last_seven_days{updated_total} = sum @{$last_seven_days{updated}}; - my(@top_five_bodies); - $data{top_five_bodies} = \@top_five_bodies; - - my $bodies = FixMyStreet::DB->resultset('Body')->search; - my $substmt = "select min(id) from comment where me.problem_id=comment.problem_id and (problem_state in ('fixed', 'fixed - council', 'fixed - user') or mark_fixed)"; - while (my $body = $bodies->next) { - my $subquery = FixMyStreet::DB->resultset('Comment')->to_body($body)->search({ - -or => [ - problem_state => [ FixMyStreet::DB::Result::Problem->fixed_states() ], - mark_fixed => 1, - ], - 'me.id' => \"= ($substmt)", - 'me.state' => 'confirmed', - }, { - select => [ - { extract => "epoch from me.confirmed-problem.confirmed", -as => 'time' }, - ], - as => [ qw/time/ ], - rows => 100, - order_by => { -desc => 'me.confirmed' }, - join => 'problem' - })->as_subselect_rs; - my $avg = $subquery->search({ - }, { - select => [ { avg => "time" } ], - as => [ qw/avg/ ], - })->first->get_column('avg'); - push @top_five_bodies, { name => $body->name, days => int($avg / 60 / 60 / 24 + 0.5) } - if defined $avg; + if ($body) { + calculate_top_five_wards(\%data, $rs, $body); + } else { + calculate_top_five_bodies(\%data, $rs_c); } - @top_five_bodies = sort { $a->{days} <=> $b->{days} } @top_five_bodies; - $data{average} = @top_five_bodies - ? int((sum map { $_->{days} } @top_five_bodies) / @top_five_bodies + 0.5) : undef; - - @top_five_bodies = @top_five_bodies[0..4] if @top_five_bodies > 5; my $week_ago = $dtf->format_datetime(DateTime->now->subtract(days => 7)); - my $last_seven_days = FixMyStreet::DB->resultset("Problem")->search({ + my $last_seven_days = $rs->search({ confirmed => { '>=', $week_ago }, })->count; - my @top_five_categories = FixMyStreet::DB->resultset("Problem")->search({ + my @top_five_categories = $rs->search({ confirmed => { '>=', $week_ago }, category => { '!=', 'Other' }, }, { @@ -247,19 +224,17 @@ sub generate_dashboard { } $data{other_categories} = $last_seven_days; - my $body = encode_json( \%data ); - File::Path::mkpath( FixMyStreet->path_to( '../data/' )->stringify ); - File::Slurp::write_file( FixMyStreet->path_to( '../data/all-reports-dashboard.json' )->stringify, \$body ); + return \%data; } sub stuff_by_day_or_year { my $period = shift; - my $table = shift; + my $rs = shift; my %params = @_; - my $results = FixMyStreet::DB->resultset($table)->search({ + my $results = $rs->search({ %params }, { - select => [ { extract => \"$period from confirmed", -as => $period }, { count => 'id' } ], + select => [ { extract => \"$period from me.confirmed", -as => $period }, { count => 'me.id' } ], as => [ $period, 'count' ], group_by => [ $period ], }); @@ -271,4 +246,66 @@ sub stuff_by_day_or_year { return %out; } +sub calculate_top_five_bodies { + my ($data, $rs_c) = @_; + + my(@top_five_bodies); + + my $bodies = FixMyStreet::DB->resultset('Body')->search; + my $substmt = "select min(id) from comment where me.problem_id=comment.problem_id and (problem_state in ('fixed', 'fixed - council', 'fixed - user') or mark_fixed)"; + while (my $body = $bodies->next) { + my $subquery = $rs_c->to_body($body)->search({ + -or => [ + problem_state => [ FixMyStreet::DB::Result::Problem->fixed_states() ], + mark_fixed => 1, + ], + 'me.id' => \"= ($substmt)", + 'me.state' => 'confirmed', + }, { + select => [ + { extract => "epoch from me.confirmed-problem.confirmed", -as => 'time' }, + ], + as => [ qw/time/ ], + rows => 100, + order_by => { -desc => 'me.confirmed' }, + join => 'problem' + })->as_subselect_rs; + my $avg = $subquery->search({ + }, { + select => [ { avg => "time" } ], + as => [ qw/avg/ ], + })->first->get_column('avg'); + push @top_five_bodies, { name => $body->name, days => int($avg / 60 / 60 / 24 + 0.5) } + if defined $avg; + } + @top_five_bodies = sort { $a->{days} <=> $b->{days} } @top_five_bodies; + $data->{average} = @top_five_bodies + ? int((sum map { $_->{days} } @top_five_bodies) / @top_five_bodies + 0.5) : undef; + + @top_five_bodies = @top_five_bodies[0..4] if @top_five_bodies > 5; + $data->{top_five_bodies} = \@top_five_bodies; +} + +sub calculate_top_five_wards { + my ($data, $rs, $body) = @_; + + my $children = $body->first_area_children; + die $children->{error} if $children->{error}; + + my $week_ago = $dtf->format_datetime(DateTime->now->subtract(days => 7)); + my $last_seven_days = $rs->search({ confirmed => { '>=', $week_ago } }); + my $last_seven_days_count = $last_seven_days->count; + $last_seven_days = $last_seven_days->search(undef, { select => 'areas' }); + + while (my $row = $last_seven_days->next) { + $children->{$_}{reports}++ foreach grep { $children->{$_} } split /,/, $row->areas; + } + my @wards = sort { $b->{reports} <=> $a->{reports} } grep { $_->{reports} } values %$children; + @wards = @wards[0..4] if @wards > 5; + + my $sum_five = (sum map { $_->{reports} } @wards) || 0; + $data->{other_wards} = $last_seven_days_count - $sum_five; + $data->{wards} = \@wards; +} + 1; diff --git a/t/app/controller/reports.t b/t/app/controller/reports.t index 7773223dd..76c920562 100644 --- a/t/app/controller/reports.t +++ b/t/app/controller/reports.t @@ -12,9 +12,6 @@ END { ok( my $mech = FixMyStreet::TestMech->new, 'Created mech object' ); -# Run the cron script with empty database -FixMyStreet::Script::UpdateAllReports::generate_dashboard(); - $mech->create_body_ok(2514, 'Birmingham City Council'); my $body_edin_id = $mech->create_body_ok(2651, 'City of Edinburgh Council')->id; my $body_west_id = $mech->create_body_ok(2504, 'Westminster City Council')->id; @@ -99,10 +96,14 @@ $fife_problems[10]->update( { }); # Run the cron script that makes the data for /reports so we don't get an error. -FixMyStreet::Script::UpdateAllReports::generate_dashboard(); +my $data = FixMyStreet::Script::UpdateAllReports::generate_dashboard(); # check that we can get the page -$mech->get_ok('/reports'); +FixMyStreet::override_config { + TEST_DASHBOARD_DATA => $data, +}, sub { + $mech->get_ok('/reports'); +}; $mech->title_like(qr{Dashboard}); $mech->content_contains('Birmingham'); @@ -138,6 +139,7 @@ is scalar @$problems, 5, 'correct number of problems displayed'; FixMyStreet::override_config { MAPIT_URL => 'http://mapit.uk/', + TEST_DASHBOARD_DATA => $data, }, sub { $mech->get_ok('/reports'); $mech->submit_form_ok({ with_fields => { body => $body_slash_id } }, 'Submitted dropdown okay'); @@ -199,13 +201,18 @@ is scalar @$problems, 4, 'only public problems are displayed'; $mech->content_lacks('All reports Test 3 for ' . $body_west_id, 'non public problem is not visible'); # No change to numbers if report is non-public -$mech->get_ok('/reports'); +FixMyStreet::override_config { + TEST_DASHBOARD_DATA => $data, +}, sub { + $mech->get_ok('/reports'); +}; $mech->content_contains('"Apr","May","Jun","Jul"'); $mech->content_contains('5,9,10,22'); subtest "test fiksgatami all reports page" => sub { FixMyStreet::override_config { ALLOWED_COBRANDS => [ 'fiksgatami' ], + TEST_DASHBOARD_DATA => $data, # Not relevant to what we're testing, just so page loads }, sub { $mech->create_body_ok(3, 'Oslo'); ok $mech->host("fiksgatami.no"), 'change host to fiksgatami'; diff --git a/t/cobrand/fixmystreet.t b/t/cobrand/fixmystreet.t new file mode 100644 index 000000000..eda93e187 --- /dev/null +++ b/t/cobrand/fixmystreet.t @@ -0,0 +1,47 @@ +use FixMyStreet::Script::UpdateAllReports; + +use FixMyStreet::TestMech; +my $mech = FixMyStreet::TestMech->new; + +my $body = $mech->create_body_ok( 2514, 'Birmingham' ); + +my $data; +FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', +}, sub { + $data = FixMyStreet::Script::UpdateAllReports::generate_dashboard($body); +}; + +FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + TEST_DASHBOARD_DATA => $data, + ALLOWED_COBRANDS => 'fixmystreet', +}, sub { + # Not logged in, redirected + $mech->get_ok('/reports/Birmingham/summary'); + is $mech->uri->path, '/about/council-dashboard'; + + $mech->submit_form_ok({ with_fields => { username => 'someone@somewhere.example.org' }}); + $mech->content_contains('We will be in touch'); + # XXX Check email arrives + + $mech->log_in_ok('someone@somewhere.example.org'); + $mech->get_ok('/reports/Birmingham/summary'); + is $mech->uri->path, '/about/council-dashboard'; + $mech->content_contains('Ending in .gov.uk'); + + $mech->submit_form_ok({ with_fields => { name => 'Someone', username => 'someone@birmingham.gov.uk' }}); + $mech->content_contains('Now check your email'); + # XXX Check email arrives, click link + + $mech->log_in_ok('someone@birmingham.gov.uk'); + # Logged in, redirects + $mech->get_ok('/about/council-dashboard'); + is $mech->uri->path, '/reports/Birmingham/summary'; + $mech->content_contains('Top 5 wards'); + +}; + +END { + done_testing(); +} diff --git a/templates/web/base/reports/index.html b/templates/web/base/reports/index.html index a653a2686..70f4b3929 100755 --- a/templates/web/base/reports/index.html +++ b/templates/web/base/reports/index.html @@ -15,7 +15,9 @@ [% INCLUDE 'header.html', title = loc('Dashboard'), bodyclass => 'dashboard fullwidthpage' %] <div class="dashboard-header"> - <h1>[% loc('Dashboard') %]</h1> + <h1>[% loc('Dashboard') %] + [% IF body %] – [% body.name %] [% END %] + </h1> </div> <div class="dashboard-row"> @@ -58,19 +60,31 @@ </div> </div> <div class="dashboard-item dashboard-item--6"> - <form class="dashboard-search" action="/reports"> + <form class="dashboard-search"> <h2>[% loc('Show reports in your area') %]</h2> + [% IF body %] + <label for="ward">[% loc('Pick your ward') %]</label> + <div class="dashboard-search__input"> + <select id="ward" name="ward" class="js-autocomplete"> + <option value="">[% loc('Pick your ward') %]</option> + [% FOR child IN children.values.sort('name') %] + <option>[% child.name | html ~%]</option> + [% END %] + </select> + </div> + [% ELSE %] <label for="body">[% loc('Pick your council') %]</label> <div class="dashboard-search__input"> <select id="body" name="body" class="js-autocomplete"> <option value="">[% loc('Pick your council') %]</option> - [% FOR body IN bodies %] - <option value="[% body.id %]">[% body.name | html ~%] - [% IF NOT body.get_column("area_count") %] [% loc('(no longer exists)') %][% END ~%] + [% FOR b IN bodies # Not body as 'body' may be on stash %] + <option value="[% b.id %]">[% b.name | html ~%] + [% IF NOT b.get_column("area_count") %] [% loc('(no longer exists)') %][% END ~%] </option> [% END %] </select> </div> + [% END %] <div class="dashboard-search__submit"> <input type="submit" value="[% loc('Go') %]"> </div> @@ -80,6 +94,20 @@ <div class="dashboard-row"> <div class="dashboard-item dashboard-item--6"> + [% IF body %] + <h2 class="dashboard-subheading">[% loc('Top 5 wards') %]</h2> + <p>[% loc('Number of problems reported in each ward, in the last 7 days.') %]</p> + <table class="dashboard-ranking-table"> + <tbody> + [% FOR line IN wards %] + <tr><td>[% line.name %]</td><td>[% tprintf(nget("%s report", "%s reports", line.reports), line.reports) %]</td></tr> + [% END %] + </tbody> + <tfoot> + <tr><td>[% loc('Other wards') %]</td><td>[% tprintf(nget("%s report", "%s reports", other_wards), other_wards) %]</td></tr> + </tfoot> + </table> + [% ELSE %] <h2 class="dashboard-subheading">[% loc('Top 5 responsive councils') %]</h2> <p>[% loc('Average time between a problem being reported and being fixed, last 100 reports.') %]</p> <table class="dashboard-ranking-table"> @@ -92,6 +120,7 @@ <tr><td>[% loc('Overall average') %]</td><td>[% tprintf(nget("%s day", "%s days", average), average) %]</td></tr> </tfoot> </table> + [% END %] </div> <div class="dashboard-item dashboard-item--6"> <h2 class="dashboard-subheading">[% loc('Top 5 most used categories') %]</h2> diff --git a/templates/web/fixmystreet.com/about/council-dashboard.html b/templates/web/fixmystreet.com/about/council-dashboard.html new file mode 100644 index 000000000..7acaee207 --- /dev/null +++ b/templates/web/fixmystreet.com/about/council-dashboard.html @@ -0,0 +1,60 @@ +[% extra_css = BLOCK %] + <link rel="stylesheet" href="[% version('/cobrands/fixmystreet.com/fmsforcouncils.css') %]"> + <link href="https://fonts.googleapis.com/css?family=Rubik:400,500" rel="stylesheet"> +[% END %] + +[% IF no_body_found %] + +[% INCLUDE header.html + title = 'FixMyStreet Professional', bodyclass = 'fullwidthpage' +%] + +<div class="confirmation-header confirmation-header--inbox"> + <h1>Thanks!</h1> + <p>We will be in touch with a confirmation link soon.</p> +</div> + +[% ELSE %] + +[% INCLUDE header.html + title = 'FixMyStreet Professional', bodyclass = 'fms-for-councils fullwidthpage' +%] + +<div class="fixed-container"> + <div class="council-header"> + <h1 class="councils-logo">FixMyStreet Professional</h1> + </div> + <div class="councils-hero"> + <div class="councils-hero__demo-access"> + <p>To access a council-specific version of our main dashboard page, + please provide your name and email below and we'll send you a link.</p> + <form method="post" class="councils-hero__demo-access__form"> + <div class="form-group"> + <label for="demo-name">Name</label> + <span class="required">required</span> + <input type="text" name="name" id="demo-name" required value="[% form_name | html %]"> + </div> + <div class="form-group"> + <label for="demo-email">Contact email</label> + <span class="required">required</span> + <input type="email" name="username" id="demo-email" required value="[% email | html %]"> + <p class="form-note">Ending in .gov.uk, or other official council domain</p> + </div> + <div class="form-group submit-group"> + <input type="hidden" name="r" value="about/council-dashboard"> + <input type="hidden" name="extra.referer" value="[% c.req.headers.referer | html %]"> + <input type="hidden" name="subject" value="Council dashboard request"> + <input type="hidden" name="message" value="Filled in the council dashboard form"> + <input type="hidden" name="recipient" value="bettercities"> + <input type="hidden" name="dest" value="from_council"> + <input type="submit" value="Let me in" class="btn"> + </div> + </form> + </div> + </div> + +</div> + +[% END %] + +[% INCLUDE footer.html %] diff --git a/web/cobrands/fixmystreet.com/fmsforcouncils.scss b/web/cobrands/fixmystreet.com/fmsforcouncils.scss index ee166b8fd..0e7c6a281 100644 --- a/web/cobrands/fixmystreet.com/fmsforcouncils.scss +++ b/web/cobrands/fixmystreet.com/fmsforcouncils.scss @@ -147,7 +147,7 @@ $fms-pink: #E65376; border-radius: 3px; color: #fff; padding: 2em; - margin: 4em auto -4em auto; + margin: 4em auto 4em auto; max-width: 26em; position: relative; z-index: 1; |