Adds notification stats update into the callback process

- when a provider callback occurs and we update the status of the notification, also update the statistics table

Adds:
- Mapping object to the clients to handle mapping to various states from the response codes, this replaces the map.
- query lookup in the DAO to get the query based on response type / template type

Tests around rest class and dao to check correct updating of stats

Missing:
- multiple client callbacks will keep incrementing the counts of success/failure. This edge case needs to be handle in a future story.
This commit is contained in:
Martyn Inglis
2016-03-21 13:24:37 +00:00
parent e2cfbce8c4
commit e0316d1881
10 changed files with 289 additions and 54 deletions

View File

@@ -13,6 +13,11 @@ class Client(object):
pass
STATISTICS_REQUESTED = 'requested'
STATISTICS_DELIVERED = 'delivered'
STATISTICS_FAILURE = 'failure'
class ClientResponse:
def __init__(self):
self.__response_model__ = None
@@ -24,7 +29,6 @@ class ClientResponse:
return self.response_code_to_object(response_code)['message']
def response_code_to_notification_status(self, response_code):
print(response_code)
return self.response_code_to_object(response_code)['notification_status']
def response_code_to_notification_statistics_status(self, response_code):

View File

@@ -1,7 +1,7 @@
import boto3
from flask import current_app
from monotonic import monotonic
from app.clients import ClientResponse
from app.clients import ClientResponse, STATISTICS_DELIVERED, STATISTICS_FAILURE
from app.clients.email import (EmailClientException, EmailClient)
@@ -13,19 +13,19 @@ class AwsSesResponses(ClientResponse):
"message": 'Bounced',
"success": False,
"notification_status": 'bounce',
"notification_statistics_status": 'failed'
"notification_statistics_status": STATISTICS_FAILURE
},
'Delivery': {
"message": 'Delivered',
"success": True,
"notification_status": 'delivered',
"notification_statistics_status": 'delivered'
"notification_statistics_status": STATISTICS_DELIVERED
},
'Complaint': {
"message": 'Complaint',
"success": False,
"notification_status": 'complaint',
"notification_statistics_status": 'failed'
"notification_statistics_status": STATISTICS_FAILURE
}
}

View File

