mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-11 18:37:33 -04:00
Merge pull request #2773 from alphagov/letter-branding-preview
Add preview pane for letter branding
This commit is contained in:
@@ -13,9 +13,8 @@
|
||||
|
||||
const $paneWrapper = $('<div class="column-full"></div>');
|
||||
const $form = $('form');
|
||||
const $previewPane = $('<iframe src="/_email?' +
|
||||
buildQueryString(['branding_style', branding_style]) +
|
||||
'" class="email-branding-preview"></iframe>');
|
||||
const previewType = $form.data('previewType');
|
||||
const $previewPane = $(`<iframe src="/_${previewType}?${buildQueryString(['branding_style', branding_style])}" class="branding-preview"></iframe>`);
|
||||
|
||||
function buildQueryString () {
|
||||
return $.map(arguments, (val, idx) => encodeURI(val[0]) + '=' + encodeURI(val[1])).join('&');
|
||||
@@ -26,12 +25,12 @@
|
||||
if ($target.attr('name') == 'branding_style') {
|
||||
branding_style = $target.val();
|
||||
}
|
||||
$previewPane.attr('src', '/_email?' + buildQueryString(['branding_style', branding_style]));
|
||||
$previewPane.attr('src', `/_${previewType}?${buildQueryString(['branding_style', branding_style])}`);
|
||||
}
|
||||
|
||||
$paneWrapper.append($previewPane);
|
||||
$form.find('.grid-row').eq(0).prepend($paneWrapper);
|
||||
$form.attr('action', location.pathname.replace(/set-email-branding$/, 'preview-email-branding'));
|
||||
$form.attr('action', location.pathname.replace(new RegExp(`set-${previewType}-branding$`), `preview-${previewType}-branding`));
|
||||
$form.find('button[type="submit"]').text('Save');
|
||||
|
||||
$('fieldset').on('change', 'input[name="branding_style"]', setPreviewPane);
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
.email-branding-preview {
|
||||
.branding-preview {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border: solid 1px $border-colour;
|
||||
@@ -61,7 +61,7 @@ $path: '/static/images/';
|
||||
@import 'components/conditional-radios';
|
||||
@import 'components/vendor/breadcrumbs';
|
||||
@import 'components/vendor/responsive-embed';
|
||||
@import 'components/email-preview-pane';
|
||||
@import 'components/preview-pane';
|
||||
@import 'components/task-list';
|
||||
|
||||
@import 'views/dashboard';
|
||||
|
||||
+31
-2
@@ -10,9 +10,9 @@ from flask_login import current_user, login_required
|
||||
from notifications_utils.international_billing_rates import (
|
||||
INTERNATIONAL_BILLING_RATES,
|
||||
)
|
||||
from notifications_utils.template import HTMLEmailTemplate
|
||||
from notifications_utils.template import HTMLEmailTemplate, LetterImageTemplate
|
||||
|
||||
from app import email_branding_client
|
||||
from app import email_branding_client, letter_branding_client
|
||||
from app.main import main
|
||||
from app.main.forms import FieldWithNoneOption, SearchByNameForm
|
||||
from app.main.views.sub_navigation_dictionaries import features_nav
|
||||
@@ -174,6 +174,35 @@ def email_template():
|
||||
return resp
|
||||
|
||||
|
||||
@main.route('/_letter')
|
||||
def letter_template():
|
||||
branding_style = request.args.get('branding_style')
|
||||
|
||||
if branding_style == FieldWithNoneOption.NONE_OPTION_VALUE:
|
||||
branding_style = None
|
||||
|
||||
if branding_style:
|
||||
filename = letter_branding_client.get_letter_branding(branding_style)['filename']
|
||||
else:
|
||||
filename = 'no-branding'
|
||||
|
||||
template = {'subject': '', 'content': ''}
|
||||
image_url = url_for('main.letter_branding_preview_image', filename=filename)
|
||||
|
||||
template_image = str(LetterImageTemplate(
|
||||
template,
|
||||
image_url=image_url,
|
||||
page_count=1,
|
||||
))
|
||||
|
||||
resp = make_response(
|
||||
render_template('views/service-settings/letter-preview.html', template=template_image)
|
||||
)
|
||||
|
||||
resp.headers['X-Frame-Options'] = 'SAMEORIGIN'
|
||||
return resp
|
||||
|
||||
|
||||
@main.route('/documentation')
|
||||
def documentation():
|
||||
return render_template('views/documentation.html')
|
||||
|
||||
@@ -810,6 +810,28 @@ def service_set_letter_branding(service_id):
|
||||
current_branding=current_service.letter_branding_id,
|
||||
)
|
||||
|
||||
if form.validate_on_submit():
|
||||
return redirect(url_for(
|
||||
'.service_preview_letter_branding',
|
||||
service_id=service_id,
|
||||
branding_style=form.branding_style.data,
|
||||
))
|
||||
|
||||
return render_template(
|
||||
'views/service-settings/set-letter-branding.html',
|
||||
form=form,
|
||||
search_form=SearchByNameForm()
|
||||
)
|
||||
|
||||
|
||||
@main.route("/services/<service_id>/service-settings/preview-letter-branding", methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@user_is_platform_admin
|
||||
def service_preview_letter_branding(service_id):
|
||||
branding_style = request.args.get('branding_style')
|
||||
|
||||
form = ServicePreviewBranding(branding_style=branding_style)
|
||||
|
||||
if form.validate_on_submit():
|
||||
current_service.update(
|
||||
letter_branding=form.branding_style.data
|
||||
@@ -817,9 +839,10 @@ def service_set_letter_branding(service_id):
|
||||
return redirect(url_for('.service_settings', service_id=service_id))
|
||||
|
||||
return render_template(
|
||||
'views/service-settings/set-letter-branding.html',
|
||||
'views/service-settings/preview-letter-branding.html',
|
||||
form=form,
|
||||
search_form=SearchByNameForm()
|
||||
service_id=service_id,
|
||||
action=url_for('main.service_preview_letter_branding', service_id=service_id),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ from app.utils import (
|
||||
get_template,
|
||||
should_skip_template_page,
|
||||
user_has_permissions,
|
||||
user_is_platform_admin,
|
||||
)
|
||||
|
||||
form_objects = {
|
||||
@@ -222,6 +223,34 @@ def view_letter_template_preview(service_id, template_id, filetype):
|
||||
return TemplatePreview.from_database_object(db_template, filetype, page=request.args.get('page'))
|
||||
|
||||
|
||||
@main.route("/templates/letter-preview-image/<filename>")
|
||||
@login_required
|
||||
@user_is_platform_admin
|
||||
def letter_branding_preview_image(filename):
|
||||
template = {
|
||||
'subject': 'An example letter',
|
||||
'content': (
|
||||
'Lorem Ipsum is simply dummy text of the printing and typesetting '
|
||||
'industry.\n\nLorem Ipsum has been the industry’s standard dummy '
|
||||
'text ever since the 1500s, when an unknown printer took a galley '
|
||||
'of type and scrambled it to make a type specimen book.\n\n'
|
||||
'# History\n\nIt has survived not only\n\n'
|
||||
'* five centuries\n'
|
||||
'* but also the leap into electronic typesetting\n\n'
|
||||
'It was popularised in the 1960s with the release of Letraset '
|
||||
'sheets containing Lorem Ipsum passages, and more recently with '
|
||||
'desktop publishing software like Aldus PageMaker including '
|
||||
'versions of Lorem Ipsum.\n\n'
|
||||
'The point of using Lorem Ipsum is that it has a more-or-less '
|
||||
'normal distribution of letters, as opposed to using ‘Content '
|
||||
'here, content here’, making it look like readable English.'
|
||||
)
|
||||
}
|
||||
filename = None if filename == 'no-branding' else filename
|
||||
|
||||
return TemplatePreview.from_example_template(template, filename)
|
||||
|
||||
|
||||
def _view_template_version(service_id, template_id, version, letters_as_pdf=False):
|
||||
return dict(template=get_template(
|
||||
service_api_client.get_service_template(service_id, template_id, version=version)['data'],
|
||||
|
||||
+13
-1
@@ -173,6 +173,8 @@ class HeaderNavigation(Navigation):
|
||||
'information_security',
|
||||
'invite_org_user',
|
||||
'invite_user',
|
||||
'letter_branding_preview_image',
|
||||
'letter_template',
|
||||
'link_service_to_organisation',
|
||||
'manage_org_users',
|
||||
'manage_template_folder',
|
||||
@@ -228,6 +230,7 @@ class HeaderNavigation(Navigation):
|
||||
'service_name_change',
|
||||
'service_name_change_confirm',
|
||||
'service_preview_email_branding',
|
||||
'service_preview_letter_branding',
|
||||
'service_set_auth_type',
|
||||
'service_set_channel',
|
||||
'service_set_contact_link',
|
||||
@@ -235,6 +238,7 @@ class HeaderNavigation(Navigation):
|
||||
'service_set_inbound_number',
|
||||
'service_set_inbound_sms',
|
||||
'service_set_international_sms',
|
||||
'service_set_letter_branding',
|
||||
'service_set_letter_contact_block',
|
||||
'service_set_letters',
|
||||
'service_set_reply_to_email',
|
||||
@@ -248,7 +252,6 @@ class HeaderNavigation(Navigation):
|
||||
'service_switch_research_mode',
|
||||
'services_or_dashboard',
|
||||
'set_free_sms_allowance',
|
||||
'service_set_letter_branding',
|
||||
'set_organisation_type',
|
||||
'set_sender',
|
||||
'set_template_sender',
|
||||
@@ -348,6 +351,7 @@ class MainNavigation(Navigation):
|
||||
'service_name_change',
|
||||
'service_name_change_confirm',
|
||||
'service_preview_email_branding',
|
||||
'service_preview_letter_branding',
|
||||
'service_set_auth_type',
|
||||
'service_set_channel',
|
||||
'service_set_contact_link',
|
||||
@@ -436,7 +440,9 @@ class MainNavigation(Navigation):
|
||||
'integration_testing',
|
||||
'invite_org_user',
|
||||
'letter_branding',
|
||||
'letter_branding_preview_image',
|
||||
'live_services',
|
||||
'letter_template',
|
||||
'manage_org_users',
|
||||
'new_password',
|
||||
'old_integration_testing',
|
||||
@@ -622,6 +628,8 @@ class CaseworkNavigation(Navigation):
|
||||
'invite_org_user',
|
||||
'invite_user',
|
||||
'letter_branding',
|
||||
'letter_branding_preview_image',
|
||||
'letter_template',
|
||||
'link_service_to_organisation',
|
||||
'live_services',
|
||||
'manage_org_users',
|
||||
@@ -682,6 +690,7 @@ class CaseworkNavigation(Navigation):
|
||||
'service_name_change',
|
||||
'service_name_change_confirm',
|
||||
'service_preview_email_branding',
|
||||
'service_preview_letter_branding',
|
||||
'service_set_auth_type',
|
||||
'service_set_channel',
|
||||
'service_set_contact_link',
|
||||
@@ -854,6 +863,8 @@ class OrgNavigation(Navigation):
|
||||
'integration_testing',
|
||||
'invite_user',
|
||||
'letter_branding',
|
||||
'letter_branding_preview_image',
|
||||
'letter_template',
|
||||
'link_service_to_organisation',
|
||||
'live_services',
|
||||
'manage_template_folder',
|
||||
@@ -914,6 +925,7 @@ class OrgNavigation(Navigation):
|
||||
'service_name_change',
|
||||
'service_name_change_confirm',
|
||||
'service_preview_email_branding',
|
||||
'service_preview_letter_branding',
|
||||
'service_set_auth_type',
|
||||
'service_set_channel',
|
||||
'service_set_contact_link',
|
||||
|
||||
@@ -24,6 +24,21 @@ class TemplatePreview:
|
||||
)
|
||||
return (resp.content, resp.status_code, resp.headers.items())
|
||||
|
||||
@classmethod
|
||||
def from_example_template(cls, template, filename):
|
||||
data = {
|
||||
'letter_contact_block': template.get('reply_to_text'),
|
||||
'template': template,
|
||||
'values': None,
|
||||
'filename': filename
|
||||
}
|
||||
resp = requests.post(
|
||||
'{}/preview.png'.format(current_app.config['TEMPLATE_PREVIEW_API_HOST']),
|
||||
json=data,
|
||||
headers={'Authorization': 'Token {}'.format(current_app.config['TEMPLATE_PREVIEW_API_KEY'])}
|
||||
)
|
||||
return (resp.content, resp.status_code, resp.headers.items())
|
||||
|
||||
@classmethod
|
||||
def from_utils_template(cls, template, filetype, page=None):
|
||||
return cls.from_database_object(
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>Letter preview</title>
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
background-color: #dee0e2;
|
||||
margin: 0;
|
||||
}
|
||||
img {
|
||||
display: block;
|
||||
background-color: #fff;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.letter-postage,
|
||||
.visually-hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{ template }}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -9,7 +9,7 @@
|
||||
<h1 class="heading-large">Preview email branding</h1>
|
||||
<div class="grid-row">
|
||||
<div class="column-full">
|
||||
<iframe src="{{ url_for('main.email_template', branding_style=form.branding_style.data) }}" class="email-branding-preview"></iframe>
|
||||
<iframe src="{{ url_for('main.email_template', branding_style=form.branding_style.data) }}" class="branding-preview"></iframe>
|
||||
{% call form_wrapper(action=action) %}
|
||||
<div class="form-group">
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{% extends "views/platform-admin/_base_template.html" %}
|
||||
{% from "components/form.html" import form_wrapper %}
|
||||
{% block per_page_title %}
|
||||
Preview letter branding
|
||||
{% endblock %}
|
||||
|
||||
{% block platform_admin_content %}
|
||||
|
||||
<h1 class="heading-large">Preview letter branding</h1>
|
||||
<div class="grid-row">
|
||||
<div class="column-full">
|
||||
<iframe src="{{ url_for('main.letter_template', branding_style=form.branding_style.data) }}" class="branding-preview"></iframe>
|
||||
{% call form_wrapper(action=action) %}
|
||||
<div class="form-group">
|
||||
{{ form.hidden_tag() }}
|
||||
<div class="page-footer">
|
||||
<button type="submit" class="button">Save</button>
|
||||
<a class="page-footer-back-link" href="{{ url_for('main.service_settings', service_id=service_id) }}">Back to service settings</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -11,7 +11,7 @@
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<h1 class="heading-large">Set email branding</h1>
|
||||
{% call form_wrapper() %}
|
||||
{% call form_wrapper(data_kwargs={'preview-type': 'email'}) %}
|
||||
<div class="grid-row">
|
||||
<div class="column-full preview-pane">
|
||||
</div>
|
||||
|
||||
@@ -11,12 +11,20 @@
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<h1 class="heading-large">Set letter branding</h1>
|
||||
{% call form_wrapper() %}
|
||||
{{ live_search(target_selector='.multiple-choice', show=True, form=search_form, label='Search by name') }}
|
||||
{{ radios(form.branding_style, hide_legend=True) }}
|
||||
{% call form_wrapper(data_kwargs={'preview-type': 'letter'}) %}
|
||||
<div class="grid-row">
|
||||
<div class="column-full preview-pane">
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-row">
|
||||
<div class="column-full">
|
||||
{{ live_search(target_selector='.multiple-choice', show=True, form=search_form, label='Search by name') }}
|
||||
{{ radios(form.branding_style, hide_legend=True) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="js-stick-at-bottom-when-scrolling">
|
||||
{{ page_footer(
|
||||
'Save',
|
||||
'Preview',
|
||||
back_link=url_for('.service_settings', service_id=current_service.id),
|
||||
back_link_text='Back to settings'
|
||||
) }}
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ gulp.task('javascripts', () => gulp
|
||||
paths.src + 'javascripts/errorTracking.js',
|
||||
paths.src + 'javascripts/preventDuplicateFormSubmissions.js',
|
||||
paths.src + 'javascripts/fullscreenTable.js',
|
||||
paths.src + 'javascripts/emailPreviewPane.js',
|
||||
paths.src + 'javascripts/previewPane.js',
|
||||
paths.src + 'javascripts/colourPreview.js',
|
||||
paths.src + 'javascripts/templateFolderForm.js',
|
||||
paths.src + 'javascripts/main.js'
|
||||
|
||||
@@ -4,6 +4,7 @@ import pytest
|
||||
from bs4 import BeautifulSoup
|
||||
from flask import url_for
|
||||
|
||||
from app.main.forms import FieldWithNoneOption
|
||||
from tests.conftest import (
|
||||
active_user_with_permissions,
|
||||
normalize_spaces,
|
||||
@@ -288,3 +289,41 @@ def test_email_branding_preview(
|
||||
**extra_args
|
||||
)
|
||||
assert mock_get_email_branding.called is email_branding_retrieved
|
||||
|
||||
|
||||
@pytest.mark.parametrize('branding_style, filename', [
|
||||
('hm-government', 'hm-government'),
|
||||
(None, 'no-branding'),
|
||||
(FieldWithNoneOption.NONE_OPTION_VALUE, 'no-branding')
|
||||
])
|
||||
def test_letter_template_preview_links_to_the_correct_image(
|
||||
client_request,
|
||||
mocker,
|
||||
mock_get_letter_branding_by_id,
|
||||
branding_style,
|
||||
filename,
|
||||
):
|
||||
page = client_request.get(
|
||||
'main.letter_template',
|
||||
_test_page_title=False,
|
||||
branding_style=branding_style
|
||||
)
|
||||
|
||||
image_link = page.find('img')['src']
|
||||
|
||||
assert image_link == url_for(
|
||||
'main.letter_branding_preview_image',
|
||||
filename=filename,
|
||||
page=1
|
||||
)
|
||||
|
||||
|
||||
def test_letter_template_preview_headers(
|
||||
client,
|
||||
mock_get_letter_branding_by_id,
|
||||
):
|
||||
response = client.get(
|
||||
url_for('main.letter_template', branding_style='hm-government')
|
||||
)
|
||||
|
||||
assert response.headers.get('X-Frame-Options') == 'SAMEORIGIN'
|
||||
|
||||
@@ -2175,7 +2175,44 @@ def test_service_set_letter_branding_prepopulates(
|
||||
(str(UUID(int=1)), str(UUID(int=1))),
|
||||
('__NONE__', None),
|
||||
])
|
||||
def test_service_set_letter_branding_saves(
|
||||
def test_service_set_letter_branding_redirects_to_preview_page_when_form_submitted(
|
||||
logged_in_platform_admin_client,
|
||||
service_one,
|
||||
mock_get_all_letter_branding,
|
||||
selected_letter_branding,
|
||||
expected_post_data
|
||||
):
|
||||
response = logged_in_platform_admin_client.post(
|
||||
url_for('main.service_set_letter_branding', service_id=service_one['id']),
|
||||
data={'branding_style': selected_letter_branding},
|
||||
)
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for(
|
||||
'main.service_preview_letter_branding',
|
||||
service_id=service_one['id'],
|
||||
branding_style=expected_post_data,
|
||||
_external=True)
|
||||
|
||||
|
||||
def test_service_preview_letter_branding_shows_preview_letter(
|
||||
logged_in_platform_admin_client,
|
||||
service_one,
|
||||
mock_get_all_letter_branding,
|
||||
):
|
||||
response = logged_in_platform_admin_client.get(
|
||||
url_for('main.service_preview_letter_branding', service_id=service_one['id'], branding_style='hm-government')
|
||||
)
|
||||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||
|
||||
assert response.status_code == 200
|
||||
assert page.find('iframe')['src'] == url_for('main.letter_template', branding_style='hm-government')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('selected_letter_branding, expected_post_data', [
|
||||
(str(UUID(int=1)), str(UUID(int=1))),
|
||||
('__NONE__', None),
|
||||
])
|
||||
def test_service_preview_letter_branding_saves(
|
||||
logged_in_platform_admin_client,
|
||||
service_one,
|
||||
mock_update_service,
|
||||
@@ -2184,7 +2221,7 @@ def test_service_set_letter_branding_saves(
|
||||
expected_post_data
|
||||
):
|
||||
response = logged_in_platform_admin_client.post(
|
||||
url_for('main.service_set_letter_branding', service_id=service_one['id']),
|
||||
url_for('main.service_preview_letter_branding', service_id=service_one['id']),
|
||||
data={'branding_style': selected_letter_branding}
|
||||
)
|
||||
assert response.status_code == 302
|
||||
@@ -2276,7 +2313,7 @@ def test_should_preview_email_branding(
|
||||
))
|
||||
assert response.status_code == 200
|
||||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||
iframe = page.find('iframe', attrs={"class": "email-branding-preview"})
|
||||
iframe = page.find('iframe', attrs={"class": "branding-preview"})
|
||||
iframeURLComponents = urlparse(iframe['src'])
|
||||
iframeQString = parse_qs(iframeURLComponents.query)
|
||||
|
||||
|
||||
@@ -798,6 +798,28 @@ def test_dont_show_preview_letter_templates_for_bad_filetype(
|
||||
assert mock_get_service_template.called is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize('original_filename, new_filename', [
|
||||
('geo', 'geo'),
|
||||
('no-branding', None)
|
||||
])
|
||||
def test_letter_branding_preview_image(
|
||||
mocker,
|
||||
logged_in_platform_admin_client,
|
||||
original_filename,
|
||||
new_filename,
|
||||
):
|
||||
mocked_preview = mocker.patch(
|
||||
'app.main.views.templates.TemplatePreview.from_example_template',
|
||||
return_value='foo'
|
||||
)
|
||||
resp = logged_in_platform_admin_client.get(
|
||||
url_for('.letter_branding_preview_image', filename=original_filename)
|
||||
)
|
||||
|
||||
mocked_preview.assert_called_with(ANY, new_filename)
|
||||
assert resp.get_data(as_text=True) == 'foo'
|
||||
|
||||
|
||||
def test_choosing_to_copy_redirects(
|
||||
client_request,
|
||||
service_one,
|
||||
|
||||
@@ -102,3 +102,20 @@ def test_page_count_unpacks_from_json_response(
|
||||
|
||||
assert partial_call({'template_type': 'letter'}) == 99
|
||||
mock_template_preview.assert_called_once_with(*expected_template_preview_args)
|
||||
|
||||
|
||||
def test_from_example_template_makes_request(mocker):
|
||||
request_mock = mocker.patch('app.template_previews.requests.post')
|
||||
template = {}
|
||||
filename = 'geo'
|
||||
|
||||
TemplatePreview.from_example_template(template, filename)
|
||||
|
||||
request_mock.assert_called_once_with(
|
||||
'http://localhost:9999/preview.png',
|
||||
headers={'Authorization': 'Token my-secret-key'},
|
||||
json={'values': None,
|
||||
'template': template,
|
||||
'filename': filename,
|
||||
'letter_contact_block': None}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user