def view_job_csv(service_id, job_id): job = job_api_client.get_job(service_id, job_id)['data'] template = service_api_client.get_service_template( service_id=service_id, template_id=job['template'], version=job['template_version'] )['data'] filter_args = _parse_filter_args(request.args) filter_args['status'] = _set_status_filters(filter_args) return ( generate_notifications_csv( notification_api_client.get_notifications_for_service( service_id, job_id, status=filter_args.get('status'), page_size=job['notification_count'] )['notifications'] ), 200, { 'Content-Type': 'text/csv; charset=utf-8', 'Content-Disposition': 'inline; filename="{} - {}.csv"'.format( template['name'], format_datetime_short(job['created_at']) ) } )
def test_generate_notifications_csv_only_calls_once_if_no_next_link( app_, _get_notifications_csv_mock, ): list(generate_notifications_csv(service_id='1234')) assert _get_notifications_csv_mock.call_count == 1
def test_generate_notifications_csv_returns_correct_csv_file( _get_notifications_csv_mock): csv_content = generate_notifications_csv(service_id='1234') csv_file = DictReader(StringIO('\n'.join(csv_content))) assert csv_file.fieldnames == [ 'Row number', 'Recipient', 'Template', 'Type', 'Job', 'Status', 'Time' ]
def download_notifications_csv(service_id): filter_args = parse_filter_args(request.args) filter_args['status'] = set_status_filters(filter_args) service_data_retention_days = current_service.get_days_of_retention( filter_args.get('message_type')[0]) return Response( stream_with_context( generate_notifications_csv( service_id=service_id, job_id=None, status=filter_args.get('status'), page=request.args.get('page', 1), page_size=10000, format_for_csv=True, template_type=filter_args.get('message_type'), limit_days=service_data_retention_days, )), mimetype='text/csv', headers={ 'Content-Disposition': 'inline; filename="{} - {} - {} report.csv"'.format( format_date_numeric( datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ")), filter_args['message_type'][0], current_service.name) })
def view_job_csv(service_id, job_id): job = Job.from_id(job_id, service_id=service_id) filter_args = parse_filter_args(request.args) filter_args['status'] = set_status_filters(filter_args) return Response( stream_with_context( generate_notifications_csv( service_id=service_id, job_id=job_id, status=filter_args.get('status'), page=request.args.get('page', 1), page_size=5000, format_for_csv=True, template_type=job.template_type, ) ), mimetype='text/csv', headers={ 'Content-Disposition': 'inline; filename="{} - {}.csv"'.format( job.template['name'], format_datetime_short(job.created_at) ) } )
def view_job_csv(service_id, job_id): job = job_api_client.get_job(service_id, job_id)['data'] template = service_api_client.get_service_template( service_id=service_id, template_id=job['template'], version=job['template_version'])['data'] filter_args = parse_filter_args(request.args) filter_args['status'] = set_status_filters(filter_args) return Response(stream_with_context( generate_notifications_csv( service_id=service_id, job_id=job_id, status=filter_args.get('status'), page=request.args.get('page', 1), page_size=5000, format_for_csv=True, template_type=template['template_type'], )), mimetype='text/csv', headers={ 'Content-Disposition': 'inline; filename="{} - {}.csv"'.format( template['name'], format_datetime_short(job['created_at'])) })
def view_job_csv(service_id, job_id): job = job_api_client.get_job(service_id, job_id)["data"] template = service_api_client.get_service_template( service_id=service_id, template_id=job["template"], version=job["template_version"], )["data"] filter_args = parse_filter_args(request.args) filter_args["status"] = set_status_filters(filter_args) return Response( stream_with_context( generate_notifications_csv( service_id=service_id, job_id=job_id, status=filter_args.get("status"), page=request.args.get("page", 1), page_size=5000, format_for_csv=True, template_type=template["template_type"], )), mimetype="text/csv", headers={ "Content-Disposition": 'inline; filename="{} - {}.csv"'.format( template["name"], format_datetime_short(job["created_at"])) }, )
def test_generate_notifications_csv_calls_twice_if_next_link( app_, mocker, job_id, ): mocker.patch( "app.s3_client.s3_csv_client.s3download", return_value=""" phone_number 07700900000 07700900001 07700900002 07700900003 07700900004 07700900005 07700900006 07700900007 07700900008 07700900009 """, ) service_id = "1234" response_with_links = _get_notifications_csv(rows=7, with_links=True) response_with_no_links = _get_notifications_csv(rows=3, row_number=8, with_links=False) mock_get_notifications = mocker.patch( "app.notification_api_client.get_notifications_for_service", side_effect=[ response_with_links(service_id), response_with_no_links(service_id), ], ) csv_content = generate_notifications_csv( service_id=service_id, job_id=job_id or fake_uuid, template_type="sms", ) csv = list(DictReader(StringIO("\n".join(csv_content)))) assert len(csv) == 10 assert csv[0]["phone_number"] == "07700900000" assert csv[9]["phone_number"] == "07700900009" assert mock_get_notifications.call_count == 2 # mock_calls[0][2] is the kwargs from first call assert mock_get_notifications.mock_calls[0][2]["page"] == 1 assert mock_get_notifications.mock_calls[1][2]["page"] == 2
def test_generate_notifications_csv_calls_twice_if_next_link( app_, mocker, job_id, ): mocker.patch('app.main.s3_client.s3download', return_value=""" phone_number 07700900000 0409000001 0409000002 0409000003 07700900004 07700900005 07700900006 07700900007 07700900008 07700900009 """) service_id = '1234' response_with_links = _get_notifications_csv(service_id, rows=7, with_links=True) response_with_no_links = _get_notifications_csv(service_id, rows=3, row_number=8, with_links=False) mock_get_notifications = mocker.patch( 'app.notification_api_client.get_notifications_for_service', side_effect=[ response_with_links, response_with_no_links, ]) csv_content = generate_notifications_csv( service_id=service_id, job_id=job_id or fake_uuid, template_type='sms', ) csv = list(DictReader(StringIO('\n'.join(csv_content)))) assert len(csv) == 10 assert csv[0]['phone_number'] == '07700900000' assert csv[9]['phone_number'] == '07700900009' assert mock_get_notifications.call_count == 2 # mock_calls[0][2] is the kwargs from first call assert mock_get_notifications.mock_calls[0][2]['page'] == 1 assert mock_get_notifications.mock_calls[1][2]['page'] == 2
def test_generate_notifications_csv_without_job( app_, mocker, created_by_name, expected_content, ): mocker.patch('app.notification_api_client.get_notifications_for_service', side_effect=_get_notifications_csv( created_by_name=created_by_name, created_by_email_address="*****@*****.**", job_id=None, job_name=None)) assert list( generate_notifications_csv(service_id=fake_uuid)) == expected_content
def test_generate_notifications_csv_returns_correct_csv_file( app_, mocker, _get_notifications_csv_mock, original_file_contents, expected_column_headers, expected_1st_row, ): mocker.patch( 'app.s3_client.s3_csv_client.s3download', return_value=original_file_contents, ) csv_content = generate_notifications_csv(service_id='1234', job_id=fake_uuid, template_type='sms') csv_file = DictReader(StringIO('\n'.join(csv_content))) assert csv_file.fieldnames == expected_column_headers assert next(csv_file) == dict(zip(expected_column_headers, expected_1st_row))
def test_should_download_notifications_for_a_job( app_, api_user_active, mock_login, mock_get_service, mock_get_job, mock_get_notifications, mock_get_template_version, mock_has_permissions, fake_uuid): with app_.test_request_context(): with app_.test_client() as client: client.login(api_user_active) response = client.get( url_for( 'main.view_job_csv', service_id=fake_uuid, job_id=fake_uuid, )) csv_content = generate_notifications_csv( mock_get_notifications(fake_uuid, job_id=fake_uuid)['notifications']) assert response.status_code == 200 assert response.get_data(as_text=True) == csv_content assert 'text/csv' in response.headers['Content-Type'] assert 'sample template - 1 January at 11:09am.csv"' in response.headers[ 'Content-Disposition']
def test_generate_csv_from_notifications( app_, service_one, active_user_with_permissions, mock_get_notifications, status, template_type, expected_status ): with app_.test_request_context(): csv_content = generate_notifications_csv( mock_get_notifications( service_one['id'], rows=1, set_template_type=template_type, set_status=status )['notifications'] ) for row in DictReader(StringIO(csv_content)): assert row['Time'] == 'Friday 01 January 2016 at 15:09' assert row['Status'] == expected_status
def test_generate_csv_from_notifications( app_, service_one, active_user_with_permissions, mock_get_notifications, status, template_type, expected_status ): with app_.test_request_context(): csv_content = generate_notifications_csv( mock_get_notifications( service_one['id'], rows=1, set_template_type=template_type, set_status=status )['notifications'] ) for row in DictReader(StringIO(csv_content)): assert row['Time'] == 'Friday 01 January 2016 at 11:09' assert row['Status'] == expected_status
def view_job_csv(service_id, job_id): job = job_api_client.get_job(service_id, job_id)["data"] template = service_api_client.get_service_template( service_id=service_id, template_id=job["template"], version=job["template_version"] )["data"] filter_args = _parse_filter_args(request.args) filter_args["status"] = _set_status_filters(filter_args) return ( generate_notifications_csv( notification_api_client.get_notifications_for_service( service_id, job_id, status=filter_args.get("status"), page_size=job["notification_count"] )["notifications"] ), 200, { "Content-Type": "text/csv; charset=utf-8", "Content-Disposition": 'inline; filename="{} - {}.csv"'.format( template["name"], format_datetime_short(job["created_at"]) ), }, )
def test_generate_notifications_csv_calls_twice_if_next_link(mocker): service_id = '1234' response_with_links = _get_notifications_csv(service_id, rows=7, with_links=True) response_with_no_links = _get_notifications_csv(service_id, rows=3, with_links=False) mock_get_notifications = mocker.patch( 'app.notification_api_client.get_notifications_for_service', side_effect=[ response_with_links, response_with_no_links, ]) csv_content = generate_notifications_csv(service_id=service_id) csv = DictReader(StringIO('\n'.join(csv_content))) assert len(list(csv)) == 10 assert mock_get_notifications.call_count == 2 # mock_calls[0][2] is the kwargs from first call assert mock_get_notifications.mock_calls[0][2]['page'] == 1 assert mock_get_notifications.mock_calls[1][2]['page'] == 2
def get_notifications(service_id, message_type, status_override=None): # TODO get the api to return count of pages as well. page = get_page_from_request() if page is None: abort(404, "Invalid page argument ({}).".format(request.args.get('page'))) filter_args = parse_filter_args(request.args) filter_args['status'] = set_status_filters(filter_args) service_data_retention_days = None if message_type is not None: service_data_retention_days = current_service.get_days_of_retention( message_type) if request.path.endswith('csv') and current_user.has_permissions( 'view_activity'): return Response(generate_notifications_csv( service_id=service_id, page=page, page_size=5000, template_type=[message_type], status=filter_args.get('status'), limit_days=service_data_retention_days), mimetype='text/csv', headers={ 'Content-Disposition': 'inline; filename="notifications.csv"' }) notifications = notification_api_client.get_notifications_for_service( service_id=service_id, page=page, template_type=[message_type] if message_type else [], status=filter_args.get('status'), limit_days=service_data_retention_days, to=request.form.get('to', ''), ) url_args = { 'message_type': message_type, 'status': request.args.get('status') } prev_page = None if 'links' in notifications and notifications['links'].get('prev', None): prev_page = generate_previous_dict('main.view_notifications', service_id, page, url_args=url_args) next_page = None if 'links' in notifications and notifications['links'].get('next', None): next_page = generate_next_dict('main.view_notifications', service_id, page, url_args) if message_type: download_link = url_for('.view_notifications_csv', service_id=current_service.id, message_type=message_type, status=request.args.get('status')) else: download_link = None return { 'service_data_retention_days': service_data_retention_days, 'counts': render_template('views/activity/counts.html', status=request.args.get('status'), status_filters=get_status_filters( current_service, message_type, service_api_client.get_service_statistics( service_id, today_only=False, limit_days=service_data_retention_days))), 'notifications': render_template( 'views/activity/notifications.html', notifications=list( add_preview_of_content_to_notifications( notifications['notifications'])), page=page, limit_days=service_data_retention_days, prev_page=prev_page, next_page=next_page, status=request.args.get('status'), message_type=message_type, download_link=download_link, ), }
def get_notifications(service_id, message_type, status_override=None): # TODO get the api to return count of pages as well. page = get_page_from_request() if page is None: abort(404, "Invalid page argument ({}) reverting to page 1.".format(request.args["page"], None)) if message_type not in ["email", "sms"]: abort(404) filter_args = _parse_filter_args(request.args) filter_args["status"] = _set_status_filters(filter_args) notifications = notification_api_client.get_notifications_for_service( service_id=service_id, page=page, template_type=[message_type], status=filter_args.get("status"), limit_days=current_app.config["ACTIVITY_STATS_LIMIT_DAYS"], ) url_args = {"message_type": message_type, "status": request.args.get("status")} prev_page = None if notifications["links"].get("prev", None): prev_page = generate_previous_dict("main.view_notifications", service_id, page, url_args=url_args) next_page = None if notifications["links"].get("next", None): next_page = generate_next_dict("main.view_notifications", service_id, page, url_args) if request.path.endswith("csv"): csv_content = generate_notifications_csv( notification_api_client.get_notifications_for_service( service_id=service_id, page=page, page_size=notifications["total"], template_type=[message_type], status=filter_args.get("status"), limit_days=current_app.config["ACTIVITY_STATS_LIMIT_DAYS"], )["notifications"] ) return ( csv_content, 200, {"Content-Type": "text/csv; charset=utf-8", "Content-Disposition": 'inline; filename="notifications.csv"'}, ) return { "counts": render_template( "views/activity/counts.html", status=request.args.get("status"), status_filters=get_status_filters( current_service, message_type, service_api_client.get_detailed_service(service_id)["data"]["statistics"] ), ), "notifications": render_template( "views/activity/notifications.html", notifications=notifications["notifications"], page=page, prev_page=prev_page, next_page=next_page, status=request.args.get("status"), message_type=message_type, download_link=url_for( ".view_notifications_csv", service_id=current_service["id"], message_type=message_type, status=request.args.get("status"), ), ), }
def get_notifications(service_id, message_type, status_override=None): # TODO get the api to return count of pages as well. page = get_page_from_request() if page is None: abort( 404, "Invalid page argument ({}) reverting to page 1.".format( request.args['page'], None)) if message_type not in ['email', 'sms', 'letter']: abort(404) filter_args = parse_filter_args(request.args) filter_args['status'] = set_status_filters(filter_args) if request.path.endswith('csv'): return Response(generate_notifications_csv( service_id=service_id, page=page, page_size=5000, template_type=[message_type], status=filter_args.get('status'), limit_days=current_app.config['ACTIVITY_STATS_LIMIT_DAYS']), mimetype='text/csv', headers={ 'Content-Disposition': 'inline; filename="notifications.csv"' }) notifications = notification_api_client.get_notifications_for_service( service_id=service_id, page=page, template_type=[message_type], status=filter_args.get('status'), limit_days=current_app.config['ACTIVITY_STATS_LIMIT_DAYS'], to=request.form.get('to', ''), ) url_args = { 'message_type': message_type, 'status': request.args.get('status') } prev_page = None if 'links' in notifications and notifications['links'].get('prev', None): prev_page = generate_previous_dict('main.view_notifications', service_id, page, url_args=url_args) next_page = None if 'links' in notifications and notifications['links'].get('next', None): next_page = generate_next_dict('main.view_notifications', service_id, page, url_args) return { 'counts': render_template('views/activity/counts.html', status=request.args.get('status'), status_filters=get_status_filters( current_service, message_type, service_api_client.get_detailed_service(service_id) ['data']['statistics'])), 'notifications': render_template('views/activity/notifications.html', notifications=list( add_preview_of_content_to_notifications( notifications['notifications'])), page=page, prev_page=prev_page, next_page=next_page, status=request.args.get('status'), message_type=message_type, download_link=url_for( '.view_notifications_csv', service_id=current_service['id'], message_type=message_type, status=request.args.get('status'))), }
def test_can_show_notifications( app_, service_one, active_user_with_permissions, mock_get_notifications, mock_get_detailed_service, mocker, message_type, page_title, status_argument, expected_api_call, page_argument, expected_page_argument): with app_.test_request_context(): with app_.test_client() as client: client.login(active_user_with_permissions, mocker, service_one) response = client.get( url_for('main.view_notifications', service_id=service_one['id'], message_type=message_type, status=status_argument, page=page_argument)) assert response.status_code == 200 content = response.get_data(as_text=True) notifications = notification_json(service_one['id']) notification = notifications['notifications'][0] assert notification['to'] in content assert notification['status'] in content assert notification['template']['name'] in content assert 'csv' in content page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') assert page_title in page.h1.text.strip() assert url_for('.view_notifications_csv', service_id=service_one['id'], message_type=message_type, status=status_argument) == page.findAll( "a", {"download": "download"})[0]['href'] path_to_json = page.find( "div", {'data-key': 'notifications'})['data-resource'] url = urlparse(path_to_json) assert url.path == '/services/{}/notifications/{}.json'.format( service_one['id'], message_type) query_dict = parse_qs(url.query) if status_argument: assert query_dict['status'] == [status_argument] if expected_page_argument: assert query_dict['page'] == [str(expected_page_argument)] mock_get_notifications.assert_called_with(limit_days=7, page=expected_page_argument, service_id=service_one['id'], status=expected_api_call, template_type=[message_type]) csv_response = client.get( url_for('main.view_notifications_csv', service_id=service_one['id'], message_type='email', download='csv')) csv_content = generate_notifications_csv( mock_get_notifications(service_one['id'])['notifications']) assert csv_response.status_code == 200 assert csv_response.get_data(as_text=True) == csv_content assert 'text/csv' in csv_response.headers['Content-Type'] json_response = client.get( url_for('main.get_notifications_as_json', service_id=service_one['id'], message_type=message_type, status=status_argument)) json_content = json.loads(json_response.get_data(as_text=True)) assert json_content.keys() == {'counts', 'notifications'}
def view_notifications(service_id, message_type): # TODO get the api to return count of pages as well. page = get_page_from_request() if page is None: abort(404, "Invalid page argument ({}) reverting to page 1.".format(request.args['page'], None)) if message_type not in ['email', 'sms']: abort(404) filter_args = _parse_filter_args(request.args) filter_args['status'] = _set_status_filters(filter_args) notifications = notification_api_client.get_notifications_for_service( service_id=service_id, page=page, template_type=[message_type], status=filter_args.get('status'), limit_days=current_app.config['ACTIVITY_STATS_LIMIT_DAYS']) view_dict = dict( message_type=message_type, status=request.args.get('status') ) prev_page = None if notifications['links'].get('prev', None): prev_page = generate_previous_next_dict( 'main.view_notifications', service_id, view_dict, page - 1, 'Previous page', 'page {}'.format(page - 1)) next_page = None if notifications['links'].get('next', None): next_page = generate_previous_next_dict( 'main.view_notifications', service_id, view_dict, page + 1, 'Next page', 'page {}'.format(page + 1)) if request.path.endswith('csv'): csv_content = generate_notifications_csv( notification_api_client.get_notifications_for_service( service_id=service_id, page=page, page_size=notifications['total'], template_type=[message_type], status=filter_args.get('status'), limit_days=current_app.config['ACTIVITY_STATS_LIMIT_DAYS'])['notifications']) return csv_content, 200, { 'Content-Type': 'text/csv; charset=utf-8', 'Content-Disposition': 'inline; filename="notifications.csv"' } return render_template( 'views/notifications.html', notifications=notifications['notifications'], page=page, prev_page=prev_page, next_page=next_page, status=request.args.get('status'), message_type=message_type, download_link=url_for( '.view_notifications_csv', service_id=current_service['id'], message_type=message_type, status=request.args.get('status') ), status_filters=get_status_filters( current_service, message_type, service_api_client.get_detailed_service(service_id)['data']['statistics'] ) )
def get_notifications(service_id, message_type, status_override=None): # TODO get the api to return count of pages as well. page = get_page_from_request() if page is None: abort(404, "Invalid page argument ({}).".format(request.args.get("page"))) if message_type not in ["email", "sms", "letter", None]: abort(404) filter_args = parse_filter_args(request.args) filter_args["status"] = set_status_filters(filter_args) service_data_retention_days = current_app.config.get( "ACTIVITY_STATS_LIMIT_DAYS", None) if message_type is not None: service_data_retention_days = current_service.get_days_of_retention( message_type) if request.path.endswith("csv") and current_user.has_permissions( "view_activity"): return Response( generate_notifications_csv( service_id=service_id, page=page, page_size=5000, template_type=[message_type], status=filter_args.get("status"), limit_days=service_data_retention_days, ), mimetype="text/csv", headers={ "Content-Disposition": 'inline; filename="notifications.csv"' }, ) notifications = notification_api_client.get_notifications_for_service( service_id=service_id, page=page, template_type=[message_type] if message_type else [], status=filter_args.get("status"), limit_days=service_data_retention_days, to=request.form.get("to", ""), ) url_args = { "message_type": message_type, "status": request.args.get("status") } prev_page = None if "links" in notifications and notifications["links"].get("prev", None): prev_page = generate_previous_dict("main.view_notifications", service_id, page, url_args=url_args) next_page = None if "links" in notifications and notifications["links"].get("next", None): next_page = generate_next_dict("main.view_notifications", service_id, page, url_args) if message_type: download_link = url_for( ".view_notifications_csv", service_id=current_service.id, message_type=message_type, status=request.args.get("status"), ) else: download_link = None return { "service_data_retention_days": service_data_retention_days, "counts": render_template( "views/activity/counts.html", status=request.args.get("status"), status_filters=get_status_filters( current_service, message_type, service_api_client.get_service_statistics( service_id, today_only=False, limit_days=service_data_retention_days), ), ), "notifications": render_template( "views/activity/notifications.html", notifications=list( add_preview_of_content_to_notifications( notifications["notifications"])), page=page, limit_days=service_data_retention_days, prev_page=prev_page, next_page=next_page, status=request.args.get("status"), message_type=message_type, download_link=download_link, ), }