@@ -6,7 +6,7 @@ from app.clients.sms import (
)
from flask import current_app
from requests import request, RequestException, HTTPError
from app.clients import ClientResponse
from app.clients import ClientResponse, STATISTICS_DELIVERED, STATISTICS_FAILURE
logger = logging.getLogger(__name__)
@@ -17,14 +17,14 @@ class FiretextResponses(ClientResponse):
self.__response_model__ = {
'0': {
"message": 'Delivered',
"notification_statistics_status": 'delivered',
"notification_statistics_status": STATISTICS_DELIVERED,
"success": True,
"notification_status": 'delivered'
},
'1': {
"message": 'Declined',
"success": False,
"notification_statistics_status": 'failed',
"notification_statistics_status": STATISTICS_FAILURE,
"notification_status": 'failed'
},
'2': {

View File

@@ -3,7 +3,8 @@ from app import db
from app.models import Notification, Job, NotificationStatistics, TEMPLATE_TYPE_SMS, TEMPLATE_TYPE_EMAIL
from sqlalchemy import desc
from datetime import datetime, timedelta
from app.clients.sms.firetext import FiretextResponses
from app.clients import (STATISTICS_FAILURE, STATISTICS_DELIVERED, STATISTICS_REQUESTED)
def dao_get_notification_statistics_for_service(service_id):
return NotificationStatistics.query.filter_by(
@@ -49,18 +50,16 @@ def dao_create_notification(notification, notification_type):
def update_query(notification_type, status):
print(notification_type)
print(status)
mapping = {
'sms': {
'requested': NotificationStatistics.sms_requested,
'success': NotificationStatistics.sms_delivered,
'failure': NotificationStatistics.sms_error
TEMPLATE_TYPE_SMS: {
STATISTICS_REQUESTED: NotificationStatistics.sms_requested,
STATISTICS_DELIVERED: NotificationStatistics.sms_delivered,
STATISTICS_FAILURE: NotificationStatistics.sms_error
},
'email': {
'requested': NotificationStatistics.emails_requested,
'success': NotificationStatistics.emails_delivered,
'failure': NotificationStatistics.emails_error
TEMPLATE_TYPE_EMAIL: {
STATISTICS_REQUESTED: NotificationStatistics.emails_requested,
STATISTICS_DELIVERED: NotificationStatistics.emails_delivered,
STATISTICS_FAILURE: NotificationStatistics.emails_error
}
}
return {
@@ -74,29 +73,46 @@ def dao_update_notification(notification):
db.session.commit()
def update_notification_status_by_id(notification_id, status):
def update_notification_status_by_id(notification_id, status, notification_statistics_status):
count = db.session.query(Notification).filter_by(
id=notification_id
).update({
Notification.status: status
})
if count == 1:
if count == 1 and notification_statistics_status:
notification = Notification.query.get(notification_id)
db.session.query(NotificationStatistics).filter_by(
day=notification.created_at.strftime('%Y-%m-%d'),
service_id=notification.service_id
).update(update_query(notification.template.template_type, FiretextResponses.response_code_to_notify_stats(status)))
).update(
update_query(notification.template.template_type, notification_statistics_status)
)
db.session.commit()
return count
def update_notification_status_by_reference(reference, status):
def update_notification_status_by_reference(reference, status, notification_statistics_status):
count = db.session.query(Notification).filter_by(
reference=reference
).update({
Notification.status: status
})
if count == 1:
notification = Notification.query.filter_by(
reference=reference
).first()
db.session.query(NotificationStatistics).filter_by(
day=notification.created_at.strftime('%Y-%m-%d'),
service_id=notification.service_id
).update(
update_query(notification.template.template_type, notification_statistics_status)
)
db.session.commit()
return count

View File

@@ -11,8 +11,8 @@ from flask import (
)
from utils.template import Template
from app.clients.sms.firetext import firetext_response_status
from app.clients.email.aws_ses import ses_response_status
from app.clients.sms.firetext import FiretextResponses
from app.clients.email.aws_ses import AwsSesResponses
from app import api_user, encryption, create_uuid, DATETIME_FORMAT, DATE_FORMAT
from app.authentication.auth import require_admin
from app.dao import (
@@ -34,6 +34,9 @@ from app.errors import register_errors
register_errors(notifications)
aws_response = AwsSesResponses()
firetext_response = FiretextResponses()
@notifications.route('/notifications/email/ses', methods=['POST'])
def process_ses_response():
@@ -57,35 +60,54 @@ def process_ses_response():
result="error", message="SES callback failed: notificationType missing"
), 400
status = ses_response_status.get(ses_message['notificationType'], None)
if not status:
try:
aws_response.response_code_to_object(ses_message['notificationType'])
except KeyError:
current_app.logger.info(
"SES callback failed: status {} not found.".format(status)
"SES callback failed: status {} not found.".format(ses_message['notificationType'])
)
return jsonify(
result="error",
message="SES callback failed: status {} not found".format(ses_message['notificationType'])
), 400
notification_status = aws_response.response_code_to_notification_status(ses_message['notificationType'])
notification_statistics_status = aws_response.response_code_to_notification_statistics_status(
ses_message['notificationType']
)
try:
source = ses_message['mail']['source']
if is_not_a_notification(source):
current_app.logger.info(
"SES callback for notify success:. source {} status {}".format(source, status['notify_status'])
"SES callback for notify success:. source {} status {}".format(source, notification_status)
)
return jsonify(
result="success", message="SES callback succeeded"
), 200
reference = ses_message['mail']['messageId']
if notifications_dao.update_notification_status_by_reference(reference, status['notify_status']) == 0:
if notifications_dao.update_notification_status_by_reference(
reference,
notification_status,
notification_statistics_status
) == 0:
current_app.logger.info(
"SES callback failed: notification not found. Status {}".format(status['notify_status'])
"SES callback failed: notification not found. Status {}".format(notification_status)
)
return jsonify(
result="error",
message="SES callback failed: notification not found. Status {}".format(status['notify_status'])
message="SES callback failed: notification not found. Status {}".format(notification_status)
), 404
if not aws_response.response_code_to_notification_success(ses_message['notificationType']):
current_app.logger.info(
"SES delivery failed: notification {} has error found. Status {}".format(
reference,
aws_response.response_code_to_message(ses_message['notificationType'])
)
)
return jsonify(
result="success", message="SES callback succeeded"
), 200
@@ -149,14 +171,22 @@ def process_firetext_response():
result="error", message="Firetext callback with invalid reference {}".format(reference)
), 400
notification_status = firetext_response_status.get(status, None)
if not notification_status:
try:
firetext_response.response_code_to_object(status)
except KeyError:
current_app.logger.info(
"Firetext callback failed: status {} not found.".format(status)
)
return jsonify(result="error", message="Firetext callback failed: status {} not found.".format(status)), 400
if notifications_dao.update_notification_status_by_id(reference, notification_status['notify_status']) == 0:
notification_status = firetext_response.response_code_to_notification_status(status)
notification_statistics_status = firetext_response.response_code_to_notification_statistics_status(status)
if notifications_dao.update_notification_status_by_id(
reference,
notification_status,
notification_statistics_status
) == 0:
current_app.logger.info(
"Firetext callback failed: notification {} not found. Status {}".format(reference, status)
)
@@ -164,15 +194,15 @@ def process_firetext_response():
result="error",
message="Firetext callback failed: notification {} not found. Status {}".format(
reference,
notification_status['firetext_message']
firetext_response.response_code_to_message(status)
)
), 404
if not notification_status['success']:
if not firetext_response.response_code_to_notification_success(status):
current_app.logger.info(
"Firetext delivery failed: notification {} has error found. Status {}".format(
reference,
firetext_response_status[status]['firetext_message']
FiretextResponses().response_code_to_message(status)
)
)
return jsonify(