mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-04 08:58:25 -04:00
Merge pull request #438 from alphagov/activity-page-refinements
Allow filtering of notifications by type, status or both
This commit is contained in:
@@ -2,6 +2,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
import dateutil
|
import dateutil
|
||||||
|
import datetime
|
||||||
import urllib
|
import urllib
|
||||||
from flask import (
|
from flask import (
|
||||||
Flask,
|
Flask,
|
||||||
@@ -20,6 +21,7 @@ from pygments import highlight
|
|||||||
from pygments.lexers import JavascriptLexer
|
from pygments.lexers import JavascriptLexer
|
||||||
from pygments.formatters import HtmlFormatter
|
from pygments.formatters import HtmlFormatter
|
||||||
from werkzeug.exceptions import abort
|
from werkzeug.exceptions import abort
|
||||||
|
from babel.dates import format_timedelta
|
||||||
|
|
||||||
from app.notify_client.api_client import ServiceAPIClient
|
from app.notify_client.api_client import ServiceAPIClient
|
||||||
from app.notify_client.api_key_api_client import ApiKeyApiClient
|
from app.notify_client.api_key_api_client import ApiKeyApiClient
|
||||||
@@ -100,6 +102,7 @@ def create_app():
|
|||||||
application.add_template_filter(valid_phone_number)
|
application.add_template_filter(valid_phone_number)
|
||||||
application.add_template_filter(linkable_name)
|
application.add_template_filter(linkable_name)
|
||||||
application.add_template_filter(format_date)
|
application.add_template_filter(format_date)
|
||||||
|
application.add_template_filter(format_delta)
|
||||||
|
|
||||||
application.after_request(useful_headers_after_request)
|
application.after_request(useful_headers_after_request)
|
||||||
application.after_request(save_service_after_request)
|
application.after_request(save_service_after_request)
|
||||||
@@ -185,6 +188,17 @@ def format_date(date):
|
|||||||
return date.strftime('%A %d %B %Y')
|
return date.strftime('%A %d %B %Y')
|
||||||
|
|
||||||
|
|
||||||
|
def format_delta(date):
|
||||||
|
date = dateutil.parser.parse(date)
|
||||||
|
native = date.replace(tzinfo=None)
|
||||||
|
difference = native - datetime.datetime.now()
|
||||||
|
return format_timedelta(
|
||||||
|
datetime.timedelta(seconds=difference.total_seconds()),
|
||||||
|
add_direction=True,
|
||||||
|
format='short'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def valid_phone_number(phone_number):
|
def valid_phone_number(phone_number):
|
||||||
try:
|
try:
|
||||||
validate_phone_number(phone_number)
|
validate_phone_number(phone_number)
|
||||||
|
|||||||
41
app/assets/stylesheets/components/pill.scss
Normal file
41
app/assets/stylesheets/components/pill.scss
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
.pill {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
a, span {
|
||||||
|
display: block;
|
||||||
|
padding: 10px;
|
||||||
|
flex-grow: 1;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
background: $panel-colour;
|
||||||
|
color: $link-colour;
|
||||||
|
border: 1px solid $panel-colour;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $text-colour;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active,
|
||||||
|
&:focus {
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
border: 1px solid $grey-1;
|
||||||
|
color: $text-colour;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,6 +77,10 @@
|
|||||||
width: 15px;
|
width: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-field-heading {
|
.table-field-heading {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ $path: '/static/images/';
|
|||||||
@import 'components/email-message';
|
@import 'components/email-message';
|
||||||
@import 'components/api-key';
|
@import 'components/api-key';
|
||||||
@import 'components/vendor/previous-next-navigation';
|
@import 'components/vendor/previous-next-navigation';
|
||||||
|
@import 'components/pill';
|
||||||
|
|
||||||
@import 'views/job';
|
@import 'views/job';
|
||||||
@import 'views/edit-template';
|
@import 'views/edit-template';
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
import itertools
|
||||||
|
|
||||||
from flask import (
|
from flask import (
|
||||||
render_template,
|
render_template,
|
||||||
abort,
|
abort,
|
||||||
jsonify,
|
jsonify,
|
||||||
request
|
request,
|
||||||
|
url_for
|
||||||
)
|
)
|
||||||
from flask_login import login_required
|
from flask_login import login_required
|
||||||
from werkzeug.datastructures import MultiDict
|
from werkzeug.datastructures import MultiDict
|
||||||
@@ -26,14 +28,18 @@ from app.utils import (
|
|||||||
|
|
||||||
|
|
||||||
def _parse_filter_args(filter_dict):
|
def _parse_filter_args(filter_dict):
|
||||||
|
|
||||||
if not isinstance(filter_dict, MultiDict):
|
if not isinstance(filter_dict, MultiDict):
|
||||||
filter_dict = MultiDict(filter_dict)
|
filter_dict = MultiDict(filter_dict)
|
||||||
out_dict = MultiDict()
|
|
||||||
if 'type' in filter_dict:
|
return MultiDict(
|
||||||
out_dict.setlist('template_type', filter_dict.getlist('type'))
|
(
|
||||||
if 'status' in filter_dict:
|
key,
|
||||||
out_dict.setlist('status', filter_dict.getlist('status'))
|
(','.join(filter_dict.getlist(key))).split(',')
|
||||||
return out_dict
|
)
|
||||||
|
for key in filter_dict.keys()
|
||||||
|
if ''.join(filter_dict.getlist(key))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@main.route("/services/<service_id>/jobs")
|
@main.route("/services/<service_id>/jobs")
|
||||||
@@ -144,7 +150,32 @@ def view_notifications(service_id):
|
|||||||
notifications=notifications['notifications'],
|
notifications=notifications['notifications'],
|
||||||
page=page,
|
page=page,
|
||||||
prev_page=prev_page,
|
prev_page=prev_page,
|
||||||
next_page=next_page
|
next_page=next_page,
|
||||||
|
request_args=request.args,
|
||||||
|
type_filters=[
|
||||||
|
[item[0], item[1], url_for(
|
||||||
|
'.view_notifications',
|
||||||
|
service_id=current_service['id'],
|
||||||
|
template_type=item[1],
|
||||||
|
status=request.args.get('status', '')
|
||||||
|
)] for item in [
|
||||||
|
['Emails', 'email'],
|
||||||
|
['Text messages', 'sms'],
|
||||||
|
['Both', '']
|
||||||
|
]
|
||||||
|
],
|
||||||
|
status_filters=[
|
||||||
|
[item[0], item[1], url_for(
|
||||||
|
'.view_notifications',
|
||||||
|
service_id=current_service['id'],
|
||||||
|
template_type=request.args.get('template_type', ''),
|
||||||
|
status=item[1]
|
||||||
|
)] for item in [
|
||||||
|
['Successful', 'sent,delivered'],
|
||||||
|
['Failed', 'failed,complaint,bounce'],
|
||||||
|
['Both', '']
|
||||||
|
]
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
16
app/templates/components/pill.html
Normal file
16
app/templates/components/pill.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{% macro pill(
|
||||||
|
title,
|
||||||
|
items=[],
|
||||||
|
current_value=None
|
||||||
|
) %}
|
||||||
|
<nav role='navigation' class='pill' aria-labelledby="pill_{{title}}">
|
||||||
|
<h2 id="pill_{{title}}" class="visuallyhidden">{{title}}</h2>
|
||||||
|
{% for label, option, link in items %}
|
||||||
|
{% if current_value == option %}
|
||||||
|
<span aria-hidden="true">{{ label }}</span>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ link }}">{{ label }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</nav>
|
||||||
|
{% endmacro %}
|
||||||
@@ -67,6 +67,12 @@
|
|||||||
{% endcall %}
|
{% endcall %}
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% macro link_field(text, link) -%}
|
||||||
|
{% call field() %}
|
||||||
|
<a href="{{ link }}">{{ text }}</a>
|
||||||
|
{% endcall %}
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
{% macro boolean_field(yes) -%}
|
{% macro boolean_field(yes) -%}
|
||||||
{% call field(status='yes' if yes else 'no') %}
|
{% call field(status='yes' if yes else 'no') %}
|
||||||
{{ "Yes" if yes else "No" }}
|
{{ "Yes" if yes else "No" }}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
{% extends "withnav_template.html" %}
|
{% extends "withnav_template.html" %}
|
||||||
{% from "components/table.html" import list_table, field, right_aligned_field_heading %}
|
{% from "components/table.html" import list_table, field, text_field, link_field, right_aligned_field_heading, hidden_field_heading %}
|
||||||
{% from "components/previous-next-navigation.html" import previous_next_navigation %}
|
{% from "components/previous-next-navigation.html" import previous_next_navigation %}
|
||||||
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
{% from "components/pill.html" import pill %}
|
||||||
|
|
||||||
{% block page_title %}
|
{% block page_title %}
|
||||||
Activity – GOV.UK Notify
|
Activity – GOV.UK Notify
|
||||||
@@ -8,50 +10,96 @@
|
|||||||
|
|
||||||
{% block maincolumn_content %}
|
{% block maincolumn_content %}
|
||||||
|
|
||||||
<h1 class="heading-large">Activity</h1>
|
<h1 class="heading-large">
|
||||||
<p>
|
|
||||||
<a href="{{ url_for(".view_notifications", service_id=current_service.id, page=1) }}">All messages</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a href="{{ url_for(".view_notifications", service_id=current_service.id, type="sms", page=1) }}">Text messages</a> 
|
|
||||||
<a href="{{ url_for(".view_notifications", service_id=current_service.id, type="email", page=1) }}">Email messages</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a href="{{ url_for(".view_notifications", service_id=current_service.id, status=['delivered'], page=1) }}">Successful messages</a> 
|
|
||||||
<a href="{{ url_for(".view_notifications", service_id=current_service.id, status=['failed'], page=1) }}">Failed messages</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{% call(item, row_number) list_table(
|
{%- if (request_args.get('template_type', '') == '') and (request_args.get('status', '') == '') -%}
|
||||||
notifications,
|
|
||||||
caption="Recent activity",
|
Activity
|
||||||
caption_visible=False,
|
|
||||||
empty_message='You haven’t sent any notifications yet',
|
{%- else -%}
|
||||||
field_headings=['Recipient', 'Template', 'Type', 'Job', 'Status', 'Time'])
|
|
||||||
%}
|
{% if request_args.get('status') != '' %}
|
||||||
{% call field() %}
|
{% for label, option, _ in status_filters %}
|
||||||
|
{% if request_args.get('status') == option %}
|
||||||
|
{{ label }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if request_args.get('template_type') == '' %}
|
||||||
|
emails and text messages
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
{% for template_label, template_option, _ in type_filters %}
|
||||||
|
{% if request_args.get('template_type') == template_option %}
|
||||||
|
{% if request_args.get('status', '') == '' %}
|
||||||
|
{{ template_label }}
|
||||||
|
{% else %}
|
||||||
|
{{ template_label | lower }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div class='grid-row bottom-gutter'>
|
||||||
|
<div class='column-half'>
|
||||||
|
{{ pill(
|
||||||
|
'Type',
|
||||||
|
type_filters,
|
||||||
|
request_args.get('template_type', '')
|
||||||
|
) }}
|
||||||
|
</div>
|
||||||
|
<div class='column-half'>
|
||||||
|
{{ pill(
|
||||||
|
'Status',
|
||||||
|
status_filters,
|
||||||
|
request_args.get('status', '')
|
||||||
|
) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if notifications %}
|
||||||
|
<p class="heading-small bottom-gutter">
|
||||||
|
<a href="{{ request.url }}&download=csv" download>Download as a CSV file</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% call(item, row_number) list_table(
|
||||||
|
notifications,
|
||||||
|
caption="Recent activity",
|
||||||
|
caption_visible=False,
|
||||||
|
empty_message='No messages found',
|
||||||
|
field_headings=['Recipient', 'Status', 'Started'],
|
||||||
|
field_headings_visible=False
|
||||||
|
) %}
|
||||||
|
|
||||||
|
{% call field() %}
|
||||||
|
<p>
|
||||||
{{ item.to }}
|
{{ item.to }}
|
||||||
{% endcall %}
|
</p>
|
||||||
{% call field() %}
|
<p class="hint">
|
||||||
<a href="{{ url_for('.view_template', service_id=current_service.id, template_id=item.template.id) }}">{{ item.template.name }}</a>
|
<a href="{{ url_for('.view_template', service_id=current_service.id, template_id=item.template.id) }}">{{ item.template.name }}</a>
|
||||||
{% endcall %}
|
sent from
|
||||||
{% call field() %}
|
|
||||||
{{ item.template.template_type }}
|
|
||||||
{% endcall %}
|
|
||||||
{% call field() %}
|
|
||||||
{% if item.job %}
|
{% if item.job %}
|
||||||
<a href="{{ url_for(".view_job", service_id=current_service.id, job_id=item.job.id) }}">{{ item.job.original_file_name }}</a>
|
<a href="{{ url_for(".view_job", service_id=current_service.id, job_id=item.job.id) }}">{{ item.job.original_file_name }}</a>
|
||||||
|
{% else %}
|
||||||
|
an API call
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endcall %}
|
</p>
|
||||||
{% call field() %}
|
|
||||||
{{ item.status }}
|
|
||||||
{% endcall %}
|
|
||||||
{% call field() %}
|
|
||||||
{{ item.created_at | format_datetime }}
|
|
||||||
{% endcall %}
|
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
<p class="table-show-more-link">
|
|
||||||
<a href="{{ request.url }}&download=csv">Download csv</a>
|
|
||||||
</p>
|
|
||||||
{{ previous_next_navigation(prev_page, next_page) }}
|
|
||||||
|
|
||||||
{% endblock %}
|
{{ text_field(item.status|title) }}
|
||||||
|
|
||||||
|
{% call field(align='right') %}
|
||||||
|
{{ item.created_at|format_delta }}
|
||||||
|
{% endcall %}
|
||||||
|
{% endcall %}
|
||||||
|
|
||||||
|
{{ previous_next_navigation(prev_page, next_page) }}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Flask-Bcrypt==0.6.2
|
|||||||
credstash==1.8.0
|
credstash==1.8.0
|
||||||
boto3==1.2.3
|
boto3==1.2.3
|
||||||
Pygments==2.0.2
|
Pygments==2.0.2
|
||||||
|
Babel==2.3.3
|
||||||
|
|
||||||
git+https://github.com/alphagov/notifications-python-client.git@0.3.1#egg=notifications-python-client==0.3.1
|
git+https://github.com/alphagov/notifications-python-client.git@0.3.1#egg=notifications-python-client==0.3.1
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user