Build rest endpoint to read service stats

- get stats by service id
- returns a list of stats objects

Not paginated - have 1 row per day.
This commit is contained in:
Martyn Inglis
2016-03-08 16:34:03 +00:00
parent f5f50e00ff
commit 67c4bd2263
8 changed files with 174 additions and 66 deletions

View File

@@ -49,6 +49,7 @@ def create_app():
from app.invite.rest import invite as invite_blueprint from app.invite.rest import invite as invite_blueprint
from app.permission.rest import permission as permission_blueprint from app.permission.rest import permission as permission_blueprint
from app.accept_invite.rest import accept_invite from app.accept_invite.rest import accept_invite
from app.notifications_statistics.rest import notifications_statistics as notifications_statistics_blueprint
application.register_blueprint(service_blueprint, url_prefix='/service') application.register_blueprint(service_blueprint, url_prefix='/service')
application.register_blueprint(user_blueprint, url_prefix='/user') application.register_blueprint(user_blueprint, url_prefix='/user')
@@ -59,6 +60,7 @@ def create_app():
application.register_blueprint(invite_blueprint) application.register_blueprint(invite_blueprint)
application.register_blueprint(permission_blueprint, url_prefix='/permission') application.register_blueprint(permission_blueprint, url_prefix='/permission')
application.register_blueprint(accept_invite, url_prefix='/invite') application.register_blueprint(accept_invite, url_prefix='/invite')
application.register_blueprint(notifications_statistics_blueprint)
return application return application

View File

