diff --git a/app/main/views/uploads.py b/app/main/views/uploads.py index ef9d53da6..5d8809dd9 100644 --- a/app/main/views/uploads.py +++ b/app/main/views/uploads.py @@ -513,9 +513,11 @@ def save_contact_list(service_id, upload_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=ContactList.from_id(contact_list_id, service_id=service_id), + contact_list=contact_list, + jobs=contact_list.get_jobs(page=1), ) diff --git a/app/models/__init__.py b/app/models/__init__.py index 791c4a0c8..0acd9a31a 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -41,3 +41,22 @@ class ModelList(SerialisedModelCollection): def __init__(self, *args): self.items = self.client_method(*args) + + +class PaginatedModelList(ModelList): + + response_key = 'data' + + def __init__(self, *args, page=None, **kwargs): + try: + self.current_page = int(page) + except TypeError: + self.current_page = 1 + response = self.client_method( + *args, + **kwargs, + page=self.current_page, + ) + self.items = response[self.response_key] + self.prev_page = response.get('links', {}).get('prev', None) + self.next_page = response.get('links', {}).get('next', None) diff --git a/app/models/contact_list.py b/app/models/contact_list.py index 9d4dfaaa7..294c53165 100644 --- a/app/models/contact_list.py +++ b/app/models/contact_list.py @@ -4,9 +4,11 @@ from os import path from flask import abort, current_app from notifications_utils.formatters import strip_whitespace from notifications_utils.recipients import RecipientCSV +from notifications_utils.timezones import utc_string_to_aware_gmt_datetime 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.s3_csv_client import ( get_csv_metadata, @@ -21,8 +23,9 @@ class ContactList(JSONModel): ALLOWED_PROPERTIES = { 'id', - 'created_at', 'created_by', + 'has_jobs', + 'recent_job_count', 'service_id', 'original_file_name', 'row_count', @@ -112,6 +115,10 @@ class ContactList(JSONModel): contact_list_id=self.id, ) + @property + def created_at(self): + return utc_string_to_aware_gmt_datetime(self._dict['created_at']) + @property def contents(self): return self.download(self.service_id, self.id) @@ -130,6 +137,13 @@ class ContactList(JSONModel): file_name, extention = path.splitext(self.original_file_name) return f'{file_name}.csv' + def get_jobs(self, *, page): + return PaginatedJobsAndScheduledJobs( + self.service_id, + contact_list_id=self.id, + page=page, + ) + class ContactLists(ModelList): diff --git a/app/models/job.py b/app/models/job.py index 65679f3aa..66a33ac38 100644 --- a/app/models/job.py +++ b/app/models/job.py @@ -9,7 +9,7 @@ from notifications_utils.letter_timings import ( from notifications_utils.timezones import utc_string_to_aware_gmt_datetime from werkzeug.utils import cached_property -from app.models import JSONModel, ModelList +from app.models import JSONModel, ModelList, PaginatedModelList from app.notify_client.job_api_client import job_api_client from app.notify_client.notification_api_client import notification_api_client from app.notify_client.service_api_client import service_api_client @@ -25,6 +25,7 @@ class Job(JSONModel): ALLOWED_PROPERTIES = { 'id', 'service', + 'template_name', 'template_version', 'original_file_name', 'created_at', @@ -230,20 +231,22 @@ class ScheduledJobs(ImmediateJobs): client_method = job_api_client.get_scheduled_jobs -class PaginatedJobs(ImmediateJobs): - +class PaginatedJobs(PaginatedModelList, ImmediateJobs): client_method = job_api_client.get_page_of_jobs + statuses = None - def __init__(self, service_id, page=None): - try: - self.current_page = int(page) - except TypeError: - self.current_page = 1 - response = self.client_method(service_id, page=self.current_page) - self.items = response['data'] - self.prev_page = response.get('links', {}).get('prev', None) - self.next_page = response.get('links', {}).get('next', None) + def __init__(self, service_id, *, contact_list_id=None, page=None): + super().__init__( + service_id, + contact_list_id=contact_list_id, + statuses=self.statuses, + page=page, + ) -class PaginatedUploads(PaginatedJobs): +class PaginatedJobsAndScheduledJobs(PaginatedJobs): + statuses = job_api_client.NON_CANCELLED_JOB_STATUSES + + +class PaginatedUploads(PaginatedModelList, ImmediateJobs): client_method = job_api_client.get_uploads diff --git a/app/notify_client/job_api_client.py b/app/notify_client/job_api_client.py index 9e514af6f..e94a09dbe 100644 --- a/app/notify_client/job_api_client.py +++ b/app/notify_client/job_api_client.py @@ -16,6 +16,7 @@ class JobApiClient(NotifyAdminAPIClient): } SCHEDULED_JOB_STATUS = 'scheduled' CANCELLED_JOB_STATUS = 'cancelled' + NON_CANCELLED_JOB_STATUSES = JOB_STATUSES - {CANCELLED_JOB_STATUS} NON_SCHEDULED_JOB_STATUSES = JOB_STATUSES - {SCHEDULED_JOB_STATUS, CANCELLED_JOB_STATUS} def get_job(self, service_id, job_id): @@ -24,12 +25,14 @@ class JobApiClient(NotifyAdminAPIClient): return job - def get_jobs(self, service_id, limit_days=None, statuses=None, page=1): + def get_jobs(self, service_id, *, limit_days=None, contact_list_id=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) @@ -50,11 +53,12 @@ class JobApiClient(NotifyAdminAPIClient): if job['job_status'] != 'cancelled' ) - def get_page_of_jobs(self, service_id, page): + def get_page_of_jobs(self, service_id, *, page, statuses=None, contact_list_id=None): return self.get_jobs( service_id, - statuses=self.NON_SCHEDULED_JOB_STATUSES, + statuses=statuses or self.NON_SCHEDULED_JOB_STATUSES, page=page, + contact_list_id=contact_list_id, ) def get_immediate_jobs(self, service_id): diff --git a/app/templates/components/message-count-label.html b/app/templates/components/message-count-label.html index a4a9078de..b85d8f420 100644 --- a/app/templates/components/message-count-label.html +++ b/app/templates/components/message-count-label.html @@ -61,3 +61,19 @@ {%- endif -%} {%- endif %} {%- endmacro %} + + +{% macro recipient_count(count, template_type, prefix='') -%} + {{ count|format_thousands }} {{ prefix }} {{ recipient_count_label(count, template_type)}} +{% endmacro %} + + +{% macro iteration_count(count) -%} + {% if count == 1 %} + once + {% elif count == 2 %} + twice + {% else %} + {{ count }} times + {% endif %} +{% endmacro %} diff --git a/app/templates/views/dashboard/_jobs.html b/app/templates/views/dashboard/_jobs.html index d65e3573f..7a8e0f9a2 100644 --- a/app/templates/views/dashboard/_jobs.html +++ b/app/templates/views/dashboard/_jobs.html @@ -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, recipient_count_label -%} +{% from "components/message-count-label.html" import message_count_label, recipient_count_label, iteration_count -%}
{% call(item, row_number) list_table( @@ -35,10 +35,18 @@ {% elif item.upload_type == 'contact_list' %} - Uploaded {{ - item.created_at|format_datetime_relative - }} - + {% if item.recent_job_count %} + Used {{ iteration_count(item.recent_job_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 %} {% elif item.upload_type == 'letter_day' %} {{ item.letter_printing_statement }} diff --git a/app/templates/views/uploads/contact-list/contact-list.html b/app/templates/views/uploads/contact-list/contact-list.html index eee292712..4fbd5397f 100644 --- a/app/templates/views/uploads/contact-list/contact-list.html +++ b/app/templates/views/uploads/contact-list/contact-list.html @@ -1,9 +1,10 @@ {% 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 %} +{% 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/message-count-label.html" import message_count_label, recipient_count_label %} +{% from "components/message-count-label.html" import message_count_label, recipient_count, recipient_count_label, iteration_count %} {% from "components/button/macro.njk" import govukButton %} {% block service_page_title %} @@ -18,41 +19,124 @@ ) }}

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

-

- Download this list  - {{ contact_list.recipients|length|format_thousands }} - {{ recipient_count_label(contact_list.recipients|length, contact_list.recipients.template_type) }} -

+ {% if jobs %} +

+ Used {{ iteration_count(jobs|length) }} + 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 %} - {% 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 %} -
+ {% endcall %} + {% call field() %} + {% if item.scheduled %} + {{ big_number( + item.notification_count, + smallest=True, + label=message_count_label( + item.notification_count, + 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 %} - {% if not confirm_delete_banner %} -
- + {% endblock %} diff --git a/tests/__init__.py b/tests/__init__.py index 144b545f1..ff4866012 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -361,6 +361,7 @@ def job_json( template_id=None, template_version=1, template_type='sms', + template_name='Example template', created_at=None, bucket_name='', original_file_name="thisisatest.csv", @@ -381,6 +382,7 @@ def job_json( 'id': job_id, 'service': service_id, 'template': template_id, + 'template_name': template_name, 'template_version': template_version, 'template_type': template_type, 'original_file_name': original_file_name, diff --git a/tests/app/main/views/test_send.py b/tests/app/main/views/test_send.py index 875c9206f..cc906312f 100644 --- a/tests/app/main/views/test_send.py +++ b/tests/app/main/views/test_send.py @@ -4174,21 +4174,21 @@ def test_redirects_to_template_if_job_exists_already( @pytest.mark.parametrize(( 'template_type, ' 'expected_list_id, ' - 'expected_filename, ' + 'expected_filenames, ' 'expected_time, ' 'expected_count' ), ( ( 'email', '6ce466d0-fd6a-11e5-82f5-e0accb9d11a6', - 'EmergencyContactList.xls', + ['EmergencyContactList.xls'], 'Uploaded today at 10:59am', '100 email addresses', ), ( 'sms', 'd7b0bd1a-d1c7-4621-be5c-3c1b4278a2ad', - 'phone number list.csv', + ['phone number list.csv', 'UnusedList.tsv'], 'Uploaded today at 1:00pm', '123 phone numbers', ), @@ -4201,7 +4201,7 @@ def test_choose_from_contact_list( fake_uuid, template_type, expected_list_id, - expected_filename, + expected_filenames, expected_time, expected_count, ): @@ -4215,10 +4215,11 @@ def test_choose_from_contact_list( service_id=SERVICE_ONE_ID, template_id=fake_uuid, ) - assert len(page.select('.file-list-filename-large')) == 1 - assert normalize_spaces(page.select_one('.file-list-filename-large').text) == ( - expected_filename - ) + assert [ + normalize_spaces(filename.text) + for filename in page.select('.file-list-filename-large') + ] == expected_filenames + assert page.select_one('a.file-list-filename-large')['href'] == url_for( 'main.send_from_contact_list', service_id=SERVICE_ONE_ID, diff --git a/tests/app/main/views/uploads/test_upload_contact_list.py b/tests/app/main/views/uploads/test_upload_contact_list.py index ba2e0fa1a..e3bfd34cb 100644 --- a/tests/app/main/views/uploads/test_upload_contact_list.py +++ b/tests/app/main/views/uploads/test_upload_contact_list.py @@ -452,13 +452,104 @@ def test_cant_save_bad_contact_list( assert mock_create_contact_list.called is False +@pytest.mark.parametrize('has_jobs, expected_empty_message', [ + (False, 'Not used yet.'), + (True, 'Not used in the last 7 days.'), +]) @freeze_time('2020-03-13 16:51:56') def test_view_contact_list( mocker, client_request, mock_get_contact_list, + mock_get_no_jobs, + mock_get_service_data_retention, + fake_uuid, + has_jobs, + expected_empty_message, +): + mocker.patch( + 'app.models.contact_list.contact_list_api_client.get_contact_list', + return_value={ + 'created_at': '2020-03-03 12:12:12', + 'created_by': 'Test User', + 'id': fake_uuid, + 'original_file_name': 'EmergencyContactList.xls', + 'row_count': 100, + 'recent_job_count': 0, + 'has_jobs': has_jobs, + 'service_id': SERVICE_ONE_ID, + 'template_type': 'email', + }, + ) + mocker.patch('app.models.contact_list.s3download', return_value='\n'.join( + ['email address'] + [ + f'test-{i}@example.com' for i in range(51) + ] + )) + page = client_request.get( + 'main.contact_list', + service_id=SERVICE_ONE_ID, + contact_list_id=fake_uuid, + ) + assert normalize_spaces(page.select_one('h1').text) == ( + 'EmergencyContactList.xls' + ) + assert normalize_spaces(page.select('main p')[0].text) == ( + 'Uploaded by Test User on 3 March at 12:12pm.' + ) + assert normalize_spaces(page.select('main p')[1].text) == ( + expected_empty_message + ) + assert normalize_spaces(page.select_one('main h2').text) == ( + '51 saved email addresses' + ) + assert page.select_one('.js-stick-at-bottom-when-scrolling a[download]')['href'] == url_for( + 'main.download_contact_list', + service_id=SERVICE_ONE_ID, + contact_list_id=fake_uuid, + ) + assert len(page.select('tbody tr')) == 50 + assert [ + normalize_spaces(page.select('tbody tr')[0].text), + normalize_spaces(page.select('tbody tr')[1].text), + + normalize_spaces(page.select('tbody tr')[48].text), + normalize_spaces(page.select('tbody tr')[49].text), + ] == [ + 'test-0@example.com', + 'test-1@example.com', + + 'test-48@example.com', + 'test-49@example.com', + ] + assert 'test-50@example.com' not in page.select_one('tbody').text + assert normalize_spaces(page.select_one('.table-show-more-link').text) == ( + 'Only showing the first 50 rows' + ) + + +@freeze_time('2015-12-31 16:51:56') +def test_view_jobs_for_contact_list( + mocker, + client_request, + mock_get_jobs, + mock_get_service_data_retention, fake_uuid, ): + mocker.patch( + 'app.models.contact_list.contact_list_api_client.get_contact_list', + return_value={ + 'created_at': '2015-12-31 12:12:12', + 'created_by': 'Test User', + 'id': fake_uuid, + 'original_file_name': 'EmergencyContactList.xls', + 'row_count': 100, + 'recent_job_count': 0, + 'has_jobs': True, + 'service_id': SERVICE_ONE_ID, + 'template_type': 'email', + }, + ) mocker.patch('app.models.contact_list.s3download', return_value='\n'.join( ['email address'] + ['test@example.com'] * 51 )) @@ -471,28 +562,51 @@ def test_view_contact_list( 'EmergencyContactList.xls' ) assert normalize_spaces(page.select('main p')[0].text) == ( - 'Uploaded by Test User today at 10:59am' + 'Uploaded by Test User today at 12:12pm.' ) assert normalize_spaces(page.select('main p')[1].text) == ( - 'Download this list 51 email addresses' + 'Used 6 times in the last 7 days.' ) - assert page.select_one('a[download]')['href'] == url_for( - 'main.download_contact_list', + assert [ + normalize_spaces(row.text) + for row in page.select_one('table').select('tr') + ] == [ + 'Template Status', + ( + 'Template Y ' + 'Sending tomorrow at 11:09pm ' + '1 text message waiting to send' + ), + ( + 'Template Z ' + 'Sending tomorrow at 11:09am ' + '1 text message waiting to send' + ), + ( + 'Template A ' + 'Sent today at 4:51pm ' + '1 sending 0 delivered 0 failed' + ), + ( + 'Template B ' + 'Sent today at 4:51pm ' + '1 sending 0 delivered 0 failed' + ), + ( + 'Template C ' + 'Sent today at 4:51pm ' + '1 sending 0 delivered 0 failed' + ), + ( + 'Template D ' + 'Sent today at 4:51pm ' + '1 sending 0 delivered 0 failed' + ), + ] + assert page.select_one('table a')['href'] == url_for( + 'main.view_job', service_id=SERVICE_ONE_ID, - contact_list_id=fake_uuid, - ) - assert normalize_spaces(page.select_one('table').text).startswith( - 'Email addresses ' - '1 email address ' - '2 test@example.com ' - '3 test@example.com ' - ) - assert normalize_spaces(page.select_one('table').text).endswith( - '50 test@example.com ' - '51 test@example.com' - ) - assert normalize_spaces(page.select_one('.table-show-more-link').text) == ( - 'Only showing the first 50 rows' + job_id=fake_uuid, ) @@ -543,6 +657,8 @@ def test_confirm_delete_contact_list( mocker, client_request, fake_uuid, + mock_get_jobs, + mock_get_service_data_retention, mock_get_contact_list, ): mocker.patch( diff --git a/tests/app/main/views/uploads/test_upload_hub.py b/tests/app/main/views/uploads/test_upload_hub.py index fae8de5d8..f9e44fafe 100644 --- a/tests/app/main/views/uploads/test_upload_hub.py +++ b/tests/app/main/views/uploads/test_upload_hub.py @@ -338,6 +338,7 @@ def test_uploads_page_shows_contact_lists_first( mock_get_no_uploads, mock_get_jobs, mock_get_contact_lists, + mock_get_service_data_retention, ): page = client_request.get('main.uploads', service_id=SERVICE_ONE_ID) @@ -349,14 +350,19 @@ def test_uploads_page_shows_contact_lists_first( ), ( 'phone number list.csv ' - 'Uploaded 13 March at 1:00pm ' + 'Used twice in the last 7 days ' '123 saved phone numbers' ), ( 'EmergencyContactList.xls ' - 'Uploaded 13 March at 10:59am ' + 'Not used in the last 7 days ' '100 saved email addresses' ), + ( + 'UnusedList.tsv ' + 'Not used yet ' + '1 saved phone number' + ), ( 'even_later.csv ' 'Sending 1 January 2016 at 11:09pm ' diff --git a/tests/app/models/test_contact_list.py b/tests/app/models/test_contact_list.py new file mode 100644 index 000000000..95a93126c --- /dev/null +++ b/tests/app/models/test_contact_list.py @@ -0,0 +1,31 @@ +from datetime import datetime + +from app.models.contact_list import ContactList +from app.models.job import PaginatedJobs + + +def test_created_at(): + created_at = ContactList({'created_at': '2016-05-06T07:08:09.061258'}).created_at + assert isinstance(created_at, datetime) + assert created_at.isoformat() == '2016-05-06T08:08:09.061258+01:00' + + +def test_get_jobs(mock_get_jobs): + contact_list = ContactList({'id': 'a', 'service_id': 'b'}) + assert isinstance(contact_list.get_jobs(page=123), PaginatedJobs) + # mock_get_jobs mocks the underlying API client method, not + # contact_list.get_jobs + mock_get_jobs.assert_called_once_with( + 'b', + contact_list_id='a', + statuses={ + 'finished', + 'sending limits exceeded', + 'ready to send', + 'scheduled', + 'sent to dvla', + 'pending', + 'in progress', + }, + page=123, + ) diff --git a/tests/conftest.py b/tests/conftest.py index e7046e7d5..a283d3b82 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1807,8 +1807,8 @@ def mock_has_no_jobs(mocker): @pytest.fixture(scope='function') -def mock_get_jobs(mocker, api_user_active): - def _get_jobs(service_id, limit_days=None, statuses=None, page=1): +def mock_get_jobs(mocker, api_user_active, fake_uuid): + def _get_jobs(service_id, limit_days=None, statuses=None, contact_list_id=None, page=1): if statuses is None: statuses = ['', 'scheduled', 'pending', 'cancelled', 'finished'] @@ -1816,19 +1816,21 @@ def mock_get_jobs(mocker, api_user_active): job_json( service_id, api_user_active, + job_id=fake_uuid, original_file_name=filename, scheduled_for=scheduled_for, job_status=job_status, template_version=template_version, + template_name=template_name, ) - for filename, scheduled_for, job_status, template_version in ( - ('export 1/1/2016.xls', '', 'finished', 1), - ('all email addresses.xlsx', '', 'pending', 1), - ('applicants.ods', '', 'finished', 1), - ('thisisatest.csv', '', 'finished', 2), - ('send_me_later.csv', '2016-01-01 11:09:00.061258', 'scheduled', 1), - ('even_later.csv', '2016-01-01 23:09:00.061258', 'scheduled', 1), - ('full_of_regret.csv', '2016-01-01 23:09:00.061258', 'cancelled', 1) + for filename, scheduled_for, job_status, template_name, template_version in ( + ('full_of_regret.csv', '2016-01-01 23:09:00.061258', 'cancelled', 'Template X', 1), + ('even_later.csv', '2016-01-01 23:09:00.061258', 'scheduled', 'Template Y', 1), + ('send_me_later.csv', '2016-01-01 11:09:00.061258', 'scheduled', 'Template Z', 1), + ('export 1/1/2016.xls', '', 'finished', 'Template A', 1), + ('all email addresses.xlsx', '', 'pending', 'Template B', 1), + ('applicants.ods', '', 'finished', 'Template C', 1), + ('thisisatest.csv', '', 'finished', 'Template D', 2), ) ] return { @@ -2030,6 +2032,17 @@ def mock_get_no_uploads(mocker, api_user_active): ) +@pytest.fixture(scope='function') +def mock_get_no_jobs(mocker, api_user_active): + mocker.patch( + 'app.models.job.PaginatedJobs.client_method', + return_value={ + 'data': [], + 'links': {}, + } + ) + + @pytest.fixture(scope='function') def mock_create_contact_list(mocker, api_user_active): def _create( @@ -2062,6 +2075,8 @@ def mock_get_contact_lists(mocker, api_user_active, fake_uuid): 'id': fake_uuid, 'original_file_name': 'EmergencyContactList.xls', 'row_count': 100, + 'recent_job_count': 0, + 'has_jobs': True, 'service_id': service_id, 'template_type': 'email', }, { @@ -2070,6 +2085,18 @@ def mock_get_contact_lists(mocker, api_user_active, fake_uuid): 'id': 'd7b0bd1a-d1c7-4621-be5c-3c1b4278a2ad', 'original_file_name': 'phone number list.csv', 'row_count': 123, + 'recent_job_count': 2, + 'has_jobs': True, + 'service_id': service_id, + 'template_type': 'sms', + }, { + 'created_at': '2020-02-02 02:00:00', + 'created_by': 'Test User', + 'id': fake_uuid, + 'original_file_name': 'UnusedList.tsv', + 'row_count': 1, + 'recent_job_count': 0, + 'has_jobs': False, 'service_id': service_id, 'template_type': 'sms', }] @@ -2089,6 +2116,8 @@ def mock_get_contact_list(mocker, api_user_active, fake_uuid): 'id': fake_uuid, 'original_file_name': 'EmergencyContactList.xls', 'row_count': 100, + 'recent_job_count': 0, + 'has_jobs': True, 'service_id': service_id, 'template_type': 'email', }