mirror of
https://github.com/GSA/notifications-api.git
synced 2026-02-01 15:46:07 -05:00
Merge pull request #804 from alphagov/feat-add-perf-platform-client-and-job
Add client and job to update the performance platform daily
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
import json
|
|
||||||
|
|
||||||
from flask import Flask, _request_ctx_stack
|
from flask import Flask, _request_ctx_stack
|
||||||
from flask import request, url_for, g, jsonify
|
from flask import request, url_for, g, jsonify
|
||||||
@@ -18,6 +17,7 @@ from app.clients.email.aws_ses import AwsSesClient
|
|||||||
from app.clients.sms.firetext import FiretextClient
|
from app.clients.sms.firetext import FiretextClient
|
||||||
from app.clients.sms.loadtesting import LoadtestingClient
|
from app.clients.sms.loadtesting import LoadtestingClient
|
||||||
from app.clients.sms.mmg import MMGClient
|
from app.clients.sms.mmg import MMGClient
|
||||||
|
from app.clients.performance_platform.performance_platform_client import PerformancePlatformClient
|
||||||
from app.encryption import Encryption
|
from app.encryption import Encryption
|
||||||
|
|
||||||
|
|
||||||
@@ -34,6 +34,7 @@ aws_ses_client = AwsSesClient()
|
|||||||
encryption = Encryption()
|
encryption = Encryption()
|
||||||
statsd_client = StatsdClient()
|
statsd_client = StatsdClient()
|
||||||
redis_store = RedisClient()
|
redis_store = RedisClient()
|
||||||
|
performance_platform_client = PerformancePlatformClient()
|
||||||
|
|
||||||
clients = Clients()
|
clients = Clients()
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ from sqlalchemy.exc import SQLAlchemyError
|
|||||||
|
|
||||||
from app.aws import s3
|
from app.aws import s3
|
||||||
from app import notify_celery
|
from app import notify_celery
|
||||||
|
from app import performance_platform_client
|
||||||
from app.dao.invited_user_dao import delete_invitations_created_more_than_two_days_ago
|
from app.dao.invited_user_dao import delete_invitations_created_more_than_two_days_ago
|
||||||
from app.dao.jobs_dao import dao_set_scheduled_jobs_to_pending, dao_get_jobs_older_than
|
from app.dao.jobs_dao import dao_set_scheduled_jobs_to_pending, dao_get_jobs_older_than
|
||||||
from app.dao.notifications_dao import (delete_notifications_created_more_than_a_week_ago,
|
from app.dao.notifications_dao import (
|
||||||
dao_timeout_notifications)
|
delete_notifications_created_more_than_a_week_ago,
|
||||||
|
dao_timeout_notifications
|
||||||
|
)
|
||||||
from app.dao.users_dao import delete_codes_older_created_more_than_a_day_ago
|
from app.dao.users_dao import delete_codes_older_created_more_than_a_day_ago
|
||||||
from app.statsd_decorators import statsd
|
from app.statsd_decorators import statsd
|
||||||
from app.celery.tasks import process_job
|
from app.celery.tasks import process_job
|
||||||
@@ -109,3 +112,24 @@ def timeout_notifications():
|
|||||||
if updated:
|
if updated:
|
||||||
current_app.logger.info(
|
current_app.logger.info(
|
||||||
"Timeout period reached for {} notifications, status has been updated.".format(updated))
|
"Timeout period reached for {} notifications, status has been updated.".format(updated))
|
||||||
|
|
||||||
|
|
||||||
|
@notify_celery.task(name='send-daily-performance-platform-stats')
|
||||||
|
@statsd(namespace="tasks")
|
||||||
|
def send_daily_performance_stats():
|
||||||
|
count_dict = performance_platform_client.get_total_sent_notifications_yesterday()
|
||||||
|
start_date = count_dict.get('start_date')
|
||||||
|
|
||||||
|
performance_platform_client.send_performance_stats(
|
||||||
|
start_date,
|
||||||
|
'sms',
|
||||||
|
count_dict.get('sms').get('count'),
|
||||||
|
'day'
|
||||||
|
)
|
||||||
|
|
||||||
|
performance_platform_client.send_performance_stats(
|
||||||
|
start_date,
|
||||||
|
'email',
|
||||||
|
count_dict.get('email').get('count'),
|
||||||
|
'day'
|
||||||
|
)
|
||||||
|
|||||||
0
app/clients/performance_platform/__init__.py
Normal file
0
app/clients/performance_platform/__init__.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import base64
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
from requests import request
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
from app.utils import (
|
||||||
|
get_midnight_for_day_before,
|
||||||
|
get_london_midnight_in_utc
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PerformancePlatformClient:
|
||||||
|
|
||||||
|
def init_app(self, app):
|
||||||
|
self.active = app.config.get('PERFORMANCE_PLATFORM_ENABLED')
|
||||||
|
if self.active:
|
||||||
|
self.bearer_token = app.config.get('PERFORMANCE_PLATFORM_TOKEN')
|
||||||
|
self.performance_platform_url = current_app.config.get('PERFORMANCE_PLATFORM_URL')
|
||||||
|
|
||||||
|
def send_performance_stats(self, date, channel, count, period):
|
||||||
|
if self.active:
|
||||||
|
payload = {
|
||||||
|
'_timestamp': date,
|
||||||
|
'service': 'govuk-notify',
|
||||||
|
'channel': channel,
|
||||||
|
'count': count,
|
||||||
|
'dataType': 'notifications',
|
||||||
|
'period': period
|
||||||
|
}
|
||||||
|
self._add_id_for_payload(payload)
|
||||||
|
self._send_stats_to_performance_platform(payload)
|
||||||
|
|
||||||
|
def get_total_sent_notifications_yesterday(self):
|
||||||
|
today = datetime.utcnow()
|
||||||
|
start_date = get_midnight_for_day_before(today)
|
||||||
|
end_date = get_london_midnight_in_utc(today)
|
||||||
|
|
||||||
|
from app.dao.notifications_dao import get_total_sent_notifications_in_date_range
|
||||||
|
return {
|
||||||
|
"start_date": start_date,
|
||||||
|
"end_date": end_date,
|
||||||
|
"email": {
|
||||||
|
"count": get_total_sent_notifications_in_date_range(start_date, end_date, 'email')
|
||||||
|
},
|
||||||
|
"sms": {
|
||||||
|
"count": get_total_sent_notifications_in_date_range(start_date, end_date, 'sms')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _send_stats_to_performance_platform(self, payload):
|
||||||
|
headers = {
|
||||||
|
'Content-Type': "application/json",
|
||||||
|
'Authorization': 'Bearer {}'.format(self.bearer_token)
|
||||||
|
}
|
||||||
|
resp = request(
|
||||||
|
"POST",
|
||||||
|
self.performance_platform_url,
|
||||||
|
data=json.dumps(payload),
|
||||||
|
headers=headers
|
||||||
|
)
|
||||||
|
|
||||||
|
if resp.status_code != 200:
|
||||||
|
current_app.logger.error(
|
||||||
|
"Performance platform update request failed with {} '{}'".format(
|
||||||
|
resp.status_code,
|
||||||
|
resp.json())
|
||||||
|
)
|
||||||
|
|
||||||
|
def _add_id_for_payload(self, payload):
|
||||||
|
payload_string = '{}{}{}{}{}'.format(
|
||||||
|
payload['_timestamp'],
|
||||||
|
payload['service'],
|
||||||
|
payload['channel'],
|
||||||
|
payload['dataType'],
|
||||||
|
payload['period']
|
||||||
|
)
|
||||||
|
_id = base64.b64encode(payload_string.encode('utf-8'))
|
||||||
|
payload.update({'_id': _id.decode('utf-8')})
|
||||||
@@ -50,6 +50,11 @@ class Config(object):
|
|||||||
REDIS_URL = os.getenv('REDIS_URL')
|
REDIS_URL = os.getenv('REDIS_URL')
|
||||||
REDIS_ENABLED = os.getenv('REDIS_ENABLED') == '1'
|
REDIS_ENABLED = os.getenv('REDIS_ENABLED') == '1'
|
||||||
|
|
||||||
|
# Performance platform
|
||||||
|
PERFORMANCE_PLATFORM_ENABLED = os.getenv('PERFORMANCE_PLATFORM_ENABLED') == '1'
|
||||||
|
PERFORMANCE_PLATFORM_URL = 'https://www.performance.service.gov.uk/data/govuk-notify/notifications'
|
||||||
|
PERFORMANCE_PLATFORM_TOKEN = os.getenv('PERFORMANCE_PLATFORM_TOKEN')
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
LOGGING_STDOUT_JSON = os.getenv('LOGGING_STDOUT_JSON') == '1'
|
LOGGING_STDOUT_JSON = os.getenv('LOGGING_STDOUT_JSON') == '1'
|
||||||
@@ -119,6 +124,11 @@ class Config(object):
|
|||||||
'schedule': crontab(minute=0, hour='0,1,2'),
|
'schedule': crontab(minute=0, hour='0,1,2'),
|
||||||
'options': {'queue': 'periodic'}
|
'options': {'queue': 'periodic'}
|
||||||
},
|
},
|
||||||
|
'send-daily-performance-platform-stats': {
|
||||||
|
'task': 'send-daily-performance-platform-stats',
|
||||||
|
'schedule': crontab(minute=30, hour=0), # 00:30
|
||||||
|
'options': {'queue': 'periodic'}
|
||||||
|
},
|
||||||
'timeout-sending-notifications': {
|
'timeout-sending-notifications': {
|
||||||
'task': 'timeout-sending-notifications',
|
'task': 'timeout-sending-notifications',
|
||||||
'schedule': crontab(minute=0, hour='0,1,2'),
|
'schedule': crontab(minute=0, hour='0,1,2'),
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
from datetime import (
|
from datetime import (
|
||||||
datetime,
|
datetime,
|
||||||
@@ -7,7 +6,7 @@ from datetime import (
|
|||||||
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from werkzeug.datastructures import MultiDict
|
from werkzeug.datastructures import MultiDict
|
||||||
from sqlalchemy import (desc, func, or_, and_, asc, extract)
|
from sqlalchemy import (desc, func, or_, and_, asc)
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
|
|
||||||
from app import db, create_uuid
|
from app import db, create_uuid
|
||||||
@@ -23,7 +22,8 @@ from app.models import (
|
|||||||
NOTIFICATION_PENDING,
|
NOTIFICATION_PENDING,
|
||||||
NOTIFICATION_TECHNICAL_FAILURE,
|
NOTIFICATION_TECHNICAL_FAILURE,
|
||||||
NOTIFICATION_TEMPORARY_FAILURE,
|
NOTIFICATION_TEMPORARY_FAILURE,
|
||||||
KEY_TYPE_NORMAL, KEY_TYPE_TEST)
|
KEY_TYPE_NORMAL, KEY_TYPE_TEST
|
||||||
|
)
|
||||||
|
|
||||||
from app.dao.dao_utils import transactional
|
from app.dao.dao_utils import transactional
|
||||||
from app.statsd_decorators import statsd
|
from app.statsd_decorators import statsd
|
||||||
@@ -408,3 +408,16 @@ def get_april_fools(year):
|
|||||||
"""
|
"""
|
||||||
return pytz.timezone('Europe/London').localize(datetime(year, 4, 1, 0, 0, 0)).astimezone(pytz.UTC).replace(
|
return pytz.timezone('Europe/London').localize(datetime(year, 4, 1, 0, 0, 0)).astimezone(pytz.UTC).replace(
|
||||||
tzinfo=None)
|
tzinfo=None)
|
||||||
|
|
||||||
|
|
||||||
|
def get_total_sent_notifications_in_date_range(start_date, end_date, notification_type):
|
||||||
|
result = db.session.query(
|
||||||
|
func.count(NotificationHistory.id).label('count')
|
||||||
|
).filter(
|
||||||
|
NotificationHistory.key_type != KEY_TYPE_TEST,
|
||||||
|
NotificationHistory.created_at >= start_date,
|
||||||
|
NotificationHistory.created_at <= end_date,
|
||||||
|
NotificationHistory.notification_type == notification_type
|
||||||
|
).scalar()
|
||||||
|
|
||||||
|
return result or 0
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ from app.schemas import (
|
|||||||
notifications_filter_schema,
|
notifications_filter_schema,
|
||||||
detailed_service_schema
|
detailed_service_schema
|
||||||
)
|
)
|
||||||
from app.utils import pagination_links
|
from app.utils import pagination_links, get_london_midnight_in_utc
|
||||||
|
|
||||||
service_blueprint = Blueprint('service', __name__)
|
service_blueprint = Blueprint('service', __name__)
|
||||||
register_errors(service_blueprint)
|
register_errors(service_blueprint)
|
||||||
@@ -287,8 +287,9 @@ def get_detailed_services(start_date, end_date, only_active=False, include_from_
|
|||||||
if start_date == datetime.utcnow().date():
|
if start_date == datetime.utcnow().date():
|
||||||
stats = dao_fetch_todays_stats_for_all_services(include_from_test_key=include_from_test_key)
|
stats = dao_fetch_todays_stats_for_all_services(include_from_test_key=include_from_test_key)
|
||||||
else:
|
else:
|
||||||
stats = fetch_stats_by_date_range_for_all_services(start_date=start_date,
|
|
||||||
end_date=end_date,
|
stats = fetch_stats_by_date_range_for_all_services(start_date=get_london_midnight_in_utc(start_date),
|
||||||
|
end_date=get_london_midnight_in_utc(end_date),
|
||||||
include_from_test_key=include_from_test_key)
|
include_from_test_key=include_from_test_key)
|
||||||
|
|
||||||
for service_id, rows in itertools.groupby(stats, lambda x: x.service_id):
|
for service_id, rows in itertools.groupby(stats, lambda x: x.service_id):
|
||||||
|
|||||||
22
app/utils.py
22
app/utils.py
@@ -1,5 +1,7 @@
|
|||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
import pytz
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
from app.models import SMS_TYPE, EMAIL_TYPE
|
|
||||||
from notifications_utils.template import SMSMessageTemplate, PlainTextEmailTemplate
|
from notifications_utils.template import SMSMessageTemplate, PlainTextEmailTemplate
|
||||||
|
|
||||||
|
|
||||||
@@ -23,6 +25,24 @@ def url_with_token(data, url, config):
|
|||||||
|
|
||||||
|
|
||||||
def get_template_instance(template, values):
|
def get_template_instance(template, values):
|
||||||
|
from app.models import SMS_TYPE, EMAIL_TYPE
|
||||||
return {
|
return {
|
||||||
SMS_TYPE: SMSMessageTemplate, EMAIL_TYPE: PlainTextEmailTemplate
|
SMS_TYPE: SMSMessageTemplate, EMAIL_TYPE: PlainTextEmailTemplate
|
||||||
}[template['template_type']](template, values)
|
}[template['template_type']](template, values)
|
||||||
|
|
||||||
|
|
||||||
|
def get_london_midnight_in_utc(date):
|
||||||
|
"""
|
||||||
|
This function converts date to midnight as BST (British Standard Time) to UTC,
|
||||||
|
the tzinfo is lastly removed from the datetime because the database stores the timestamps without timezone.
|
||||||
|
:param date: the day to calculate the London midnight in UTC for
|
||||||
|
:return: the datetime of London midnight in UTC, for example 2016-06-17 = 2016-06-17 23:00:00
|
||||||
|
"""
|
||||||
|
return pytz.timezone('Europe/London').localize(datetime.combine(date, datetime.min.time())).astimezone(
|
||||||
|
pytz.UTC).replace(
|
||||||
|
tzinfo=None)
|
||||||
|
|
||||||
|
|
||||||
|
def get_midnight_for_day_before(date):
|
||||||
|
day_before = date - timedelta(1)
|
||||||
|
return get_london_midnight_in_utc(day_before)
|
||||||
|
|||||||
@@ -1,18 +1,27 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from freezegun import freeze_time
|
from freezegun import freeze_time
|
||||||
from app.celery.scheduled_tasks import s3
|
from app.celery.scheduled_tasks import s3
|
||||||
from app.celery import scheduled_tasks
|
from app.celery import scheduled_tasks
|
||||||
from app.celery.scheduled_tasks import (delete_verify_codes,
|
from app.celery.scheduled_tasks import (
|
||||||
|
delete_verify_codes,
|
||||||
remove_csv_files,
|
remove_csv_files,
|
||||||
delete_successful_notifications,
|
delete_successful_notifications,
|
||||||
delete_failed_notifications,
|
delete_failed_notifications,
|
||||||
delete_invitations,
|
delete_invitations,
|
||||||
timeout_notifications,
|
timeout_notifications,
|
||||||
run_scheduled_jobs)
|
run_scheduled_jobs,
|
||||||
|
send_daily_performance_stats
|
||||||
|
)
|
||||||
from app.dao.jobs_dao import dao_get_job_by_id
|
from app.dao.jobs_dao import dao_get_job_by_id
|
||||||
from tests.app.conftest import sample_notification, sample_job
|
from app.utils import get_london_midnight_in_utc
|
||||||
|
from tests.app.conftest import (
|
||||||
|
sample_notification as create_sample_notification,
|
||||||
|
sample_job as create_sample_job,
|
||||||
|
sample_notification_history as create_notification_history
|
||||||
|
)
|
||||||
from unittest.mock import call
|
from unittest.mock import call
|
||||||
|
|
||||||
|
|
||||||
@@ -24,6 +33,7 @@ def test_should_have_decorated_tasks_functions():
|
|||||||
assert delete_invitations.__wrapped__.__name__ == 'delete_invitations'
|
assert delete_invitations.__wrapped__.__name__ == 'delete_invitations'
|
||||||
assert run_scheduled_jobs.__wrapped__.__name__ == 'run_scheduled_jobs'
|
assert run_scheduled_jobs.__wrapped__.__name__ == 'run_scheduled_jobs'
|
||||||
assert remove_csv_files.__wrapped__.__name__ == 'remove_csv_files'
|
assert remove_csv_files.__wrapped__.__name__ == 'remove_csv_files'
|
||||||
|
assert send_daily_performance_stats.__wrapped__.__name__ == 'send_daily_performance_stats'
|
||||||
|
|
||||||
|
|
||||||
def test_should_call_delete_notifications_more_than_week_in_task(notify_api, mocker):
|
def test_should_call_delete_notifications_more_than_week_in_task(notify_api, mocker):
|
||||||
@@ -58,7 +68,7 @@ def test_update_status_of_notifications_after_timeout(notify_api,
|
|||||||
sample_template,
|
sample_template,
|
||||||
mmg_provider):
|
mmg_provider):
|
||||||
with notify_api.test_request_context():
|
with notify_api.test_request_context():
|
||||||
not1 = sample_notification(
|
not1 = create_sample_notification(
|
||||||
notify_db,
|
notify_db,
|
||||||
notify_db_session,
|
notify_db_session,
|
||||||
service=sample_service,
|
service=sample_service,
|
||||||
@@ -66,7 +76,7 @@ def test_update_status_of_notifications_after_timeout(notify_api,
|
|||||||
status='sending',
|
status='sending',
|
||||||
created_at=datetime.utcnow() - timedelta(
|
created_at=datetime.utcnow() - timedelta(
|
||||||
seconds=current_app.config.get('SENDING_NOTIFICATIONS_TIMEOUT_PERIOD') + 10))
|
seconds=current_app.config.get('SENDING_NOTIFICATIONS_TIMEOUT_PERIOD') + 10))
|
||||||
not2 = sample_notification(
|
not2 = create_sample_notification(
|
||||||
notify_db,
|
notify_db,
|
||||||
notify_db_session,
|
notify_db_session,
|
||||||
service=sample_service,
|
service=sample_service,
|
||||||
@@ -74,7 +84,7 @@ def test_update_status_of_notifications_after_timeout(notify_api,
|
|||||||
status='created',
|
status='created',
|
||||||
created_at=datetime.utcnow() - timedelta(
|
created_at=datetime.utcnow() - timedelta(
|
||||||
seconds=current_app.config.get('SENDING_NOTIFICATIONS_TIMEOUT_PERIOD') + 10))
|
seconds=current_app.config.get('SENDING_NOTIFICATIONS_TIMEOUT_PERIOD') + 10))
|
||||||
not3 = sample_notification(
|
not3 = create_sample_notification(
|
||||||
notify_db,
|
notify_db,
|
||||||
notify_db_session,
|
notify_db_session,
|
||||||
service=sample_service,
|
service=sample_service,
|
||||||
@@ -95,7 +105,7 @@ def test_not_update_status_of_notification_before_timeout(notify_api,
|
|||||||
sample_template,
|
sample_template,
|
||||||
mmg_provider):
|
mmg_provider):
|
||||||
with notify_api.test_request_context():
|
with notify_api.test_request_context():
|
||||||
not1 = sample_notification(
|
not1 = create_sample_notification(
|
||||||
notify_db,
|
notify_db,
|
||||||
notify_db_session,
|
notify_db_session,
|
||||||
service=sample_service,
|
service=sample_service,
|
||||||
@@ -111,7 +121,7 @@ def test_should_update_scheduled_jobs_and_put_on_queue(notify_db, notify_db_sess
|
|||||||
mocked = mocker.patch('app.celery.tasks.process_job.apply_async')
|
mocked = mocker.patch('app.celery.tasks.process_job.apply_async')
|
||||||
|
|
||||||
one_minute_in_the_past = datetime.utcnow() - timedelta(minutes=1)
|
one_minute_in_the_past = datetime.utcnow() - timedelta(minutes=1)
|
||||||
job = sample_job(notify_db, notify_db_session, scheduled_for=one_minute_in_the_past, job_status='scheduled')
|
job = create_sample_job(notify_db, notify_db_session, scheduled_for=one_minute_in_the_past, job_status='scheduled')
|
||||||
|
|
||||||
run_scheduled_jobs()
|
run_scheduled_jobs()
|
||||||
|
|
||||||
@@ -126,9 +136,24 @@ def test_should_update_all_scheduled_jobs_and_put_on_queue(notify_db, notify_db_
|
|||||||
one_minute_in_the_past = datetime.utcnow() - timedelta(minutes=1)
|
one_minute_in_the_past = datetime.utcnow() - timedelta(minutes=1)
|
||||||
ten_minutes_in_the_past = datetime.utcnow() - timedelta(minutes=10)
|
ten_minutes_in_the_past = datetime.utcnow() - timedelta(minutes=10)
|
||||||
twenty_minutes_in_the_past = datetime.utcnow() - timedelta(minutes=20)
|
twenty_minutes_in_the_past = datetime.utcnow() - timedelta(minutes=20)
|
||||||
job_1 = sample_job(notify_db, notify_db_session, scheduled_for=one_minute_in_the_past, job_status='scheduled')
|
job_1 = create_sample_job(
|
||||||
job_2 = sample_job(notify_db, notify_db_session, scheduled_for=ten_minutes_in_the_past, job_status='scheduled')
|
notify_db,
|
||||||
job_3 = sample_job(notify_db, notify_db_session, scheduled_for=twenty_minutes_in_the_past, job_status='scheduled')
|
notify_db_session,
|
||||||
|
scheduled_for=one_minute_in_the_past,
|
||||||
|
job_status='scheduled'
|
||||||
|
)
|
||||||
|
job_2 = create_sample_job(
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
scheduled_for=ten_minutes_in_the_past,
|
||||||
|
job_status='scheduled'
|
||||||
|
)
|
||||||
|
job_3 = create_sample_job(
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
scheduled_for=twenty_minutes_in_the_past,
|
||||||
|
job_status='scheduled'
|
||||||
|
)
|
||||||
|
|
||||||
run_scheduled_jobs()
|
run_scheduled_jobs()
|
||||||
|
|
||||||
@@ -150,10 +175,42 @@ def test_will_remove_csv_files_for_jobs_older_than_seven_days(notify_db, notify_
|
|||||||
midnight = datetime(2016, 10, 10, 0, 0, 0, 0)
|
midnight = datetime(2016, 10, 10, 0, 0, 0, 0)
|
||||||
one_millisecond_past_midnight = datetime(2016, 10, 10, 0, 0, 0, 1)
|
one_millisecond_past_midnight = datetime(2016, 10, 10, 0, 0, 0, 1)
|
||||||
|
|
||||||
job_1 = sample_job(notify_db, notify_db_session, created_at=one_millisecond_before_midnight)
|
job_1 = create_sample_job(notify_db, notify_db_session, created_at=one_millisecond_before_midnight)
|
||||||
sample_job(notify_db, notify_db_session, created_at=midnight)
|
create_sample_job(notify_db, notify_db_session, created_at=midnight)
|
||||||
sample_job(notify_db, notify_db_session, created_at=one_millisecond_past_midnight)
|
create_sample_job(notify_db, notify_db_session, created_at=one_millisecond_past_midnight)
|
||||||
|
|
||||||
with freeze_time('2016-10-17T00:00:00'):
|
with freeze_time('2016-10-17T00:00:00'):
|
||||||
remove_csv_files()
|
remove_csv_files()
|
||||||
s3.remove_job_from_s3.assert_called_once_with(job_1.service_id, job_1.id)
|
s3.remove_job_from_s3.assert_called_once_with(job_1.service_id, job_1.id)
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2016-01-11 12:30:00")
|
||||||
|
def test_send_daily_performance_stats_calls_with_correct_totals(notify_db, notify_db_session, sample_template, mocker):
|
||||||
|
perf_mock = mocker.patch('app.celery.scheduled_tasks.performance_platform_client.send_performance_stats')
|
||||||
|
|
||||||
|
notification_history = partial(
|
||||||
|
create_notification_history,
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
sample_template,
|
||||||
|
status='delivered'
|
||||||
|
)
|
||||||
|
|
||||||
|
notification_history(notification_type='email')
|
||||||
|
notification_history(notification_type='sms')
|
||||||
|
|
||||||
|
# Create some notifications for the day before
|
||||||
|
yesterday = datetime(2016, 1, 10, 15, 30, 0, 0)
|
||||||
|
with freeze_time(yesterday):
|
||||||
|
notification_history(notification_type='sms')
|
||||||
|
notification_history(notification_type='sms')
|
||||||
|
notification_history(notification_type='email')
|
||||||
|
notification_history(notification_type='email')
|
||||||
|
notification_history(notification_type='email')
|
||||||
|
|
||||||
|
send_daily_performance_stats()
|
||||||
|
|
||||||
|
perf_mock.assert_has_calls([
|
||||||
|
call(get_london_midnight_in_utc(yesterday), 'sms', 2, 'day'),
|
||||||
|
call(get_london_midnight_in_utc(yesterday), 'email', 3, 'day')
|
||||||
|
])
|
||||||
|
|||||||
120
tests/app/clients/test_performance_platform.py
Normal file
120
tests/app/clients/test_performance_platform.py
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import requests_mock
|
||||||
|
import pytest
|
||||||
|
from datetime import datetime
|
||||||
|
from freezegun import freeze_time
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
from app.clients.performance_platform.performance_platform_client import PerformancePlatformClient
|
||||||
|
from app.utils import (
|
||||||
|
get_london_midnight_in_utc,
|
||||||
|
get_midnight_for_day_before
|
||||||
|
)
|
||||||
|
from tests.app.conftest import sample_notification_history as create_notification_history
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def client(mocker):
|
||||||
|
client = PerformancePlatformClient()
|
||||||
|
current_app = mocker.Mock(config={
|
||||||
|
'PERFORMANCE_PLATFORM_ENABLED': True,
|
||||||
|
'PERFORMANCE_PLATFORM_URL': 'performance-platform-url',
|
||||||
|
'PERFORMANCE_PLATFORM_TOKEN': 'token'
|
||||||
|
})
|
||||||
|
client.init_app(current_app)
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
def test_should_not_call_if_not_enabled(notify_api, client, mocker):
|
||||||
|
mocker.patch.object(client, '_send_stats_to_performance_platform')
|
||||||
|
client.active = False
|
||||||
|
client.send_performance_stats(
|
||||||
|
date='2016-10-16T00:00:00+00:00',
|
||||||
|
channel='sms',
|
||||||
|
count=142,
|
||||||
|
period='day'
|
||||||
|
)
|
||||||
|
|
||||||
|
client._send_stats_to_performance_platform.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
def test_should_call_if_enabled(notify_api, client, mocker):
|
||||||
|
mocker.patch.object(client, '_send_stats_to_performance_platform')
|
||||||
|
client.send_performance_stats(
|
||||||
|
date='2016-10-16T00:00:00+00:00',
|
||||||
|
channel='sms',
|
||||||
|
count=142,
|
||||||
|
period='day'
|
||||||
|
)
|
||||||
|
|
||||||
|
assert client._send_stats_to_performance_platform.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_send_platform_stats_creates_correct_call(notify_api, client):
|
||||||
|
with requests_mock.Mocker() as request_mock:
|
||||||
|
request_mock.post(
|
||||||
|
client.performance_platform_url,
|
||||||
|
json={},
|
||||||
|
status_code=200
|
||||||
|
)
|
||||||
|
client.send_performance_stats(
|
||||||
|
date='2016-10-16T00:00:00+00:00',
|
||||||
|
channel='sms',
|
||||||
|
count=142,
|
||||||
|
period='day'
|
||||||
|
)
|
||||||
|
|
||||||
|
assert request_mock.call_count == 1
|
||||||
|
|
||||||
|
assert request_mock.request_history[0].url == client.performance_platform_url
|
||||||
|
assert request_mock.request_history[0].method == 'POST'
|
||||||
|
|
||||||
|
request_args = request_mock.request_history[0].json()
|
||||||
|
assert request_args['dataType'] == 'notifications'
|
||||||
|
assert request_args['service'] == 'govuk-notify'
|
||||||
|
assert request_args['period'] == 'day'
|
||||||
|
assert request_args['channel'] == 'sms'
|
||||||
|
assert request_args['_timestamp'] == '2016-10-16T00:00:00+00:00'
|
||||||
|
assert request_args['count'] == 142
|
||||||
|
expected_base64_id = 'MjAxNi0xMC0xNlQwMDowMDowMCswMDowMGdvdnVrLW5vdGlmeXNtc25vdGlmaWNhdGlvbnNkYXk='
|
||||||
|
assert request_args['_id'] == expected_base64_id
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2016-01-11 12:30:00")
|
||||||
|
def test_get_total_sent_notifications_yesterday_returns_expected_totals_dict(
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
client,
|
||||||
|
sample_template
|
||||||
|
):
|
||||||
|
notification_history = partial(
|
||||||
|
create_notification_history,
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
sample_template,
|
||||||
|
status='delivered'
|
||||||
|
)
|
||||||
|
|
||||||
|
notification_history(notification_type='email')
|
||||||
|
notification_history(notification_type='sms')
|
||||||
|
|
||||||
|
# Create some notifications for the day before
|
||||||
|
yesterday = datetime(2016, 1, 10, 15, 30, 0, 0)
|
||||||
|
with freeze_time(yesterday):
|
||||||
|
notification_history(notification_type='sms')
|
||||||
|
notification_history(notification_type='sms')
|
||||||
|
notification_history(notification_type='email')
|
||||||
|
notification_history(notification_type='email')
|
||||||
|
notification_history(notification_type='email')
|
||||||
|
|
||||||
|
total_count_dict = client.get_total_sent_notifications_yesterday()
|
||||||
|
|
||||||
|
assert total_count_dict == {
|
||||||
|
"start_date": get_midnight_for_day_before(datetime.utcnow()),
|
||||||
|
"end_date": get_london_midnight_in_utc(datetime.utcnow()),
|
||||||
|
"email": {
|
||||||
|
"count": 3
|
||||||
|
},
|
||||||
|
"sms": {
|
||||||
|
"count": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -526,14 +526,21 @@ def mock_statsd_timing(mocker):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def sample_notification_history(notify_db,
|
def sample_notification_history(
|
||||||
|
notify_db,
|
||||||
notify_db_session,
|
notify_db_session,
|
||||||
sample_template,
|
sample_template,
|
||||||
status='created',
|
status='created',
|
||||||
created_at=None):
|
created_at=None,
|
||||||
|
notification_type=None,
|
||||||
|
key_type=KEY_TYPE_NORMAL
|
||||||
|
):
|
||||||
if created_at is None:
|
if created_at is None:
|
||||||
created_at = datetime.utcnow()
|
created_at = datetime.utcnow()
|
||||||
|
|
||||||
|
if notification_type is None:
|
||||||
|
notification_type = sample_template.template_type
|
||||||
|
|
||||||
notification_history = NotificationHistory(
|
notification_history = NotificationHistory(
|
||||||
id=uuid.uuid4(),
|
id=uuid.uuid4(),
|
||||||
service=sample_template.service,
|
service=sample_template.service,
|
||||||
@@ -541,8 +548,8 @@ def sample_notification_history(notify_db,
|
|||||||
template_version=sample_template.version,
|
template_version=sample_template.version,
|
||||||
status=status,
|
status=status,
|
||||||
created_at=created_at,
|
created_at=created_at,
|
||||||
notification_type=sample_template.template_type,
|
notification_type=notification_type,
|
||||||
key_type=KEY_TYPE_NORMAL
|
key_type=key_type
|
||||||
)
|
)
|
||||||
notify_db.session.add(notification_history)
|
notify_db.session.add(notification_history)
|
||||||
notify_db.session.commit()
|
notify_db.session.commit()
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from datetime import datetime, timedelta, date
|
from datetime import datetime, timedelta, date
|
||||||
import pytz
|
|
||||||
import uuid
|
import uuid
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
@@ -35,6 +34,7 @@ from app.dao.notifications_dao import (
|
|||||||
get_notification_with_personalisation,
|
get_notification_with_personalisation,
|
||||||
get_notifications_for_job,
|
get_notifications_for_job,
|
||||||
get_notifications_for_service,
|
get_notifications_for_service,
|
||||||
|
get_total_sent_notifications_in_date_range,
|
||||||
update_notification_status_by_id,
|
update_notification_status_by_id,
|
||||||
update_notification_status_by_reference,
|
update_notification_status_by_reference,
|
||||||
dao_delete_notifications_and_history_by_id,
|
dao_delete_notifications_and_history_by_id,
|
||||||
@@ -43,9 +43,15 @@ from app.dao.notifications_dao import (
|
|||||||
get_april_fools)
|
get_april_fools)
|
||||||
|
|
||||||
from app.dao.services_dao import dao_update_service
|
from app.dao.services_dao import dao_update_service
|
||||||
|
from tests.app.conftest import (
|
||||||
from tests.app.conftest import (sample_notification, sample_template, sample_email_template, sample_service, sample_job,
|
sample_notification,
|
||||||
sample_api_key)
|
sample_template,
|
||||||
|
sample_email_template,
|
||||||
|
sample_service,
|
||||||
|
sample_job,
|
||||||
|
sample_api_key,
|
||||||
|
sample_notification_history as create_notification_history
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_should_have_decorated_notifications_dao_functions():
|
def test_should_have_decorated_notifications_dao_functions():
|
||||||
@@ -1306,3 +1312,113 @@ def test_get_april_fools():
|
|||||||
april_fools = get_april_fools(2016)
|
april_fools = get_april_fools(2016)
|
||||||
assert str(april_fools) == '2016-03-31 23:00:00'
|
assert str(april_fools) == '2016-03-31 23:00:00'
|
||||||
assert april_fools.tzinfo is None
|
assert april_fools.tzinfo is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('notification_type', ['sms', 'email'])
|
||||||
|
def test_get_total_sent_notifications_in_date_range_returns_only_in_date_range(
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
sample_template,
|
||||||
|
notification_type
|
||||||
|
):
|
||||||
|
notification_history = partial(
|
||||||
|
create_notification_history,
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
sample_template,
|
||||||
|
notification_type=notification_type,
|
||||||
|
status='delivered'
|
||||||
|
)
|
||||||
|
|
||||||
|
start_date = datetime(2000, 3, 30, 0, 0, 0, 0)
|
||||||
|
with freeze_time(start_date):
|
||||||
|
notification_history(created_at=start_date + timedelta(hours=3))
|
||||||
|
notification_history(created_at=start_date + timedelta(hours=5, minutes=10))
|
||||||
|
notification_history(created_at=start_date + timedelta(hours=11, minutes=59))
|
||||||
|
|
||||||
|
end_date = datetime(2000, 3, 31, 0, 0, 0, 0)
|
||||||
|
notification_history(created_at=end_date + timedelta(seconds=1))
|
||||||
|
notification_history(created_at=end_date + timedelta(minutes=10))
|
||||||
|
|
||||||
|
total_count = get_total_sent_notifications_in_date_range(start_date, end_date, notification_type)
|
||||||
|
assert total_count == 3
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('notification_type', ['sms', 'email'])
|
||||||
|
def test_get_total_sent_notifications_in_date_range_excludes_test_key_notifications(
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
sample_template,
|
||||||
|
notification_type
|
||||||
|
):
|
||||||
|
notification_history = partial(
|
||||||
|
create_notification_history,
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
sample_template,
|
||||||
|
notification_type=notification_type,
|
||||||
|
status='delivered'
|
||||||
|
)
|
||||||
|
|
||||||
|
start_date = datetime(2000, 3, 30, 0, 0, 0, 0)
|
||||||
|
end_date = datetime(2000, 3, 31, 0, 0, 0, 0)
|
||||||
|
with freeze_time(start_date):
|
||||||
|
notification_history(key_type=KEY_TYPE_TEAM)
|
||||||
|
notification_history(key_type=KEY_TYPE_TEAM)
|
||||||
|
notification_history(key_type=KEY_TYPE_NORMAL)
|
||||||
|
notification_history(key_type=KEY_TYPE_TEST)
|
||||||
|
|
||||||
|
total_count = get_total_sent_notifications_in_date_range(start_date, end_date, notification_type)
|
||||||
|
assert total_count == 3
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_total_sent_notifications_for_sms_excludes_email_counts(
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
sample_template
|
||||||
|
):
|
||||||
|
notification_history = partial(
|
||||||
|
create_notification_history,
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
sample_template,
|
||||||
|
status='delivered'
|
||||||
|
)
|
||||||
|
|
||||||
|
start_date = datetime(2000, 3, 30, 0, 0, 0, 0)
|
||||||
|
end_date = datetime(2000, 3, 31, 0, 0, 0, 0)
|
||||||
|
with freeze_time(start_date):
|
||||||
|
notification_history(notification_type='email')
|
||||||
|
notification_history(notification_type='email')
|
||||||
|
notification_history(notification_type='sms')
|
||||||
|
notification_history(notification_type='sms')
|
||||||
|
notification_history(notification_type='sms')
|
||||||
|
|
||||||
|
total_count = get_total_sent_notifications_in_date_range(start_date, end_date, 'sms')
|
||||||
|
assert total_count == 3
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_total_sent_notifications_for_email_excludes_sms_counts(
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
sample_template
|
||||||
|
):
|
||||||
|
notification_history = partial(
|
||||||
|
create_notification_history,
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
sample_template,
|
||||||
|
status='delivered'
|
||||||
|
)
|
||||||
|
|
||||||
|
start_date = datetime(2000, 3, 30, 0, 0, 0, 0)
|
||||||
|
end_date = datetime(2000, 3, 31, 0, 0, 0, 0)
|
||||||
|
with freeze_time(start_date):
|
||||||
|
notification_history(notification_type='email')
|
||||||
|
notification_history(notification_type='email')
|
||||||
|
notification_history(notification_type='sms')
|
||||||
|
notification_history(notification_type='sms')
|
||||||
|
notification_history(notification_type='sms')
|
||||||
|
|
||||||
|
total_count = get_total_sent_notifications_in_date_range(start_date, end_date, 'email')
|
||||||
|
assert total_count == 2
|
||||||
|
|||||||
25
tests/app/test_utils.py
Normal file
25
tests/app/test_utils.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from app.utils import (
|
||||||
|
get_london_midnight_in_utc,
|
||||||
|
get_midnight_for_day_before
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('date, expected_date', [
|
||||||
|
(datetime(2016, 1, 15, 0, 30), datetime(2016, 1, 15, 0, 0)),
|
||||||
|
(datetime(2016, 6, 15, 0, 0), datetime(2016, 6, 14, 23, 0)),
|
||||||
|
(datetime(2016, 9, 15, 11, 59), datetime(2016, 9, 14, 23, 0)),
|
||||||
|
])
|
||||||
|
def test_get_london_midnight_in_utc_returns_expected_date(date, expected_date):
|
||||||
|
assert get_london_midnight_in_utc(date) == expected_date
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('date, expected_date', [
|
||||||
|
(datetime(2016, 1, 15, 0, 30), datetime(2016, 1, 14, 0, 0)),
|
||||||
|
(datetime(2016, 7, 15, 0, 0), datetime(2016, 7, 13, 23, 0)),
|
||||||
|
(datetime(2016, 8, 23, 11, 59), datetime(2016, 8, 21, 23, 0)),
|
||||||
|
])
|
||||||
|
def test_get_midnight_for_day_before_returns_expected_date(date, expected_date):
|
||||||
|
assert get_midnight_for_day_before(date) == expected_date
|
||||||
Reference in New Issue
Block a user