mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-23 07:46:23 -04:00
Use a ModelList for lists of jobs
This follows the pattern of what we’ve done with services, users and events. It gives us a way of neatly instantiating a model for each item in the list we get back from the API and reduces the complexity of the view layer code. Now is a good time to do this because we’re going to be making a bunch of changes to the jobs pages, and those changes will be easier to code and understand with a sensible model behind them.
This commit is contained in:
@@ -20,12 +20,11 @@ from app import (
|
||||
current_service,
|
||||
format_date_numeric,
|
||||
format_datetime_numeric,
|
||||
job_api_client,
|
||||
service_api_client,
|
||||
template_statistics_client,
|
||||
)
|
||||
from app.main import main
|
||||
from app.statistics_utils import add_rate_to_job, get_formatted_percentage
|
||||
from app.statistics_utils import get_formatted_percentage
|
||||
from app.utils import (
|
||||
DELIVERED_STATUSES,
|
||||
FAILURE_STATUSES,
|
||||
@@ -281,14 +280,6 @@ def get_dashboard_partials(service_id):
|
||||
all_statistics = template_statistics_client.get_template_statistics_for_service(service_id, limit_days=7)
|
||||
template_statistics = aggregate_template_usage(all_statistics)
|
||||
|
||||
scheduled_jobs, immediate_jobs = [], []
|
||||
if job_api_client.has_jobs(service_id):
|
||||
scheduled_jobs = job_api_client.get_scheduled_jobs(service_id)
|
||||
immediate_jobs = [
|
||||
add_rate_to_job(job)
|
||||
for job in job_api_client.get_immediate_jobs(service_id)
|
||||
]
|
||||
|
||||
stats = aggregate_notifications_stats(all_statistics)
|
||||
column_width, max_notifiction_count = get_column_properties(3)
|
||||
|
||||
@@ -310,7 +301,6 @@ def get_dashboard_partials(service_id):
|
||||
return {
|
||||
'upcoming': render_template(
|
||||
'views/dashboard/_upcoming.html',
|
||||
scheduled_jobs=scheduled_jobs
|
||||
),
|
||||
'inbox': render_template(
|
||||
'views/dashboard/_inbox.html',
|
||||
@@ -337,9 +327,8 @@ def get_dashboard_partials(service_id):
|
||||
),
|
||||
'jobs': render_template(
|
||||
'views/dashboard/_jobs.html',
|
||||
jobs=immediate_jobs
|
||||
jobs=current_service.immediate_jobs,
|
||||
),
|
||||
'has_jobs': bool(immediate_jobs),
|
||||
'usage': render_template(
|
||||
'views/dashboard/_usage.html',
|
||||
column_width=column_width,
|
||||
|
||||
@@ -19,14 +19,12 @@ from app import (
|
||||
current_service,
|
||||
format_datetime_short,
|
||||
format_thousands,
|
||||
job_api_client,
|
||||
notification_api_client,
|
||||
service_api_client,
|
||||
)
|
||||
from app.main import main
|
||||
from app.main.forms import SearchNotificationsForm
|
||||
from app.models.job import Job
|
||||
from app.statistics_utils import add_rate_to_job
|
||||
from app.utils import (
|
||||
generate_next_dict,
|
||||
generate_notifications_csv,
|
||||
@@ -44,31 +42,25 @@ from app.utils import (
|
||||
@main.route("/services/<uuid:service_id>/jobs")
|
||||
@user_has_permissions()
|
||||
def view_jobs(service_id):
|
||||
page = int(request.args.get('page', 1))
|
||||
jobs_response = job_api_client.get_page_of_jobs(service_id, page=page)
|
||||
jobs = [
|
||||
add_rate_to_job(job) for job in jobs_response['data']
|
||||
]
|
||||
jobs = current_service.get_page_of_jobs(page=request.args.get('page'))
|
||||
|
||||
prev_page = None
|
||||
if jobs_response['links'].get('prev', None):
|
||||
prev_page = generate_previous_dict('main.view_jobs', service_id, page)
|
||||
if jobs.prev_page:
|
||||
prev_page = generate_previous_dict('main.view_jobs', service_id, jobs.current_page)
|
||||
next_page = None
|
||||
if jobs_response['links'].get('next', None):
|
||||
next_page = generate_next_dict('main.view_jobs', service_id, page)
|
||||
if jobs.next_page:
|
||||
next_page = generate_next_dict('main.view_jobs', service_id, jobs.current_page)
|
||||
|
||||
scheduled_jobs = ''
|
||||
if not current_user.has_permissions('view_activity') and page == 1:
|
||||
if not current_user.has_permissions('view_activity') and jobs.current_page == 1:
|
||||
scheduled_jobs = render_template(
|
||||
'views/dashboard/_upcoming.html',
|
||||
scheduled_jobs=job_api_client.get_scheduled_jobs(service_id),
|
||||
hide_heading=True,
|
||||
)
|
||||
|
||||
return render_template(
|
||||
'views/jobs/jobs.html',
|
||||
jobs=jobs,
|
||||
page=page,
|
||||
prev_page=prev_page,
|
||||
next_page=next_page,
|
||||
scheduled_jobs=scheduled_jobs,
|
||||
|
||||
@@ -15,12 +15,7 @@ from notifications_utils.pdf import pdf_page_count
|
||||
from PyPDF2.utils import PdfReadError
|
||||
from requests import RequestException
|
||||
|
||||
from app import (
|
||||
current_service,
|
||||
job_api_client,
|
||||
notification_api_client,
|
||||
service_api_client,
|
||||
)
|
||||
from app import current_service, notification_api_client, service_api_client
|
||||
from app.extensions import antivirus_client
|
||||
from app.main import main
|
||||
from app.main.forms import LetterUploadPostageForm, PDFUploadForm
|
||||
@@ -47,20 +42,18 @@ MAX_FILE_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB
|
||||
def uploads(service_id):
|
||||
# No tests have been written, this has been quickly prepared for user research.
|
||||
# It's also very like that a new view will be created to show uploads.
|
||||
page = int(request.args.get('page', 1))
|
||||
uploads_response = job_api_client.get_uploads(service_id, page=page)
|
||||
uploads = current_service.get_page_of_uploads(page=request.args.get('page'))
|
||||
|
||||
prev_page = None
|
||||
if uploads_response['links'].get('prev', None):
|
||||
prev_page = generate_previous_dict('main.uploads', service_id, page)
|
||||
if uploads.next_page:
|
||||
prev_page = generate_previous_dict('main.uploads', service_id, uploads.current_page)
|
||||
next_page = None
|
||||
if uploads_response['links'].get('next', None):
|
||||
next_page = generate_next_dict('main.uploads', service_id, page)
|
||||
if uploads.prev_page:
|
||||
next_page = generate_next_dict('main.uploads', service_id, uploads.current_page)
|
||||
|
||||
return render_template(
|
||||
'views/jobs/jobs.html',
|
||||
jobs=uploads_response['data'],
|
||||
page=page,
|
||||
jobs=uploads,
|
||||
prev_page=prev_page,
|
||||
next_page=next_page,
|
||||
scheduled_jobs='',
|
||||
|
||||
@@ -7,7 +7,7 @@ from notifications_utils.letter_timings import (
|
||||
)
|
||||
from werkzeug.utils import cached_property
|
||||
|
||||
from app.models import JSONModel
|
||||
from app.models import JSONModel, ModelList
|
||||
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
|
||||
@@ -147,6 +147,20 @@ class Job(JSONModel):
|
||||
def letter_timings(self):
|
||||
return get_letter_timings(self.created_at, postage=self.postage)
|
||||
|
||||
@property
|
||||
def failure_rate(self):
|
||||
if not self.notifications_delivered:
|
||||
return 100 if self.notifications_failed else 0
|
||||
return (
|
||||
self.notifications_failed / (
|
||||
self.notifications_failed + self.notifications_delivered
|
||||
) * 100
|
||||
)
|
||||
|
||||
@property
|
||||
def high_failure_rate(self):
|
||||
return self.failure_rate > 30
|
||||
|
||||
def get_notifications(self, status):
|
||||
return notification_api_client.get_notifications_for_service(
|
||||
self.service, self.id, status=status,
|
||||
@@ -157,3 +171,31 @@ class Job(JSONModel):
|
||||
return job_api_client.cancel_letter_job(self.service, self.id)
|
||||
else:
|
||||
return job_api_client.cancel_job(self.service, self.id)
|
||||
|
||||
|
||||
class ImmediateJobs(ModelList):
|
||||
client = job_api_client.get_immediate_jobs
|
||||
model = Job
|
||||
|
||||
|
||||
class ScheduledJobs(ImmediateJobs):
|
||||
client = job_api_client.get_scheduled_jobs
|
||||
|
||||
|
||||
class PaginatedJobs(ImmediateJobs):
|
||||
|
||||
client = job_api_client.get_page_of_jobs
|
||||
|
||||
def __init__(self, service_id, page):
|
||||
try:
|
||||
self.current_page = int(page)
|
||||
except TypeError:
|
||||
self.current_page = 1
|
||||
response = self.client(service_id, page=self.current_page)
|
||||
self.items = response['data']
|
||||
self.prev_page = response['links'].get('prev', None)
|
||||
self.next_page = response['links'].get('next', None)
|
||||
|
||||
|
||||
class PaginatedUploads(PaginatedJobs):
|
||||
client = job_api_client.get_uploads
|
||||
|
||||
@@ -5,6 +5,12 @@ from notifications_utils.take import Take
|
||||
from werkzeug.utils import cached_property
|
||||
|
||||
from app.models import JSONModel
|
||||
from app.models.job import (
|
||||
ImmediateJobs,
|
||||
PaginatedJobs,
|
||||
PaginatedUploads,
|
||||
ScheduledJobs,
|
||||
)
|
||||
from app.models.organisation import Organisation
|
||||
from app.models.user import InvitedUsers, User, Users
|
||||
from app.notify_client.api_key_api_client import api_key_api_client
|
||||
@@ -103,10 +109,28 @@ class Service(JSONModel):
|
||||
def has_permission(self, permission):
|
||||
return permission in self.permissions
|
||||
|
||||
def get_page_of_jobs(self, page):
|
||||
return PaginatedJobs(self.id, page=page)
|
||||
|
||||
def get_page_of_uploads(self, page):
|
||||
return PaginatedUploads(self.id, page=page)
|
||||
|
||||
@cached_property
|
||||
def has_jobs(self):
|
||||
return job_api_client.has_jobs(self.id)
|
||||
|
||||
@cached_property
|
||||
def immediate_jobs(self):
|
||||
if not self.has_jobs:
|
||||
return []
|
||||
return ImmediateJobs(self.id)
|
||||
|
||||
@cached_property
|
||||
def scheduled_jobs(self):
|
||||
if not self.has_jobs:
|
||||
return []
|
||||
return ScheduledJobs(self.id)
|
||||
|
||||
@cached_property
|
||||
def invited_users(self):
|
||||
return InvitedUsers(self.id)
|
||||
|
||||
@@ -78,19 +78,3 @@ def statistics_by_state(statistics):
|
||||
'failed': statistics['emails_failed']
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_failure_rate_for_job(job):
|
||||
if not job.get('notifications_delivered'):
|
||||
return 1 if job.get('notifications_failed') else 0
|
||||
return (
|
||||
job.get('notifications_failed', 0) /
|
||||
(job.get('notifications_failed', 0) + job.get('notifications_delivered', 0))
|
||||
)
|
||||
|
||||
|
||||
def add_rate_to_job(job):
|
||||
return dict(
|
||||
failure_rate=(get_failure_rate_for_job(job)) * 100,
|
||||
**job
|
||||
)
|
||||
|
||||
@@ -26,22 +26,22 @@
|
||||
{% endif %}
|
||||
<span class="file-list-hint">
|
||||
Sent {{
|
||||
(item.scheduled_for if item.scheduled_for else item.created_at)|format_datetime_relative
|
||||
(item.scheduled_for or item.created_at)|format_datetime_relative
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% call field() %}
|
||||
{{ big_number(
|
||||
item.get('notification_count', 0) - item.get('notifications_delivered', 0) - item.get('notifications_failed', 0),
|
||||
item.notifications_sending,
|
||||
smallest=True
|
||||
) }}
|
||||
{% endcall %}
|
||||
{% call field() %}
|
||||
{{ big_number(item.get('notifications_delivered', 0), smallest=True) }}
|
||||
{{ big_number(item.notifications_delivered, smallest=True) }}
|
||||
{% endcall %}
|
||||
{% call field(status='error' if item.get('failure_rate', 0) > 3 else '') %}
|
||||
{{ big_number(item.get('notifications_failed', 0), smallest=True) }}
|
||||
{% call field(status='error' if item.high_failure_rate else '') %}
|
||||
{{ big_number(item.notifications_failed, smallest=True) }}
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% from "components/show-more.html" import show_more %}
|
||||
|
||||
<div class="ajax-block-container">
|
||||
{% if scheduled_jobs %}
|
||||
{% if current_service.scheduled_jobs %}
|
||||
<div class='dashboard-table'>
|
||||
{% if not hide_heading %}
|
||||
<h2 class="heading-medium heading-upcoming-jobs">
|
||||
@@ -11,7 +11,7 @@
|
||||
</h2>
|
||||
{% endif %}
|
||||
{% call(item, row_number) list_table(
|
||||
scheduled_jobs,
|
||||
current_service.scheduled_jobs,
|
||||
caption="In the next few days",
|
||||
caption_visible=False,
|
||||
empty_message='Nothing to see here',
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
{{ ajax_block(partials, updates_url, 'template-statistics', interval=5) }}
|
||||
|
||||
{% if partials['has_jobs'] %}
|
||||
{% if current_service.immediate_jobs %}
|
||||
{{ ajax_block(partials, updates_url, 'jobs', interval=5) }}
|
||||
{{ show_more(
|
||||
url_for('.view_jobs', service_id=current_service.id),
|
||||
|
||||
Reference in New Issue
Block a user