Changed the scheduled_for datetime to only send and hour of a day to send.

Also expect the date being passed in is BST. The date is converted to UTC before saving. And converted to BST when returning a notification.
This commit is contained in:
Rebecca Law
2017-05-17 15:06:15 +01:00
parent 5f8338dd80
commit 973cc2c4c9
14 changed files with 64 additions and 46 deletions

View File

@@ -1,11 +1,11 @@
import base64 import base64
import json import json
from datetime import datetime, timedelta from datetime import datetime
import requests import requests
from flask import current_app from flask import current_app
from app.utils import get_midnight_for_day_before, get_london_midnight_in_utc, get_utc_time_in_bst from app.utils import get_midnight_for_day_before, get_london_midnight_in_utc, convert_utc_time_in_bst
class PerformancePlatformClient: class PerformancePlatformClient:
@@ -27,7 +27,7 @@ class PerformancePlatformClient:
def send_performance_stats(self, date, channel, count, period): def send_performance_stats(self, date, channel, count, period):
if self.active: if self.active:
payload = { payload = {
'_timestamp': get_utc_time_in_bst(date).isoformat(), '_timestamp': convert_utc_time_in_bst(date).isoformat(),
'service': 'govuk-notify', 'service': 'govuk-notify',
'channel': channel, 'channel': channel,
'count': count, 'count': count,

View File

@@ -29,7 +29,7 @@ from app import (
) )
from app.history_meta import Versioned from app.history_meta import Versioned
from app.utils import get_utc_time_in_bst from app.utils import convert_utc_time_in_bst
SMS_TYPE = 'sms' SMS_TYPE = 'sms'
EMAIL_TYPE = 'email' EMAIL_TYPE = 'email'
@@ -832,7 +832,7 @@ class Notification(db.Model):
}[self.template.template_type].get(self.status, self.status) }[self.template.template_type].get(self.status, self.status)
def serialize_for_csv(self): def serialize_for_csv(self):
created_at_in_bst = get_utc_time_in_bst(self.created_at) created_at_in_bst = convert_utc_time_in_bst(self.created_at)
serialized = { serialized = {
"row_number": '' if self.job_row_number is None else self.job_row_number + 1, "row_number": '' if self.job_row_number is None else self.job_row_number + 1,
"recipient": self.to, "recipient": self.to,
@@ -873,7 +873,7 @@ class Notification(db.Model):
"sent_at": self.sent_at.strftime(DATETIME_FORMAT) if self.sent_at else None, "sent_at": self.sent_at.strftime(DATETIME_FORMAT) if self.sent_at else None,
"completed_at": self.completed_at(), "completed_at": self.completed_at(),
"scheduled_for": self.scheduled_notification.scheduled_for.strftime( "scheduled_for": self.scheduled_notification.scheduled_for.strftime(
DATETIME_FORMAT) if self.scheduled_notification else None "%Y-%m-%d %H") if self.scheduled_notification else None
} }
return serialized return serialized

View File

@@ -15,7 +15,7 @@ from app.dao.notifications_dao import (dao_create_notification,
dao_created_scheduled_notification) dao_created_scheduled_notification)
from app.models import SMS_TYPE, Notification, KEY_TYPE_TEST, EMAIL_TYPE, ScheduledNotification from app.models import SMS_TYPE, Notification, KEY_TYPE_TEST, EMAIL_TYPE, ScheduledNotification
from app.v2.errors import BadRequestError, SendNotificationToQueueError from app.v2.errors import BadRequestError, SendNotificationToQueueError
from app.utils import get_template_instance, cache_key_for_service_template_counter from app.utils import get_template_instance, cache_key_for_service_template_counter, convert_bst_to_utc
def create_content_for_notification(template, personalisation): def create_content_for_notification(template, personalisation):
@@ -125,6 +125,7 @@ def simulated_recipient(to_address, notification_type):
def persist_scheduled_notification(notification_id, scheduled_for): def persist_scheduled_notification(notification_id, scheduled_for):
scheduled_datetime = convert_bst_to_utc(datetime.strptime(scheduled_for, "%Y-%m-%d %H"))
scheduled_notification = ScheduledNotification(notification_id=notification_id, scheduled_notification = ScheduledNotification(notification_id=notification_id,
scheduled_for=scheduled_for) scheduled_for=scheduled_datetime)
dao_created_scheduled_notification(scheduled_notification) dao_created_scheduled_notification(scheduled_notification)

