mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-16 20:49:00 -04:00
Merge pull request #2815 from GSA/2813-add-quick-filters
add quick filters for easy data consumption
This commit is contained in:
13
app/assets/javascripts/scrollPosition.js
Normal file
13
app/assets/javascripts/scrollPosition.js
Normal file
@@ -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');
|
||||
}
|
||||
});
|
||||
@@ -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/<uuid:service_id>")
|
||||
@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
|
||||
)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="usa-pagination__item">
|
||||
<a class="usa-pagination__button" href="?page={{ page }}">
|
||||
<a class="usa-pagination__button" href="?page={{ page }}{% if request.args.get('filter') %}&filter={{ request.args.get('filter') }}{% endif %}">
|
||||
{{ page }}
|
||||
</a>
|
||||
</li>
|
||||
@@ -63,6 +63,36 @@
|
||||
<h1 class="usa-sr-only">All activity</h1>
|
||||
<h2 class="font-body-2xl line-height-sans-2 margin-0">All activity</h2>
|
||||
<h2 class="margin-top-4 margin-bottom-1">Sent jobs</h2>
|
||||
<div class="flex-wrap gap-3 display-flex flex-align-start margin-top-2">
|
||||
<div class="flex-1">
|
||||
<ul class="usa-button-group usa-button-group--segmented">
|
||||
<li class="usa-button-group__item">
|
||||
<a href="{{ url_for('main.all_jobs_activity', service_id=current_service.id) }}"
|
||||
class="usa-button usa-button--small {% if not request.args.get('filter') %}{% else %}usa-button--outline{% endif %}">
|
||||
All
|
||||
</a>
|
||||
</li>
|
||||
<li class="usa-button-group__item">
|
||||
<a href="{{ url_for('main.all_jobs_activity', service_id=current_service.id, filter='24hours') }}"
|
||||
class="usa-button usa-button--small {% if request.args.get('filter') == '24hours' %}{% else %}usa-button--outline{% endif %}">
|
||||
Last 24 hours
|
||||
</a>
|
||||
</li>
|
||||
<li class="usa-button-group__item">
|
||||
<a href="{{ url_for('main.all_jobs_activity', service_id=current_service.id, filter='3days') }}"
|
||||
class="usa-button usa-button--small {% if request.args.get('filter') == '3days' %}{% else %}usa-button--outline{% endif %}">
|
||||
Last 3 days
|
||||
</a>
|
||||
</li>
|
||||
<li class="usa-button-group__item">
|
||||
<a href="{{ url_for('main.all_jobs_activity', service_id=current_service.id, filter='7days') }}"
|
||||
class="usa-button usa-button--small {% if request.args.get('filter') == '7days' %}{% else %}usa-button--outline{% endif %}">
|
||||
Last 7 days
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="usa-table-container--scrollable-mobile table-overflow-x-auto">
|
||||
<table class="usa-table usa-table--compact job-table">
|
||||
<caption class="usa-sr-only">Table showing all sent jobs for this service</caption>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user