Merge pull request #3366 from alphagov/upload-contact-list

Let teams upload an emergency contact list for later use
This commit is contained in:
Chris Hill-Scott
2020-03-16 14:49:15 +00:00
committed by GitHub
26 changed files with 2019 additions and 38 deletions

View File

@@ -1,6 +1,6 @@
{% from "components/table.html" import list_table, field, right_aligned_field_heading, row_heading %}
{% from "components/big-number.html" import big_number -%}
{% from "components/message-count-label.html" import message_count_label -%}
{% from "components/message-count-label.html" import message_count_label, recipient_count_label -%}
<div class='dashboard-table ajax-block-container'>
{% call(item, row_number) list_table(
@@ -20,17 +20,25 @@
<div class="file-list">
{% if item.upload_type == 'letter' %}
<a class="file-list-filename-large govuk-link govuk-link--no-visited-state" href="{{ url_for('.view_notification', service_id=current_service.id, notification_id=item.id) }}">{{ item.original_file_name }}</a>
{% elif item.upload_type == 'contact_list' %}
<a class="file-list-filename-large govuk-link govuk-link--no-visited-state" href="{{ url_for('.contact_list', service_id=current_service.id, contact_list_id=item.id) }}">{{ item.original_file_name }}</a>
{% else %}
<a class="file-list-filename-large govuk-link govuk-link--no-visited-state" href="{{ url_for('.view_job', service_id=current_service.id, job_id=item.id) }}">{{ item.original_file_name }}</a>
{% endif %}
{% if item.scheduled %}
<span class="file-list-hint">
<span class="file-list-hint-large">
Sending {{
item.scheduled_for|format_datetime_relative
}}
</span>
{% elif item.upload_type == 'contact_list' %}
<span class="file-list-hint-large">
Uploaded {{
item.created_at|format_datetime_relative
}}
</span>
{% else %}
<span class="file-list-hint">
<span class="file-list-hint-large">
Sent {{
(item.scheduled_for or item.created_at)|format_datetime_relative
}}
@@ -60,8 +68,17 @@
suffix=''
)
) }}
{% elif item.upload_type == 'contact_list' %}
{{ big_number(
item.row_count,
smallest=True,
label="saved {}".format(recipient_count_label(
item.row_count,
item.template_type
))
) }}
{% elif item.pdf_letter %}
<p class="govuk-body govuk-!-margin-bottom-1">
<p class="govuk-body letter-recipient-summary">
{% for line in item.recipient.splitlines() %}
{% if loop.index < 3 %}
{{ line }}<br>

View File

@@ -31,7 +31,10 @@
</p>
{% endif %}
{{ previous_next_navigation(prev_page, next_page) }}
{% if current_service.can_upload_letters and current_user.has_permissions('send_messages') %}
{% if (
current_service.can_upload_letters and
current_user.has_permissions('send_messages')
) or current_user.platform_admin %}
<div class="js-stick-at-bottom-when-scrolling">
{{ govukButton({
"element": "a",
@@ -39,6 +42,14 @@
"href": url_for('.upload_letter', service_id=current_service.id),
"classes": "govuk-button--secondary"
}) }}
{% if current_user.platform_admin %}
{{ govukButton({
"element": "a",
"text": "Upload an emergency contact list",
"href": url_for('.upload_contact_list', service_id=current_service.id),
"classes": "govuk-button--secondary govuk-!-margin-left-3"
}) }}
{% endif %}
</div>
{% endif %}
</div>

View File

