diff options
author | Dave Arter <davea@mysociety.org> | 2020-01-28 15:42:39 +0000 |
---|---|---|
committer | Dave Arter <davea@mysociety.org> | 2020-02-26 15:25:31 +0000 |
commit | 1c58ffa769b8f381bbddda062491bddb2fdd6608 (patch) | |
tree | 71812126e6b4affeca4b893359a7067863663728 | |
parent | d887072ed2c71997377782ada0d765970d4bbfef (diff) |
Add admin UI for configuring manifest theme
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm | 80 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/ManifestTheme.pm | 28 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/Default.pm | 1 | ||||
-rw-r--r-- | perllib/FixMyStreet/DB/Result/AdminLog.pm | 6 | ||||
-rw-r--r-- | t/app/controller/admin/manifesttheme.t | 244 | ||||
-rw-r--r-- | t/app/controller/offline.t | 16 | ||||
-rw-r--r-- | templates/web/base/admin/manifesttheme/form.html | 42 | ||||
-rw-r--r-- | templates/web/base/admin/manifesttheme/index.html | 35 |
9 files changed, 453 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ac73269b..809a24675 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ - Allow report as another user with only name. - Allow staff users to sign other people up for alerts. - Group categories on body page. #2850 + - Add admin UI for managing web manifest themes. #2792 - 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/ManifestTheme.pm b/perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm new file mode 100644 index 000000000..d2eed4839 --- /dev/null +++ b/perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm @@ -0,0 +1,80 @@ +package FixMyStreet::App::Controller::Admin::ManifestTheme; +use Moose; +use namespace::autoclean; + +BEGIN { extends 'Catalyst::Controller'; } + +use FixMyStreet::App::Form::ManifestTheme; + +sub auto :Private { + my ($self, $c) = @_; + + if ( $c->cobrand->moniker eq 'fixmystreet' ) { + $c->stash(rs => $c->model('DB::ManifestTheme')->search_rs({}), show_all => 1); + } else { + $c->stash(rs => $c->model('DB::ManifestTheme')->search_rs({ cobrand => $c->cobrand->moniker })); + } +} + +sub index :Path :Args(0) { + my ( $self, $c ) = @_; + + unless ( $c->stash->{show_all} ) { + if ( $c->stash->{rs}->count ) { + $c->res->redirect($c->uri_for($self->action_for('edit'), [ $c->stash->{rs}->first->cobrand ])); + } else { + $c->res->redirect($c->uri_for($self->action_for('create'))); + } + $c->detach; + } +} + +sub item :PathPart('admin/manifesttheme') :Chained :CaptureArgs(1) { + my ($self, $c, $cobrand) = @_; + + my $obj = $c->stash->{rs}->find({ cobrand => $cobrand }) + or $c->detach('/page_error_404_not_found', []); + $c->stash(obj => $obj); +} + +sub edit :PathPart('') :Chained('item') :Args(0) { + my ($self, $c) = @_; + return $self->form($c, $c->stash->{obj}); +} + + +sub create :Local :Args(0) { + my ($self, $c) = @_; + + unless ( $c->stash->{show_all} || $c->stash->{rs}->count == 0) { + $c->res->redirect($c->uri_for($self->action_for('edit'), [ $c->stash->{rs}->first->cobrand ])); + $c->detach; + } + + my $theme = $c->stash->{rs}->new_result({}); + return $self->form($c, $theme); +} + +sub form { + my ($self, $c, $theme) = @_; + + if ($c->get_param('delete_theme')) { + $theme->delete; + $c->forward('/admin/log_edit', [ $theme->id, 'manifesttheme', 'delete' ]); + $c->response->redirect($c->uri_for($self->action_for('index'))); + $c->detach; + } + + my $action = $theme->in_storage ? 'edit' : 'add'; + my $form = FixMyStreet::App::Form::ManifestTheme->new( cobrand => $c->cobrand->moniker ); + $c->stash(template => 'admin/manifesttheme/form.html', form => $form); + $form->process(item => $theme, params => $c->req->params); + return unless $form->validated; + + $c->forward('/admin/log_edit', [ $theme->id, 'manifesttheme', $action ]); + $c->response->redirect($c->uri_for($self->action_for('index'))); +} + + + +1; diff --git a/perllib/FixMyStreet/App/Form/ManifestTheme.pm b/perllib/FixMyStreet/App/Form/ManifestTheme.pm new file mode 100644 index 000000000..17e43ff58 --- /dev/null +++ b/perllib/FixMyStreet/App/Form/ManifestTheme.pm @@ -0,0 +1,28 @@ +package FixMyStreet::App::Form::ManifestTheme; + +use HTML::FormHandler::Moose; +use FixMyStreet::App::Form::I18N; +extends 'HTML::FormHandler::Model::DBIC'; +use namespace::autoclean; + +has 'cobrand' => ( isa => 'Str', is => 'ro' ); + +has '+widget_name_space' => ( default => sub { ['FixMyStreet::App::Form::Widget'] } ); +has '+widget_tags' => ( default => sub { { wrapper_tag => 'p' } } ); +has '+item_class' => ( default => 'ManifestTheme' ); +has_field 'cobrand' => ( required => 0 ); +has_field 'name' => ( required => 1 ); +has_field 'short_name' => ( required => 1 ); +has_field 'background_colour' => ( required => 0 ); +has_field 'theme_colour' => ( required => 0 ); + +before 'update_model' => sub { + my $self = shift; + $self->item->cobrand($self->cobrand) if $self->cobrand && !$self->item->cobrand; +}; + +sub _build_language_handle { FixMyStreet::App::Form::I18N->new } + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm index 9851b4896..695487268 100644 --- a/perllib/FixMyStreet/Cobrand/Default.pm +++ b/perllib/FixMyStreet/Cobrand/Default.pm @@ -685,6 +685,7 @@ sub admin_pages { $pages->{flagged} = [ _('Flagged'), 7 ]; $pages->{states} = [ _('States'), 8 ]; $pages->{config} = [ _('Configuration'), 9]; + $pages->{manifesttheme} = [ _('Manifest Theme'), 11]; $pages->{user_import} = [ undef, undef ]; }; # And some that need special permissions diff --git a/perllib/FixMyStreet/DB/Result/AdminLog.pm b/perllib/FixMyStreet/DB/Result/AdminLog.pm index 5564d829a..4c89138c9 100644 --- a/perllib/FixMyStreet/DB/Result/AdminLog.pm +++ b/perllib/FixMyStreet/DB/Result/AdminLog.pm @@ -91,6 +91,10 @@ sub link { my $category = $self->object; return "/admin/body/" . $category->body_id . '/' . $category->category; } + if ($type eq 'manifesttheme') { + my $theme = $self->object; + return "/admin/manifesttheme/" . $theme->cobrand; + } return ''; } @@ -114,6 +118,7 @@ sub object_summary { role => 'name', template => 'title', category => 'category', + manifesttheme => 'cobrand', }; my $thing = $type_to_thing->{$self->object_type} || 'id'; @@ -130,6 +135,7 @@ sub object { template => 'ResponseTemplate', category => 'Contact', update => 'Comment', + manifesttheme => 'ManifestTheme', }; $type = $type_to_object->{$type} || ucfirst $type; my $object = $self->result_source->schema->resultset($type)->find($id); diff --git a/t/app/controller/admin/manifesttheme.t b/t/app/controller/admin/manifesttheme.t new file mode 100644 index 000000000..ba798f64f --- /dev/null +++ b/t/app/controller/admin/manifesttheme.t @@ -0,0 +1,244 @@ +use FixMyStreet::TestMech; +use FixMyStreet::DB; + +my $mech = FixMyStreet::TestMech->new; + +my $superuser = $mech->create_user_ok('superuser@example.com', name => 'Super User', is_superuser => 1); + +$mech->log_in_ok( $superuser->email ); + +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'lincolnshire', 'fixmystreet' ], +}, sub { + +ok $mech->host('lincolnshire.fixmystreet.com'); + +subtest "theme link on cobrand admin goes to create form if no theme exists" => sub { + is( FixMyStreet::DB->resultset('ManifestTheme')->count, 0, "no themes yet" ); + + $mech->get_ok("/admin"); + $mech->follow_link_ok({ text => "Manifest Theme" }); + + is $mech->res->previous->code, 302, "got 302 for redirect"; + is $mech->res->previous->base->path, "/admin/manifesttheme", "redirected from index"; + is $mech->uri->path, '/admin/manifesttheme/create', "redirected to create page"; +}; + +subtest "name and short_name are required fields" => sub { + is( FixMyStreet::DB->resultset('ManifestTheme')->count, 0, "no themes yet" ); + + $mech->get_ok("/admin/manifesttheme/create"); + $mech->content_lacks("Delete theme"); + + $mech->submit_form_ok({}); + is $mech->uri->path, '/admin/manifesttheme/create', "stayed on create page"; + $mech->content_contains("field is required"); + is( FixMyStreet::DB->resultset('ManifestTheme')->count, 0, "theme not created" ); + + $mech->get_ok("/admin/manifesttheme/create"); + $mech->submit_form_ok({ with_fields => { short_name => "Lincs FMS" } }); + is $mech->uri->path, '/admin/manifesttheme/create', "stayed on create page"; + $mech->content_contains("field is required", "name is required"); + is( FixMyStreet::DB->resultset('ManifestTheme')->count, 0, "theme not created" ); + + $mech->get_ok("/admin/manifesttheme/create"); + $mech->submit_form_ok({ with_fields => { name => "Lincolnshire FixMyStreet" } }); + is $mech->uri->path, '/admin/manifesttheme/create', "stayed on create page"; + $mech->content_contains("field is required", "short_name is required"); + is( FixMyStreet::DB->resultset('ManifestTheme')->count, 0, "theme not created" ); +}; + +subtest "cobrand admin lets you create a new theme" => sub { + is( FixMyStreet::DB->resultset('ManifestTheme')->count, 0, "no themes yet" ); + + $mech->get_ok("/admin/manifesttheme/create"); + $mech->content_lacks("Delete theme"); + + my $fields = { + name => "Lincolnshire FixMyStreet", + short_name => "Lincs FMS", + }; + $mech->submit_form_ok( { with_fields => $fields } ); + is $mech->uri->path, '/admin/manifesttheme/lincolnshire', "redirected to edit page"; + is( FixMyStreet::DB->resultset('ManifestTheme')->count, 1, "theme was created" ); + + my $theme = FixMyStreet::DB->resultset('ManifestTheme')->find({ cobrand => 'lincolnshire' }); + is $theme->name, "Lincolnshire FixMyStreet"; + is $theme->short_name, "Lincs FMS"; + is $theme->background_colour, undef; + + my $log = $superuser->admin_logs->search({}, { order_by => { -desc => 'id' } })->first; + is $log->object_id, $theme->id; + is $log->action, "add"; + is $log->object_summary, "lincolnshire"; + is $log->link, "/admin/manifesttheme/lincolnshire"; + + $fields = { + background_colour => "#663399", + theme_colour => "rgb(102, 51, 153)", + }; + $mech->submit_form_ok( { with_fields => $fields } ); + $theme->discard_changes; + is $theme->background_colour, "#663399"; + is $theme->theme_colour, "rgb(102, 51, 153)"; + + $log = $superuser->admin_logs->search({}, { order_by => { -desc => 'id' } })->first; + is $log->object_id, $theme->id; + is $log->action, "edit"; +}; + +subtest "theme link on cobrand admin goes to edit form when theme exists" => sub { + is( FixMyStreet::DB->resultset('ManifestTheme')->count, 1, "theme exists" ); + + $mech->get_ok("/admin"); + $mech->follow_link_ok({ text => "Manifest Theme" }); + + is $mech->res->previous->code, 302, "got 302 for redirect"; + is $mech->res->previous->base->path, "/admin/manifesttheme", "redirected from index"; + is $mech->uri->path, '/admin/manifesttheme/lincolnshire', "redirected to edit page"; +}; + +subtest "create page on cobrand admin redirects to edit form when theme exists" => sub { + is( FixMyStreet::DB->resultset('ManifestTheme')->count, 1, "theme exists" ); + + $mech->get_ok("/admin/manifesttheme/create"); + + is $mech->res->previous->code, 302, "got 302 for redirect"; + is $mech->uri->path, '/admin/manifesttheme/lincolnshire', "redirected to edit page"; +}; + +subtest "can delete theme" => sub { + is( FixMyStreet::DB->resultset('ManifestTheme')->count, 1, "theme exists" ); + + $mech->get_ok("/admin/manifesttheme/lincolnshire"); + my $theme_id = FixMyStreet::DB->resultset('ManifestTheme')->find({ cobrand => 'lincolnshire' })->id; + + $mech->submit_form_ok({ button => 'delete_theme' }); + is $mech->uri->path, '/admin/manifesttheme/create', "redirected to create page"; + + is( FixMyStreet::DB->resultset('ManifestTheme')->count, 0, "theme deleted" ); + + my $log = $superuser->admin_logs->search({}, { order_by => { -desc => 'id' } })->first; + is $log->object_id, $theme_id; + is $log->action, "delete"; +}; + +subtest "can't edit another cobrand's theme" => sub { + FixMyStreet::DB->resultset('ManifestTheme')->create({ + cobrand => "tfl", + name => "Transport for London Street Care", + short_name => "TfL Street Care", + }); + + $mech->get("/admin/manifesttheme/tfl"); + ok !$mech->res->is_success(), "want a bad response"; + is $mech->res->code, 404, "got 404"; +}; + +ok $mech->host('www.fixmystreet.com'); + +subtest "fms cobrand lets you view all manifest themes" => sub { + is( FixMyStreet::DB->resultset('ManifestTheme')->count, 1, "theme already exists" ); + + $mech->get_ok("/admin"); + $mech->follow_link_ok({ text => "Manifest Theme" }); + + is $mech->uri->path, '/admin/manifesttheme', "taken to list page"; + + $mech->content_contains("Transport for London Street Care"); + $mech->content_contains("TfL Street Care"); + +}; + +subtest "fms cobrand lets you edit a cobrand's manifest theme" => sub { + $mech->get_ok("/admin/manifesttheme"); + $mech->follow_link_ok({ url => "manifesttheme/tfl" }) or diag $mech->content; + + my $fields = { + name => "Transport for London Report It", + }; + $mech->submit_form_ok( { with_fields => $fields } ); + is $mech->uri->path, '/admin/manifesttheme', "redirected back to list page"; + + my $theme = FixMyStreet::DB->resultset('ManifestTheme')->find({ cobrand => 'tfl' }); + is $theme->name, "Transport for London Report It"; + +}; + +subtest "fms cobrand lets you create a new manifest theme" => sub { + $mech->get_ok("/admin/manifesttheme"); + $mech->follow_link_ok({ text => "Create" }); + + my $fields = { + name => "FixMyStreet Pro", + short_name => "FMS Pro", + cobrand => "fixmystreet", + }; + $mech->submit_form_ok( { with_fields => $fields } ); + is $mech->uri->path, '/admin/manifesttheme', "redirected to list page"; + + is( FixMyStreet::DB->resultset('ManifestTheme')->count, 2, "theme added" ); + my $theme = FixMyStreet::DB->resultset('ManifestTheme')->find({ cobrand => 'fixmystreet' }); + is $theme->name, "FixMyStreet Pro"; +}; + +subtest "fms cobrand prevents you creating a duplicate theme" => sub { + $mech->get_ok("/admin/manifesttheme"); + $mech->follow_link_ok({ text => "Create" }); + + my $fields = { + name => "FixMyStreet Pro", + short_name => "FMS Pro", + cobrand => "fixmystreet", + }; + $mech->submit_form_ok( { with_fields => $fields } ); + is $mech->uri->path, '/admin/manifesttheme/create', "stayed on create form"; + + is( FixMyStreet::DB->resultset('ManifestTheme')->count, 2, "theme not added" ); +}; + +subtest "fms cobrand prevents creating a duplicate by editing" => sub { + $mech->get_ok("/admin/manifesttheme"); + $mech->follow_link_ok({ url => "manifesttheme/tfl" }); + + my $fields = { + cobrand => "fixmystreet", + }; + $mech->submit_form_ok( { with_fields => $fields } ); + is $mech->uri->path, '/admin/manifesttheme/tfl', "stayed on edit page"; +}; + +}; + +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fixamingata' ], +}, sub { + +ok $mech->host("www.fixamingata.se"), "change host to FixaMinGata"; + +subtest "single cobrand behaves correctly" => sub { + FixMyStreet::DB->resultset('ManifestTheme')->delete_all; + is( FixMyStreet::DB->resultset('ManifestTheme')->count, 0, "themes all deleted" ); + + $mech->get_ok("/admin/manifesttheme"); + is $mech->uri->path, '/admin/manifesttheme/create', "redirected to create page"; + + my $fields = { + name => "FixaMinGata Theme Test", + short_name => "FixaMinGata Short Name", + cobrand => "fixamingata", + }; + $mech->submit_form_ok( { with_fields => $fields } ); + is $mech->uri->path, '/admin/manifesttheme/fixamingata', "redirected to edit form page"; + $mech->content_contains("FixaMinGata Theme Test"); + $mech->content_contains("FixaMinGata Short Name"); + + is( FixMyStreet::DB->resultset('ManifestTheme')->count, 1, "theme added" ); + my $theme = FixMyStreet::DB->resultset('ManifestTheme')->find({ cobrand => 'fixamingata' }); + is $theme->name, "FixaMinGata Theme Test"; +}; + + +}; + +done_testing(); diff --git a/t/app/controller/offline.t b/t/app/controller/offline.t index d2a5009ec..a3feae514 100644 --- a/t/app/controller/offline.t +++ b/t/app/controller/offline.t @@ -1,4 +1,5 @@ use FixMyStreet::TestMech; +use FixMyStreet::DB; use Path::Tiny; my $mech = FixMyStreet::TestMech->new; @@ -20,6 +21,21 @@ FixMyStreet::override_config { sizes => '133x100' }, 'correct icon'; }; + subtest 'themed manifest' => sub { + FixMyStreet::DB->resultset('ManifestTheme')->create({ + cobrand => "test", + name => "My Test Cobrand FMS", + short_name => "Test FMS", + background_colour => "#ff00ff", + theme_colour => "#ffffff", + }); + + my $j = $mech->get_ok_json('/.well-known/manifest.webmanifest'); + is $j->{name}, 'My Test Cobrand FMS', 'correctly overridden name'; + is $j->{short_name}, 'Test FMS', 'correctly overridden short_name'; + is $j->{background_color}, '#ff00ff', 'correctly overridden background colour'; + is $j->{theme_color}, '#ffffff', 'correctly overridden theme colour'; + }; $theme_dir->remove_tree; }; diff --git a/templates/web/base/admin/manifesttheme/form.html b/templates/web/base/admin/manifesttheme/form.html new file mode 100644 index 000000000..72a703ab2 --- /dev/null +++ b/templates/web/base/admin/manifesttheme/form.html @@ -0,0 +1,42 @@ +[% INCLUDE 'admin/header.html' title=loc('Theme') -%] + +<form method="post"> + <div class="admin-hint"> + <p>[% loc("The <strong>name</strong> is a string that represents the name of the web application as it is usually displayed to the user (e.g., amongst a list of other applications, or as a label for an icon).") %]</p> + </div> + [% form.field('name').render | safe %] + + <div class="admin-hint"> + <p>[% loc("The <strong>short name</strong> is a string that represents the name of the web application displayed to the user if there is not enough space to display name (e.g., as a label for an icon on the phone home screen).") %]</p> + </div> + [% form.field('short_name').render | safe %] + + <div class="admin-hint"> + <p>[% loc("The <strong>theme colour</strong> defines the default theme colour for the application. This sometimes affects how the OS displays the site (e.g., on Android's task switcher, the theme colour surrounds the site). Colours should be specified with CSS syntax, e.g. <strong><code>#ff00ff</code></strong> or <strong><code>rgb(255, 0, 255)</code></strong> or a named colour like <strong><code>fuchsia</code></strong>.") %]</p> + </div> + [% form.field('theme_colour').render | safe %] + + <div class="admin-hint"> + <p>[% loc("The <strong>background colour</strong> defines a placeholder background colour for the application splash screen before it has loaded. Colours should be specified with CSS syntax, e.g. <strong><code>#ff00ff</code></strong> or <strong><code>rgb(255, 0, 255)</code></strong> or a named colour like <strong><code>fuchsia</code></strong>.") %]</p> + </div> + [% form.field('background_colour').render | safe %] + + [% IF show_all %] + [% form.field('cobrand').render | safe %] + [% END %] + + <p> + <input class="btn" type="submit" name="submit" value="[% loc('Save changes') %]"> + </p> + [% IF form.item.id %] + <p> + <input class="btn-danger" type="submit" name="delete_theme" value="[% loc('Delete theme') %]" data-confirm="[% loc('Are you sure?') %]"> + </p> + [% END %] +</form> + +[% IF show_all %] + <p><a href="[% c.uri_for(c.controller.action_for('list')) %]">Return to themes list</a></p> +[% END %] + +[% INCLUDE 'admin/footer.html' %] diff --git a/templates/web/base/admin/manifesttheme/index.html b/templates/web/base/admin/manifesttheme/index.html new file mode 100644 index 000000000..df94c394f --- /dev/null +++ b/templates/web/base/admin/manifesttheme/index.html @@ -0,0 +1,35 @@ +[% INCLUDE 'admin/header.html' title=loc('Themes') %] + +<table> + <thead> + <tr> + <th> [% loc('Cobrand') %] </th> + <th> [% loc('Name') %] </th> + <th> [% loc('Short Name') %] </th> + <th> [% loc('Background Colour') %] </th> + <th> [% loc('Theme Colour') %] </th> + <th> </th> + </tr> + </thead> + <tbody> + [% FOR theme IN rs.all %] + <tr> + <td>[% theme.cobrand %]</td> + <td>[% theme.name %]</td> + <td>[% theme.short_name %]</td> + <td>[% theme.background_colour %]</td> + <td>[% theme.theme_colour %]</td> + <td> <a href="[% c.uri_for(c.controller.action_for('edit'), [theme.cobrand]) %]" class="btn">[% loc('Edit') %]</a> </td> + </tr> + [% END %] + </tbody> +</table> + + + +<p> + <a href="[% c.uri_for(c.controller.action_for('create')) %]">[% loc('Create') %]</a> + </p> + + +[% INCLUDE 'admin/footer.html' %] |