aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md3
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin.pm45
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/New.pm18
-rw-r--r--perllib/FixMyStreet/TestMech.pm8
-rw-r--r--perllib/Open311/GetServiceRequestUpdates.pm23
-rw-r--r--perllib/Open311/PopulateServiceList.pm14
-rw-r--r--t/app/controller/admin.t112
-rw-r--r--t/open311/getservicerequestupdates.t30
-rw-r--r--t/open311/populate-service-list.t9
-rw-r--r--templates/web/base/admin/template_edit.html39
-rw-r--r--templates/web/base/admin/templates.html24
-rw-r--r--templates/web/base/report/new/category.html33
-rw-r--r--templates/web/fixmystreet.com/header.html43
-rw-r--r--templates/web/zurich/admin/templates.html28
-rw-r--r--web/cobrands/fixmystreet/fixmystreet.js70
15 files changed, 454 insertions, 45 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e81c1644b..0f950e974 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@
- Add Expand map toggle to more mobile maps.
- Add functionality to have per-body /reports page.
- Cobrands can disable sending of moderation emails. #1910
+ - Open311 category group support. #1923
- Front end improvements:
- SVG assets for core elements like button icons and map controls #1888
- Remove unneeded 2x PNG fallback images.
@@ -52,6 +53,8 @@
- Council dashboard CSV export now has token based authentication #1911
- And uses machine-readable dates. #1929
- Consolidate various admin summary statistics page. #1919.
+ - 'Auto-response' flag on response templates is honoured for fetched
+ Open311 updates. #1924
- UK:
- Use SVG logo, inlined on front page. #1887
- Inline critical CSS on front page.
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm
index b485ea2dc..e21b0cc2f 100644
--- a/perllib/FixMyStreet/App/Controller/Admin.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin.pm
@@ -1102,19 +1102,52 @@ sub template_edit : Path('templates') : Args(2) {
} } @live_contacts;
$c->stash->{contacts} = \@all_contacts;
- if ($c->req->method eq 'POST') {
+ # bare block to use 'last' if form is invalid.
+ if ($c->req->method eq 'POST') { {
if ($c->get_param('delete_template') && $c->get_param('delete_template') eq _("Delete template")) {
$template->contact_response_templates->delete_all;
$template->delete;
} else {
+ my @live_contact_ids = map { $_->id } @live_contacts;
+ my @new_contact_ids = grep { $c->get_param("contacts[$_]") } @live_contact_ids;
+ my %new_contacts = map { $_ => 1 } @new_contact_ids;
+ for my $contact (@all_contacts) {
+ $contact->{active} = $new_contacts{$contact->{id}};
+ }
+
$template->title( $c->get_param('title') );
$template->text( $c->get_param('text') );
$template->state( $c->get_param('state') );
- $template->auto_response( $c->get_param('auto_response') ? 1 : 0 );
- $template->update_or_insert;
- my @live_contact_ids = map { $_->id } @live_contacts;
- my @new_contact_ids = grep { $c->get_param("contacts[$_]") } @live_contact_ids;
+ $template->auto_response( $c->get_param('auto_response') && $template->state ? 1 : 0 );
+ if ($template->auto_response) {
+ my @check_contact_ids = @new_contact_ids;
+ # If the new template has not specific categories (i.e. it
+ # applies to all categories) then we need to check each of those
+ # category ids for existing auto-response templates.
+ if (!scalar @check_contact_ids) {
+ @check_contact_ids = @live_contact_ids;
+ }
+ my $query = {
+ 'auto_response' => 1,
+ 'contact.id' => [ @check_contact_ids, undef ],
+ 'me.state' => $template->state,
+ };
+ if ($template->in_storage) {
+ $query->{'me.id'} = { '!=', $template->id };
+ }
+ if ($c->stash->{body}->response_templates->search($query, {
+ join => { 'contact_response_templates' => 'contact' },
+ })->count) {
+ $c->stash->{errors} = {
+ auto_response => _("There is already an auto-response template for this category/state.")
+ };
+ }
+ }
+
+ last if $c->stash->{errors};
+
+ $template->update_or_insert;
$template->contact_response_templates->search({
contact_id => { '!=' => \@new_contact_ids },
})->delete;
@@ -1126,7 +1159,7 @@ sub template_edit : Path('templates') : Args(2) {
}
$c->res->redirect( $c->uri_for( 'templates', $c->stash->{body}->id ) );
- }
+ } }
$c->stash->{response_template} = $template;
diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm
index 7f1de3ed4..f63b0af1e 100644
--- a/perllib/FixMyStreet/App/Controller/Report/New.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/New.pm
@@ -664,7 +664,7 @@ sub setup_categories_and_bodies : Private {
$bodies_to_list{ $contact->body_id } = $contact->body;
unless ( $seen{$contact->category} ) {
- push @category_options, { name => $contact->category, value => $contact->category_display };
+ push @category_options, { name => $contact->category, value => $contact->category_display, group => $contact->get_extra_metadata('group') || '' };
my $metas = $contact->get_metadata_for_input;
$category_extras{$contact->category} = $metas if @$metas;
@@ -682,9 +682,9 @@ sub setup_categories_and_bodies : Private {
if (@category_options) {
# If there's an Other category present, put it at the bottom
@category_options = (
- { name => _('-- Pick a category --'), value => _('-- Pick a category --') },
+ { name => _('-- Pick a category --'), value => _('-- Pick a category --'), group => '' },
grep { $_->{name} ne _('Other') } @category_options );
- push @category_options, { name => _('Other'), value => $seen{_('Other')} } if $seen{_('Other')};
+ push @category_options, { name => _('Other'), value => $seen{_('Other')}, group => _('Other') } if $seen{_('Other')};
}
$c->cobrand->call_hook(munge_category_list => \@category_options, \@contacts, \%category_extras);
@@ -705,6 +705,18 @@ sub setup_categories_and_bodies : Private {
$c->stash->{missing_details_bodies} = \@missing_details_bodies;
$c->stash->{missing_details_body_names} = \@missing_details_body_names;
+
+ my %category_groups = ();
+ for my $category (@category_options) {
+ push @{$category_groups{$category->{group}}}, $category;
+ }
+
+ my @category_groups = ();
+ for my $group ( grep { $_ ne _('Other') } sort keys %category_groups ) {
+ push @category_groups, { name => $group, categories => $category_groups{$group} };
+ }
+ push @category_groups, { name => _('Other'), categories => $category_groups{_('Other')} } if ($category_groups{_('Other')});
+ $c->stash->{category_groups} = \@category_groups;
}
sub setup_report_extra_fields : Private {
diff --git a/perllib/FixMyStreet/TestMech.pm b/perllib/FixMyStreet/TestMech.pm
index dbfc94286..ac2ac023d 100644
--- a/perllib/FixMyStreet/TestMech.pm
+++ b/perllib/FixMyStreet/TestMech.pm
@@ -630,6 +630,14 @@ sub delete_defect_type {
$defect_type->delete;
}
+sub delete_response_template {
+ my $mech = shift;
+ my $response_template = shift;
+
+ $response_template->contact_response_templates->delete_all;
+ $response_template->delete;
+}
+
sub create_contact_ok {
my $self = shift;
my %contact_params = (
diff --git a/perllib/Open311/GetServiceRequestUpdates.pm b/perllib/Open311/GetServiceRequestUpdates.pm
index db2a452da..2620b176a 100644
--- a/perllib/Open311/GetServiceRequestUpdates.pm
+++ b/perllib/Open311/GetServiceRequestUpdates.pm
@@ -103,16 +103,17 @@ sub update_comments {
$problem = $self->schema->resultset('Problem')->to_body($body)->search( $criteria );
if (my $p = $problem->first) {
- next unless defined $request->{update_id} && defined $request->{description};
+ next unless defined $request->{update_id};
my $c = $p->comments->search( { external_id => $request->{update_id} } );
if ( !$c->first ) {
+ my $state = $self->map_state( $request->{status} );
my $comment = $self->schema->resultset('Comment')->new(
{
problem => $p,
user => $self->system_user,
external_id => $request->{update_id},
- text => $request->{description},
+ text => $self->comment_text_for_request($request, $p, $state),
mark_fixed => 0,
mark_open => 0,
anonymous => 0,
@@ -138,8 +139,6 @@ sub update_comments {
# do not change the status of the problem as it's
# tricky to determine the right thing to do.
if ( $comment->created > $p->lastupdate ) {
- my $state = $self->map_state( $request->{status} );
-
# don't update state unless it's an allowed state and it's
# actually changing the state of the problem
if ( FixMyStreet::DB::Result::Problem->visible_states()->{$state} && $p->state ne $state &&
@@ -180,6 +179,22 @@ sub update_comments {
return 1;
}
+sub comment_text_for_request {
+ my ($self, $request, $problem, $state) = @_;
+
+ return $request->{description} if $request->{description};
+
+ if (my $template = $problem->response_templates->search({
+ auto_response => 1,
+ 'me.state' => $state
+ })->first) {
+ return $template->text;
+ }
+
+ print STDERR "Couldn't determine update text for $request->{update_id} (report " . $problem->id . ")\n";
+ return "";
+}
+
sub map_state {
my $self = shift;
my $incoming_state = shift;
diff --git a/perllib/Open311/PopulateServiceList.pm b/perllib/Open311/PopulateServiceList.pm
index dbd0d1c68..e8d06efdf 100644
--- a/perllib/Open311/PopulateServiceList.pm
+++ b/perllib/Open311/PopulateServiceList.pm
@@ -156,7 +156,13 @@ sub _handle_existing_contact {
if ( $contact and lc($metadata) eq 'true' ) {
$self->_add_meta_to_contact( $contact );
} elsif ( $contact and $contact->extra and lc($metadata) eq 'false' ) {
- $contact->update( { extra => undef } );
+ $contact->set_extra_fields();
+ $contact->update;
+ }
+
+ if (my $group = $self->_current_service->{group}) {
+ $contact->set_extra_metadata(group => $group);
+ $contact->update;
}
push @{ $self->found_contacts }, $self->_current_service->{service_code};
@@ -182,6 +188,12 @@ sub _create_contact {
);
};
+ if (my $group = $self->_current_service->{group}) {
+ $contact->set_extra_metadata(group => $group);
+ $contact->update;
+ }
+
+
if ( $@ ) {
warn "Failed to create contact for service code " . $self->_current_service->{service_code} . " for body @{[$self->_current_body->id]}: $@\n"
if $self->verbose >= 1;
diff --git a/t/app/controller/admin.t b/t/app/controller/admin.t
index 0be54dbc5..179557976 100644
--- a/t/app/controller/admin.t
+++ b/t/app/controller/admin.t
@@ -1659,6 +1659,108 @@ subtest "response templates are included on page" => sub {
};
};
+subtest "auto-response templates that duplicate a single category can't be added" => sub {
+ $mech->delete_response_template($_) for $oxfordshire->response_templates;
+ my $template = $oxfordshire->response_templates->create({
+ title => "Report fixed - potholes",
+ text => "Thank you for your report. This problem has been fixed.",
+ auto_response => 1,
+ state => 'fixed - council',
+ });
+ $template->contact_response_templates->find_or_create({
+ contact_id => $oxfordshirecontact->id,
+ });
+ 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" );
+
+ # This response template has the same category & state as an existing one
+ # so won't be allowed.
+ my $fields = {
+ title => "Report marked fixed - potholes",
+ text => "Thank you for your report. This pothole has been fixed.",
+ auto_response => 'on',
+ state => 'fixed - council',
+ "contacts[".$oxfordshirecontact->id."]" => 1,
+ };
+ $mech->submit_form_ok( { with_fields => $fields } );
+ is $mech->uri->path, '/admin/templates/' . $oxfordshire->id . '/new', 'not redirected';
+ $mech->content_contains( 'Please correct the errors below' );
+ $mech->content_contains( 'There is already an auto-response template for this category/state.' );
+
+ is $oxfordshire->response_templates->count, 1, "Duplicate response template wasn't added";
+};
+
+subtest "auto-response templates that duplicate all categories can't be added" => sub {
+ $mech->delete_response_template($_) for $oxfordshire->response_templates;
+ $oxfordshire->response_templates->create({
+ title => "Report investigating - all cats",
+ text => "Thank you for your report. This problem has been fixed.",
+ auto_response => 1,
+ state => 'fixed - council',
+ });
+ 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" );
+
+ # There's already a response template for all categories and this state, so
+ # this new template won't be allowed.
+ my $fields = {
+ title => "Report investigating - single cat",
+ text => "Thank you for your report. This problem has been fixed.",
+ auto_response => 'on',
+ state => 'fixed - council',
+ "contacts[".$oxfordshirecontact->id."]" => 1,
+ };
+ $mech->submit_form_ok( { with_fields => $fields } );
+ is $mech->uri->path, '/admin/templates/' . $oxfordshire->id . '/new', 'not redirected';
+ $mech->content_contains( 'Please correct the errors below' );
+ $mech->content_contains( 'There is already an auto-response template for this category/state.' );
+
+
+ is $oxfordshire->response_templates->count, 1, "Duplicate response template wasn't added";
+};
+
+subtest "all-category auto-response templates that duplicate a single category can't be added" => sub {
+ $mech->delete_response_template($_) for $oxfordshire->response_templates;
+ my $template = $oxfordshire->response_templates->create({
+ title => "Report fixed - potholes",
+ text => "Thank you for your report. This problem has been fixed.",
+ auto_response => 1,
+ state => 'fixed - council',
+ });
+ $template->contact_response_templates->find_or_create({
+ contact_id => $oxfordshirecontact->id,
+ });
+ 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" );
+
+ # This response template is implicitly for all categories, but there's
+ # already a template for a specific category in this state, so it won't be
+ # allowed.
+ my $fields = {
+ title => "Report marked fixed - all cats",
+ text => "Thank you for your report. This problem has been fixed.",
+ auto_response => 'on',
+ state => 'fixed - council',
+ };
+ $mech->submit_form_ok( { with_fields => $fields } );
+ is $mech->uri->path, '/admin/templates/' . $oxfordshire->id . '/new', 'not redirected';
+ $mech->content_contains( 'Please correct the errors below' );
+ $mech->content_contains( 'There is already an auto-response template for this category/state.' );
+
+ is $oxfordshire->response_templates->count, 1, "Duplicate response template wasn't added";
+};
+
+
+
$mech->log_in_ok( $superuser->email );
subtest "response priorities can be added" => sub {
@@ -1674,8 +1776,8 @@ subtest "response priorities can be added" => sub {
};
$mech->submit_form_ok( { with_fields => $fields } );
- is $oxfordshire->response_priorities->count, 1, "Response template was added to body";
- is $oxfordshirecontact->response_priorities->count, 1, "Response template was added to contact";
+ is $oxfordshire->response_priorities->count, 1, "Response priority was added to body";
+ is $oxfordshirecontact->response_priorities->count, 1, "Response priority was added to contact";
};
subtest "response priorities can set to default" => sub {
@@ -1693,7 +1795,7 @@ subtest "response priorities can set to default" => sub {
$mech->submit_form_ok( { with_fields => $fields } );
is $oxfordshire->response_priorities->count, 1, "Still one response priority";
- is $oxfordshirecontact->response_priorities->count, 1, "Still one response template";
+ is $oxfordshirecontact->response_priorities->count, 1, "Still one response priority";
ok $oxfordshire->response_priorities->first->is_default, "Response priority set to default";
};
@@ -1710,8 +1812,8 @@ subtest "response priorities are limited by body" => sub {
name => "Bromley Cat 0",
} );
- is $bromley->response_priorities->count, 1, "Response template was added to Bromley";
- is $oxfordshire->response_priorities->count, 1, "Response template wasn't added to Oxfordshire";
+ is $bromley->response_priorities->count, 1, "Response priority was added to Bromley";
+ is $oxfordshire->response_priorities->count, 1, "Response priority wasn't added to Oxfordshire";
$mech->get_ok( "/admin/responsepriorities/" . $oxfordshire->id );
$mech->content_lacks( $bromleypriority->name );
diff --git a/t/open311/getservicerequestupdates.t b/t/open311/getservicerequestupdates.t
index 9a8db4374..da427e505 100644
--- a/t/open311/getservicerequestupdates.t
+++ b/t/open311/getservicerequestupdates.t
@@ -20,11 +20,18 @@ my $user = FixMyStreet::DB->resultset('User')->find_or_create(
my %bodies = (
2237 => FixMyStreet::DB->resultset("Body")->create({ name => 'Oxfordshire' }),
- 2482 => FixMyStreet::DB->resultset("Body")->new({ id => 2482 }),
+ 2482 => FixMyStreet::DB->resultset("Body")->create({ name=> 'Bromley', id => 2482 }),
2651 => FixMyStreet::DB->resultset("Body")->new({ id => 2651 }),
);
$bodies{2237}->body_areas->create({ area_id => 2237 });
+my $response_template = $bodies{2482}->response_templates->create({
+ title => "investigating template",
+ text => "We are investigating this report.",
+ auto_response => 1,
+ state => "investigating"
+});
+
my $requests_xml = qq{<?xml version="1.0" encoding="utf-8"?>
<service_requests_updates>
<request_update>
@@ -157,6 +164,10 @@ for my $test (
end_state => 'confirmed',
},
+ # NB because we have an auto-response ResponseTemplate set up for
+ # the 'investigating' state, this test is also testing that the
+ # response template isn't used if the update XML has a non-empty
+ # <description>.
{
desc => 'investigating status changes problem status',
description => 'This is a note',
@@ -334,6 +345,18 @@ for my $test (
end_state => 'fixed - council',
},
{
+ desc => 'empty description triggers auto-response template',
+ description => 'We are investigating this report.',
+ xml_description => '',
+ external_id => 638344,
+ start_state => 'fixed - council',
+ comment_status => 'INVESTIGATING',
+ mark_fixed => 0,
+ mark_open => 0,
+ problem_state => 'investigating',
+ end_state => 'investigating',
+ },
+ {
desc => 'open status does not re-open hidden report',
description => 'This is a note',
external_id => 638344,
@@ -346,7 +369,7 @@ for my $test (
},
) {
subtest $test->{desc} => sub {
- my $local_requests_xml = setup_xml($problem->external_id, $problem->id, $test->{comment_status});
+ my $local_requests_xml = setup_xml($problem->external_id, $problem->id, $test->{comment_status}, $test->{xml_description});
my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'servicerequestupdates.xml' => $local_requests_xml } );
$problem->lastupdate( DateTime->now()->subtract( days => 1 ) );
@@ -762,13 +785,14 @@ foreach my $test ( {
done_testing();
sub setup_xml {
- my ($id, $id_ext, $status) = @_;
+ my ($id, $id_ext, $status, $description) = @_;
my $xml = $requests_xml;
my $updated_datetime = sprintf( '<updated_datetime>%s</updated_datetime>', $dt );
$xml =~ s/UPDATED_DATETIME/$updated_datetime/;
$xml =~ s#<service_request_id>\d+</service_request_id>#<service_request_id>$id</service_request_id>#;
$xml =~ s#<service_request_id_ext>\d+</service_request_id_ext>#<service_request_id_ext>$id_ext</service_request_id_ext>#;
$xml =~ s#<status>\w+</status>#<status>$status</status># if $status;
+ $xml =~ s#<description>.+</description>#<description>$description</description># if defined $description;
return $xml;
diff --git a/t/open311/populate-service-list.t b/t/open311/populate-service-list.t
index 04740a9e8..7d4f491c6 100644
--- a/t/open311/populate-service-list.t
+++ b/t/open311/populate-service-list.t
@@ -39,6 +39,15 @@ subtest 'check basic functionality' => sub {
my $contact_count = FixMyStreet::DB->resultset('Contact')->search( { body_id => 1 } )->count();
is $contact_count, 3, 'correct number of contacts';
+
+ for my $test (
+ { code => "001", group => "sanitation" },
+ { code => "002", group => "street" },
+ { code => "003", group => "street" },
+ ) {
+ my $contact = FixMyStreet::DB->resultset('Contact')->search( { body_id => 1, email => $test->{code} } )->first;
+ is $contact->get_extra->{group}, $test->{group}, "Group set correctly";
+ }
};
subtest 'check non open311 contacts marked as deleted' => sub {
diff --git a/templates/web/base/admin/template_edit.html b/templates/web/base/admin/template_edit.html
index 76de70dcc..3e436dbf9 100644
--- a/templates/web/base/admin/template_edit.html
+++ b/templates/web/base/admin/template_edit.html
@@ -9,20 +9,30 @@
accept-charset="utf-8"
class="validate">
+ [% IF errors %]
+ <p class="error">[% loc('Please correct the errors below') %]</p>
+ [% END %]
+
+
+ <div class="admin-hint">
+ <p>
+ [% loc('This is a <strong>private</strong> name for this template so you can identify it when updating reports or editing in the admin.') %]
+ </p>
+ </div>
<p>
<strong>[% loc('Title:') %] </strong>
<input type="text" name="title" class="required form-control" size="30" value="[% rt.title| html %]">
</p>
+
+ <div class="admin-hint">
+ <p>
+ [% loc('This is the <strong>public</strong> text that will be shown on the site.') %]
+ </p>
+ </div>
<p>
<strong>[% loc('Text:') %] </strong>
<textarea class="form-control" name="text" class="required">[% rt.text |html %]</textarea>
</p>
- <p>
- <label>
- <strong>[% loc('Auto-response:') %]</strong>
- <input type="checkbox" name="auto_response" [% 'checked' IF rt.auto_response %] />
- </label>
- </p>
<div class="admin-hint">
<p>
@@ -41,13 +51,28 @@
[% INCLUDE 'admin/state_groups_select.html' current_state=rt.state include_empty=1 %]
</p>
+ [% IF errors.auto_response %]
+ <div class="form-error">[% errors.auto_response %]</div>
+ [% END %]
+ <div class="admin-hint">
+ <p>
+ [% loc('If ticked, this template will be used for Open311 updates that put problems in this state.') %]
+ </p>
+ </div>
+ <p>
+ <label>
+ <strong>[% loc('Auto-response:') %]</strong>
+ <input type="checkbox" name="auto_response" [% 'checked' IF rt.auto_response %] />
+ </label>
+ </p>
+
<p>
<input type="hidden" name="token" value="[% csrf_token %]" >
<input type="submit" class="btn" name="Edit templates" value="[% rt.id ? loc('Save changes') : loc('Create template') %]" >
</p>
[% IF rt.id %]
<p>
- <input class="delete btn-danger" type="submit" name="delete_template" value="[% loc('Delete template') %]">
+ <input class="delete btn-danger" type="submit" name="delete_template" value="[% loc('Delete template') %]" data-confirm="[% loc('Are you sure?') %]">
</p>
[% END %]
</form>
diff --git a/templates/web/base/admin/templates.html b/templates/web/base/admin/templates.html
index f9dda7a4c..444f2734d 100644
--- a/templates/web/base/admin/templates.html
+++ b/templates/web/base/admin/templates.html
@@ -1,24 +1,30 @@
[% INCLUDE 'admin/header.html' title=tprintf(loc('Response Templates for %s'), body.name) -%]
-[% IF c.cobrand.moniker == 'zurich' %]
- <h2> [% tprintf(loc('Response Templates for %s'), body.name) %] </h2>
-[% END %]
-
<table>
<thead>
<tr>
<th> [% loc('Title') %] </th>
- <th> [% loc('Text') %] </th>
- <th> [% loc('Created') %] </th>
+ <th> [% loc('Categories') %] </th>
+ <th> [% loc('State') %] </th>
+ <th> [% loc('Auto Response') %] </th>
<th> &nbsp; </th>
</tr>
</thead>
<tbody>
[% FOR t IN response_templates %]
<tr>
- <td> [% t.title %] </td>
- <td> [% t.text %] </td>
- <td> [% t.created %] </td>
+ <td> [% t.title | html %] </td>
+ <td>
+ [% UNLESS t.contacts.size %]
+ <em>[% loc('All categories') %]</em>
+ [% ELSE %]
+ [% FOR contact IN t.contacts %]
+ [% contact.category_display %][% ',' UNLESS loop.last %]
+ [% END %]
+ [% END %]
+ </td>
+ <td> [% t.state | html %] </td>
+ <td> [% IF t.auto_response %]X[% END %] </td>
<td> <a href="[% c.uri_for('templates', body.id, t.id) %]" class="btn">[% loc('Edit') %]</a> </td>
</tr>
[% END %]
diff --git a/templates/web/base/report/new/category.html b/templates/web/base/report/new/category.html
index 16f6113f2..94d5479a6 100644
--- a/templates/web/base/report/new/category.html
+++ b/templates/web/base/report/new/category.html
@@ -1,19 +1,38 @@
-[% IF category_options.size ~%]
+[% IF category_options.size OR category_groups.size ~%]
+ [%~ BLOCK category_option ~%]
+ [% cat_op_lc = cat_op.name | lower =%]
+ <option value='[% cat_op.name | html %]'[% ' selected' IF report.category == cat_op.name || category_lc == cat_op_lc || (category_options.size == 2 AND loop.last) ~%]
+ >[% IF loop.first %][% cat_op.value %][% ELSE %][% cat_op.value | html %][% END %]</option>
+ [%~ END ~%]
+
[% IF category;
category_lc = category | lower;
END; ~%]
<label for='form_category' id="form_category_label">
[%~ loc('Category') ~%]
</label>[% =%]
- <select class="form-control" name="category" id="form_category"
+ <select class="form-control[% IF category_groups.size %] js-grouped-select[% END %]" name="category" id="form_category"
[%~ IF c.user.from_body =%]
data-role="[% c.user.has_body_permission_to('planned_reports') ? 'inspector' : 'user' %]" data-body="[% c.user.from_body.name %]" data-prefill="[% c.cobrand.prefill_report_fields_for_inspector %]"
[%~ END ~%]
>
- [%~ FOREACH cat_op IN category_options ~%]
- [% cat_op_lc = cat_op.name | lower =%]
- <option value='[% cat_op.name | html %]'[% ' selected' IF report.category == cat_op.name || category_lc == cat_op_lc || (category_options.size == 2 AND loop.last) ~%]
- >[% IF loop.first %][% cat_op.value %][% ELSE %][% cat_op.value | html %][% END %]</option>
- [%~ END =%]
+ [%~ IF category_groups.size ~%]
+ [%~ FOREACH group IN category_groups ~%]
+ [% IF group.name %]<optgroup label="[% group.name %]">[% END %]
+ [%~ FOREACH cat_op IN group.categories ~%]
+ [% INCLUDE category_option %]
+ [%~ END ~%]
+ [% IF group.name %]</optgroup>[% END %]
+ [%~ END =%]
+ [%~ ELSE ~%]
+ [%~ FOREACH cat_op IN category_options ~%]
+ [% INCLUDE category_option %]
+ [%~ END =%]
+ [%~ END ~%]
</select>
+ [%~ IF category_groups.size ~%]
+ <label id="form_subcategory_label" class="hidden">
+ [%~ loc('Subcategory') ~%]
+ </label>
+ [%~ END ~%]
[%~ END ~%]
diff --git a/templates/web/fixmystreet.com/header.html b/templates/web/fixmystreet.com/header.html
new file mode 100644
index 000000000..6f01f4184
--- /dev/null
+++ b/templates/web/fixmystreet.com/header.html
@@ -0,0 +1,43 @@
+[%
+ SET html_att = ' lang="' _ lang_code _ '"';
+ # For a right-to-left language, use the following line in your own header:
+ # SET html_att = html_att _ ' dir="rtl"';
+-%]
+<!doctype html>
+<!--[if IE 7]> <html class="no-js ie7 iel8"[% html_att %]><![endif]-->
+<!--[if IE 8]> <html class="no-js ie8 iel8"[% html_att %]><![endif]-->
+<!--[if IE 9]> <html class="no-js ie9"[% html_att %]><![endif]-->
+<!--[if gt IE 9]><!--><html class="no-js"[% html_att %]
+[% IF appcache ~%]
+ manifest="/offline/appcache.manifest"
+[%~ END %]><!--<![endif]-->
+ <head>
+ <meta name="viewport" content="initial-scale=1.0">
+
+ <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
+ <meta name="HandHeldFriendly" content="true">
+ <meta name="mobileoptimized" content="0">
+
+ [% INCLUDE 'header_opengraph.html' %]
+ [% INCLUDE 'header/css.html' %]
+ [% INCLUDE 'common_header_tags.html' %]
+ [% TRY %][% PROCESS 'header_extra.html' %][% CATCH file %][% END %]
+
+ </head>
+ [% TRY %][% PROCESS 'set_body_class.html' %][% CATCH file %][% END %]
+ <body class="[% bodyclass | html IF bodyclass %]">
+ <div class="top_banner top_banner--donate"><p>
+ <strong>We’re hiring for an exciting new project.</strong>
+ <a href="https://mysociety.workable.com/">Come and join our friendly team</a>.
+ </p></div>
+
+ <div class="wrapper">
+ <div class="table-cell">
+ [% INCLUDE 'header_site.html' %]
+
+ [% pre_container_extra %]
+
+ <div class="container">
+ <div class="content[% " $mainclass" | html IF mainclass %]" role="main">
+
+ <!-- [% INCLUDE 'debug_header.html' %] -->
diff --git a/templates/web/zurich/admin/templates.html b/templates/web/zurich/admin/templates.html
new file mode 100644
index 000000000..2db9e2e34
--- /dev/null
+++ b/templates/web/zurich/admin/templates.html
@@ -0,0 +1,28 @@
+[% INCLUDE 'admin/header.html' title=tprintf(loc('Response Templates for %s'), body.name) -%]
+
+<h2> [% tprintf(loc('Response Templates for %s'), body.name) %] </h2>
+
+<table>
+ <thead>
+ <tr>
+ <th> [% loc('Title') %] </th>
+ <th> [% loc('Text') %] </th>
+ <th> [% loc('Created') %] </th>
+ <th> &nbsp; </th>
+ </tr>
+ </thead>
+ <tbody>
+[% FOR t IN response_templates %]
+ <tr>
+ <td> [% t.title %] </td>
+ <td> [% t.text %] </td>
+ <td> [% t.created %] </td>
+ <td> <a href="[% c.uri_for('templates', body.id, t.id) %]" class="btn">[% loc('Edit') %]</a> </td>
+ </tr>
+[% END %]
+ </tbody>
+</table>
+
+<a href="[% c.uri_for('templates', body.id, 'new') %]" class="btn">[% loc('New template') %]</a>
+
+[% INCLUDE 'admin/footer.html' %]
diff --git a/web/cobrands/fixmystreet/fixmystreet.js b/web/cobrands/fixmystreet/fixmystreet.js
index 16ee7b511..d6ea9de18 100644
--- a/web/cobrands/fixmystreet/fixmystreet.js
+++ b/web/cobrands/fixmystreet/fixmystreet.js
@@ -424,6 +424,75 @@ $.extend(fixmystreet.set_up, {
});
},
+ category_groups: function() {
+ var $category_select = $("select#form_category.js-grouped-select");
+ if ($category_select.length === 0) {
+ return;
+ }
+ var $group_select = $("<select></select>").addClass("form-control");
+ var $subcategory_label = $("#form_subcategory_label");
+ var $empty_option = $category_select.find("option").first();
+
+ $group_select.change(function() {
+ var subcategory_id = $(this).find(":selected").data("subcategory_id");
+ $(".js-subcategory").hide();
+ if (subcategory_id === undefined) {
+ $subcategory_label.addClass("hidden");
+ $category_select.val($(this).val()).change();
+ } else {
+ $("#" + subcategory_id).show().change();
+ $("#form_subcategory_label").removeClass("hidden");
+ }
+ });
+
+ var subcategory_change = function() {
+ $category_select.val($(this).val()).change();
+ };
+
+ var add_optgroup = function(el) {
+ var $el = $(el);
+ var $options = $el.find("option");
+
+ if ($options.length == 1) {
+ add_option($options.get(0));
+ } else {
+ var label = $el.attr("label");
+ var subcategory_id = "subcategory_" + label.replace(/[^a-zA-Z]+/g, '');
+ var $opt = $("<option></option>").text(label).val(label);
+ $opt.data("subcategory_id", subcategory_id);
+ $group_select.append($opt);
+
+ var $sub_select = $("<select></select>").addClass("form-control js-subcategory");
+ $sub_select.attr("id", subcategory_id);
+ $sub_select.append($empty_option.clone());
+ $options.each(function() {
+ var $newopt = $(this).clone();
+ $sub_select.append($newopt);
+ // Make sure any preselected value is preserved in the new UI:
+ if ($newopt.attr('selected')) {
+ $group_select.val(label);
+ }
+ });
+ $sub_select.hide().insertAfter($subcategory_label).change(subcategory_change);
+ }
+ };
+
+ var add_option = function(el) {
+ $group_select.append($(el).clone());
+ };
+
+ $category_select.hide();
+ $group_select.insertAfter($category_select);
+ $category_select.find("optgroup, > option").each(function() {
+ if (this.tagName.toLowerCase() === 'optgroup') {
+ add_optgroup(this);
+ } else if (this.tagName.toLowerCase() === 'option') {
+ add_option(this);
+ }
+ });
+ $group_select.change();
+ },
+
hide_name: function() {
$('body').on('click', '.js-hide-name', function(e){
e.preventDefault();
@@ -862,6 +931,7 @@ fixmystreet.update_pin = function(lonlat, savePushState) {
if (category_select.val() != '-- Pick a category --') {
category_select.change();
}
+ fixmystreet.run(fixmystreet.set_up.category_groups);
if (data.contribute_as) {
var $select = $('.js-contribute-as');