View File

@@ -22,13 +22,13 @@ def validate(json_to_validate, schema):
return True return True
@format_checker.checks('datetime', raises=ValidationError) @format_checker.checks('datetime', raises=ValidationError)
def validate_schema_datetime(instance): def validate_schema_date_with_hour(instance):
if isinstance(instance, str): if isinstance(instance, str):
try: try:
datetime.strptime(instance, "%Y-%m-%d %H:%M:%S") datetime.strptime(instance, "%Y-%m-%d %H")
except ValueError as e: except ValueError as e:
raise ValidationError("datetime format is invalid. Use the format: " raise ValidationError("datetime format is invalid. Use the format: "
"YYYY-MM-DD HH:MM:SS, for example 2017-05-30 13:00:00") "YYYY-MM-DD HH, for example 2017-05-30 13")
return True return True
validator = Draft4Validator(schema, format_checker=format_checker) validator = Draft4Validator(schema, format_checker=format_checker)

View File

@@ -51,10 +51,14 @@ def get_midnight_for_day_before(date):
return get_london_midnight_in_utc(day_before) return get_london_midnight_in_utc(day_before)
def get_utc_time_in_bst(utc_dt): def convert_utc_time_in_bst(utc_dt):
return pytz.utc.localize(utc_dt).astimezone(local_timezone).replace(tzinfo=None) return pytz.utc.localize(utc_dt).astimezone(local_timezone).replace(tzinfo=None)
def convert_bst_to_utc(date):
return local_timezone.localize(date).astimezone(pytz.UTC).replace(tzinfo=None)
def get_london_month_from_utc_column(column): def get_london_month_from_utc_column(column):
""" """
Where queries need to count notifications by month it needs to be Where queries need to count notifications by month it needs to be

View File

@@ -1,10 +1,7 @@
from datetime import datetime
from app import DATETIME_FORMAT
from app.models import NOTIFICATION_STATUS_TYPES, TEMPLATE_TYPES from app.models import NOTIFICATION_STATUS_TYPES, TEMPLATE_TYPES
from app.schema_validation.definitions import (uuid, personalisation) from app.schema_validation.definitions import (uuid, personalisation)
# this may belong in a templates module
template = { template = {
"$schema": "http://json-schema.org/draft-04/schema#", "$schema": "http://json-schema.org/draft-04/schema#",
"description": "template schema", "description": "template schema",
@@ -204,8 +201,7 @@ def create_post_sms_response_from_notification(notification, body, from_number,
"template": __create_template_from_notification(notification=notification, "template": __create_template_from_notification(notification=notification,
url_root=url_root, url_root=url_root,
service_id=service_id), service_id=service_id),
"scheduled_for": datetime.strptime(scheduled_for, "scheduled_for": scheduled_for if scheduled_for else None
"%Y-%m-%d %H:%M:%S").strftime(DATETIME_FORMAT) if scheduled_for else None
} }
@@ -223,8 +219,7 @@ def create_post_email_response_from_notification(notification, content, subject,
"template": __create_template_from_notification(notification=notification, "template": __create_template_from_notification(notification=notification,
url_root=url_root, url_root=url_root,
service_id=service_id), service_id=service_id),
"scheduled_for": datetime.strptime(scheduled_for, "scheduled_for": scheduled_for if scheduled_for else None
"%Y-%m-%d %H:%M:%S").strftime(DATETIME_FORMAT) if scheduled_for else None
} }

