Merge pull request #438 from alphagov/activity-page-refinements

Allow filtering of notifications by type, status or both
This commit is contained in:
Chris Hill-Scott
2016-04-13 16:24:56 +01:00
9 changed files with 210 additions and 48 deletions

View File

@@ -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)

View 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;
}
}

View File

@@ -77,6 +77,10 @@
width: 15px; width: 15px;
} }
p {
margin: 0 0 5px 0;
}
} }
.table-field-heading { .table-field-heading {

View File

@@ -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';

View File

@@ -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', '']
]
]
) )

View 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 %}

View File

@@ -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" }}

View File

@@ -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>&emsp;
<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>&emsp;
<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 havent 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 }}&amp;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 %}

View File

@@ -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