diff --git a/app/__init__.py b/app/__init__.py index 062f5b892..8d172b5b5 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -94,7 +94,6 @@ from app.notify_client import InviteTokenError from app.notify_client.api_key_api_client import api_key_api_client from app.notify_client.billing_api_client import billing_api_client from app.notify_client.complaint_api_client import complaint_api_client -from app.notify_client.contact_list_api_client import contact_list_api_client from app.notify_client.email_branding_client import email_branding_client from app.notify_client.events_api_client import events_api_client from app.notify_client.inbound_number_client import inbound_number_client @@ -216,7 +215,6 @@ def create_app(application): # API clients api_key_api_client, billing_api_client, - contact_list_api_client, complaint_api_client, email_branding_client, events_api_client, diff --git a/app/config.py b/app/config.py index f873d5b8f..2779364df 100644 --- a/app/config.py +++ b/app/config.py @@ -94,7 +94,6 @@ class Development(Config): # Buckets CSV_UPLOAD_BUCKET = _s3_credentials_from_env('CSV') - CONTACT_LIST_BUCKET = _s3_credentials_from_env('CONTACT') LOGO_UPLOAD_BUCKET = _s3_credentials_from_env('LOGO') # credential overrides @@ -127,8 +126,6 @@ class Production(Config): # buckets CSV_UPLOAD_BUCKET = cloud_config.s3_credentials( f"notify-api-csv-upload-bucket-{getenv('NOTIFY_ENVIRONMENT')}") - CONTACT_LIST_BUCKET = cloud_config.s3_credentials( - f"notify-api-contact-list-bucket-{getenv('NOTIFY_ENVIRONMENT')}") LOGO_UPLOAD_BUCKET = cloud_config.s3_credentials( f"notify-admin-logo-upload-bucket-{getenv('NOTIFY_ENVIRONMENT')}") diff --git a/app/main/views/send.py b/app/main/views/send.py index cc37f5427..764b3daee 100644 --- a/app/main/views/send.py +++ b/app/main/views/send.py @@ -35,7 +35,6 @@ from app.main.forms import ( SetSenderForm, get_placeholder_form_instance, ) -from app.models.contact_list import ContactList, ContactListsAlphabetical from app.models.user import Users from app.s3_client.s3_csv_client import ( get_csv_metadata, @@ -390,47 +389,6 @@ def send_one_off_step(service_id, template_id, step_index): ) -@main.route( - '/services//send/' - '/from-contact-list' -) -@user_has_permissions('send_messages') -def choose_from_contact_list(service_id, template_id): - db_template = current_service.get_template_with_user_permission_or_403( - template_id, current_user - ) - template = get_template( - db_template, current_service, - ) - return render_template( - 'views/send-contact-list.html', - contact_lists=ContactListsAlphabetical( - current_service.id, - template_type=template.template_type, - ), - template=template, - ) - - -@main.route( - '/services//send/' - '/from-contact-list/' -) -@user_has_permissions('send_messages') -def send_from_contact_list(service_id, template_id, contact_list_id): - contact_list = ContactList.from_id( - contact_list_id, - service_id=current_service.id, - ) - return redirect(url_for( - 'main.check_messages', - service_id=current_service.id, - template_id=template_id, - upload_id=contact_list.copy_to_uploads(), - contact_list_id=contact_list.id, - )) - - def _check_messages(service_id, template_id, upload_id, preview_row): try: # The happy path is that the job doesn’t already exist, so the @@ -573,7 +531,6 @@ def start_job(service_id, upload_id): upload_id, service_id, scheduled_for=request.form.get('scheduled_for', ''), - contact_list_id=request.form.get('contact_list_id', ''), ) session.pop('sender_id', None) diff --git a/app/main/views/uploads.py b/app/main/views/uploads.py index 7cbe73410..0fcc6184d 100644 --- a/app/main/views/uploads.py +++ b/app/main/views/uploads.py @@ -1,23 +1,10 @@ -import itertools from datetime import datetime -from io import BytesIO -from zipfile import BadZipFile -from flask import flash, redirect, render_template, request, send_file, url_for -from notifications_utils.insensitive_dict import InsensitiveDict -from notifications_utils.recipients import RecipientCSV -from notifications_utils.sanitise_text import SanitiseASCII -from xlrd.biffh import XLRDError -from xlrd.xldate import XLDateError +from flask import render_template, request from app import current_service from app.main import main -from app.main.forms import CsvUploadForm -from app.models.contact_list import ContactList -from app.utils import unicode_truncate -from app.utils.csv import Spreadsheet, get_errors_for_csv from app.utils.pagination import generate_next_dict, generate_previous_dict -from app.utils.templates import get_sample_template from app.utils.user import user_has_permissions MAX_FILE_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB @@ -39,7 +26,6 @@ def uploads(service_id): if uploads.current_page == 1: listed_uploads = ( - current_service.contact_lists + current_service.scheduled_jobs + uploads ) @@ -53,198 +39,3 @@ def uploads(service_id): next_page=next_page, now=datetime.utcnow().isoformat(), ) - - -@main.route("/services//upload-contact-list", methods=['GET', 'POST']) -@user_has_permissions('send_messages') -def upload_contact_list(service_id): - form = CsvUploadForm() - - if form.validate_on_submit(): - try: - upload_id = ContactList.upload( - current_service.id, - Spreadsheet.from_file_form(form).as_dict, - ) - file_name_metadata = unicode_truncate( - SanitiseASCII.encode(form.file.data.filename), - 1600 - ) - ContactList.set_metadata( - current_service.id, - upload_id, - original_file_name=file_name_metadata - ) - return redirect(url_for( - '.check_contact_list', - service_id=service_id, - upload_id=upload_id, - )) - except (UnicodeDecodeError, BadZipFile, XLRDError): - flash('Could not read {}. Try using a different file format.'.format( - form.file.data.filename - )) - except (XLDateError): - flash(( - '{} contains numbers or dates that Notify cannot understand. ' - 'Try formatting all columns as ‘text’ or export your file as CSV.' - ).format( - form.file.data.filename - )) - elif form.errors: - # just show the first error, as we don't expect the form to have more - # than one, since it only has one field - first_field_errors = list(form.errors.values())[0] - flash(first_field_errors[0]) - - return render_template( - 'views/uploads/contact-list/upload.html', - form=form, - allowed_file_extensions=Spreadsheet.ALLOWED_FILE_EXTENSIONS, - ) - - -@main.route( - "/services//check-contact-list/", - methods=['GET', 'POST'], -) -@user_has_permissions('send_messages') -def check_contact_list(service_id, upload_id): - - form = CsvUploadForm() - - contents = ContactList.download(service_id, upload_id) - first_row = contents.splitlines()[0].strip().rstrip(',') if contents else '' - original_file_name = ContactList.get_metadata(service_id, upload_id).get('original_file_name', '') - - template_type = InsensitiveDict({ - 'email address': 'email', - 'phone number': 'sms', - }).get(first_row) - - recipients = RecipientCSV( - contents, - template=get_sample_template(template_type or 'sms'), - guestlist=itertools.chain.from_iterable( - [user.name, user.mobile_number, user.email_address] - for user in current_service.active_users - ) if current_service.trial_mode else None, - allow_international_sms=current_service.has_permission('international_sms'), - max_initial_rows_shown=50, - max_errors_shown=50, - ) - - non_empty_column_headers = list(filter(None, recipients.column_headers)) - - if len(non_empty_column_headers) > 1 or not template_type or not recipients: - return render_template( - 'views/uploads/contact-list/too-many-columns.html', - recipients=recipients, - original_file_name=original_file_name, - template_type=template_type, - form=form, - allowed_file_extensions=Spreadsheet.ALLOWED_FILE_EXTENSIONS - ) - - if recipients.too_many_rows or not len(recipients): - return render_template( - 'views/uploads/contact-list/column-errors.html', - recipients=recipients, - original_file_name=original_file_name, - form=form, - allowed_file_extensions=Spreadsheet.ALLOWED_FILE_EXTENSIONS - ) - - row_errors = get_errors_for_csv(recipients, template_type) - if row_errors: - return render_template( - 'views/uploads/contact-list/row-errors.html', - recipients=recipients, - original_file_name=original_file_name, - row_errors=row_errors, - form=form, - allowed_file_extensions=Spreadsheet.ALLOWED_FILE_EXTENSIONS - ) - - if recipients.has_errors: - return render_template( - 'views/uploads/contact-list/column-errors.html', - recipients=recipients, - original_file_name=original_file_name, - form=form, - allowed_file_extensions=Spreadsheet.ALLOWED_FILE_EXTENSIONS - ) - - metadata_kwargs = { - 'row_count': len(recipients), - 'valid': True, - 'original_file_name': original_file_name, - 'template_type': template_type - } - - ContactList.set_metadata(service_id, upload_id, **metadata_kwargs) - - return render_template( - 'views/uploads/contact-list/ok.html', - recipients=recipients, - original_file_name=original_file_name, - upload_id=upload_id, - ) - - -@main.route("/services//save-contact-list/", methods=['POST']) -@user_has_permissions('send_messages') -def save_contact_list(service_id, upload_id): - ContactList.create(current_service.id, upload_id) - return redirect(url_for( - '.uploads', - service_id=current_service.id, - )) - - -@main.route("/services//contact-list/", methods=['GET']) -@user_has_permissions('send_messages') -def contact_list(service_id, contact_list_id): - contact_list = ContactList.from_id(contact_list_id, service_id=service_id) - return render_template( - 'views/uploads/contact-list/contact-list.html', - contact_list=contact_list, - jobs=contact_list.get_jobs( - page=1, - limit_days=current_service.get_days_of_retention(contact_list.template_type), - ), - ) - - -@main.route("/services//contact-list//delete", methods=['GET', 'POST']) -@user_has_permissions('manage_templates') -def delete_contact_list(service_id, contact_list_id): - contact_list = ContactList.from_id(contact_list_id, service_id=service_id) - - if request.method == 'POST': - contact_list.delete() - return redirect(url_for( - '.uploads', - service_id=service_id, - )) - - flash([ - f"Are you sure you want to delete ‘{contact_list.original_file_name}’?", - ], 'delete') - - return render_template( - 'views/uploads/contact-list/contact-list.html', - contact_list=contact_list, - confirm_delete_banner=True, - ) - - -@main.route("/services//contact-list/.csv", methods=['GET']) -@user_has_permissions('send_messages') -def download_contact_list(service_id, contact_list_id): - contact_list = ContactList.from_id(contact_list_id, service_id=service_id) - return send_file( - path_or_file=BytesIO(contact_list.contents.encode('utf-8')), - download_name=contact_list.saved_file_name, - as_attachment=True, - ) diff --git a/app/models/contact_list.py b/app/models/contact_list.py deleted file mode 100644 index dfecaf50a..000000000 --- a/app/models/contact_list.py +++ /dev/null @@ -1,195 +0,0 @@ -from functools import partial -from os import path -from uuid import uuid4 - -from flask import abort, current_app -from notifications_utils.formatters import strip_all_whitespace -from notifications_utils.recipients import RecipientCSV -from notifications_utils.s3 import s3upload as utils_s3upload -from werkzeug.utils import cached_property - -from app.models import JSONModel, ModelList -from app.models.job import PaginatedJobsAndScheduledJobs -from app.notify_client.contact_list_api_client import contact_list_api_client -from app.s3_client import ( - get_s3_contents, - get_s3_metadata, - get_s3_object, - set_s3_metadata, -) -from app.s3_client.s3_csv_client import s3upload, set_metadata_on_csv_upload -from app.utils.templates import get_sample_template - - -class ContactList(JSONModel): - - ALLOWED_PROPERTIES = { - 'id', - 'created_at', - 'created_by', - 'has_jobs', - 'recent_job_count', - 'service_id', - 'original_file_name', - 'row_count', - 'template_type', - } - - upload_type = 'contact_list' - - @classmethod - def from_id(cls, contact_list_id, *, service_id): - return cls(contact_list_api_client.get_contact_list( - service_id=service_id, - contact_list_id=contact_list_id, - )) - - @staticmethod - def get_bucket_credentials(key): - return current_app.config['CONTACT_LIST_BUCKET'][key] - - @staticmethod - def get_bucket_name(): - return ContactList.get_bucket_credentials('bucket') - - @staticmethod - def get_access_key(): - return ContactList.get_bucket_credentials('access_key_id') - - @staticmethod - def get_secret_key(): - return ContactList.get_bucket_credentials('secret_access_key') - - @staticmethod - def get_region(): - return ContactList.get_bucket_credentials('region') - - @staticmethod - def get_filename(service_id, upload_id): - return f"service-{service_id}-notify/{upload_id}.csv" - - @staticmethod - def get_s3_arguments(service_id, upload_id): - return ( - ContactList.get_bucket_name(), - ContactList.get_filename(service_id, upload_id), - ContactList.get_access_key(), - ContactList.get_secret_key(), - ContactList.get_region(), - ) - - @staticmethod - def upload(service_id, file_dict): - upload_id = str(uuid4()) - utils_s3upload( - filedata=file_dict['data'], - region=ContactList.get_region(), - bucket_name=ContactList.get_bucket_name(), - file_location=ContactList.get_filename(service_id, upload_id), - access_key=ContactList.get_access_key(), - secret_key=ContactList.get_secret_key(), - ) - return upload_id - - @staticmethod - def download(service_id, upload_id): - return strip_all_whitespace( - get_s3_contents( - get_s3_object(*ContactList.get_s3_arguments(service_id, upload_id)))) - - @staticmethod - def set_metadata(service_id, upload_id, **kwargs): - return set_s3_metadata(get_s3_object(*ContactList.get_s3_arguments(service_id, upload_id)), **kwargs) - - @staticmethod - def get_metadata(service_id, upload_id): - return get_s3_metadata(get_s3_object(*ContactList.get_s3_arguments(service_id, upload_id))) - - def copy_to_uploads(self): - raise RuntimeError("RCA probably an issue with copying between buckets") - metadata = self.get_metadata(self.service_id, self.id) - new_upload_id = s3upload( - self.service_id, - {'data': self.contents}, - ContactList.get_region(), - ) - set_metadata_on_csv_upload( - self.service_id, - new_upload_id, - **metadata, - ) - return new_upload_id - - @classmethod - def create(cls, service_id, upload_id): - - metadata = cls.get_metadata(service_id, upload_id) - - if not metadata.get('valid'): - abort(403) - - return cls(contact_list_api_client.create_contact_list( - service_id=service_id, - upload_id=upload_id, - original_file_name=metadata['original_file_name'], - row_count=int(metadata['row_count']), - template_type=metadata['template_type'], - )) - - def delete(self): - contact_list_api_client.delete_contact_list( - service_id=self.service_id, - contact_list_id=self.id, - ) - - @property - def contents(self): - return self.download(self.service_id, self.id) - - @cached_property - def recipients(self): - return RecipientCSV( - self.contents, - template=get_sample_template(self.template_type), - allow_international_sms=True, - max_initial_rows_shown=50, - ) - - @property - def saved_file_name(self): - file_name, extention = path.splitext(self.original_file_name) - return f'{file_name}.csv' - - def get_jobs(self, *, page, limit_days=None): - return PaginatedJobsAndScheduledJobs( - self.service_id, - contact_list_id=self.id, - page=page, - limit_days=limit_days, - ) - - -class ContactLists(ModelList): - - client_method = contact_list_api_client.get_contact_lists - model = ContactList - sort_function = partial( - sorted, - key=lambda item: item['created_at'], - reverse=True, - ) - - def __init__(self, service_id, template_type=None): - super().__init__(service_id) - self.items = self.sort_function([ - item for item in self.items - if template_type in {item['template_type'], None} - ]) - - -class ContactListsAlphabetical(ContactLists): - - sort_function = partial( - sorted, - key=lambda item: item['original_file_name'].lower(), - ) diff --git a/app/models/job.py b/app/models/job.py index 237452965..3fa1f7d33 100644 --- a/app/models/job.py +++ b/app/models/job.py @@ -172,10 +172,9 @@ class PaginatedJobs(PaginatedModelList, ImmediateJobs): client_method = job_api_client.get_page_of_jobs statuses = None - def __init__(self, service_id, *, contact_list_id=None, page=None, limit_days=None): + def __init__(self, service_id, *, page=None, limit_days=None): super().__init__( service_id, - contact_list_id=contact_list_id, statuses=self.statuses, page=page, limit_days=limit_days, diff --git a/app/models/service.py b/app/models/service.py index 3ea3d75aa..b16faae37 100644 --- a/app/models/service.py +++ b/app/models/service.py @@ -3,7 +3,6 @@ from notifications_utils.serialised_model import SerialisedModelCollection from werkzeug.utils import cached_property from app.models import JSONModel, SortByNameMixin -from app.models.contact_list import ContactLists from app.models.job import ( ImmediateJobs, PaginatedJobs, @@ -527,10 +526,6 @@ class Service(JSONModel, SortByNameMixin): } ) - @property - def contact_lists(self): - return ContactLists(self.id) - class Services(SerialisedModelCollection): model = Service diff --git a/app/navigation.py b/app/navigation.py index 44b6395d2..ef29424f4 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -141,7 +141,6 @@ class MainNavigation(Navigation): 'add_service_template', 'check_messages', 'check_notification', - 'choose_from_contact_list', 'choose_template', 'choose_template_to_copy', 'confirm_redact_template', @@ -161,11 +160,6 @@ class MainNavigation(Navigation): 'view_template_versions', }, 'uploads': { - 'upload_contact_list', - 'check_contact_list', - 'save_contact_list', - 'contact_list', - 'delete_contact_list', 'uploads', 'view_job', 'view_jobs', @@ -244,7 +238,6 @@ class CaseworkNavigation(Navigation): mapping = { 'send-one-off': { - 'choose_from_contact_list', 'choose_template', 'send_one_off', 'send_one_off_step', @@ -257,11 +250,6 @@ class CaseworkNavigation(Navigation): 'uploads': { 'view_jobs', 'view_job', - 'upload_contact_list', - 'check_contact_list', - 'save_contact_list', - 'contact_list', - 'delete_contact_list', 'uploads', }, } diff --git a/app/notify_client/contact_list_api_client.py b/app/notify_client/contact_list_api_client.py deleted file mode 100644 index 9f7c04dc7..000000000 --- a/app/notify_client/contact_list_api_client.py +++ /dev/null @@ -1,37 +0,0 @@ -from app.notify_client import NotifyAdminAPIClient, _attach_current_user - - -class ContactListApiClient(NotifyAdminAPIClient): - - def create_contact_list( - self, - *, - service_id, - upload_id, - original_file_name, - row_count, - template_type, - ): - data = { - "id": upload_id, - "original_file_name": original_file_name, - "row_count": row_count, - "template_type": template_type, - } - - data = _attach_current_user(data) - job = self.post(url='/service/{}/contact-list'.format(service_id), data=data) - - return job - - def get_contact_lists(self, service_id): - return self.get(f'/service/{service_id}/contact-list') - - def get_contact_list(self, *, service_id, contact_list_id): - return self.get(f'/service/{service_id}/contact-list/{contact_list_id}') - - def delete_contact_list(self, *, service_id, contact_list_id): - return self.delete(f'/service/{service_id}/contact-list/{contact_list_id}') - - -contact_list_api_client = ContactListApiClient() diff --git a/app/notify_client/job_api_client.py b/app/notify_client/job_api_client.py index 7fd8af213..c54220657 100644 --- a/app/notify_client/job_api_client.py +++ b/app/notify_client/job_api_client.py @@ -25,14 +25,12 @@ class JobApiClient(NotifyAdminAPIClient): return job - def get_jobs(self, service_id, *, limit_days=None, contact_list_id=None, statuses=None, page=1): + def get_jobs(self, service_id, *, limit_days=None, statuses=None, page=1): params = {'page': page} if limit_days is not None: params['limit_days'] = limit_days if statuses is not None: params['statuses'] = ','.join(statuses) - if contact_list_id is not None: - params['contact_list_id'] = contact_list_id return self.get(url='/service/{}/job'.format(service_id), params=params) @@ -53,12 +51,11 @@ class JobApiClient(NotifyAdminAPIClient): if job['job_status'] != 'cancelled' ) - def get_page_of_jobs(self, service_id, *, page, statuses=None, contact_list_id=None, limit_days=None): + def get_page_of_jobs(self, service_id, *, page, statuses=None, limit_days=None): return self.get_jobs( service_id, statuses=statuses or self.NON_SCHEDULED_JOB_STATUSES, page=page, - contact_list_id=contact_list_id, limit_days=limit_days, ) @@ -88,15 +85,12 @@ class JobApiClient(NotifyAdminAPIClient): def has_jobs(self, service_id): return bool(self.get_jobs(service_id)['data']) - def create_job(self, job_id, service_id, scheduled_for=None, contact_list_id=None): + def create_job(self, job_id, service_id, scheduled_for=None): data = {"id": job_id} if scheduled_for: data.update({'scheduled_for': scheduled_for}) - if contact_list_id: - data.update({'contact_list_id': contact_list_id}) - data = _attach_current_user(data) job = self.post(url='/service/{}/job'.format(service_id), data=data) diff --git a/app/templates/views/check/ok.html b/app/templates/views/check/ok.html index f933099ab..b584b6930 100644 --- a/app/templates/views/check/ok.html +++ b/app/templates/views/check/ok.html @@ -30,7 +30,6 @@
diff --git a/app/templates/views/dashboard/_jobs.html b/app/templates/views/dashboard/_jobs.html index 0c50a57e1..eaa8d026d 100644 --- a/app/templates/views/dashboard/_jobs.html +++ b/app/templates/views/dashboard/_jobs.html @@ -17,31 +17,13 @@ ) %} {% call row_heading() %}
- {% if item.upload_type == 'contact_list' %} - {{ item.original_file_name }} - {% else %} - {{ item.original_file_name }} - {% endif %} + {{ item.original_file_name }} {% if item.scheduled %} Sending {{ item.scheduled_for|format_datetime_relative }} - {% elif item.upload_type == 'contact_list' %} - - {% if item.recent_job_count %} - Used {{ item.recent_job_count|iteration_count }} - in the last - {{ current_service.get_days_of_retention(item.template_type) }} - days - {% elif item.has_jobs %} - Not used in the last - {{ current_service.get_days_of_retention(item.template_type) }} - days - {% else %} - Not used yet - {% endif %} {% else %} Sent {{ @@ -62,12 +44,6 @@ suffix='waiting to send' ) ) }} - {% elif item.upload_type == 'contact_list' %} - {{ big_number( - item.row_count, - smallest=True, - label="saved {}".format(item.row_count|recipient_count_label(item.template_type)) - ) }} {% else %}
diff --git a/app/templates/views/jobs/jobs.html b/app/templates/views/jobs/jobs.html index 27b907506..a647c3a25 100644 --- a/app/templates/views/jobs/jobs.html +++ b/app/templates/views/jobs/jobs.html @@ -17,15 +17,5 @@