View File

@@ -417,13 +417,13 @@ def test_should_send_all_scheduled_notifications_to_deliver_queue(notify_db,
sample_template, mocker): sample_template, mocker):
mocked = mocker.patch('app.celery.provider_tasks.deliver_sms') mocked = mocker.patch('app.celery.provider_tasks.deliver_sms')
message_to_deliver = sample_notification(notify_db=notify_db, notify_db_session=notify_db_session, message_to_deliver = sample_notification(notify_db=notify_db, notify_db_session=notify_db_session,
template=sample_template, scheduled_for="2017-05-01 13:50:00") template=sample_template, scheduled_for="2017-05-01 13")
sample_notification(notify_db=notify_db, notify_db_session=notify_db_session, sample_notification(notify_db=notify_db, notify_db_session=notify_db_session,
template=sample_template, scheduled_for="2017-05-01 10:50:00", status='delivered') template=sample_template, scheduled_for="2017-05-01 10", status='delivered')
sample_notification(notify_db=notify_db, notify_db_session=notify_db_session, sample_notification(notify_db=notify_db, notify_db_session=notify_db_session,
template=sample_template) template=sample_template)
sample_notification(notify_db=notify_db, notify_db_session=notify_db_session, sample_notification(notify_db=notify_db, notify_db_session=notify_db_session,
template=sample_template, scheduled_for="2017-05-01 14:30:00") template=sample_template, scheduled_for="2017-05-01 14")
send_scheduled_notifications() send_scheduled_notifications()

View File