@@ -0,0 +1,80 @@
{% extends "withnav_template.html" %}
{% from "components/big-number.html" import big_number -%}
{% from "components/list.html" import list_of_placeholders %}
{% from "components/message-count-label.html" import recipient_count_label %}
{% from "components/page-header.html" import page_header %}
{% from "components/table.html" import list_table, field, right_aligned_field_heading, row_heading %}
{% block service_page_title %}
Choose a saved contact list
{% endblock %}
{% block maincolumn_content %}
{{ page_header(
'Choose a saved contact list',
back_link=url_for('.send_one_off', service_id=current_service.id, template_id=template.id)
) }}
{% if template.placeholders %}
<div class="govuk-grid-row">
<div class="govuk-grid-column-five-sixths">
<p class="govuk-body">
You cannot use a saved contact list with this template because it
is personalised with {{ list_of_placeholders(template.placeholders) }}.
</p>
<p>
Saved contact lists can only store email addresses or phone
numbers.
</p>
</div>
</div>
{% elif contact_lists %}
<div class='dashboard-table ajax-block-container'>
{% call(item, row_number) list_table(
contact_lists,
caption="Existing contact lists",
caption_visible=False,
empty_message=(
'You dont have any contact lists yet'
),
field_headings=[
'File',
'Status'
],
field_headings_visible=False
) %}
{% call row_heading() %}
<div class="file-list">
<a class="file-list-filename-large govuk-link govuk-link--no-visited-state" href="{{ url_for('main.send_from_contact_list', service_id=current_service.id, template_id=template.id, contact_list_id=item.id) }}">{{ item.original_file_name }}</a>
<span class="file-list-hint-large">
Uploaded {{ item.created_at|format_datetime_relative }}
</span>
</div>
{% endcall %}
{% call field() %}
{{ big_number(
item.row_count,
smallest=True,
label=recipient_count_label(
item.row_count,
item.template_type
)
) }}
{% endcall %}
{% endcall %}
</div>
{% else %}
<div class="govuk-grid-row">
<div class="govuk-grid-column-five-sixths">
<p class="govuk-body">
You have not saved any lists of {{ recipient_count_label(99, template.template_type) }} yet.
</p>
<p class="govuk-body">
To upload and save a new contact list, go to the <a class="govuk-link govuk-link--no-visited-state" href="{{ url_for('main.uploads', service_id=current_service.id) }}">uploads</a> page.
</p>
</div>
</div>
{% endif %}
{% endblock %}

View File

@@ -22,24 +22,30 @@
data_kwargs={'force-focus': True}
) %}
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds {% if form.placeholder_value.label.text == 'phone number' %}extra-tracking{% endif %}">
<div class="govuk-grid-column-full {% if form.placeholder_value.label.text == 'phone number' %}extra-tracking{% endif %}">
{{ textbox(
form.placeholder_value,
hint='Optional' if optional_placeholder else None,
width='1-1',
) }}
</div>
{% if skip_link %}
<div class="govuk-grid-column-one-third">
<a href="{{ skip_link[1] }}" class="govuk-link govuk-link--no-visited-state top-gutter-4-3">{{ skip_link[0] }}</a>
{% if skip_link or link_to_upload %}
<div class="govuk-grid-column-full">
{% if link_to_upload %}
<a class="govuk-link govuk-link--no-visited-state govuk-!-margin-right-3" href="{{ url_for('.send_messages', service_id=current_service.id, template_id=template.id) }}">Upload a list of {{ recipient_count_label(999, template.template_type) }}</a>
{% if current_user.platform_admin and current_service.contact_lists %}
<a class="govuk-link govuk-link--no-visited-state govuk-!-margin-right-3" href="{{ url_for('.choose_from_contact_list', service_id=current_service.id, template_id=template.id) }}">Use a saved list</a>
{% endif %}
{% endif %}
{% if skip_link %}
<a href="{{ skip_link[1] }}" class="govuk-link govuk-link--no-visited-state govuk-!-margin-right-3">{{ skip_link[0] }}</a>
{% endif %}
</div>
{% endif %}
</div>
{% if link_to_upload %}
<p>
<a class="govuk-link govuk-link--no-visited-state" href="{{ url_for('.send_messages', service_id=current_service.id, template_id=template.id) }}">
Upload a list of {{ recipient_count_label(999, template.template_type) }}
</a>
</p>
{% endif %}
{{ page_footer('Continue') }}

View File