{% endif %} {{ previous_next_navigation(prev_page, next_page) }} - {% if current_user.has_permissions('send_messages') %} -
- {{ govukButton({ - "element": "a", - "text": "Upload an emergency contact list", - "href": url_for('.upload_contact_list', service_id=current_service.id), - "classes": "govuk-button--secondary" - }) }} -
- {% endif %}
{% endblock %} diff --git a/app/templates/views/send-contact-list.html b/app/templates/views/send-contact-list.html deleted file mode 100644 index 13c4b667c..000000000 --- a/app/templates/views/send-contact-list.html +++ /dev/null @@ -1,78 +0,0 @@ -{% extends "withnav_template.html" %} -{% from "components/big-number.html" import big_number -%} -{% from "components/list.html" import list_of_placeholders %} -{% from "components/page-header.html" import page_header %} -{% from "components/table.html" import list_table, field, right_aligned_field_heading, row_heading %} -{% from "components/uk_components/back-link/macro.njk" import govukBackLink %} - -{% block service_page_title %} - Choose an emergency contact list -{% endblock %} - -{% block backLink %} - {{ govukBackLink({ "href": url_for('.send_one_off', service_id=current_service.id, template_id=template.id) }) }} -{% endblock %} - -{% block maincolumn_content %} - - {{ page_header('Choose an emergency contact list') }} - - {% if template.placeholders %} -
-
-

