mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-10 01:55:41 -04:00
Show upcoming jobs on the dashboard
On the dashboard: - adds a new ‘in the next 24 hours’ section to the dashboard which lists upcoming jobs - tweaks some spacing on the dashboard so that it doesn’t look like too much of a mess - don’t show scheduled jobs in the table of normal jobs On the jobs page: - don’t show scheduled jobs
This commit is contained in:
@@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
@extend %big-number;
|
@extend %big-number;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: $gutter-half;
|
margin-bottom: $gutter-two-thirds;
|
||||||
|
|
||||||
.big-number {
|
.big-number {
|
||||||
padding: $gutter-half;
|
padding: $gutter-half;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
%show-more,
|
||||||
.show-more {
|
.show-more {
|
||||||
|
|
||||||
@include core-16;
|
@include core-16;
|
||||||
@@ -8,7 +9,7 @@
|
|||||||
border-top: 1px solid $border-colour;
|
border-top: 1px solid $border-colour;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
|
|
||||||
outline: none;
|
outline: none;
|
||||||
color: $text-colour;
|
color: $text-colour;
|
||||||
box-shadow: 0 -10px 0 0 $yellow;
|
box-shadow: 0 -10px 0 0 $yellow;
|
||||||
@@ -32,3 +33,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.show-more-empty {
|
||||||
|
@extend %show-more;
|
||||||
|
margin-top: -10px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,11 +10,20 @@
|
|||||||
|
|
||||||
.dashboard-table {
|
.dashboard-table {
|
||||||
|
|
||||||
|
.heading-medium {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-field-headings,
|
.table-field-headings {
|
||||||
|
th {
|
||||||
|
font-size: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.table-field-headings-visible {
|
.table-field-headings-visible {
|
||||||
th {
|
th {
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
|
|||||||
@@ -116,9 +116,21 @@ def get_dashboard_partials(service_id):
|
|||||||
lambda job: job['original_file_name'] != current_app.config['TEST_MESSAGE_FILENAME'],
|
lambda job: job['original_file_name'] != current_app.config['TEST_MESSAGE_FILENAME'],
|
||||||
job_api_client.get_job(service_id, limit_days=7)['data']
|
job_api_client.get_job(service_id, limit_days=7)['data']
|
||||||
))
|
))
|
||||||
|
scheduled_jobs = filter(
|
||||||
|
lambda job: job['job_status'] == 'scheduled',
|
||||||
|
jobs
|
||||||
|
)
|
||||||
|
immediate_jobs = filter(
|
||||||
|
lambda job: job['job_status'] != 'scheduled',
|
||||||
|
jobs
|
||||||
|
)
|
||||||
service = service_api_client.get_detailed_service(service_id)
|
service = service_api_client.get_detailed_service(service_id)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
'upcoming': render_template(
|
||||||
|
'views/dashboard/_upcoming.html',
|
||||||
|
scheduled_jobs=scheduled_jobs
|
||||||
|
),
|
||||||
'totals': render_template(
|
'totals': render_template(
|
||||||
'views/dashboard/_totals.html',
|
'views/dashboard/_totals.html',
|
||||||
service_id=service_id,
|
service_id=service_id,
|
||||||
@@ -134,7 +146,7 @@ def get_dashboard_partials(service_id):
|
|||||||
'has_template_statistics': bool(template_statistics),
|
'has_template_statistics': bool(template_statistics),
|
||||||
'jobs': render_template(
|
'jobs': render_template(
|
||||||
'views/dashboard/_jobs.html',
|
'views/dashboard/_jobs.html',
|
||||||
jobs=jobs
|
jobs=immediate_jobs
|
||||||
),
|
),
|
||||||
'has_jobs': bool(jobs),
|
'has_jobs': bool(jobs),
|
||||||
'usage': render_template(
|
'usage': render_template(
|
||||||
|
|||||||
@@ -65,7 +65,10 @@ def _set_status_filters(filter_args):
|
|||||||
def view_jobs(service_id):
|
def view_jobs(service_id):
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/jobs/jobs.html',
|
'views/jobs/jobs.html',
|
||||||
jobs=add_rate_to_jobs(job_api_client.get_job(service_id)['data'])
|
jobs=filter(
|
||||||
|
lambda job: job['job_status'] != 'scheduled',
|
||||||
|
add_rate_to_jobs(job_api_client.get_job(service_id)['data'])
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -274,6 +277,11 @@ def get_status_filters(service, message_type, statistics):
|
|||||||
|
|
||||||
|
|
||||||
def _get_job_counts(job, help_argument):
|
def _get_job_counts(job, help_argument):
|
||||||
|
sending = 0 if job['job_status'] == 'pending' else (
|
||||||
|
job.get('notification_count', 0) -
|
||||||
|
job.get('notifications_delivered', 0) -
|
||||||
|
job.get('notifications_failed', 0)
|
||||||
|
)
|
||||||
return [
|
return [
|
||||||
(
|
(
|
||||||
label,
|
label,
|
||||||
@@ -293,9 +301,7 @@ def _get_job_counts(job, help_argument):
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
'sending', 'sending',
|
'sending', 'sending',
|
||||||
job.get('notification_count', 0) -
|
sending
|
||||||
job.get('notifications_delivered', 0) -
|
|
||||||
job.get('notifications_failed', 0)
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'delivered', 'delivered',
|
'delivered', 'delivered',
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
{% macro show_more(url, label) %}
|
{% macro show_more(url=None, label=None) %}
|
||||||
<a href="{{ url }}" class="show-more"><span>{{ label }}</span></a>
|
{% if url and label %}
|
||||||
|
<a href="{{ url }}" class="show-more"><span>{{ label }}</span></a>
|
||||||
|
{% else %}
|
||||||
|
<span class="show-more-empty"></span>
|
||||||
|
{% endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
Report is {{ "{:.0f}%".format(percentage_complete) }} complete…
|
Report is {{ "{:.0f}%".format(percentage_complete) }} complete…
|
||||||
</p>
|
</p>
|
||||||
{% elif notifications %}
|
{% elif notifications %}
|
||||||
<p class="bottom-gutter-1-2">
|
<p class="bottom-gutter">
|
||||||
<a href="{{ download_link }}" download="download" class="heading-small">Download this report</a>
|
<a href="{{ download_link }}" download="download" class="heading-small">Download this report</a>
|
||||||
 
|
 
|
||||||
<span id="time-left">{{ time_left }}</span>
|
<span id="time-left">{{ time_left }}</span>
|
||||||
|
|||||||
@@ -18,7 +18,11 @@
|
|||||||
{% call row_heading() %}
|
{% call row_heading() %}
|
||||||
<div class="file-list">
|
<div class="file-list">
|
||||||
<a class="file-list-filename" href="{{ url_for('.view_job', service_id=current_service.id, job_id=item.id) }}">{{ item.original_file_name }}</a>
|
<a class="file-list-filename" href="{{ url_for('.view_job', service_id=current_service.id, job_id=item.id) }}">{{ item.original_file_name }}</a>
|
||||||
<span class="file-list-hint">Uploaded {{ item.created_at|format_datetime_short }}</span>
|
<span class="file-list-hint">
|
||||||
|
Sent {{
|
||||||
|
item.scheduled_for|format_datetime_short if item.scheduled_for else item.created_at|format_datetime_short
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
{% call field() %}
|
{% call field() %}
|
||||||
|
|||||||
38
app/templates/views/dashboard/_upcoming.html
Normal file
38
app/templates/views/dashboard/_upcoming.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{% from "components/table.html" import list_table, field, right_aligned_field_heading, row_heading %}
|
||||||
|
{% from "components/big-number.html" import big_number %}
|
||||||
|
{% from "components/show-more.html" import show_more %}
|
||||||
|
|
||||||
|
{% if scheduled_jobs %}
|
||||||
|
<div class='dashboard-table'>
|
||||||
|
<h2 class="heading-medium">
|
||||||
|
In the next 24 hours
|
||||||
|
</h2>
|
||||||
|
{% call(item, row_number) list_table(
|
||||||
|
scheduled_jobs,
|
||||||
|
caption="In the next 24 hours",
|
||||||
|
caption_visible=False,
|
||||||
|
empty_message='Nothing to see here',
|
||||||
|
field_headings=[
|
||||||
|
'File',
|
||||||
|
'Messages to be sent'
|
||||||
|
],
|
||||||
|
field_headings_visible=True
|
||||||
|
) %}
|
||||||
|
{% call row_heading() %}
|
||||||
|
<div class="file-list">
|
||||||
|
<a class="file-list-filename" href="{{ url_for('.view_job', service_id=current_service.id, job_id=item.id) }}">{{ item.original_file_name }}</a>
|
||||||
|
<span class="file-list-hint">
|
||||||
|
Sending at {{ item.scheduled_for|format_time }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endcall %}
|
||||||
|
{% call field() %}
|
||||||
|
{{ big_number(
|
||||||
|
item.notification_count,
|
||||||
|
smallest=True
|
||||||
|
) }}
|
||||||
|
{% endcall %}
|
||||||
|
{% endcall %}
|
||||||
|
{{ show_more() }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
@@ -23,6 +23,8 @@
|
|||||||
{% include 'views/dashboard/no-permissions-banner.html' %}
|
{% include 'views/dashboard/no-permissions-banner.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{{ ajax_block(partials, updates_url, 'upcoming') }}
|
||||||
|
|
||||||
<h2 class="heading-medium">
|
<h2 class="heading-medium">
|
||||||
In the last 7 days
|
In the last 7 days
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if notifications %}
|
{% if notifications %}
|
||||||
<p class="bottom-gutter-1-2">
|
<p class="bottom-gutter">
|
||||||
<a href="{{ download_link }}" download="download" class="heading-small">Download this report</a>
|
<a href="{{ download_link }}" download="download" class="heading-small">Download this report</a>
|
||||||
 
|
 
|
||||||
Data available for 7 days
|
Data available for 7 days
|
||||||
|
|||||||
@@ -158,7 +158,8 @@ def job_json(
|
|||||||
notification_count=1,
|
notification_count=1,
|
||||||
notifications_sent=1,
|
notifications_sent=1,
|
||||||
notifications_requested=1,
|
notifications_requested=1,
|
||||||
job_status='Delivered'
|
job_status='Delivered',
|
||||||
|
scheduled_for=''
|
||||||
):
|
):
|
||||||
if job_id is None:
|
if job_id is None:
|
||||||
job_id = str(generate_uuid())
|
job_id = str(generate_uuid())
|
||||||
@@ -180,8 +181,10 @@ def job_json(
|
|||||||
'created_by': created_by_json(
|
'created_by': created_by_json(
|
||||||
created_by.id,
|
created_by.id,
|
||||||
created_by.name,
|
created_by.name,
|
||||||
created_by.email_address)
|
created_by.email_address
|
||||||
}
|
),
|
||||||
|
'scheduled_for': scheduled_for
|
||||||
|
}
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ def test_should_show_recent_templates_on_dashboard(app_,
|
|||||||
assert 'Test Service' in headers
|
assert 'Test Service' in headers
|
||||||
assert 'In the last 7 days' in headers
|
assert 'In the last 7 days' in headers
|
||||||
|
|
||||||
table_rows = page.find_all('tbody')[0].find_all('tr')
|
table_rows = page.find_all('tbody')[1].find_all('tr')
|
||||||
|
|
||||||
assert len(table_rows) == 2
|
assert len(table_rows) == 2
|
||||||
|
|
||||||
@@ -159,20 +159,20 @@ def test_should_show_all_templates_on_template_statistics_page(
|
|||||||
|
|
||||||
|
|
||||||
@freeze_time("2016-01-01 11:09:00.061258")
|
@freeze_time("2016-01-01 11:09:00.061258")
|
||||||
def test_should_show_recent_jobs_on_dashboard(
|
def test_should_show_upcoming_jobs_on_dashboard(
|
||||||
app_,
|
app_,
|
||||||
mocker,
|
mocker,
|
||||||
api_user_active,
|
api_user_active,
|
||||||
mock_get_service,
|
mock_get_service,
|
||||||
mock_get_service_templates,
|
mock_get_service_templates,
|
||||||
mock_get_user,
|
mock_get_user,
|
||||||
mock_get_user_by_email,
|
mock_get_user_by_email,
|
||||||
mock_login,
|
mock_login,
|
||||||
mock_get_template_statistics,
|
mock_get_template_statistics,
|
||||||
mock_get_detailed_service,
|
mock_get_detailed_service,
|
||||||
mock_get_jobs,
|
mock_get_jobs,
|
||||||
mock_has_permissions,
|
mock_has_permissions,
|
||||||
mock_get_usage
|
mock_get_usage
|
||||||
):
|
):
|
||||||
with app_.test_request_context(), app_.test_client() as client:
|
with app_.test_request_context(), app_.test_client() as client:
|
||||||
client.login(api_user_active)
|
client.login(api_user_active)
|
||||||
@@ -182,7 +182,40 @@ def test_should_show_recent_jobs_on_dashboard(
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||||
table_rows = page.find_all('tbody')[1].find_all('tr')
|
|
||||||
|
table_rows = page.find_all('tbody')[0].find_all('tr')
|
||||||
|
assert len(table_rows) == 1
|
||||||
|
|
||||||
|
assert 'send_me_later.csv' in table_rows[0].find_all('th')[0].text
|
||||||
|
assert 'Sending at 11:09am' in table_rows[0].find_all('th')[0].text
|
||||||
|
assert table_rows[0].find_all('td')[0].text.strip() == '1'
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2016-01-01 11:09:00.061258")
|
||||||
|
def test_should_show_recent_jobs_on_dashboard(
|
||||||
|
app_,
|
||||||
|
mocker,
|
||||||
|
api_user_active,
|
||||||
|
mock_get_service,
|
||||||
|
mock_get_service_templates,
|
||||||
|
mock_get_user,
|
||||||
|
mock_get_user_by_email,
|
||||||
|
mock_login,
|
||||||
|
mock_get_template_statistics,
|
||||||
|
mock_get_detailed_service,
|
||||||
|
mock_get_jobs,
|
||||||
|
mock_has_permissions,
|
||||||
|
mock_get_usage
|
||||||
|
):
|
||||||
|
with app_.test_request_context(), app_.test_client() as client:
|
||||||
|
client.login(api_user_active)
|
||||||
|
response = client.get(url_for('main.service_dashboard', service_id=SERVICE_ONE_ID))
|
||||||
|
|
||||||
|
mock_get_jobs.assert_called_once_with(SERVICE_ONE_ID, limit_days=7)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||||
|
table_rows = page.find_all('tbody')[2].find_all('tr')
|
||||||
|
|
||||||
assert "Test message" not in page.text
|
assert "Test message" not in page.text
|
||||||
assert len(table_rows) == 4
|
assert len(table_rows) == 4
|
||||||
@@ -194,7 +227,7 @@ def test_should_show_recent_jobs_on_dashboard(
|
|||||||
"thisisatest.csv",
|
"thisisatest.csv",
|
||||||
)):
|
)):
|
||||||
assert filename in table_rows[index].find_all('th')[0].text
|
assert filename in table_rows[index].find_all('th')[0].text
|
||||||
assert 'Uploaded 1 January at 11:09' in table_rows[index].find_all('th')[0].text
|
assert 'Sent 1 January at 11:09' in table_rows[index].find_all('th')[0].text
|
||||||
for column_index, count in enumerate((1, 0, 0)):
|
for column_index, count in enumerate((1, 0, 0)):
|
||||||
assert table_rows[index].find_all('td')[column_index].text.strip() == str(count)
|
assert table_rows[index].find_all('td')[column_index].text.strip() == str(count)
|
||||||
|
|
||||||
|
|||||||
@@ -874,13 +874,21 @@ def mock_get_job_in_progress(mocker, api_user_active):
|
|||||||
def mock_get_jobs(mocker, api_user_active):
|
def mock_get_jobs(mocker, api_user_active):
|
||||||
def _get_jobs(service_id, limit_days=None):
|
def _get_jobs(service_id, limit_days=None):
|
||||||
return {"data": [
|
return {"data": [
|
||||||
job_json(service_id, api_user_active, original_file_name=filename)
|
job_json(
|
||||||
for filename in (
|
service_id,
|
||||||
"Test message",
|
api_user_active,
|
||||||
"export 1/1/2016.xls",
|
original_file_name=filename,
|
||||||
"all email addresses.xlsx",
|
scheduled_for=scheduled_for,
|
||||||
"applicants.ods",
|
job_status=job_status
|
||||||
"thisisatest.csv",
|
)
|
||||||
|
for filename, scheduled_for, job_status in (
|
||||||
|
("Test message", '', ''),
|
||||||
|
("Test message", '2016-01-01 11:09:00.061258', 'scheduled'),
|
||||||
|
("export 1/1/2016.xls", '', ''),
|
||||||
|
("all email addresses.xlsx", '', 'pending'),
|
||||||
|
("applicants.ods", '', ''),
|
||||||
|
("thisisatest.csv", '', ''),
|
||||||
|
("send_me_later.csv", '2016-01-01 11:09:00.061258', 'scheduled')
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user