@@ -494,7 +494,7 @@ def sample_notification(
scheduled_notification = ScheduledNotification(id=uuid.uuid4(), scheduled_notification = ScheduledNotification(id=uuid.uuid4(),
notification_id=notification.id, notification_id=notification.id,
scheduled_for=datetime.strptime(scheduled_for, scheduled_for=datetime.strptime(scheduled_for,
"%Y-%m-%d %H:%M:%S")) "%Y-%m-%d %H"))
db.session.add(scheduled_notification) db.session.add(scheduled_notification)
db.session.commit() db.session.commit()

View File

@@ -716,10 +716,10 @@ def test_save_notification_with_no_job(sample_template, mmg_provider):
assert notification_from_db.status == 'created' assert notification_from_db.status == 'created'
def test_get_notification_by_id(notify_db, notify_db_session, client, sample_template): def test_get_notification_by_id(notify_db, notify_db_session, sample_template):
notification = sample_notification(notify_db=notify_db, notify_db_session=notify_db_session, notification = sample_notification(notify_db=notify_db, notify_db_session=notify_db_session,
template=sample_template, template=sample_template,
scheduled_for='2017-05-05 14:00:00', scheduled_for='2017-05-05 14',
status='created') status='created')
notification_from_db = get_notification_with_personalisation( notification_from_db = get_notification_with_personalisation(
sample_template.service.id, sample_template.service.id,
@@ -727,7 +727,7 @@ def test_get_notification_by_id(notify_db, notify_db_session, client, sample_tem
key_type=None key_type=None
) )
assert notification == notification_from_db assert notification == notification_from_db
assert notification_from_db.scheduled_notification.scheduled_for == datetime(2017, 5, 5, 14, 0) assert notification_from_db.scheduled_notification.scheduled_for == datetime(2017, 5, 5, 14)
def test_get_notifications_by_reference(notify_db, notify_db_session, sample_service): def test_get_notifications_by_reference(notify_db, notify_db_session, sample_service):
@@ -1702,8 +1702,9 @@ def test_dao_get_notifications_by_to_field_search_ignores_spaces(sample_template
def test_dao_created_scheduled_notification(sample_notification): def test_dao_created_scheduled_notification(sample_notification):
scheduled_notification = ScheduledNotification(notification_id=sample_notification.id, scheduled_notification = ScheduledNotification(notification_id=sample_notification.id,
scheduled_for="2017-01-05 14:00:00") scheduled_for=datetime.strptime("2017-01-05 14", "%Y-%m-%d %H"))
dao_created_scheduled_notification(scheduled_notification) dao_created_scheduled_notification(scheduled_notification)
saved_notification = ScheduledNotification.query.all() saved_notification = ScheduledNotification.query.all()
assert len(saved_notification) == 1 assert len(saved_notification) == 1
@@ -1713,10 +1714,10 @@ def test_dao_created_scheduled_notification(sample_notification):
def test_dao_get_scheduled_notifications(notify_db, notify_db_session, sample_template): def test_dao_get_scheduled_notifications(notify_db, notify_db_session, sample_template):
notification_1 = sample_notification(notify_db=notify_db, notify_db_session=notify_db_session, notification_1 = sample_notification(notify_db=notify_db, notify_db_session=notify_db_session,
template=sample_template, scheduled_for='2017-05-05 14:00:00', template=sample_template, scheduled_for='2017-05-05 14',
status='created') status='created')
sample_notification(notify_db=notify_db, notify_db_session=notify_db_session, sample_notification(notify_db=notify_db, notify_db_session=notify_db_session,
template=sample_template, scheduled_for='2017-05-04 14:00:00', status='delivered') template=sample_template, scheduled_for='2017-05-04 14', status='delivered')
sample_notification(notify_db=notify_db, notify_db_session=notify_db_session, sample_notification(notify_db=notify_db, notify_db_session=notify_db_session,
template=sample_template, status='created') template=sample_template, status='created')
scheduled_notifications = dao_get_scheduled_notifications() scheduled_notifications = dao_get_scheduled_notifications()

View File

@@ -7,13 +7,14 @@ from sqlalchemy.exc import SQLAlchemyError
from freezegun import freeze_time from freezegun import freeze_time
from collections import namedtuple from collections import namedtuple
from app.models import Template, Notification, NotificationHistory from app.models import Template, Notification, NotificationHistory, ScheduledNotification
from app.notifications import SendNotificationToQueueError from app.notifications import SendNotificationToQueueError
from app.notifications.process_notifications import ( from app.notifications.process_notifications import (
create_content_for_notification, create_content_for_notification,
persist_notification, persist_notification,
send_notification_to_queue, send_notification_to_queue,
simulated_recipient simulated_recipient,
persist_scheduled_notification
) )
from notifications_utils.recipients import validate_and_format_phone_number, validate_and_format_email_address from notifications_utils.recipients import validate_and_format_phone_number, validate_and_format_email_address
from app.utils import cache_key_for_service_template_counter from app.utils import cache_key_for_service_template_counter
@@ -358,3 +359,11 @@ def test_persist_notification_with_international_info_does_not_store_for_email(
assert persisted_notification.international is False assert persisted_notification.international is False
assert persisted_notification.phone_prefix is None assert persisted_notification.phone_prefix is None
assert persisted_notification.rate_multiplier is None assert persisted_notification.rate_multiplier is None
def test_persist_scheduled_notification(sample_notification):
persist_scheduled_notification(sample_notification.id, '2017-05-12 14')
scheduled_notification = ScheduledNotification.query.all()
assert len(scheduled_notification) == 1
assert scheduled_notification[0].notification_id == sample_notification.id
assert scheduled_notification[0].scheduled_for == datetime.datetime(2017, 5, 12, 13, 0)

View File

@@ -4,8 +4,8 @@ import pytest
from app.utils import ( from app.utils import (
get_london_midnight_in_utc, get_london_midnight_in_utc,
get_midnight_for_day_before, get_midnight_for_day_before,
get_utc_time_in_bst convert_utc_time_in_bst,
) convert_bst_to_utc)
@pytest.mark.parametrize('date, expected_date', [ @pytest.mark.parametrize('date, expected_date', [
@@ -32,7 +32,15 @@ def test_get_midnight_for_day_before_returns_expected_date(date, expected_date):
(datetime(2017, 3, 28, 10, 0), datetime(2017, 3, 28, 11, 0)), (datetime(2017, 3, 28, 10, 0), datetime(2017, 3, 28, 11, 0)),
(datetime(2017, 10, 28, 1, 0), datetime(2017, 10, 28, 2, 0)), (datetime(2017, 10, 28, 1, 0), datetime(2017, 10, 28, 2, 0)),
(datetime(2017, 10, 29, 1, 0), datetime(2017, 10, 29, 1, 0)), (datetime(2017, 10, 29, 1, 0), datetime(2017, 10, 29, 1, 0)),
(datetime(2017, 5, 12, 14), datetime(2017, 5, 12, 15, 0))
]) ])
def test_get_utc_in_bst_returns_expected_date(date, expected_date): def test_get_utc_in_bst_returns_expected_date(date, expected_date):
ret_date = get_utc_time_in_bst(date) ret_date = convert_utc_time_in_bst(date)
assert ret_date == expected_date assert ret_date == expected_date
def test_convert_bst_to_utc():
bst = "2017-05-12 13"
bst_datetime = datetime.strptime(bst, "%Y-%m-%d %H")
utc = convert_bst_to_utc(bst_datetime)
assert utc == datetime(2017, 5, 12, 12, 0)

View File

@@ -20,12 +20,12 @@ def test_get_notification_by_id_returns_200(
): ):
sample_notification = create_sample_notification( sample_notification = create_sample_notification(
notify_db, notify_db_session, billable_units=billable_units, sent_by=provider, notify_db, notify_db_session, billable_units=billable_units, sent_by=provider,
scheduled_for="2017-05-12 14:00:00" scheduled_for="2017-05-12 14"
) )
another = create_sample_notification( another = create_sample_notification(
notify_db, notify_db_session, billable_units=billable_units, sent_by=provider, notify_db, notify_db_session, billable_units=billable_units, sent_by=provider,
scheduled_for="2017-06-12 14:00:00" scheduled_for="2017-06-12 14"
) )
auth_header = create_authorization_header(service_id=sample_notification.service_id) auth_header = create_authorization_header(service_id=sample_notification.service_id)
response = client.get( response = client.get(
@@ -63,7 +63,7 @@ def test_get_notification_by_id_returns_200(
"subject": None, "subject": None,
'sent_at': sample_notification.sent_at, 'sent_at': sample_notification.sent_at,
'completed_at': sample_notification.completed_at(), 'completed_at': sample_notification.completed_at(),
'scheduled_for': '2017-05-12T14:00:00.000000Z' 'scheduled_for': '2017-05-12 14'
} }
assert json_response == expected_response assert json_response == expected_response
@@ -139,7 +139,7 @@ def test_get_notification_by_reference_returns_200(client, notify_db, notify_db_
def test_get_notifications_returns_scheduled_for(client, notify_db, notify_db_session): def test_get_notifications_returns_scheduled_for(client, notify_db, notify_db_session):
sample_notification_with_reference = create_sample_notification( sample_notification_with_reference = create_sample_notification(
notify_db, notify_db_session, client_reference='some-client-reference', scheduled_for='2017-05-23 16:00:00') notify_db, notify_db_session, client_reference='some-client-reference', scheduled_for='2017-05-23 16')
auth_header = create_authorization_header(service_id=sample_notification_with_reference.service_id) auth_header = create_authorization_header(service_id=sample_notification_with_reference.service_id)
response = client.get( response = client.get(
@@ -153,7 +153,7 @@ def test_get_notifications_returns_scheduled_for(client, notify_db, notify_db_se
assert len(json_response['notifications']) == 1 assert len(json_response['notifications']) == 1
assert json_response['notifications'][0]['id'] == str(sample_notification_with_reference.id) assert json_response['notifications'][0]['id'] == str(sample_notification_with_reference.id)
assert json_response['notifications'][0]['scheduled_for'] == "2017-05-23T16:00:00.000000Z" assert json_response['notifications'][0]['scheduled_for'] == "2017-05-23 16"
def test_get_notification_by_reference_nonexistent_reference_returns_no_notifications(client, sample_service): def test_get_notification_by_reference_nonexistent_reference_returns_no_notifications(client, sample_service):

View File

@@ -360,7 +360,7 @@ def test_get_notifications_response_with_email_and_phone_number():
def test_post_schema_valid_scheduled_for(schema): def test_post_schema_valid_scheduled_for(schema):
j = {"template_id": str(uuid.uuid4()), j = {"template_id": str(uuid.uuid4()),
"email_address": "joe@gmail.com", "email_address": "joe@gmail.com",
"scheduled_for": "2017-05-12 13:00:00"} "scheduled_for": "2017-05-12 13"}
if schema == post_email_request_schema: if schema == post_email_request_schema:
j.update({"email_address": "joe@gmail.com"}) j.update({"email_address": "joe@gmail.com"})
else: else:
@@ -369,7 +369,7 @@ def test_post_schema_valid_scheduled_for(schema):
@pytest.mark.parametrize("invalid_datetime", @pytest.mark.parametrize("invalid_datetime",
["2017-05-12 13:00", "13:00:00 2017-01-01"]) ["2017-05-12 13:00:00", "13:00:00 2017-01-01"])
@pytest.mark.parametrize("schema", @pytest.mark.parametrize("schema",
[post_email_request_schema, post_sms_request_schema]) [post_email_request_schema, post_sms_request_schema])
def test_post_email_schema_invalid_scheduled_for(invalid_datetime, schema): def test_post_email_schema_invalid_scheduled_for(invalid_datetime, schema):
@@ -385,4 +385,4 @@ def test_post_email_schema_invalid_scheduled_for(invalid_datetime, schema):
assert error['status_code'] == 400 assert error['status_code'] == 400
assert error['errors'] == [{'error': 'ValidationError', assert error['errors'] == [{'error': 'ValidationError',
'message': "scheduled_for datetime format is invalid. Use the format: " 'message': "scheduled_for datetime format is invalid. Use the format: "
"YYYY-MM-DD HH:MM:SS, for example 2017-05-30 13:00:00"}] "YYYY-MM-DD HH, for example 2017-05-30 13"}]

View File

@@ -1,8 +1,8 @@
import uuid import uuid
import pytest import pytest
from flask import json from flask import json
from app import DATETIME_FORMAT
from app.models import Notification, ScheduledNotification from app.models import Notification, ScheduledNotification
from app.v2.errors import RateLimitError from app.v2.errors import RateLimitError
from tests import create_authorization_header from tests import create_authorization_header
@@ -358,7 +358,7 @@ def test_post_notification_with_scheduled_for(client, sample_template, sample_em
data = { data = {
key_send_to: send_to, key_send_to: send_to,
'template_id': str(sample_email_template.id) if notification_type == 'email' else str(sample_template.id), 'template_id': str(sample_email_template.id) if notification_type == 'email' else str(sample_template.id),
'scheduled_for': '2017-05-14 15:00:00' 'scheduled_for': '2017-05-14 14'
} }
auth_header = create_authorization_header(service_id=sample_template.service_id) auth_header = create_authorization_header(service_id=sample_template.service_id)
@@ -370,4 +370,4 @@ def test_post_notification_with_scheduled_for(client, sample_template, sample_em
scheduled_notification = ScheduledNotification.query.all() scheduled_notification = ScheduledNotification.query.all()
assert len(scheduled_notification) == 1 assert len(scheduled_notification) == 1
assert resp_json["id"] == str(scheduled_notification[0].notification_id) assert resp_json["id"] == str(scheduled_notification[0].notification_id)
assert resp_json["scheduled_for"] == scheduled_notification[0].scheduled_for.strftime(DATETIME_FORMAT) assert resp_json["scheduled_for"] == '2017-05-14 14'