aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/FixMyStreet/App/Controller/Dashboard.pm
diff options
context:
space:
mode:
Diffstat (limited to 'perllib/FixMyStreet/App/Controller/Dashboard.pm')
-rw-r--r--perllib/FixMyStreet/App/Controller/Dashboard.pm207
1 files changed, 144 insertions, 63 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Dashboard.pm b/perllib/FixMyStreet/App/Controller/Dashboard.pm
index 790e7ec29..bd60f8570 100644
--- a/perllib/FixMyStreet/App/Controller/Dashboard.pm
+++ b/perllib/FixMyStreet/App/Controller/Dashboard.pm
@@ -3,10 +3,12 @@ use Moose;
use namespace::autoclean;
use DateTime;
+use Encode;
use JSON::MaybeXS;
use Path::Tiny;
use Text::CSV;
use Time::Piece;
+use FixMyStreet::DateRange;
BEGIN { extends 'Catalyst::Controller'; }
@@ -54,6 +56,18 @@ Checks if we can view this page, and if not redirect to 404.
sub check_page_allowed : Private {
my ( $self, $c ) = @_;
+ # dashboard_permission can return undef (if not present, or to carry on
+ # with default behaviour), a body ID to use that body for results, or 0
+ # to refuse access entirely
+ my $cobrand_check = $c->cobrand->call_hook('dashboard_permission');
+ if (defined $cobrand_check) {
+ if ($cobrand_check) {
+ $cobrand_check = $c->model('DB::Body')->find({ id => $cobrand_check });
+ }
+ $c->detach( '/page_error_404_not_found' ) if !$cobrand_check;
+ return $cobrand_check;
+ }
+
$c->detach( '/auth/redirect' ) unless $c->user_exists;
$c->detach( '/page_error_404_not_found' )
@@ -93,13 +107,18 @@ sub index : Path : Args(0) {
# 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;
+ $c->stash->{ward} = [ $c->get_param_list('ward') ];
+ if ($c->user_exists) {
+ if (my @areas = @{$c->user->area_ids || []}) {
+ $c->stash->{ward} = $c->user->area_ids;
+ $c->stash->{body_name} = join " / ", sort map { $children->{$_}->{name} } grep { $children->{$_} } @areas;
+ }
}
} else {
- my @bodies = $c->model('DB::Body')->active->translated->with_area_count->all_sorted;
+ my @bodies = $c->model('DB::Body')->search(undef, {
+ columns => [ "id", "name" ],
+ })->active->translated->with_area_count->all_sorted;
+ $c->stash->{ward} = [];
$c->stash->{bodies} = \@bodies;
}
@@ -110,10 +129,14 @@ sub index : Path : Args(0) {
$c->stash->{end_date} = $c->get_param('end_date');
$c->stash->{q_state} = $c->get_param('state') || '';
- $c->forward('construct_rs_filter');
+ $c->forward('construct_rs_filter', [ $c->get_param('updates') ]);
if ( $c->get_param('export') ) {
- $c->forward('export_as_csv');
+ if ($c->get_param('updates')) {
+ $c->forward('export_as_csv_updates');
+ } else {
+ $c->forward('export_as_csv');
+ }
} else {
$c->forward('generate_grouped_data');
$self->generate_summary_figures($c);
@@ -121,36 +144,39 @@ sub index : Path : Args(0) {
}
sub construct_rs_filter : Private {
- my ($self, $c) = @_;
+ my ($self, $c, $updates) = @_;
my %where;
- $where{areas} = { 'like', '%,' . $c->stash->{ward} . ',%' }
- if $c->stash->{ward};
+ $where{areas} = [ map { { 'like', "%,$_,%" } } @{$c->stash->{ward}} ]
+ if @{$c->stash->{ward}};
$where{category} = $c->stash->{category}
if $c->stash->{category};
+ my $table_name = $updates ? 'problem' : 'me';
+
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() ];
+ $where{"$table_name.state"} = [ FixMyStreet::DB::Result::Problem->fixed_states() ];
} elsif ( $state ) {
- $where{'me.state'} = $state;
+ $where{"$table_name.state"} = $state;
} else {
- $where{'me.state'} = [ FixMyStreet::DB::Result::Problem->visible_states() ];
+ $where{"$table_name.state"} = [ FixMyStreet::DB::Result::Problem->visible_states() ];
}
- my $dtf = $c->model('DB')->storage->datetime_parser;
-
- my $start_date = $dtf->parse_datetime($c->stash->{start_date});
- $where{'me.confirmed'} = { '>=', $dtf->format_datetime($start_date) };
+ my $days30 = DateTime->now(time_zone => FixMyStreet->time_zone || FixMyStreet->local_time_zone)->subtract(days => 30);
+ $days30->truncate( to => 'day' );
- if (my $end_date = $c->stash->{end_date}) {
- my $one_day = DateTime::Duration->new( days => 1 );
- $end_date = $dtf->parse_datetime($end_date) + $one_day;
- $where{'me.confirmed'} = [ -and => $where{'me.confirmed'}, { '<', $dtf->format_datetime($end_date) } ];
- }
+ my $range = FixMyStreet::DateRange->new(
+ start_date => $c->stash->{start_date},
+ start_default => $days30,
+ end_date => $c->stash->{end_date},
+ formatter => $c->model('DB')->storage->datetime_parser,
+ );
+ $where{"$table_name.confirmed"} = $range->sql;
$c->stash->{params} = \%where;
- $c->stash->{problems_rs} = $c->cobrand->problems->to_body($c->stash->{body})->search( \%where );
+ my $rs = $updates ? $c->cobrand->updates : $c->cobrand->problems;
+ $c->stash->{objects_rs} = $rs->to_body($c->stash->{body})->search( \%where );
}
sub generate_grouped_data : Private {
@@ -182,7 +208,7 @@ sub generate_grouped_data : Private {
@groups = qw/category state/;
%grouped = map { $_->category => {} } @{$c->stash->{contacts}};
}
- my $problems = $c->stash->{problems_rs}->search(undef, {
+ my $problems = $c->stash->{objects_rs}->search(undef, {
group_by => [ map { ref $_ ? $_->{-as} : $_ } @groups ],
select => [ @groups, { count => 'me.id' } ],
as => [ @groups == 2 ? qw/key1 key2 count/ : qw/key1 count/ ],
@@ -238,7 +264,7 @@ sub generate_summary_figures {
# problems this month by state
$c->stash->{"summary_$_"} = 0 for values %$state_map;
- $c->stash->{summary_open} = $c->stash->{problems_rs}->count;
+ $c->stash->{summary_open} = $c->stash->{objects_rs}->count;
my $params = $c->stash->{params};
$params = { map { my $n = $_; s/me\./problem\./ unless /me\.confirmed/; $_ => $params->{$n} } keys %$params };
@@ -268,15 +294,54 @@ sub generate_summary_figures {
sub generate_body_response_time : Private {
my ( $self, $c ) = @_;
- my $avg = $c->stash->{body}->calculate_average;
+ my $avg = $c->stash->{body}->calculate_average($c->cobrand->call_hook("body_responsiveness_threshold"));
$c->stash->{body_average} = $avg ? int($avg / 60 / 60 / 24 + 0.5) : 0;
}
+sub csv_filename {
+ my ($self, $c, $updates) = @_;
+ my %where = (
+ category => $c->stash->{category},
+ state => $c->stash->{q_state},
+ ward => join(',', @{$c->stash->{ward}}),
+ );
+ $where{body} = $c->stash->{body}->id if $c->stash->{body};
+ join '-',
+ $c->req->uri->host,
+ $updates ? ('updates') : (),
+ map {
+ my $value = $where{$_};
+ (defined $value and length $value) ? ($_, $value) : ()
+ } sort keys %where
+};
+
+sub export_as_csv_updates : Private {
+ my ($self, $c) = @_;
+
+ my $csv = $c->stash->{csv} = {
+ objects => $c->stash->{objects_rs}->search_rs({}, {
+ order_by => ['me.confirmed', 'me.id'],
+ '+columns' => ['problem.bodies_str'],
+ }),
+ headers => [
+ 'Report ID', 'Update ID', 'Date', 'Status', 'Problem state',
+ 'Text', 'User Name', 'Reported As',
+ ],
+ columns => [
+ 'problem_id', 'id', 'confirmed', 'state', 'problem_state',
+ 'text', 'user_name_display', 'reported_as',
+ ],
+ filename => $self->csv_filename($c, 1),
+ };
+ $c->cobrand->call_hook("dashboard_export_updates_add_columns");
+ $c->forward('generate_csv');
+}
+
sub export_as_csv : Private {
my ($self, $c) = @_;
my $csv = $c->stash->{csv} = {
- problems => $c->stash->{problems_rs}->search_rs({}, {
+ objects => $c->stash->{objects_rs}->search_rs({}, {
prefetch => 'comments',
order_by => ['me.confirmed', 'me.id'],
}),
@@ -298,6 +363,8 @@ sub export_as_csv : Private {
'Easting',
'Northing',
'Report URL',
+ 'Site Used',
+ 'Reported As',
],
columns => [
'id',
@@ -317,23 +384,12 @@ sub export_as_csv : Private {
'local_coords_x',
'local_coords_y',
'url',
+ 'site_used',
+ 'reported_as',
],
- filename => do {
- my %where = (
- 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 {
- my $value = $where{$_};
- (defined $value and length $value) ? ($_, $value) : ()
- } sort keys %where
- },
+ filename => $self->csv_filename($c, 0),
};
- $c->cobrand->call_hook("dashboard_export_add_columns");
+ $c->cobrand->call_hook("dashboard_export_problems_add_columns");
$c->forward('generate_csv');
}
@@ -354,24 +410,44 @@ hashref of extra data to include that can be used by 'columns'.
sub generate_csv : Private {
my ($self, $c) = @_;
+ my $filename = $c->stash->{csv}->{filename};
+ $c->res->content_type('text/csv; charset=utf-8');
+ $c->res->header('content-disposition' => "attachment; filename=\"${filename}.csv\"");
+
+ # Emit a header (copying Drupal's naming) telling an intermediary (e.g.
+ # Varnish) not to buffer the output. Varnish will need to know this, e.g.:
+ # if (beresp.http.Surrogate-Control ~ "BigPipe/1.0") {
+ # set beresp.do_stream = true;
+ # set beresp.ttl = 0s;
+ # }
+ $c->res->header('Surrogate-Control' => 'content="BigPipe/1.0"');
+
+ # Tell nginx not to buffer this response
+ $c->res->header('X-Accel-Buffering' => 'no');
+
+ # Define an empty body so the web view doesn't get added at the end
+ $c->res->body("");
+
+ # Old parameter renaming
+ $c->stash->{csv}->{objects} //= $c->stash->{csv}->{problems};
+
my $csv = Text::CSV->new({ binary => 1, eol => "\n" });
- $csv->combine(@{$c->stash->{csv}->{headers}});
- my @body = ($csv->string);
+ $csv->print($c->response, $c->stash->{csv}->{headers});
my $fixed_states = FixMyStreet::DB::Result::Problem->fixed_states;
my $closed_states = FixMyStreet::DB::Result::Problem->closed_states;
my %asked_for = map { $_ => 1 } @{$c->stash->{csv}->{columns}};
- my $problems = $c->stash->{csv}->{problems};
- while ( my $report = $problems->next ) {
- my $hashref = $report->as_hashref($c, \%asked_for);
+ my $objects = $c->stash->{csv}->{objects};
+ while ( my $obj = $objects->next ) {
+ my $hashref = $obj->as_hashref($c, \%asked_for);
- $hashref->{user_name_display} = $report->anonymous
- ? '(anonymous)' : $report->name;
+ $hashref->{user_name_display} = $obj->anonymous
+ ? '(anonymous)' : $obj->name;
if ($asked_for{acknowledged}) {
- for my $comment ($report->comments) {
+ for my $comment ($obj->comments) {
my $problem_state = $comment->problem_state or next;
next unless $comment->state eq 'confirmed';
next if $problem_state eq 'confirmed';
@@ -392,28 +468,33 @@ sub generate_csv : Private {
split ',', $hashref->{areas};
}
- ($hashref->{local_coords_x}, $hashref->{local_coords_y}) =
- $report->local_coords;
- $hashref->{url} = join '', $c->cobrand->base_url_for_report($report), $report->url;
+ if ($obj->can('local_coords') && $asked_for{local_coords_x}) {
+ ($hashref->{local_coords_x}, $hashref->{local_coords_y}) =
+ $obj->local_coords;
+ }
+ if ($obj->can('url')) {
+ my $base = $c->cobrand->base_url_for_report($obj->can('problem') ? $obj->problem : $obj);
+ $hashref->{url} = join '', $base, $obj->url;
+ }
+
+ $hashref->{site_used} = $obj->can('service') ? ($obj->service || $obj->cobrand) : $obj->cobrand;
+
+ $hashref->{reported_as} = $obj->get_extra_metadata('contributed_as') || '';
if (my $fn = $c->stash->{csv}->{extra_data}) {
- my $extra = $fn->($report);
+ my $extra = $fn->($obj);
$hashref = { %$hashref, %$extra };
}
- $csv->combine(
+ $csv->print($c->response, [
+ map {
+ $_ = encode('UTF-8', $_) if $_;
+ }
@{$hashref}{
@{$c->stash->{csv}->{columns}}
},
- );
-
- push @body, $csv->string;
+ ] );
}
-
- my $filename = $c->stash->{csv}->{filename};
- $c->res->content_type('text/csv; charset=utf-8');
- $c->res->header('content-disposition' => "attachment; filename=${filename}.csv");
- $c->res->body( join "", @body );
}
=head1 AUTHOR