mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-24 16:24:08 -04:00
Let users upload a contact list to use later
We increasingly have teams wanting to do business-continuity type
messaging. They might be without access to their normal systems, which
is where they would otherwise go to get the list of email addresses or
phone numbers.
So we want to give them a place in Notify where they can store their
spreadsheets and use them at a later date.
For the initial pass we’re going to scope this to only allowing
spreadsheets with one column, ie just phone numbers/email addresses.
This is because:
- it minimises the amount of personal info we’re storing
- it reduces the chance of getting a placeholder error when you go to
send the message, which is probably a high-stress situation where you
might not be able to re-generate the file
The code for this is mostly copied from the existing upload CSV journey.
It’s quite duplicative, but that’s what I needed to do to get this out
quickly. There are opportunities for refactoring later.
Similarly, I would have liked to split this up into better commit
messages, but it really was a case of just bashing code out until it
worked 😳
This commit does not:
- implement the ‘view a contact list page’ (it just has a placeholder
because the API isn’t ready at the moment)
- link to this page (because it’s not ready to use yet)
This commit is contained in:
91
app/templates/views/uploads/contact-list/column-errors.html
Normal file
91
app/templates/views/uploads/contact-list/column-errors.html
Normal 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 %}
|
||||
70
app/templates/views/uploads/contact-list/ok.html
Normal file
70
app/templates/views/uploads/contact-list/ok.html
Normal 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 %}
|
||||
108
app/templates/views/uploads/contact-list/row-errors.html
Normal file
108
app/templates/views/uploads/contact-list/row-errors.html
Normal 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 }}">
|
||||
There’s 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 %}
|
||||
112
app/templates/views/uploads/contact-list/too-many-columns.html
Normal file
112
app/templates/views/uploads/contact-list/too-many-columns.html
Normal 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 %}
|
||||
84
app/templates/views/uploads/contact-list/upload.html
Normal file
84
app/templates/views/uploads/contact-list/upload.html
Normal file
@@ -0,0 +1,84 @@
|
||||
{% 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>Upload a list of phone numbers or email addresses.</p>
|
||||
<p>Don’t put members of the public in here.</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 %}
|
||||
Reference in New Issue
Block a user