@@ -1,34 +1,24 @@
from flask import current_app from flask import current_app
from app import db from app import db
from app.models import Notification, Job, ServiceNotificationStats, TEMPLATE_TYPE_SMS, TEMPLATE_TYPE_EMAIL from app.models import Notification, Job, NotificationStatistics, TEMPLATE_TYPE_SMS, TEMPLATE_TYPE_EMAIL
from sqlalchemy import desc from sqlalchemy import desc
from datetime import datetime from datetime import datetime
def dao_get_notification_statistics_for_service(service_id):
return NotificationStatistics.query.filter_by(
service_id=service_id
).order_by(desc(NotificationStatistics.day)).all()
def dao_create_notification(notification, notification_type): def dao_create_notification(notification, notification_type):
try: try:
if notification.job_id: if notification.job_id:
update_job_sent_count(notification) update_job_sent_count(notification)
day = datetime.utcnow().strftime('%Y-%m-%d') if update_notification_stats(notification, notification_type) == 0:
stats = NotificationStatistics(
if notification_type == TEMPLATE_TYPE_SMS: day=notification.created_at.strftime('%Y-%m-%d'),
update = {
ServiceNotificationStats.sms_requested: ServiceNotificationStats.sms_requested + 1
}
else:
update = {
ServiceNotificationStats.emails_requested: ServiceNotificationStats.emails_requested + 1
}
result = db.session.query(ServiceNotificationStats).filter_by(
day=day,
service_id=notification.service_id
).update(update)
if result == 0:
stats = ServiceNotificationStats(
day=day,
service_id=notification.service_id, service_id=notification.service_id,
sms_requested=1 if notification_type == TEMPLATE_TYPE_SMS else 0, sms_requested=1 if notification_type == TEMPLATE_TYPE_SMS else 0,
emails_requested=1 if notification_type == TEMPLATE_TYPE_EMAIL else 0 emails_requested=1 if notification_type == TEMPLATE_TYPE_EMAIL else 0
@@ -41,6 +31,22 @@ def dao_create_notification(notification, notification_type):
raise raise
def update_notification_stats(notification, notification_type):
if notification_type == TEMPLATE_TYPE_SMS:
update = {
NotificationStatistics.sms_requested: NotificationStatistics.sms_requested + 1
}
else:
update = {
NotificationStatistics.emails_requested: NotificationStatistics.emails_requested + 1
}
return db.session.query(NotificationStatistics).filter_by(
day=notification.created_at.strftime('%Y-%m-%d'),
service_id=notification.service_id
).update(update)
def update_job_sent_count(notification): def update_job_sent_count(notification):
db.session.query(Job).filter_by( db.session.query(Job).filter_by(
id=notification.job_id id=notification.job_id

View File

@@ -107,8 +107,8 @@ class ApiKey(db.Model):
) )
class ServiceNotificationStats(db.Model): class NotificationStatistics(db.Model):
__tablename__ = 'service_notification_stats' __tablename__ = 'notification_statistics'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
day = db.Column(db.String(255), nullable=False) day = db.Column(db.String(255), nullable=False)

View File

View File

@@ -0,0 +1,25 @@
from flask import (
Blueprint,
jsonify,
)
from app.dao.notifications_dao import (
dao_get_notification_statistics_for_service
)
from app.schemas import notifications_statistics_schema
notifications_statistics = Blueprint(
'notifications-statistics',
__name__, url_prefix='/service/<service_id>/notifications-statistics'
)
from app.errors import register_errors
register_errors(notifications_statistics)
@notifications_statistics.route('', methods=['GET'])
def get_all_templates_for_service(service_id):
templates = dao_get_notification_statistics_for_service(service_id=service_id)
data, errors = notifications_statistics_schema.dump(templates, many=True)
return jsonify(data=data)

View File

@@ -34,19 +34,6 @@ class BaseSchema(ma.ModelSchema):
assert key is not None, "Envelope key undefined" assert key is not None, "Envelope key undefined"
return key return key
# Code to envelope the input and response.
# TOBE added soon.
# @pre_load(pass_many=True)
# def unwrap_envelope(self, data, many):
# key = self.get_envelope_key(many)
# return data[key]
# @post_dump(pass_many=True)
# def wrap_with_envelope(self, data, many):
# key = self.get_envelope_key(many)
# return {key: data}
@post_load @post_load
def make_instance(self, data): def make_instance(self, data):
"""Deserialize data to an instance of the model. Update an existing row """Deserialize data to an instance of the model. Update an existing row
@@ -91,6 +78,11 @@ class TemplateSchema(BaseSchema):
exclude = ("updated_at", "created_at", "service_id", "jobs") exclude = ("updated_at", "created_at", "service_id", "jobs")
class NotificationsStatisticsSchema(BaseSchema):
class Meta:
model = models.NotificationStatistics
class ApiKeySchema(BaseSchema): class ApiKeySchema(BaseSchema):
class Meta: class Meta:
model = models.ApiKey model = models.ApiKey
@@ -216,3 +208,4 @@ notification_status_schema = NotificationStatusSchema()
notification_status_schema_load_json = NotificationStatusSchema(load_json=True) notification_status_schema_load_json = NotificationStatusSchema(load_json=True)
invited_user_schema = InvitedUserSchema() invited_user_schema = InvitedUserSchema()
permission_schema = PermissionSchema() permission_schema = PermissionSchema()
notifications_statistics_schema = NotificationsStatisticsSchema()

View File

@@ -15,8 +15,7 @@ import sqlalchemy as sa
from sqlalchemy.dialects import postgresql from sqlalchemy.dialects import postgresql
def upgrade(): def upgrade():
### commands auto generated by Alembic - please adjust! ### op.create_table('notification_statistics',
op.create_table('service_notification_stats',
sa.Column('id', sa.Integer(), nullable=False), sa.Column('id', sa.Integer(), nullable=False),
sa.Column('day', sa.String(length=255), nullable=False), sa.Column('day', sa.String(length=255), nullable=False),
sa.Column('service_id', postgresql.UUID(as_uuid=True), nullable=False), sa.Column('service_id', postgresql.UUID(as_uuid=True), nullable=False),
@@ -30,12 +29,9 @@ def upgrade():
sa.PrimaryKeyConstraint('id'), sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('service_id', 'day', name='uix_service_to_day') sa.UniqueConstraint('service_id', 'day', name='uix_service_to_day')
) )
op.create_index(op.f('ix_service_notification_stats_service_id'), 'service_notification_stats', ['service_id'], unique=False) op.create_index(op.f('ix_service_notification_stats_service_id'), 'notification_statistics', ['service_id'], unique=False)
### end Alembic commands ###
def downgrade(): def downgrade():
### commands auto generated by Alembic - please adjust! ### op.drop_index(op.f('ix_service_notification_stats_service_id'), table_name='notification_statistics')
op.drop_index(op.f('ix_service_notification_stats_service_id'), table_name='service_notification_stats') op.drop_table('notification_statistics')
op.drop_table('service_notification_stats')
### end Alembic commands ###

View File

@@ -1,20 +1,106 @@
import pytest import pytest
import uuid import uuid
from freezegun import freeze_time from freezegun import freeze_time
import random
from sqlalchemy.exc import SQLAlchemyError, IntegrityError from sqlalchemy.exc import SQLAlchemyError, IntegrityError
from app.models import Notification, Job, ServiceNotificationStats from app.models import Notification, Job, NotificationStatistics
from datetime import datetime from datetime import datetime, timedelta
from app.dao.notifications_dao import ( from app.dao.notifications_dao import (
dao_create_notification, dao_create_notification,
dao_update_notification, dao_update_notification,
get_notification, get_notification,
get_notification_for_job, get_notification_for_job,
get_notifications_for_job get_notifications_for_job,
dao_get_notification_statistics_for_service
) )
from tests.app.conftest import sample_job from tests.app.conftest import sample_job
def test_should_be_able_to_get_statistics_for_a_service(sample_template):
data = {
'to': '+44709123456',
'service': sample_template.service,
'service_id': sample_template.service.id,
'template': sample_template,
'created_at': datetime.utcnow()
}
notification = Notification(**data)
dao_create_notification(notification, sample_template.template_type)
stats = dao_get_notification_statistics_for_service(sample_template.service.id)
assert len(stats) == 1
assert stats[0].emails_requested == 0
assert stats[0].sms_requested == 1
assert stats[0].day == notification.created_at.strftime('%Y-%m-%d')
assert stats[0].service_id == notification.service_id
def test_should_be_able_to_get_all_statistics_for_a_service(sample_template):
data = {
'to': '+44709123456',
'service': sample_template.service,
'service_id': sample_template.service.id,
'template': sample_template,
'created_at': datetime.utcnow()
}
notification_1 = Notification(**data)
notification_2 = Notification(**data)
notification_3 = Notification(**data)
dao_create_notification(notification_1, sample_template.template_type)
dao_create_notification(notification_2, sample_template.template_type)
dao_create_notification(notification_3, sample_template.template_type)
stats = dao_get_notification_statistics_for_service(sample_template.service.id)
assert len(stats) == 1
assert stats[0].emails_requested == 0
assert stats[0].sms_requested == 3
def test_should_be_able_to_get_all_statistics_for_a_service_for_several_days(sample_template):
data = {
'to': '+44709123456',
'service': sample_template.service,
'service_id': sample_template.service.id,
'template': sample_template
}
today = datetime.utcnow()
yesterday = datetime.utcnow() - timedelta(days=1)
two_days_ago = datetime.utcnow() - timedelta(days=2)
data.update({
'created_at': today
})
notification_1 = Notification(**data)
data.update({
'created_at': yesterday
})
notification_2 = Notification(**data)
data.update({
'created_at': two_days_ago
})
notification_3 = Notification(**data)
dao_create_notification(notification_1, sample_template.template_type)
dao_create_notification(notification_2, sample_template.template_type)
dao_create_notification(notification_3, sample_template.template_type)
stats = dao_get_notification_statistics_for_service(sample_template.service.id)
assert len(stats) == 3
assert stats[0].emails_requested == 0
assert stats[0].sms_requested == 1
assert stats[0].day == today.strftime('%Y-%m-%d')
assert stats[1].emails_requested == 0
assert stats[1].sms_requested == 1
assert stats[1].day == yesterday.strftime('%Y-%m-%d')
assert stats[2].emails_requested == 0
assert stats[2].sms_requested == 1
assert stats[2].day == two_days_ago.strftime('%Y-%m-%d')
def test_should_be_empty_list_if_no_statistics_for_a_service(sample_service):
assert len(dao_get_notification_statistics_for_service(sample_service.id)) == 0
def test_save_notification_and_create_sms_stats(sample_template, sample_job): def test_save_notification_and_create_sms_stats(sample_template, sample_job):
assert Notification.query.count() == 0 assert Notification.query.count() == 0
data = { data = {
@@ -40,8 +126,8 @@ def test_save_notification_and_create_sms_stats(sample_template, sample_job):
assert 'sent' == notification_from_db.status assert 'sent' == notification_from_db.status
assert Job.query.get(sample_job.id).notifications_sent == 1 assert Job.query.get(sample_job.id).notifications_sent == 1
stats = ServiceNotificationStats.query.filter( stats = NotificationStatistics.query.filter(
ServiceNotificationStats.service_id == sample_template.service.id NotificationStatistics.service_id == sample_template.service.id
).first() ).first()
assert stats.emails_requested == 0 assert stats.emails_requested == 0
@@ -73,8 +159,8 @@ def test_save_notification_and_create_email_stats(sample_email_template, sample_
assert 'sent' == notification_from_db.status assert 'sent' == notification_from_db.status
assert Job.query.get(sample_job.id).notifications_sent == 1 assert Job.query.get(sample_job.id).notifications_sent == 1
stats = ServiceNotificationStats.query.filter( stats = NotificationStatistics.query.filter(
ServiceNotificationStats.service_id == sample_email_template.service.id NotificationStatistics.service_id == sample_email_template.service.id
).first() ).first()
assert stats.emails_requested == 1 assert stats.emails_requested == 1
@@ -98,8 +184,8 @@ def test_save_notification_handles_midnight_properly(sample_template, sample_job
assert Notification.query.count() == 1 assert Notification.query.count() == 1
stats = ServiceNotificationStats.query.filter( stats = NotificationStatistics.query.filter(
ServiceNotificationStats.service_id == sample_template.service.id NotificationStatistics.service_id == sample_template.service.id
).first() ).first()
assert stats.day == '2016-01-01' assert stats.day == '2016-01-01'
@@ -122,8 +208,8 @@ def test_save_notification_handles_just_before_midnight_properly(sample_template
assert Notification.query.count() == 1 assert Notification.query.count() == 1
stats = ServiceNotificationStats.query.filter( stats = NotificationStatistics.query.filter(
ServiceNotificationStats.service_id == sample_template.service.id NotificationStatistics.service_id == sample_template.service.id
).first() ).first()
assert stats.day == '2016-01-01' assert stats.day == '2016-01-01'
@@ -146,8 +232,8 @@ def test_save_notification_and_increment_email_stats(sample_email_template, samp
assert Notification.query.count() == 1 assert Notification.query.count() == 1
stats1 = ServiceNotificationStats.query.filter( stats1 = NotificationStatistics.query.filter(
ServiceNotificationStats.service_id == sample_email_template.service.id NotificationStatistics.service_id == sample_email_template.service.id
).first() ).first()
assert stats1.emails_requested == 1 assert stats1.emails_requested == 1
@@ -157,8 +243,8 @@ def test_save_notification_and_increment_email_stats(sample_email_template, samp
assert Notification.query.count() == 2 assert Notification.query.count() == 2
stats2 = ServiceNotificationStats.query.filter( stats2 = NotificationStatistics.query.filter(
ServiceNotificationStats.service_id == sample_email_template.service.id NotificationStatistics.service_id == sample_email_template.service.id
).first() ).first()
assert stats2.emails_requested == 2 assert stats2.emails_requested == 2
@@ -182,8 +268,8 @@ def test_save_notification_and_increment_sms_stats(sample_template, sample_job):
assert Notification.query.count() == 1 assert Notification.query.count() == 1
stats1 = ServiceNotificationStats.query.filter( stats1 = NotificationStatistics.query.filter(
ServiceNotificationStats.service_id == sample_template.service.id NotificationStatistics.service_id == sample_template.service.id
).first() ).first()
assert stats1.emails_requested == 0 assert stats1.emails_requested == 0
@@ -193,8 +279,8 @@ def test_save_notification_and_increment_sms_stats(sample_template, sample_job):
assert Notification.query.count() == 2 assert Notification.query.count() == 2
stats2 = ServiceNotificationStats.query.filter( stats2 = NotificationStatistics.query.filter(
ServiceNotificationStats.service_id == sample_template.service.id NotificationStatistics.service_id == sample_template.service.id
).first() ).first()
assert stats2.emails_requested == 0 assert stats2.emails_requested == 0
@@ -220,7 +306,7 @@ def test_not_save_notification_and_not_create_stats_on_commit_error(sample_templ
assert Notification.query.count() == 0 assert Notification.query.count() == 0
assert Job.query.get(sample_job.id).notifications_sent == 0 assert Job.query.get(sample_job.id).notifications_sent == 0
assert ServiceNotificationStats.query.count() == 0 assert NotificationStatistics.query.count() == 0
def test_save_notification_and_increment_job(sample_template, sample_job): def test_save_notification_and_increment_job(sample_template, sample_job):
@@ -389,8 +475,8 @@ def test_get_all_notifications_for_job(notify_db, notify_db_session, sample_job)
notifcations_from_db = get_notifications_for_job(sample_job.service.id, sample_job.id).items notifcations_from_db = get_notifications_for_job(sample_job.service.id, sample_job.id).items
assert len(notifcations_from_db) == 5 assert len(notifcations_from_db) == 5
stats = ServiceNotificationStats.query.filter( stats = NotificationStatistics.query.filter(
ServiceNotificationStats.service_id == sample_job.service.id NotificationStatistics.service_id == sample_job.service.id
).first() ).first()
assert stats.emails_requested == 0 assert stats.emails_requested == 0