diff --git a/app/assets/javascripts/scrollPosition.js b/app/assets/javascripts/scrollPosition.js new file mode 100644 index 000000000..520e96992 --- /dev/null +++ b/app/assets/javascripts/scrollPosition.js @@ -0,0 +1,13 @@ +document.querySelectorAll('.usa-button-group a, .usa-pagination a').forEach(function(button) { + button.addEventListener('click', function() { + sessionStorage.setItem('scrollPosition', window.pageYOffset); + }); +}); + +document.addEventListener('DOMContentLoaded', function() { + var scrollPosition = sessionStorage.getItem('scrollPosition'); + if (scrollPosition !== null) { + window.scrollTo(0, parseInt(scrollPosition)); + sessionStorage.removeItem('scrollPosition'); + } +}); diff --git a/app/main/views/activity.py b/app/main/views/activity.py index 6481a4832..4e2f9fd3e 100644 --- a/app/main/views/activity.py +++ b/app/main/views/activity.py @@ -14,9 +14,6 @@ from app.utils.user import user_has_permissions def get_download_availability(service_id): - """ - Check if there are jobs available for each download time period. - """ jobs_1_day = job_api_client.get_page_of_jobs(service_id, page=1, limit_days=1) jobs_3_days = job_api_client.get_page_of_jobs(service_id, page=1, limit_days=3) jobs_5_days = job_api_client.get_page_of_jobs(service_id, page=1, limit_days=5) @@ -39,16 +36,54 @@ def get_download_availability(service_id): } +def get_download_links(message_type): + time_periods = ["one_day", "three_day", "five_day", "seven_day"] + links = {} + + for period in time_periods: + links[f"download_link_{period}"] = url_for( + ".download_notifications_csv", + service_id=current_service.id, + message_type=message_type, + status=request.args.get("status"), + number_of_days=period, + ) + return links + + +def get_filtered_jobs(service_id, page): + filter_type = request.args.get("filter") + + limit_days = None + if filter_type == "24hours": + limit_days = 1 + elif filter_type == "3days": + limit_days = 3 + elif filter_type == "7days": + limit_days = 7 + + if limit_days: + return job_api_client.get_page_of_jobs( + service_id, page=page, limit_days=limit_days, use_processing_time=True + ) + else: + return job_api_client.get_page_of_jobs(service_id, page=page) + + @main.route("/activity/services/") @user_has_permissions(ServicePermission.VIEW_ACTIVITY) def all_jobs_activity(service_id): service_data_retention_days = 7 page = get_page_from_request() - jobs = job_api_client.get_page_of_jobs(service_id, page=page) + + jobs = get_filtered_jobs(service_id, page) + all_jobs_dict = generate_job_dict(jobs) prev_page, next_page, pagination = handle_pagination(jobs, service_id, page) message_type = ("sms",) download_availability = get_download_availability(service_id) + download_links = get_download_links(message_type) + return render_template( "views/activity/all-activity.html", all_jobs_dict=all_jobs_dict, @@ -58,42 +93,20 @@ def all_jobs_activity(service_id): pagination=pagination, total_jobs=jobs.get("total", 0), **download_availability, - download_link_one_day=url_for( - ".download_notifications_csv", - service_id=current_service.id, - message_type=message_type, - status=request.args.get("status"), - number_of_days="one_day", - ), - download_link_three_day=url_for( - ".download_notifications_csv", - service_id=current_service.id, - message_type=message_type, - status=request.args.get("status"), - number_of_days="three_day", - ), - download_link_five_day=url_for( - ".download_notifications_csv", - service_id=current_service.id, - message_type=message_type, - status=request.args.get("status"), - number_of_days="five_day", - ), - download_link_seven_day=url_for( - ".download_notifications_csv", - service_id=current_service.id, - message_type=message_type, - status=request.args.get("status"), - number_of_days="seven_day", - ), + **download_links, ) def handle_pagination(jobs, service_id, page): if page is None: abort(404, "Invalid page argument ({}).".format(request.args.get("page"))) + + url_args = {} + if request.args.get("filter"): + url_args["filter"] = request.args.get("filter") + prev_page = ( - generate_previous_dict("main.all_jobs_activity", service_id, page) + generate_previous_dict("main.all_jobs_activity", service_id, page, url_args) if page > 1 else None ) @@ -102,7 +115,7 @@ def handle_pagination(jobs, service_id, page): total_pages = (total_items + page_size - 1) // page_size has_next_link = jobs.get("links", {}).get("next") is not None next_page = ( - generate_next_dict("main.all_jobs_activity", service_id, page) + generate_next_dict("main.all_jobs_activity", service_id, page, url_args) if has_next_link and total_items > 50 and page < total_pages else None ) diff --git a/app/notify_client/job_api_client.py b/app/notify_client/job_api_client.py index 0e04cfc74..c9a02a1f2 100644 --- a/app/notify_client/job_api_client.py +++ b/app/notify_client/job_api_client.py @@ -32,12 +32,22 @@ 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, + statuses=None, + page=1, + use_processing_time=False, + ): params = {"page": page} if limit_days is not None: params["limit_days"] = limit_days if statuses is not None: params["statuses"] = ",".join(statuses) + if use_processing_time: + params["use_processing_time"] = "true" job = self.get(url=f"/service/{service_id}/job", params=params) return job @@ -61,12 +71,21 @@ class JobApiClient(NotifyAdminAPIClient): if job["job_status"] != JobStatus.CANCELLED ) - def get_page_of_jobs(self, service_id, *, page, statuses=None, limit_days=None): + def get_page_of_jobs( + self, + service_id, + *, + page, + statuses=None, + limit_days=None, + use_processing_time=False, + ): return self.get_jobs( service_id, statuses=statuses or self.NON_SCHEDULED_JOB_STATUSES, page=page, limit_days=limit_days, + use_processing_time=use_processing_time, ) def get_immediate_jobs(self, service_id): diff --git a/app/templates/views/activity/all-activity.html b/app/templates/views/activity/all-activity.html index 1db831afc..edbaf5f04 100644 --- a/app/templates/views/activity/all-activity.html +++ b/app/templates/views/activity/all-activity.html @@ -30,7 +30,7 @@ {% else %}
  • - + {{ page }}
  • @@ -63,6 +63,36 @@

    All activity

    All activity

    Sent jobs

    +
    + +
    diff --git a/gulpfile.js b/gulpfile.js index 087039a13..78b76640f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -81,6 +81,7 @@ const javascripts = () => { paths.src + 'javascripts/sidenav.js', paths.src + 'javascripts/validation.js', paths.src + 'javascripts/socketio.js', + paths.src + 'javascripts/scrollPosition.js', ]) .pipe(plugins.prettyerror()) .pipe( diff --git a/tests/app/main/views/test_jobs_activity.py b/tests/app/main/views/test_jobs_activity.py index 24e055ee0..601660003 100644 --- a/tests/app/main/views/test_jobs_activity.py +++ b/tests/app/main/views/test_jobs_activity.py @@ -1,3 +1,4 @@ +import pytest from bs4 import BeautifulSoup from app.utils.pagination import get_page_from_request @@ -113,8 +114,12 @@ def test_all_activity( assert report_cell == "N/A", f"Expected report 'N/A', but got '{report_cell}'" status_cell = cells[5].get_text(strip=True) - assert "1 delivered" in status_cell, f"Expected status to contain '1 delivered', but got '{status_cell}'" - assert "5 failed" in status_cell, f"Expected status to contain '5 failed', but got '{status_cell}'" + assert ( + "1 delivered" in status_cell + ), f"Expected status to contain '1 delivered', but got '{status_cell}'" + assert ( + "5 failed" in status_cell + ), f"Expected status to contain '5 failed', but got '{status_cell}'" def test_all_activity_no_jobs(client_request, mocker): @@ -206,3 +211,35 @@ def test_all_activity_pagination(client_request, mocker): assert ( pagination_texts == expected_pagination_texts ), f"Expected pagination controls {expected_pagination_texts}, but got {pagination_texts}" + + +@pytest.mark.parametrize( + ("filter_type", "expected_limit_days"), + [ + ("24hours", 1), + ("3days", 3), + ("7days", 7), + (None, None), + ], +) +def test_all_activity_filters(client_request, mocker, filter_type, expected_limit_days): + current_page = get_page_from_request() + mock_get_page_of_jobs = mocker.patch( + "app.job_api_client.get_page_of_jobs", return_value=MOCK_JOBS + ) + mocker.patch("app.job_api_client.get_immediate_jobs", return_value=[]) + + kwargs = {"filter": filter_type} if filter_type else {} + response = client_request.get_response( + "main.all_jobs_activity", service_id=SERVICE_ONE_ID, page=current_page, **kwargs + ) + + assert response.status_code == 200 + assert "All activity" in response.text + + if expected_limit_days: + mock_get_page_of_jobs.assert_any_call( + SERVICE_ONE_ID, page=current_page, limit_days=expected_limit_days, use_processing_time=True + ) + else: + mock_get_page_of_jobs.assert_any_call(SERVICE_ONE_ID, page=current_page)
    Table showing all sent jobs for this service