- You cannot use an emergency contact list with this template because it - is personalized with {{ list_of_placeholders(template.placeholders) }}. -

-

- Emergency contact lists can only include email addresses or phone - numbers. -

-
-
- {% elif contact_lists %} -
- {% call(item, row_number) list_table( - contact_lists, - caption="Emergency contact lists", - caption_visible=False, - empty_message=( - 'You have not saved any contact lists yet.' - ), - field_headings=[ - 'File', - 'Status' - ], - field_headings_visible=False - ) %} - {% call row_heading() %} -
- {{ item.original_file_name }} - - Uploaded {{ item.created_at|format_datetime_relative }} - -
- {% endcall %} - {% call field() %} - {{ big_number( - item.row_count, - smallest=True, - label=item.row_count|recipient_count_label(item.template_type), - ) }} - {% endcall %} - {% endcall %} -
- {% else %} -
-
-

- You have not saved any lists of {{ 99|recipient_count_label(template.template_type) }} yet. -

-

- To upload and save an emergency contact list, go to the uploads page. -

-
-
- {% endif %} - -{% endblock %} diff --git a/app/templates/views/send-test.html b/app/templates/views/send-test.html index f2c029ce5..db9de5dc2 100644 --- a/app/templates/views/send-test.html +++ b/app/templates/views/send-test.html @@ -31,9 +31,6 @@
{% if link_to_upload %} Upload a list of {{ 999|recipient_count_label(template.template_type) }} - {% if current_service.contact_lists %} - Use an emergency list - {% endif %} {% endif %} {% if skip_link %} {{ skip_link[0] }} @@ -49,6 +46,6 @@ {{ page_footer('Continue') }} {% endcall %} - + {% endblock %} diff --git a/app/templates/views/uploads/contact-list/column-errors.html b/app/templates/views/uploads/contact-list/column-errors.html deleted file mode 100644 index bdee61af8..000000000 --- a/app/templates/views/uploads/contact-list/column-errors.html +++ /dev/null @@ -1,83 +0,0 @@ -{% 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/uk_components/back-link/macro.njk" import govukBackLink %} - -{% block service_page_title %} - Error -{% endblock %} - -{% block backLink %} - {{ govukBackLink({ "href": url_for('main.upload_contact_list', service_id=current_service.id) }) }} -{% endblock %} - -{% block maincolumn_content %} - -
- {% call banner_wrapper(type='dangerous') %} - - {% if recipients.too_many_rows %} - -

- Your file has too many rows -

-

- Notify can store files up to - {{ "{:,}".format(recipients.max_rows) }} rows in size. Your - file has {{ "{:,}".format(recipients|length) }} rows. -

- - {% elif not recipients.allowed_to_send_to %} - -

- You cannot save - {{ 'this' if recipients|length == 1 else 'these' }} - {{ recipients|length|recipient_count_label(recipients.template_type) }} -

-

- In trial mode you can only - send to yourself and members of your team -

- - {% endif %} - {% endcall %} -
- - -
-
- {{ file_upload( - form.file, - allowed_file_extensions=allowed_file_extensions, - action=url_for('.upload_contact_list', service_id=current_service.id), - button_text='Upload your file again' - ) }} -
- Back to top -
- -

{{ original_file_name }}

- - {% call(item, row_number) list_table( - recipients.displayed_rows, - caption=original_file_name, - caption_visible=False, - field_headings=[ - 'Row in file '|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 %} - - {% endif %} - -{% endblock %} diff --git a/app/templates/views/uploads/contact-list/contact-list.html b/app/templates/views/uploads/contact-list/contact-list.html deleted file mode 100644 index 578a0b5f2..000000000 --- a/app/templates/views/uploads/contact-list/contact-list.html +++ /dev/null @@ -1,142 +0,0 @@ -{% extends "withnav_template.html" %} -{% from "components/banner.html" import banner_wrapper %} -{% from "components/big-number.html" import big_number %} -{% from "components/radios.html" import radio_select %} -{% from "components/table.html" import list_table, field, text_field, index_field, hidden_field_heading, row, row_heading %} -{% from "components/page-header.html" import page_header %} -{% from "components/uk_components/button/macro.njk" import govukButton %} -{% from "components/uk_components/back-link/macro.njk" import govukBackLink %} - -{% block service_page_title %} - {{ contact_list.original_file_name }} -{% endblock %} - -{% block backLink %} - {{ govukBackLink({ "href": url_for('main.uploads', service_id=current_service.id) }) }} -{% endblock %} - -{% block maincolumn_content %} - - {{ page_header(contact_list.original_file_name) }} - -

- Uploaded by {{ contact_list.created_by }} {{ contact_list.created_at|format_datetime_human }}. -

- - {% if jobs %} -

- Used {{ jobs|length|iteration_count }} - in the last {{ current_service.get_days_of_retention(contact_list.template_type) }} - days. -

-
- {% call(item, row_number) list_table( - jobs, - caption="Messages sent from this contact list", - caption_visible=False, - empty_message='', - field_headings=[ - 'Template', - 'Status' - ], - field_headings_visible=False - ) %} - {% call row_heading() %} -
- {{ item.template_name }} - {% if item.scheduled %} - - Sending {{ - item.scheduled_for|format_datetime_relative - }} - - {% else %} - - Sent {{ - (item.scheduled_for or item.created_at)|format_datetime_relative - }} - - {% endif %} - -
- {% endcall %} - {% call field() %} - {% if item.scheduled %} - {{ big_number( - item.notification_count, - smallest=True, - label=item.notification_count|message_count_label( - item.template_type, - suffix='waiting to send' - ) - ) }} - {% else %} -
-
- {{ big_number( - item.notifications_sending, - smallest=True, - label='sending', - ) }} -
-
- {{ big_number(item.notifications_delivered, smallest=True, label='delivered') }} -
-
- {{ big_number(item.notifications_failed, smallest=True, label='failed') }} -
-
- {% endif %} - {% endcall %} - {% endcall %} -
- {% else %} -

- {% if contact_list.has_jobs %} - Not used in the last {{ current_service.get_days_of_retention(contact_list.template_type) }} days. - {% else %} - Not used yet. - {% endif %} -

- {% endif %} - -

- {{ contact_list.recipients|length|format_thousands }} saved {{ contact_list.recipients|length|recipient_count_label(contact_list.template_type) }} -

- - {% set recipient_column = contact_list.recipients.column_headers[0] %} - -
- {% call(item, row_number) list_table( - contact_list.recipients.displayed_rows, - caption=contact_list.recipients|length|recipient_count_label(contact_list.template_type)|capitalize, - caption_visible=False, - field_headings=[recipient_column], - field_headings_visible=False, - ) %} - {% if not loop.first %} - {{ text_field(item[recipient_column].data) }} - {% endif %} - {% endcall %} - - {% if contact_list.recipients.displayed_rows|list|length < contact_list.recipients|length %} - - {% endif %} -
- - -
- -
- - -{% endblock %} diff --git a/app/templates/views/uploads/contact-list/ok.html b/app/templates/views/uploads/contact-list/ok.html deleted file mode 100644 index f0d71c4ac..000000000 --- a/app/templates/views/uploads/contact-list/ok.html +++ /dev/null @@ -1,64 +0,0 @@ -{% 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/uk_components/button/macro.njk" import govukButton %} -{% from "components/uk_components/back-link/macro.njk" import govukBackLink %} - -{% block service_page_title %} - {{ original_file_name }} -{% endblock %} - -{% block backLink %} - {{ govukBackLink({ "href": url_for('main.upload_contact_list', service_id=current_service.id) }) }} -{% endblock %} - -{% block maincolumn_content %} - - {{ page_header(original_file_name) }} - -

- {{ recipients|length|recipient_count(recipients.template_type) }} found -

- -
-
- - {{ govukButton({ "text": "Save contact list" }) }} -
-
- -

- File preview -

- {% call(item, row_number) list_table( - recipients.displayed_rows, - caption=original_file_name, - caption_visible=False, - field_headings=[ - 'Row in file '|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 %} - - {% endif %} - -{% endblock %} diff --git a/app/templates/views/uploads/contact-list/row-errors.html b/app/templates/views/uploads/contact-list/row-errors.html deleted file mode 100644 index 27e95165d..000000000 --- a/app/templates/views/uploads/contact-list/row-errors.html +++ /dev/null @@ -1,102 +0,0 @@ -{% 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/uk_components/back-link/macro.njk" import govukBackLink %} - -{% block service_page_title %} - Error -{% endblock %} - -{% block backLink %} - {{ govukBackLink({ "href": url_for('main.upload_contact_list', service_id=current_service.id) }) }} -{% endblock %} - -{% block maincolumn_content %} - -
- {% call banner_wrapper(type='dangerous') %} - {% if row_errors|length == 1 %} -

- There’s a problem with {{ original_file_name }} -

-

- You need to {{ row_errors[0] }}. -

- {% else %} -

- There are some problems with {{ original_file_name }} -

-

- You need to: -

-
    - {% for error in row_errors %} -
  • {{ error }}
  • - {% endfor %} -
- {% endif %} - {% endcall %} -
- -
-
- {{ file_upload( - form.file, - allowed_file_extensions=allowed_file_extensions, - action=url_for('.upload_contact_list', service_id=current_service.id), - button_text='Upload your file again' - ) }} -
- Back to top -
- - {% call(item, row_number) list_table( - recipients.displayed_rows, - caption=original_file_name, - caption_visible=False, - field_headings=[ - 'Row in file '|safe - ] + recipients.column_headers - ) %} - {% call index_field() %} - - {{ item.index + 2 }} - - {% endcall %} - {% for column in recipients.column_headers %} - {% if item[column].error and not recipients.missing_column_headers %} - {% call field() %} - - {{ item[column].error }} - {{ item[column].data if item[column].data != None }} - - {% 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 %} - - {% else %} - - {% endif %} - {% endif %} - - -{% endblock %} diff --git a/app/templates/views/uploads/contact-list/too-many-columns.html b/app/templates/views/uploads/contact-list/too-many-columns.html deleted file mode 100644 index 0be946b71..000000000 --- a/app/templates/views/uploads/contact-list/too-many-columns.html +++ /dev/null @@ -1,104 +0,0 @@ -{% 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/uk_components/back-link/macro.njk" import govukBackLink %} - -{% block service_page_title %} - Error -{% endblock %} - -{% block backLink %} - {{ govukBackLink({ "href": url_for('main.upload_contact_list', service_id=current_service.id) }) }} -{% endblock %} - -{% block maincolumn_content %} - -
- {% call banner_wrapper(type='dangerous') %} - - {% if not recipients|length %} - -

- Your file is missing some rows -

-

- It needs at least one row of data - {%- if template_type %}.{% else %}, in a column called ‘email address’ or ‘phone number’.{% endif %} -

- - {% elif recipients.column_headers|length == 1 %} - -

- Your file needs a column called ‘email address’ or ‘phone number’. -

-

- Right now it has 1 column called ‘{{ recipients._raw_column_headers[0] }}’. -

- - {% else %} - -

- Your file has too many columns -

-

- It needs to have 1 column, called ‘email address’ or ‘phone number’. -

-

- Right now it has {{ recipients._raw_column_headers|length }} columns called {{ recipients._raw_column_headers | formatted_list }}. -

- - {% endif %} - {% endcall %} -
- - -
-
- {{ file_upload( - form.file, - allowed_file_extensions=allowed_file_extensions, - action=url_for('.upload_contact_list', service_id=current_service.id), - button_text='Upload your file again' - ) }} -
- Back to top -
- - {% set column_headers = recipients._raw_column_headers if recipients.duplicate_recipient_column_headers else recipients.column_headers %} - -

{{ original_file_name }}

- -
- {% call(item, row_number) list_table( - recipients.displayed_rows, - caption=original_file_name, - caption_visible=False, - field_headings=[ - 'Row in file '|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 %} -
- - {% if recipients.displayed_rows|list|length < recipients|length %} - - {% endif %} - -{% endblock %} diff --git a/app/templates/views/uploads/contact-list/upload.html b/app/templates/views/uploads/contact-list/upload.html deleted file mode 100644 index d446d3db5..000000000 --- a/app/templates/views/uploads/contact-list/upload.html +++ /dev/null @@ -1,96 +0,0 @@ -{% 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 %} -{% from "components/uk_components/back-link/macro.njk" import govukBackLink %} - -{% block service_page_title %} - Upload an emergency contact list -{% endblock %} - -{% block backLink %} - {% if not error %} - {{ govukBackLink({ "href": url_for('main.uploads', service_id=current_service.id) }) }} - {% endif %} -{% endblock %} - -{% block maincolumn_content %} - {% if error %} - {% call banner_wrapper(type='dangerous') %} -

{{ error.title }}

- {% if error.detail %} -

{{ error.detail | safe }}

- {% endif %} - {% endcall %} - {% else %} - {{ page_header('Upload an emergency contact list') }} -

- Save a list of staff email addresses or phone numbers in Notify. -

-

- In an emergency, you can send a message to everyone on the list. -

-

- Do not include contact details for members of the public. -

- {% endif %} - -
- {{ file_upload( - form.file, - allowed_file_extensions=allowed_file_extensions, - button_text='Choose file', - show_errors=False, - )}} -
- -

Your file needs to look like one of these examples

- -

- Save your file as a - CSV, - TSV, - ODS, - or Microsoft Excel spreadsheet -

- -
-
-
- {% call(item, row_number) list_table( - [ - ['email address'], - ['test@example.gsa.gov'], - ], - caption="Example", - caption_visible=False, - field_headings=['', 'A'] - ) %} - {{ index_field(row_number - 1) }} - {% for column in item %} - {{ text_field(column) }} - {% endfor %} - {% endcall %} -
-
-
-
- {% call(item, row_number) list_table( - [ - ['phone number'], - ['555-867-5309'], - ], - caption="Example", - caption_visible=False, - field_headings=['', 'A'] - ) %} - {{ index_field(row_number - 1) }} - {% for column in item %} - {{ text_field(column) }} - {% endfor %} - {% endcall %} -
-
-
-{% endblock %} diff --git a/deploy-config/egress_proxy/notify-admin-demo.deploy.acl b/deploy-config/egress_proxy/notify-admin-demo.deploy.acl new file mode 100644 index 000000000..a05cd401b --- /dev/null +++ b/deploy-config/egress_proxy/notify-admin-demo.deploy.acl @@ -0,0 +1,3 @@ +Update this file to force a re-deploy of the egress proxy even when notify-admin-demo..acl haven't changed + +20230412: Redeploy to re-calculate the list of allowed s3 buckets diff --git a/deploy-config/egress_proxy/notify-admin-staging.deploy.acl b/deploy-config/egress_proxy/notify-admin-staging.deploy.acl index 4dd64b327..ee2d68acc 100644 --- a/deploy-config/egress_proxy/notify-admin-staging.deploy.acl +++ b/deploy-config/egress_proxy/notify-admin-staging.deploy.acl @@ -1 +1,3 @@ Update this file to force a re-deploy of the egress proxy even when notify-admin-staging..acl haven't changed + +20230412: Redeploy to re-calculate the list of allowed s3 buckets diff --git a/docs/notify-pilot-info.md b/docs/notify-pilot-info.md index 2ae0240ff..500bd98f2 100644 --- a/docs/notify-pilot-info.md +++ b/docs/notify-pilot-info.md @@ -1,4 +1,4 @@ -### The Public Benefits Studio +# The Public Benefits Studio The Public Benefits Studio is a team inside of GSA’s Technology Transformation Services (TTS), home to innovative programs like 18F and @@ -11,7 +11,7 @@ We’re a cross-functional team of technologists with specialized experience working across public benefits programs like Medicaid, SNAP, and unemployment insurance. -### WHAT WE’RE CURRENTLY EXPLORING +## WHAT WE’RE CURRENTLY EXPLORING @@ -34,7 +34,7 @@ agency money.
-OUR FIRST BET: U.S. Notify +### OUR FIRST BET: U.S. Notify @@ -63,7 +63,7 @@ send thousands of customized-to-the-user text messages per year at little-to-no-cost. The easy interface requires no technical expertise to use and the setup process takes only ten minutes. -### WHERE WE’RE AT +## WHERE WE’RE AT **We’re in the early stages of assessing this product’s market fit and targeting to pilot** this shared service with at least 3 partners in @@ -79,7 +79,7 @@ additional features based on partner needs. | Message send/failure analytics | Application status page | Multilingual interface and content library options | | 1-day records deletion | Scheduled send option | Recurring scheduled send | -### OPPORTUNITIES TO GET INVOLVED +## OPPORTUNITIES TO GET INVOLVED To get involved, email us at [notify-support@gsa.gov](mailto:notify-support@gsa.gov) with the following in the subject line! @@ -98,7 +98,7 @@ To get involved, email us at [notify-support@gsa.gov](mailto:notify-support@gsa. Early adopters will have wrap-around set-up support from the Studio and an opportunity to shape the future of this product. -### US Notify Demo +## US Notify Demo diff --git a/docs/sprint-goals.md b/docs/sprint-goals.md new file mode 100644 index 000000000..2e21359b8 --- /dev/null +++ b/docs/sprint-goals.md @@ -0,0 +1,10 @@ +# Notify Sprint Goals Log + +## Sprint: Heron (4/13/23) + +| | Goal | Impact | +|-------------|----------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------| +| Engineering | Tackle data retention and message delivery receipts | Expand user capabilities while remaining LATO compliant. Give users ability to easily view failed messages. | +| UX | Audit & create a plan for implementing some “low-hanging fruit” aspects of USWDS | Understand similarities between UK Design and USWDS. Scope lift and path to allow team to schedule migration. | +| Content | Audit current content and recommend short-term content changes | Eliminate unnecessary/confusing content and add clarity to improve UX for pilot partners. | +| Security | Complete tasks to get assessment and begin assessment | Complete LATO docs to move us closer to LATO award and thus, piloting in earnest. | diff --git a/manifest.yml b/manifest.yml index 47a351009..743a0e251 100644 --- a/manifest.yml +++ b/manifest.yml @@ -14,7 +14,6 @@ applications: services: - notify-admin-redis-((env)) - notify-api-csv-upload-bucket-((env)) - - notify-api-contact-list-bucket-((env)) - notify-admin-logo-upload-bucket-((env)) env: diff --git a/terraform/development/main.tf b/terraform/development/main.tf index 60a541abc..8e3c16a9c 100644 --- a/terraform/development/main.tf +++ b/terraform/development/main.tf @@ -32,15 +32,6 @@ resource "cloudfoundry_service_key" "csv_key" { service_instance = data.cloudfoundry_service_instance.csv_bucket.id } -data "cloudfoundry_service_instance" "contact_list_bucket" { - name_or_id = "${var.username}-contact-list-bucket" - space = data.cloudfoundry_space.dev.id -} -resource "cloudfoundry_service_key" "contact_list_key" { - name = local.key_name - service_instance = data.cloudfoundry_service_instance.contact_list_bucket.id -} - locals { credentials = <