@@ -0,0 +1,91 @@
{% extends "withnav_template.html" %}
{% from "components/banner.html" import banner_wrapper %}
{% from "components/radios.html" import radio_select %}
{% from "components/table.html" import list_table, field, text_field, index_field, hidden_field_heading %}
{% from "components/file-upload.html" import file_upload %}
{% from "components/back-link/macro.njk" import govukBackLink %}
{% from "components/message-count-label.html" import message_count_label, recipient_count_label %}
{% set file_contents_header_id = 'file-preview' %}
{% macro skip_to_file_contents() %}
<p class="govuk-visually-hidden">
<a class="govuk-link govuk-link--no-visited-state" href="#{{ file_contents_header_id }}">Skip to file contents</a>
</p>
{% endmacro %}
{% block service_page_title %}
Error
{% endblock %}
{% block maincolumn_content %}
{{ govukBackLink({ "href": url_for('main.upload_contact_list', service_id=current_service.id) }) }}
<div class="bottom-gutter-1-2">
{% call banner_wrapper(type='dangerous') %}
{% if recipients.too_many_rows %}
<h1 class='banner-title' data-module="track-error" data-error-type="Too many rows" data-error-label="{{ upload_id }}">
Your file has too many rows
</h1>
<p>
Notify can store files up to
{{ "{:,}".format(recipients.max_rows) }} rows in size. Your
file has {{ "{:,}".format(recipients|length) }} rows.
</p>
{% elif not recipients.allowed_to_send_to %}
<h1 class='banner-title' data-module="track-error" data-error-type="Trial mode: bad recipients" data-error-label="{{ upload_id }}">
You cannot save
{{ 'this' if recipients|length == 1 else 'these' }}
{{ recipient_count_label(recipients|length, recipients.template_type) }}
</h1>
<p>
In <a class="govuk-link govuk-link--no-visited-state" href="{{ url_for('.trial_mode_new') }}">trial mode</a> you can only
send to yourself and members of your team
</p>
{% endif %}
{{ skip_to_file_contents() }}
{% endcall %}
</div>
<div class="js-stick-at-top-when-scrolling">
<div class="form-group">
{{ file_upload(
form.file,
action=url_for('.upload_contact_list', service_id=current_service.id),
button_text='Upload your file again'
) }}
</div>
<a href="#content" class="govuk-link govuk-link--no-visited-state back-to-top-link">Back to top</a>
</div>
<h2 class="heading-medium" id="{{ file_contents_header_id }}">{{ original_file_name }}</h2>
{% call(item, row_number) list_table(
recipients.displayed_rows,
caption=original_file_name,
caption_visible=False,
field_headings=[
'<span class="govuk-visually-hidden">Row in file</span> <span aria-hidden="true">1</span>'|safe
] + recipients.column_headers
) %}
{{ index_field(item.index + 2) }}
{% for column in recipients.column_headers %}
{{ text_field(item[column].data or '') }}
{% endfor %}
{% endcall %}
{% if recipients.displayed_rows|list|length < recipients|length %}
<p class="table-show-more-link">
Only showing the first {{ recipients.displayed_rows|list|length }} rows
</p>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,48 @@
{% extends "withnav_template.html" %}
{% from "components/banner.html" import banner_wrapper %}
{% from "components/radios.html" import radio_select %}
{% from "components/table.html" import list_table, field, text_field, index_field, hidden_field_heading %}
{% from "components/page-header.html" import page_header %}
{% from "components/message-count-label.html" import message_count_label, recipient_count_label %}
{% from "components/button/macro.njk" import govukButton %}
{% block service_page_title %}
{{ contact_list.original_file_name }}
{% endblock %}
{% block maincolumn_content %}
{{ page_header(
contact_list.original_file_name,
back_link=url_for('main.uploads', service_id=current_service.id)
) }}
<p class="govuk-body">
Uploaded by {{ contact_list.created_by }} {{ contact_list.created_at|format_datetime_human }}
</p>
<p class="govuk-body">
<a class="govuk-link govuk-link--no-visited-state heading-small" download href="{{ url_for('main.download_contact_list', service_id=current_service.id, contact_list_id=contact_list.id) }}">Download this list</a>&emsp;
{{ contact_list.recipients|length|format_thousands }}
{{ recipient_count_label(contact_list.recipients|length, contact_list.recipients.template_type) }}
</p>
{% set recipient_column = contact_list.recipients.column_headers[0] %}
{% call(item, row_number) list_table(
contact_list.recipients.displayed_rows,
caption=recipient_count_label(contact_list.recipients|length, contact_list.template_type)|capitalize,
caption_visible=False,
field_headings=['1', recipient_column],
) %}
{{ index_field(row_number) }}
{{ text_field(item[recipient_column].data) }}
{% endcall %}
{% if contact_list.recipients.displayed_rows|list|length < contact_list.recipients|length %}
<p class="table-show-more-link">
Only showing the first {{ contact_list.recipients.displayed_rows|list|length }} rows
</p>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,70 @@
{% extends "withnav_template.html" %}
{% from "components/banner.html" import banner_wrapper %}
{% from "components/radios.html" import radio_select %}
{% from "components/table.html" import list_table, field, text_field, index_field, hidden_field_heading %}
{% from "components/page-header.html" import page_header %}
{% from "components/message-count-label.html" import message_count_label, recipient_count_label %}
{% from "components/button/macro.njk" import govukButton %}
{% set file_contents_header_id = 'file-preview' %}
{% macro skip_to_file_contents() %}
<p class="govuk-visually-hidden">
<a class="govuk-link govuk-link--no-visited-state" href="#{{ file_contents_header_id }}">Skip to file contents</a>
</p>
{% endmacro %}
{% block service_page_title %}
{{ original_file_name }}
{% endblock %}
{% block maincolumn_content %}
{{ page_header(
original_file_name,
back_link=url_for('main.upload_contact_list', service_id=current_service.id)
) }}
<p class="govuk-body">
{{ recipients|length|format_thousands }} {{ recipient_count_label(recipients|length, recipients.template_type) }} found
</p>
<div class="bottom-gutter-3-2">
<form method="post" enctype="multipart/form-data" action="{{ url_for('main.save_contact_list', service_id=current_service.id, upload_id=upload_id) }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
{{ govukButton({ "text": "Save contact list" }) }}
</form>
</div>
<h2 class="govuk-heading-m">
File preview
</h2>
{% call(item, row_number) list_table(
recipients.displayed_rows,
caption=original_file_name,
caption_visible=False,
field_headings=[
'<span class="govuk-visually-hidden">Row in file</span> <span aria-hidden="true">1</span>'|safe
] + recipients.column_headers
) %}
{{ index_field(item.index + 2) }}
{% for column in recipients.column_headers %}
{% if item[column].ignore %}
{{ text_field(item[column].data or '', status='default') }}
{% else %}
{{ text_field(item[column].data or '') }}
{% endif %}
{% endfor %}
{% if item[None].data %}
{% for column in item[None].data %}
{{ text_field(column, status='default') }}
{% endfor %}
{% endif %}
{% endcall %}
{% if recipients.displayed_rows|list|length < recipients|length %}
<p class="table-show-more-link">
Only showing the first {{ recipients.displayed_rows|list|length }} rows
</p>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,108 @@
{% extends "withnav_template.html" %}
{% from "components/banner.html" import banner_wrapper %}
{% from "components/radios.html" import radio_select %}
{% from "components/table.html" import list_table, field, text_field, index_field, hidden_field_heading %}
{% from "components/file-upload.html" import file_upload %}
{% from "components/back-link/macro.njk" import govukBackLink %}
{% from "components/message-count-label.html" import message_count_label %}
{% set file_contents_header_id = 'file-preview' %}
{% macro skip_to_file_contents() %}
<p class="govuk-visually-hidden">
<a class="govuk-link govuk-link--no-visited-state" href="#{{ file_contents_header_id }}">Skip to file contents</a>
</p>
{% endmacro %}
{% block service_page_title %}
Error
{% endblock %}
{% block maincolumn_content %}
{{ govukBackLink({ "href": url_for('main.upload_contact_list', service_id=current_service.id) }) }}
<div class="bottom-gutter-1-2">
{% call banner_wrapper(type='dangerous') %}
{% if row_errors|length == 1 %}
<h1 class='banner-title' data-module="track-error" data-error-type="Bad rows" data-error-label="{{ upload_id }}">
Theres a problem with {{ original_file_name }}
</h1>
<p>
You need to {{ row_errors[0] }}.
</p>
{% else %}
<h1 class='banner-title' data-module="track-error" data-error-type="Bad rows" data-error-label="{{ upload_id }}">
There are some problems with {{ original_file_name }}
</h1>
<p>
You need to:
</p>
<ul class="list-bullet">
{% for error in row_errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{{ skip_to_file_contents() }}
{% endcall %}
</div>
<div class="js-stick-at-top-when-scrolling">
<div class="form-group">
{{ file_upload(
form.file,
action=url_for('.upload_contact_list', service_id=current_service.id),
button_text='Upload your file again'
) }}
</div>
<a href="#content" class="govuk-link govuk-link--no-visited-state back-to-top-link">Back to top</a>
</div>
{% call(item, row_number) list_table(
recipients.displayed_rows,
caption=original_file_name,
caption_visible=False,
field_headings=[
'<span class="govuk-visually-hidden">Row in file</span> <span aria-hidden="true" class="table-field-invisible-error">1</span>'|safe
] + recipients.column_headers
) %}
{% call index_field() %}
<span class="{% if item.has_errors %}table-field-error{% endif %}">
{{ item.index + 2 }}
</span>
{% endcall %}
{% for column in recipients.column_headers %}
{% if item[column].error and not recipients.missing_column_headers %}
{% call field() %}
<span>
<span class="table-field-error-label">{{ item[column].error }}</span>
{{ item[column].data if item[column].data != None }}
</span>
{% endcall %}
{% elif item[column].ignore %}
{{ text_field(item[column].data or '', status='default') }}
{% else %}
{{ text_field(item[column].data or '') }}
{% endif %}
{% endfor %}
{% if item[None].data %}
{% for column in item[None].data %}
{{ text_field(column, status='default') }}
{% endfor %}
{% endif %}
{% endcall %}
{% if recipients.displayed_rows|list|length < recipients|length %}
{% if recipients.displayed_rows|list|length < recipients.rows_with_errors|list|length %}
<p class="table-show-more-link">
Only showing the first {{ recipients.displayed_rows|list|length }} rows with errors
</p>
{% else %}
<p class="table-show-more-link">
Only showing rows with errors
</p>
{% endif %}
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,112 @@
{% extends "withnav_template.html" %}
{% from "components/banner.html" import banner_wrapper %}
{% from "components/radios.html" import radio_select %}
{% from "components/table.html" import list_table, field, text_field, index_field, hidden_field_heading %}
{% from "components/file-upload.html" import file_upload %}
{% from "components/back-link/macro.njk" import govukBackLink %}
{% from "components/message-count-label.html" import message_count_label %}
{% set file_contents_header_id = 'file-preview' %}
{% macro skip_to_file_contents() %}
<p class="govuk-visually-hidden">
<a class="govuk-link govuk-link--no-visited-state" href="#{{ file_contents_header_id }}">Skip to file contents</a>
</p>
{% endmacro %}
{% block service_page_title %}
Error
{% endblock %}
{% block maincolumn_content %}
{{ govukBackLink({ "href": url_for('main.upload_contact_list', service_id=current_service.id) }) }}
<div class="bottom-gutter-1-2">
{% call banner_wrapper(type='dangerous') %}
{% if not recipients|length %}
<h1 class='banner-title' data-module="track-error" data-error-type="No rows" data-error-label="{{ upload_id }}">
Your file is missing some rows
</h1>
<p>
It needs at least one row of data
{%- if template_type %}.{% else %}, in a column called email address or phone number.{% endif %}
</p>
{% elif recipients.column_headers|length == 1 %}
<h1 class='banner-title' data-module="track-error" data-error-type="Too many rows" data-error-label="{{ upload_id }}">
Your file needs a column called email address or phone number.
</h1>
<p>
Right now it has 1 column called {{ recipients._raw_column_headers[0] }}.
</p>
{% else %}
<h1 class='banner-title' data-module="track-error" data-error-type="Too many rows" data-error-label="{{ upload_id }}">
Your file has too many columns
</h1>
<p>
It needs to have 1 column, called email address or phone number.
</p>
<p>
Right now it has {{ recipients._raw_column_headers|length }} columns called {{ recipients._raw_column_headers | formatted_list }}.
</p>
{% endif %}
{{ skip_to_file_contents() }}
{% endcall %}
</div>
<div class="js-stick-at-top-when-scrolling">
<div class="form-group">
{{ file_upload(
form.file,
action=url_for('.upload_contact_list', service_id=current_service.id),
button_text='Upload your file again'
) }}
</div>
<a href="#content" class="govuk-link govuk-link--no-visited-state back-to-top-link">Back to top</a>
</div>
{% set column_headers = recipients._raw_column_headers if recipients.duplicate_recipient_column_headers else recipients.column_headers %}
<h2 class="heading-medium" id="{{ file_contents_header_id }}">{{ original_file_name }}</h2>
<div class="fullscreen-content" data-module="fullscreen-table">
{% call(item, row_number) list_table(
recipients.displayed_rows,
caption=original_file_name,
caption_visible=False,
field_headings=[
'<span class="govuk-visually-hidden">Row in file</span> <span aria-hidden="true">1</span>'|safe
] + recipients._raw_column_headers
) %}
{{ index_field(item.index + 2) }}
{% for column in column_headers %}
{% if item[column].ignore %}
{{ text_field(item[column].data or '', status='default') }}
{% else %}
{{ text_field(item[column].data or '') }}
{% endif %}
{% endfor %}
{% if item[None].data %}
{% for column in item[None].data %}
{{ text_field(column, status='default') }}
{% endfor %}
{% endif %}
{% endcall %}
</div>
{% if recipients.displayed_rows|list|length < recipients|length %}
<p class="table-show-more-link">
Only showing the first {{ recipients.displayed_rows|list|length }} rows
</p>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,91 @@
{% extends "withnav_template.html" %}
{% from "components/banner.html" import banner_wrapper %}
{% from "components/file-upload.html" import file_upload %}
{% from "components/page-header.html" import page_header %}
{% from "components/table.html" import list_table, text_field, index_field, index_field_heading %}
{% block service_page_title %}
Upload an emergency contact list
{% endblock %}
{% block maincolumn_content %}
{% if error %}
{% call banner_wrapper(type='dangerous') %}
<h1 class="banner-title">{{ error.title }}</h1>
{% if error.detail %}
<p>{{ error.detail | safe }}</p>
{% endif %}
{% endcall %}
{% else %}
{{ page_header(
'Upload an emergency contact list',
back_link=url_for('main.uploads', service_id=current_service.id)
) }}
<p class="govuk-body">
Save a list of staff email addresses or phone numbers in Notify.
</p>
<p class="govuk-body">
In an emergency, you can send a message to everyone on the list.
</p>
<p class="govuk-body">
Do not include contact details for members of the public.
</p>
{% endif %}
<div class="bottom-gutter">
{{ file_upload(
form.file,
button_text='Upload your file again' if error else 'Choose file',
show_errors=False
)}}
</div>
<h2 class="heading-medium">Your file needs to look like one of these examples</h2>
<p class="hint">
Save your file as a
<acronym title="Comma Separated Values">CSV</acronym>,
<acronym title="Tab Separated Values">TSV</acronym>,
<acronym title="Open Document Spreadsheet">ODS</acronym>,
or Microsoft Excel spreadsheet
</p>
<div class="govuk-grid-row">
<div class="govuk-grid-column-one-half">
<div class="spreadsheet">
{% call(item, row_number) list_table(
[
['email address'],
['test@example.gov.uk'],
],
caption="Example",
caption_visible=False,
field_headings=['', 'A']
) %}
{{ index_field(row_number - 1) }}
{% for column in item %}
{{ text_field(column) }}
{% endfor %}
{% endcall %}
</div>
</div>
<div class="govuk-grid-column-one-half">
<div class="spreadsheet">
{% call(item, row_number) list_table(
[
['phone number'],
['07700 900123'],
],
caption="Example",
caption_visible=False,
field_headings=['', 'A']
) %}
{{ index_field(row_number - 1) }}
{% for column in item %}
{{ text_field(column) }}
{% endfor %}
{% endcall %}
</div>
</div>
</div>
{% endblock %}