aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Somerville <matthew-github@dracos.co.uk>2019-02-12 14:01:38 +0000
committerMatthew Somerville <matthew-github@dracos.co.uk>2019-02-14 16:55:24 +0000
commitc2089e945132d36468f1d9a3bf08ae4d661cf44a (patch)
tree755780edf643ebd8babb910632eb259623fd8a24
parent1825a09bbdfee97928a251a84534d859b1a43387 (diff)
Allow user to be associated with multiple areas.
Update database to store an array of IDs rather than only one; consequential changes to the admin and the dashboard to allow selection.
-rw-r--r--CHANGELOG.md1
-rwxr-xr-xbin/update-schema1
-rw-r--r--db/downgrade_0066---0065.sql8
-rw-r--r--db/schema.sql2
-rw-r--r--db/schema_0066-user-area-ids.sql7
-rw-r--r--docs/_includes/admin-tasks-content.md2
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin.pm6
-rw-r--r--perllib/FixMyStreet/App/Controller/Dashboard.pm19
-rw-r--r--perllib/FixMyStreet/App/Controller/Reports.pm2
-rw-r--r--perllib/FixMyStreet/DB/Result/User.pm39
-rw-r--r--t/app/controller/admin/permissions.t4
-rw-r--r--t/app/controller/admin/users.t14
-rw-r--r--t/app/controller/dashboard.t8
-rw-r--r--templates/web/base/admin/user-form.html10
-rw-r--r--templates/web/base/dashboard/index.html10
-rw-r--r--web/cobrands/fixmystreet/admin.js2
16 files changed, 89 insertions, 46 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index da8db9838..8b1f41378 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@
- Spot moderation conflicts and raise an error. #2384
- Allow searching for <email> in admin.
- Make staff JavaScript more self-contained.
+ - Alow staff user to be associated with multiple areas.
- Bugfixes:
- Check cached reports do still have photos before being shown. #2374
- Delete cache photos upon photo moderation. #2374
diff --git a/bin/update-schema b/bin/update-schema
index 1e1afa72c..9aff9ec5b 100755
--- a/bin/update-schema
+++ b/bin/update-schema
@@ -212,6 +212,7 @@ else {
# (assuming schema change files are never half-applied, which should be the case)
sub get_db_version {
return 'EMPTY' if ! table_exists('problem');
+ return '0066' if column_exists('users', 'area_ids');
return '0065' if constraint_contains('admin_log_object_type_check', 'moderation');
return '0064' if index_exists('moderation_original_data_problem_id_comment_id_idx');
return '0063' if column_exists('moderation_original_data', 'extra');
diff --git a/db/downgrade_0066---0065.sql b/db/downgrade_0066---0065.sql
new file mode 100644
index 000000000..7c2b14660
--- /dev/null
+++ b/db/downgrade_0066---0065.sql
@@ -0,0 +1,8 @@
+BEGIN;
+
+ALTER TABLE users ADD COLUMN area_id integer;
+UPDATE users SET area_id = area_ids[1];
+ALTER TABLE users DROP COLUMN area_ids;
+
+COMMIT;
+
diff --git a/db/schema.sql b/db/schema.sql
index c97e8d585..98005028c 100644
--- a/db/schema.sql
+++ b/db/schema.sql
@@ -35,7 +35,7 @@ create table users (
title text,
twitter_id bigint unique,
facebook_id bigint unique,
- area_id integer,
+ area_ids integer ARRAY,
extra text
);
CREATE UNIQUE INDEX users_email_verified_unique ON users (email) WHERE email_verified;
diff --git a/db/schema_0066-user-area-ids.sql b/db/schema_0066-user-area-ids.sql
new file mode 100644
index 000000000..53ffc31f0
--- /dev/null
+++ b/db/schema_0066-user-area-ids.sql
@@ -0,0 +1,7 @@
+BEGIN;
+
+ALTER TABLE users ADD COLUMN area_ids integer ARRAY;
+UPDATE users SET area_ids = ARRAY[area_id] WHERE area_id IS NOT NULL;
+ALTER TABLE users DROP COLUMN area_id;
+
+COMMIT;
diff --git a/docs/_includes/admin-tasks-content.md b/docs/_includes/admin-tasks-content.md
index 3a93b284f..abc73da1f 100644
--- a/docs/_includes/admin-tasks-content.md
+++ b/docs/_includes/admin-tasks-content.md
@@ -706,7 +706,7 @@ For a more detailed breakdown, visit the stats dashboard. This can be accessed b
From here, you can access statistics on:
- All reports made across the council area
-- Reports made within any specific ward
+- Reports made within any specific ward or wards
- Reports made within any specific category
- Reports made between specific dates
- Reports that have a specific status, eg ‘open’ or ‘fixed’
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm
index acd6ddbf2..84651ad07 100644
--- a/perllib/FixMyStreet/App/Controller/Admin.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin.pm
@@ -1647,7 +1647,7 @@ sub user_edit : Path('user_edit') : Args(1) {
if (!$user->from_body) {
# Non-staff users aren't allowed any permissions or to be in an area
$user->admin_user_body_permissions->delete;
- $user->area_id(undef);
+ $user->area_ids(undef);
delete $c->stash->{areas};
delete $c->stash->{fetched_areas_body_id};
} elsif ($c->stash->{available_permissions}) {
@@ -1667,8 +1667,8 @@ sub user_edit : Path('user_edit') : Args(1) {
if ( $user->from_body && $c->user->has_permission_to('user_assign_areas', $user->from_body->id) ) {
my %valid_areas = map { $_->{id} => 1 } @{ $c->stash->{areas} };
- my $new_area = $c->get_param('area_id');
- $user->area_id( $valid_areas{$new_area} ? $new_area : undef );
+ my @area_ids = grep { $valid_areas{$_} } $c->get_param_list('area_ids');
+ $user->area_ids( @area_ids ? \@area_ids : undef );
}
# Handle 'trusted' flag(s)
diff --git a/perllib/FixMyStreet/App/Controller/Dashboard.pm b/perllib/FixMyStreet/App/Controller/Dashboard.pm
index 7979f31f6..d597ff0ea 100644
--- a/perllib/FixMyStreet/App/Controller/Dashboard.pm
+++ b/perllib/FixMyStreet/App/Controller/Dashboard.pm
@@ -105,15 +105,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_exists && $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')->search(undef, {
columns => [ "id", "name" ],
})->active->translated->with_area_count->all_sorted;
+ $c->stash->{ward} = [];
$c->stash->{bodies} = \@bodies;
}
@@ -142,8 +145,8 @@ sub construct_rs_filter : Private {
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};
@@ -298,7 +301,7 @@ sub csv_filename {
my %where = (
category => $c->stash->{category},
state => $c->stash->{q_state},
- ward => $c->stash->{ward},
+ ward => join(',', @{$c->stash->{ward}}),
);
$where{body} = $c->stash->{body}->id if $c->stash->{body};
join '-',
@@ -475,7 +478,7 @@ sub generate_csv : Private {
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->header('content-disposition' => "attachment; filename=\"${filename}.csv\"");
$c->res->body( join "", @body );
}
diff --git a/perllib/FixMyStreet/App/Controller/Reports.pm b/perllib/FixMyStreet/App/Controller/Reports.pm
index 975b2fdd5..4c28563de 100644
--- a/perllib/FixMyStreet/App/Controller/Reports.pm
+++ b/perllib/FixMyStreet/App/Controller/Reports.pm
@@ -450,7 +450,7 @@ sub summary : Private {
# required to stop errors in generate_grouped_data
$c->stash->{q_state} = '';
- $c->stash->{ward} = $c->get_param('area');
+ $c->stash->{ward} = [ $c->get_param('area') || () ];
$c->stash->{start_date} = $dtf->format_date($start_date);
$c->stash->{end_date} = $c->get_param('end_date');
diff --git a/perllib/FixMyStreet/DB/Result/User.pm b/perllib/FixMyStreet/DB/Result/User.pm
index c50f8834c..bf74e6934 100644
--- a/perllib/FixMyStreet/DB/Result/User.pm
+++ b/perllib/FixMyStreet/DB/Result/User.pm
@@ -36,16 +36,6 @@ __PACKAGE__->add_columns(
{ data_type => "boolean", default_value => \"false", is_nullable => 0 },
"is_superuser",
{ data_type => "boolean", default_value => \"false", is_nullable => 0 },
- "title",
- { data_type => "text", is_nullable => 1 },
- "twitter_id",
- { data_type => "bigint", is_nullable => 1 },
- "facebook_id",
- { data_type => "bigint", is_nullable => 1 },
- "area_id",
- { data_type => "integer", is_nullable => 1 },
- "extra",
- { data_type => "text", is_nullable => 1 },
"created",
{
data_type => "timestamp",
@@ -60,6 +50,16 @@ __PACKAGE__->add_columns(
is_nullable => 0,
original => { default_value => \"now()" },
},
+ "title",
+ { data_type => "text", is_nullable => 1 },
+ "twitter_id",
+ { data_type => "bigint", is_nullable => 1 },
+ "facebook_id",
+ { data_type => "bigint", is_nullable => 1 },
+ "area_ids",
+ { data_type => "integer[]", is_nullable => 1 },
+ "extra",
+ { data_type => "text", is_nullable => 1 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->add_unique_constraint("users_facebook_id_key", ["facebook_id"]);
@@ -119,8 +119,8 @@ __PACKAGE__->has_many(
);
-# Created by DBIx::Class::Schema::Loader v0.07035 @ 2018-05-23 18:54:36
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:/V7+Ygv/t6VX8dDhNGN16w
+# Created by DBIx::Class::Schema::Loader v0.07035 @ 2019-02-12 15:14:37
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:4NBO3A+LfZXOh4Kj/II2LQ
# These are not fully unique constraints (they only are when the *_verified
# is true), but this is managed in ResultSet::User's find() wrapper.
@@ -597,4 +597,19 @@ sub set_last_active {
$self->last_active($time or \'current_timestamp');
}
+has areas_hash => (
+ is => 'ro',
+ lazy => 1,
+ default => sub {
+ my $self = shift;
+ my %ids = map { $_ => 1 } @{$self->area_ids || []};
+ return \%ids;
+ },
+);
+
+sub in_area {
+ my ($self, $area) = @_;
+ return $self->areas_hash->{$area};
+}
+
1;
diff --git a/t/app/controller/admin/permissions.t b/t/app/controller/admin/permissions.t
index 3fc221a63..036ec4509 100644
--- a/t/app/controller/admin/permissions.t
+++ b/t/app/controller/admin/permissions.t
@@ -181,6 +181,7 @@ FixMyStreet::override_config {
subtest "Unsetting user from_body removes all permissions and area " => sub {
is $user2->user_body_permissions->count, 1, 'user2 has 1 permission';
+ $user2->update({ area_ids => [123] }); # Set to check cleared
$mech->get_ok("/admin/user_edit/$user2_id");
$mech->content_contains('Moderate report details');
@@ -204,8 +205,9 @@ FixMyStreet::override_config {
"permissions[user_assign_areas]" => undef,
} } );
+ $user2->discard_changes;
is $user2->user_body_permissions->count, 0, 'user2 has had permissions removed';
- is $user2->area_id, undef, 'user2 has had area removed';
+ is $user2->area_ids, undef, 'user2 has had area removed';
};
};
diff --git a/t/app/controller/admin/users.t b/t/app/controller/admin/users.t
index be781d3ae..d82a7eaef 100644
--- a/t/app/controller/admin/users.t
+++ b/t/app/controller/admin/users.t
@@ -209,7 +209,7 @@ FixMyStreet::override_config {
phone_verified => undef,
flagged => undef,
is_superuser => undef,
- area_id => '',
+ area_ids => undef,
%default_perms,
},
changes => {
@@ -229,7 +229,7 @@ FixMyStreet::override_config {
phone_verified => undef,
flagged => undef,
is_superuser => undef,
- area_id => '',
+ area_ids => undef,
%default_perms,
},
changes => {
@@ -249,7 +249,7 @@ FixMyStreet::override_config {
phone_verified => undef,
flagged => undef,
is_superuser => undef,
- area_id => '',
+ area_ids => undef,
%default_perms,
},
changes => {
@@ -269,7 +269,7 @@ FixMyStreet::override_config {
phone_verified => undef,
flagged => undef,
is_superuser => undef,
- area_id => '',
+ area_ids => undef,
%default_perms,
},
changes => {
@@ -289,7 +289,7 @@ FixMyStreet::override_config {
phone_verified => undef,
flagged => 'on',
is_superuser => undef,
- area_id => '',
+ area_ids => undef,
%default_perms,
},
changes => {
@@ -309,7 +309,7 @@ FixMyStreet::override_config {
phone_verified => undef,
flagged => undef,
is_superuser => undef,
- area_id => '',
+ area_ids => undef,
%default_perms,
},
changes => {
@@ -332,7 +332,7 @@ FixMyStreet::override_config {
phone_verified => undef,
flagged => undef,
is_superuser => 'on',
- area_id => '',
+ area_ids => undef,
},
changes => {
is_superuser => undef,
diff --git a/t/app/controller/dashboard.t b/t/app/controller/dashboard.t
index a5fa8772a..37903513c 100644
--- a/t/app/controller/dashboard.t
+++ b/t/app/controller/dashboard.t
@@ -113,7 +113,7 @@ FixMyStreet::override_config {
};
subtest 'area user can only see their area' => sub {
- $counciluser->update({area_id => $area_id});
+ $counciluser->update({area_ids => [ $area_id ]});
$mech->get_ok("/dashboard");
$mech->content_contains('<h1>Trowbridge</h1>');
@@ -122,7 +122,11 @@ FixMyStreet::override_config {
$mech->get_ok("/dashboard?ward=$alt_area_id");
$mech->content_contains('<h1>Trowbridge</h1>');
- $counciluser->update({area_id => undef});
+ $counciluser->update({area_ids => [ $area_id, $alt_area_id ]});
+ $mech->get_ok("/dashboard");
+ $mech->content_contains('<h1>Bradford-on-Avon / Trowbridge</h1>');
+
+ $counciluser->update({area_ids => undef});
};
subtest 'The correct categories and totals shown by default' => sub {
diff --git a/templates/web/base/admin/user-form.html b/templates/web/base/admin/user-form.html
index bb8511726..735e15e87 100644
--- a/templates/web/base/admin/user-form.html
+++ b/templates/web/base/admin/user-form.html
@@ -79,11 +79,13 @@
%]
</p>
</div>
- <label for="area_id">[% loc('Area:') %]</label>
- <select class="form-control" id='area_id' name='area_id' [% 'disabled' UNLESS c.user.has_permission_to('user_assign_areas', user.from_body.id) %]>
- <option value=''>[% loc('No area') %]</option>
+ <label for="area_ids">[% loc('Area:') %]</label>
+ <select class="form-control js-multiple" id="area_ids" name="area_ids"
+ multiple data-none="-- [% loc('Select an area') %] --"
+ [% 'disabled' UNLESS c.user.has_permission_to('user_assign_areas', user.from_body.id) %]>
[% FOREACH area IN areas %]
- <option value="[% area.id %]"[% ' selected' IF area.id == user.area_id %]>[% area.name | html %]</option>
+ [% SET aid = area.id %]
+ <option value="[% aid %]"[% ' selected' IF user.in_area(aid) %]>[% area.name | html %]</option>
[% END %]
</select>
</li>
diff --git a/templates/web/base/dashboard/index.html b/templates/web/base/dashboard/index.html
index 7c3966b04..c90823d37 100644
--- a/templates/web/base/dashboard/index.html
+++ b/templates/web/base/dashboard/index.html
@@ -24,12 +24,12 @@
<div class="filters">
[% IF body %]
<input type="hidden" name="body" value="[% body.id | html %]">
- [% IF NOT c.user.area_id %]
+ [% IF NOT c.user.area_ids.size %]
<p>
<label for="ward">[% loc('Ward:') %]</label>
- <select class="form-control" name="ward" id="ward"><option value=''>[% loc('All') %]</option>
+ <select class="form-control js-multiple" multiple name="ward" id="ward">
[% FOR w IN children.values.sort('name') %]
- <option value="[% w.id %]"[% ' selected' IF w.id == ward %]>[% w.name %]</option>
+ <option value="[% w.id %]"[% ' selected' IF ward.grep(w.id).size %]>[% w.name %]</option>
[% END %]
</select>
</p>
@@ -47,7 +47,7 @@
[% ELSE %]
<p>
- <label for="ward">[% loc('Council:') %]</label>
+ <label for="body">[% loc('Council:') %]</label>
<select class="form-control" name="body" id="body"><option value=''>[% loc('All') %]</option>
[% FOR b IN bodies %]
[% NEXT IF NOT b.area_count %]
@@ -59,7 +59,7 @@
[% END %]
<p>
- <label for="state">[% loc('Report state:') %]</label>
+ <label for="state">[% loc('Report state:') | replace(' ', '&nbsp;') %]</label>
<select class="form-control" name="state" id="state">
<option value=''>[% loc('All') %]</option>
[% FOR group IN filter_states %]
diff --git a/web/cobrands/fixmystreet/admin.js b/web/cobrands/fixmystreet/admin.js
index c40a7f960..b947b3e49 100644
--- a/web/cobrands/fixmystreet/admin.js
+++ b/web/cobrands/fixmystreet/admin.js
@@ -76,7 +76,7 @@ $(function(){
// On user edit page, hide the area/categories fields if body changes
$("form#user_edit select#body").change(function() {
var show_area = $(this).val() == $(this).find("[data-originally-selected]").val();
- $("form#user_edit select#area_id").closest("li").toggle(show_area);
+ $("form#user_edit select#area_ids").closest("li").toggle(show_area);
$("form#user_edit .js-user-categories").toggle(show_area);
});