diff --git a/app/__init__.py b/app/__init__.py index f06598b62..f2714a8cc 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -2,6 +2,7 @@ import os import re import dateutil +import datetime import urllib from flask import ( Flask, @@ -20,6 +21,7 @@ from pygments import highlight from pygments.lexers import JavascriptLexer from pygments.formatters import HtmlFormatter from werkzeug.exceptions import abort +from babel.dates import format_timedelta from app.notify_client.api_client import ServiceAPIClient 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(linkable_name) application.add_template_filter(format_date) + application.add_template_filter(format_delta) application.after_request(useful_headers_after_request) application.after_request(save_service_after_request) @@ -185,6 +188,17 @@ def format_date(date): 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): try: validate_phone_number(phone_number) diff --git a/app/assets/stylesheets/components/pill.scss b/app/assets/stylesheets/components/pill.scss new file mode 100644 index 000000000..42c56869f --- /dev/null +++ b/app/assets/stylesheets/components/pill.scss @@ -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; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/table.scss b/app/assets/stylesheets/components/table.scss index ddb9c5eef..5455046c5 100644 --- a/app/assets/stylesheets/components/table.scss +++ b/app/assets/stylesheets/components/table.scss @@ -77,6 +77,10 @@ width: 15px; } + p { + margin: 0 0 5px 0; + } + } .table-field-heading { diff --git a/app/assets/stylesheets/main.scss b/app/assets/stylesheets/main.scss index 7bcfc9d86..2504e5ff1 100644 --- a/app/assets/stylesheets/main.scss +++ b/app/assets/stylesheets/main.scss @@ -48,6 +48,7 @@ $path: '/static/images/'; @import 'components/email-message'; @import 'components/api-key'; @import 'components/vendor/previous-next-navigation'; +@import 'components/pill'; @import 'views/job'; @import 'views/edit-template'; diff --git a/app/main/views/jobs.py b/app/main/views/jobs.py index 032653432..cf5a3128e 100644 --- a/app/main/views/jobs.py +++ b/app/main/views/jobs.py @@ -1,12 +1,14 @@ # -*- coding: utf-8 -*- import time +import itertools from flask import ( render_template, abort, jsonify, - request + request, + url_for ) from flask_login import login_required from werkzeug.datastructures import MultiDict @@ -26,14 +28,18 @@ from app.utils import ( def _parse_filter_args(filter_dict): + if not isinstance(filter_dict, MultiDict): filter_dict = MultiDict(filter_dict) - out_dict = MultiDict() - if 'type' in filter_dict: - out_dict.setlist('template_type', filter_dict.getlist('type')) - if 'status' in filter_dict: - out_dict.setlist('status', filter_dict.getlist('status')) - return out_dict + + return MultiDict( + ( + key, + (','.join(filter_dict.getlist(key))).split(',') + ) + for key in filter_dict.keys() + if ''.join(filter_dict.getlist(key)) + ) @main.route("/services//jobs") @@ -144,7 +150,32 @@ def view_notifications(service_id): notifications=notifications['notifications'], page=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', ''] + ] + ] ) diff --git a/app/templates/components/pill.html b/app/templates/components/pill.html new file mode 100644 index 000000000..2ca56fa25 --- /dev/null +++ b/app/templates/components/pill.html @@ -0,0 +1,16 @@ +{% macro pill( + title, + items=[], + current_value=None +) %} + +{% endmacro %} \ No newline at end of file diff --git a/app/templates/components/table.html b/app/templates/components/table.html index f6a1ed0fe..5f96508ce 100644 --- a/app/templates/components/table.html +++ b/app/templates/components/table.html @@ -67,6 +67,12 @@ {% endcall %} {%- endmacro %} +{% macro link_field(text, link) -%} + {% call field() %} + {{ text }} + {% endcall %} +{%- endmacro %} + {% macro boolean_field(yes) -%} {% call field(status='yes' if yes else 'no') %} {{ "Yes" if yes else "No" }} diff --git a/app/templates/views/notifications.html b/app/templates/views/notifications.html index 6c669f521..d58771beb 100644 --- a/app/templates/views/notifications.html +++ b/app/templates/views/notifications.html @@ -1,6 +1,8 @@ {% 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/page-footer.html" import page_footer %} +{% from "components/pill.html" import pill %} {% block page_title %} Activity – GOV.UK Notify @@ -8,50 +10,96 @@ {% block maincolumn_content %} -

Activity

-

- All messages -

-

- Text messages  - Email messages -

-

- Successful messages  - Failed messages -

+

- {% call(item, row_number) list_table( - notifications, - caption="Recent activity", - caption_visible=False, - empty_message='You haven’t sent any notifications yet', - field_headings=['Recipient', 'Template', 'Type', 'Job', 'Status', 'Time']) - %} - {% call field() %} + {%- if (request_args.get('template_type', '') == '') and (request_args.get('status', '') == '') -%} + + Activity + + {%- else -%} + + {% if request_args.get('status') != '' %} + {% 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 -%} + +

+ +
+
+ {{ pill( + 'Type', + type_filters, + request_args.get('template_type', '') + ) }} +
+
+ {{ pill( + 'Status', + status_filters, + request_args.get('status', '') + ) }} +
+
+ + {% if notifications %} +

+ Download as a CSV file +

+ {% 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() %} +

{{ item.to }} - {% endcall %} - {% call field() %} +

+

{{ item.template.name }} - {% endcall %} - {% call field() %} - {{ item.template.template_type }} - {% endcall %} - {% call field() %} + sent from {% if item.job %} {{ item.job.original_file_name }} + {% else %} + an API call {% endif %} - {% endcall %} - {% call field() %} - {{ item.status }} - {% endcall %} - {% call field() %} - {{ item.created_at | format_datetime }} - {% endcall %} +

{% endcall %} - - {{ previous_next_navigation(prev_page, next_page) }} -{% endblock %} \ No newline at end of file + {{ text_field(item.status|title) }} + + {% call field(align='right') %} + {{ item.created_at|format_delta }} + {% endcall %} + {% endcall %} + + {{ previous_next_navigation(prev_page, next_page) }} + +{% endblock %} diff --git a/requirements.txt b/requirements.txt index 3b1b1088c..7647b9166 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,7 @@ Flask-Bcrypt==0.6.2 credstash==1.8.0 boto3==1.2.3 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