aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin/Users.pm32
-rw-r--r--perllib/FixMyStreet/DB/Result/AdminLog.pm75
-rw-r--r--t/app/controller/admin/bodies.t7
-rw-r--r--t/app/controller/admin/roles.t9
-rw-r--r--t/app/controller/admin/templates.t9
-rw-r--r--t/app/controller/admin/users.t22
-rw-r--r--t/app/controller/moderate.t12
-rw-r--r--templates/web/base/admin/users/index.html3
-rw-r--r--templates/web/base/admin/users/log.html72
10 files changed, 237 insertions, 6 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b786b6c76..7650612cd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,8 @@
- Allow editing of category name. #1398
- Allow non-superuser staff to use 2FA, and optional enforcement of 2FA.
- Add optional enforced password expiry.
+ - Store a moderation history on admin report edit.
+ - Add user admin log page.
- New features:
- Categories can be listed under more than one group #2475
- OpenID Connect login support. #2523
diff --git a/perllib/FixMyStreet/App/Controller/Admin/Users.pm b/perllib/FixMyStreet/App/Controller/Admin/Users.pm
index ac9353921..802fbb9f5 100644
--- a/perllib/FixMyStreet/App/Controller/Admin/Users.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin/Users.pm
@@ -406,6 +406,38 @@ sub edit : Chained('user') : PathPart('') : Args(0) {
return 1;
}
+sub log : Chained('user') : PathPart('log') : Args(0) {
+ my ($self, $c) = @_;
+
+ my $user = $c->stash->{user};
+
+ my $after = $c->get_param('after');
+
+ my %time;
+ foreach ($user->admin_logs->all) {
+ push @{$time{$_->whenedited->epoch}}, { type => 'log', date => $_->whenedited, log => $_ };
+ }
+ foreach ($c->cobrand->problems->search({ extra => { like => '%contributed_by%' . $user->id . '%' } })->all) {
+ next unless $_->get_extra_metadata('contributed_by') == $user->id;
+ push @{$time{$_->created->epoch}}, { type => 'problemContributedBy', date => $_->created, obj => $_ };
+ }
+
+ foreach ($user->user_planned_reports->all) {
+ push @{$time{$_->added->epoch}}, { type => 'shortlistAdded', date => $_->added, obj => $_->report };
+ push @{$time{$_->removed->epoch}}, { type => 'shortlistRemoved', date => $_->removed, obj => $_->report } if $_->removed;
+ }
+
+ foreach ($user->problems->all) {
+ push @{$time{$_->created->epoch}}, { type => 'problem', date => $_->created, obj => $_ };
+ }
+
+ foreach ($user->comments->all) {
+ push @{$time{$_->created->epoch}}, { type => 'update', date => $_->created, obj => $_};
+ }
+
+ $c->stash->{time} = \%time;
+}
+
sub post_edit_redirect : Private {
my ( $self, $c, $user ) = @_;
diff --git a/perllib/FixMyStreet/DB/Result/AdminLog.pm b/perllib/FixMyStreet/DB/Result/AdminLog.pm
index 221690405..5564d829a 100644
--- a/perllib/FixMyStreet/DB/Result/AdminLog.pm
+++ b/perllib/FixMyStreet/DB/Result/AdminLog.pm
@@ -61,4 +61,79 @@ __PACKAGE__->belongs_to(
# Created by DBIx::Class::Schema::Loader v0.07035 @ 2019-04-25 12:06:39
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BLPP1KitphuY56ptaXhzgg
+sub link {
+ my $self = shift;
+
+ my $type = $self->object_type;
+ my $id = $self->object_id;
+ return "/report/$id" if $type eq 'problem';
+ return "/admin/users/$id" if $type eq 'user';
+ return "/admin/body/$id" if $type eq 'body';
+ return "/admin/roles/$id" if $type eq 'role';
+ if ($type eq 'update') {
+ my $update = $self->object;
+ return "/report/" . $update->problem_id . "#update_$id";
+ }
+ if ($type eq 'moderation') {
+ my $mod = $self->object;
+ if ($mod->comment_id) {
+ my $update = $self->result_source->schema->resultset('Comment')->find($mod->comment_id);
+ return "/report/" . $update->problem_id . "#update_" . $mod->comment_id;
+ } else {
+ return "/report/" . $mod->problem_id;
+ }
+ }
+ if ($type eq 'template') {
+ my $template = $self->object;
+ return "/admin/templates/" . $template->body_id . "/$id";
+ }
+ if ($type eq 'category') {
+ my $category = $self->object;
+ return "/admin/body/" . $category->body_id . '/' . $category->category;
+ }
+ return '';
+}
+
+sub actual_object_type {
+ my $self = shift;
+ my $type = $self->object_type;
+ return $type unless $type eq 'moderation' && $self->object;
+ return $self->object->comment_id ? 'update' : 'report';
+}
+
+sub object_summary {
+ my $self = shift;
+ my $object = $self->object;
+ return unless $object;
+
+ return $object->comment_id || $object->problem_id if $self->object_type eq 'moderation';
+ return $object->email || $object->phone || $object->id if $self->object_type eq 'user';
+
+ my $type_to_thing = {
+ body => 'name',
+ role => 'name',
+ template => 'title',
+ category => 'category',
+ };
+ my $thing = $type_to_thing->{$self->object_type} || 'id';
+
+ return $object->$thing;
+}
+
+sub object {
+ my $self = shift;
+
+ my $type = $self->object_type;
+ my $id = $self->object_id;
+ my $type_to_object = {
+ moderation => 'ModerationOriginalData',
+ template => 'ResponseTemplate',
+ category => 'Contact',
+ update => 'Comment',
+ };
+ $type = $type_to_object->{$type} || ucfirst $type;
+ my $object = $self->result_source->schema->resultset($type)->find($id);
+ return $object;
+}
+
1;
diff --git a/t/app/controller/admin/bodies.t b/t/app/controller/admin/bodies.t
index cd86e3da6..aaa9bca65 100644
--- a/t/app/controller/admin/bodies.t
+++ b/t/app/controller/admin/bodies.t
@@ -327,4 +327,11 @@ FixMyStreet::override_config {
};
};
+subtest 'check log of the above' => sub {
+ $mech->get_ok('/admin/users/' . $superuser->id . '/log');
+ $mech->content_contains('Added category <a href="/admin/body/' . $body->id . '/test/category">test/category</a>');
+ $mech->content_contains('Edited category <a href="/admin/body/' . $body->id . '/test category">test category</a>');
+ $mech->content_contains('Edited body <a href="/admin/body/' . $body->id . '">Aberdeen City Council</a>');
+};
+
done_testing();
diff --git a/t/app/controller/admin/roles.t b/t/app/controller/admin/roles.t
index 6dd40cbb6..bc8371404 100644
--- a/t/app/controller/admin/roles.t
+++ b/t/app/controller/admin/roles.t
@@ -22,7 +22,7 @@ $user->user_body_permissions->create({
permission_type => 'report_edit_priority',
});
-FixMyStreet::DB->resultset("Role")->create({
+my $role_a = FixMyStreet::DB->resultset("Role")->create({
body => $body,
name => 'Role A',
permissions => ['moderate', 'user_edit'],
@@ -129,4 +129,11 @@ subtest 'superuser can see all bodies' => sub {
$mech->content_contains('Role C');
};
+subtest 'check log of the above' => sub {
+ my $id = FixMyStreet::DB->resultset("Role")->find({ name => "Role B" })->id;
+ $mech->get_ok('/admin/users/' . $editor->id . '/log');
+ $mech->content_contains('Added role <a href="/admin/roles/' . $id . '">Role B</a>');
+ $mech->content_contains('Deleted role ' . $role_a->id);
+};
+
done_testing();
diff --git a/t/app/controller/admin/templates.t b/t/app/controller/admin/templates.t
index 3bbd7bf85..39903deb1 100644
--- a/t/app/controller/admin/templates.t
+++ b/t/app/controller/admin/templates.t
@@ -61,7 +61,13 @@ subtest "response templates can be added" => sub {
};
$mech->submit_form_ok( { with_fields => $fields } );
- is $oxfordshire->response_templates->count, 1, "Response template was added";
+ is $oxfordshire->response_templates->count, 1, "Response template was added";
+};
+
+subtest 'check log of the above' => sub {
+ my $template_id = $oxfordshire->response_templates->first->id;
+ $mech->get_ok('/admin/users/' . $superuser->id . '/log');
+ $mech->content_contains('Added template <a href="/admin/templates/' . $oxfordshire->id . '/' . $template_id . '">Report acknowledgement</a>');
};
subtest "but not another with the same title" => sub {
@@ -215,7 +221,6 @@ subtest "auto-response templates that duplicate external_status_code can't be ad
});
is $oxfordshire->response_templates->count, 1, "Initial response template was created";
-
$mech->log_in_ok( $superuser->email );
$mech->get_ok( "/admin/templates/" . $oxfordshire->id . "/new" );
diff --git a/t/app/controller/admin/users.t b/t/app/controller/admin/users.t
index 17256a214..95aac051d 100644
--- a/t/app/controller/admin/users.t
+++ b/t/app/controller/admin/users.t
@@ -3,6 +3,7 @@ use FixMyStreet::TestMech;
my $mech = FixMyStreet::TestMech->new;
my $user = $mech->create_user_ok('test@example.com', name => 'Test User');
+my $original_user_id = $user->id; # For log later
my $user2 = $mech->create_user_ok('test2@example.com', name => 'Test User 2');
my $user3 = $mech->create_user_ok('test3@example.com', name => 'Test User 3');
@@ -649,4 +650,25 @@ subtest "View timeline" => sub {
$mech->get_ok('/admin/timeline');
};
+subtest 'View user log' => sub {
+ my $p = FixMyStreet::DB->resultset('Problem')->search({ user_id => $user->id })->first;
+ $user->add_to_planned_reports($p);
+
+ # User 1 created all the reports
+ my $id = $p->id;
+ $mech->get_ok('/admin/users?search=' . $user->email);
+ $mech->follow_link_ok({ text => 'Timeline', n => 2 });
+ $mech->content_like(qr/Problem.*?>$id<\/a> created/);
+ $mech->content_like(qr/Problem.*?>$id<\/a> added to shortlist/);
+
+ # User 3 edited user 2 above
+ $mech->get_ok('/admin/users/' . $user3->id . '/log');
+ $mech->content_like(qr/Edited user.*?test2\@example/);
+
+ # Superuser added a user, and merged one
+ $mech->get_ok('/admin/users/' . $superuser->id . '/log');
+ $mech->content_like(qr/Added user.*?0156/);
+ $mech->content_like(qr/Merged user $original_user_id/);
+};
+
done_testing();
diff --git a/t/app/controller/moderate.t b/t/app/controller/moderate.t
index e22d9edbc..fdbd0abb6 100644
--- a/t/app/controller/moderate.t
+++ b/t/app/controller/moderate.t
@@ -475,8 +475,6 @@ subtest 'updates' => sub {
}});
$mech->content_lacks('update good good bad good');
};
-
- $update->moderation_original_data->delete;
};
my $update2 = create_update();
@@ -515,4 +513,14 @@ subtest 'And do it as a superuser' => sub {
subtest 'Check moderation history in admin' => sub {
$mech->get_ok('/admin/report_edit/' . $report->id);
};
+
+subtest 'Check moderation in user log' => sub {
+ $mech->get_ok('/admin/users/' . $user->id . '/log');
+ my $report_id = $report->id;
+ $mech->content_like(qr/Moderated report.*?$report_id/);
+ my $update_id = $update->id;
+ $mech->content_like(qr/Moderated update.*?$update_id/);
+};
+
+
done_testing();
diff --git a/templates/web/base/admin/users/index.html b/templates/web/base/admin/users/index.html
index 3b5c5bcc4..e573c10fe 100644
--- a/templates/web/base/admin/users/index.html
+++ b/templates/web/base/admin/users/index.html
@@ -46,7 +46,7 @@
[% IF c.cobrand.moniker != 'zurich' %]
<th>[% loc('Flagged') %]</th>
[% END %]
- <th>*</th>
+ <th colspan="2">*</th>
</tr>
[%- FOREACH user IN users %]
<tr>
@@ -65,6 +65,7 @@
<td>[% user.flagged == 2 ? loc('User in abuse table') : user.flagged ? loc('Yes') : '&nbsp;' %]</td>
[% END %]
<td>[% IF user.id %]<a href="[% c.uri_for_action( 'admin/users/edit', [ user.id ] ) %]">[% loc('Edit') %]</a>[% END %]</td>
+ <td>[% IF user.id %]<a href="[% c.uri_for_action( 'admin/users/log', [ user.id ] ) %]">[% loc('Timeline') %]</a>[% END %]</td>
</tr>
[%- END -%]
</table>
diff --git a/templates/web/base/admin/users/log.html b/templates/web/base/admin/users/log.html
new file mode 100644
index 000000000..a596d040c
--- /dev/null
+++ b/templates/web/base/admin/users/log.html
@@ -0,0 +1,72 @@
+[% INCLUDE 'admin/header.html' title=loc('Timeline') _ ', ' _ user.name %]
+
+<style>
+.timeline ul {
+ margin-bottom: 0;
+}
+.timeline dd {
+ margin-bottom: 0;
+}
+</style>
+
+[%
+action_map = {
+ add = 'Added'
+ delete = 'Deleted'
+ edit = 'Edited'
+ merge = 'Merged'
+ moderation = 'Moderated'
+ resend = 'Resent'
+ category_change = 'Changed category of'
+ state_change = 'Changed state of'
+}
+%]
+
+[%- date = '' %]
+[% FOREACH moment IN time.keys.sort.reverse %]
+ [%- curdate = time.$moment.0.date.strftime('%A, %e %B %Y') -%]
+ [%- IF date != curdate %]
+ [% '</dl>' IF date %]
+ <h2>[% curdate %]</h2>
+
+ <dl class="timeline">
+ [%- date = curdate -%]
+ [%- END -%]
+ <dt><b>[% time.$moment.0.date.hms %]</b></dt>
+ <dd><ul>
+ [% FOREACH item IN time.$moment %]
+ <li>
+ [%~ IF item.obj.problem_id %]
+ [%~ SET report_url = c.uri_for( '/report', item.obj.problem_id ) _ "#update_" _ item.obj.id %]
+ [%~ ELSE %]
+ [%~ SET report_url = c.uri_for('/report', item.obj.id) %]
+ [%~ END %]
+ [%~ SET report_link = "<a href='" _ report_url _ "'>" _ item.obj.id _ "</a>" %]
+ [%- SWITCH item.type -%]
+ [%~ CASE 'problem' %]
+ [%- tprintf(loc('Problem %s created'), report_link) %], ‘[% item.obj.title | html %]’
+ [%~ CASE 'problemContributedBy' %]
+ [%- tprintf(loc('Problem %s created on behalf of %s'), report_link, item.obj.name) %], ‘[% item.obj.title | html %]’
+ [%~ CASE 'update' %]
+ [% tprintf(loc("Update %s created for problem %d"), report_link, item.obj.problem_id) %]
+ [% item.obj.text | add_links | markup(item.obj.user) | html_para %]
+ [%~ CASE 'shortlistAdded' %]
+ [%- tprintf(loc('Problem %s added to shortlist'), report_link) %]
+ [%~ CASE 'shortlistRemoved' %]
+ [%- tprintf(loc('Problem %s removed from shortlist'), report_link) %]
+ [%~ CASE 'log' %]
+ [%~ SET object_summary = item.log.object_summary %]
+ [% IF object_summary %]
+ [%~ SET link = tprintf('<a href="%s">%s</a>', item.log.link, object_summary) %]
+ [%- tprintf('%s %s %s', action_map.${item.log.action}, item.log.actual_object_type, link) %]
+ [% ' – ' _ item.log.reason IF item.log.reason %]
+ [% ELSE %]
+ [%- tprintf('%s %s %s', action_map.${item.log.action}, item.log.actual_object_type, item.log.object_id) %]
+ [% END %]
+ [%- END %]
+ </li>
+ [%- END %]
+ </ul></dd>
+[% END %]
+
+[% INCLUDE 'admin/footer.html' %]