Compare commits

...

75 Commits

Author SHA1 Message Date
Andrew Shumway
e956ca5017 Add more debug 2024-07-23 13:37:52 -06:00
Andrew Shumway
bba3fb1d73 Debug 2024-07-17 07:46:00 -06:00
Andrew Shumway
ce729c2f26 pull main 2024-07-10 11:03:06 -06:00
Andrew Shumway
27b7d8c70d Merge branch 'main' of https://github.com/GSA/notifications-api 2024-07-10 08:41:11 -06:00
Carlo Costino
c671189f4e Merge pull request #1170 from GSA/notify-api-1169
wrap calls to get personalization and phone so dashboard doesn't explode
2024-07-05 14:20:26 -04:00
Kenneth Kehl
e902e69082 wrap calls to get personalization and phone number so dashboard doesn't explode 2024-07-05 11:02:28 -07:00
Andrew Shumway
5cd2e8416b Merge branch 'main' of https://github.com/GSA/notifications-api 2024-07-05 09:07:34 -06:00
Carlo Costino
c242bcb1f2 Merge pull request #1168 from GSA/dependabot/pip/certifi-2024.7.4
Bump certifi from 2024.6.2 to 2024.7.4
2024-07-05 09:23:48 -04:00
dependabot[bot]
f8fb65d6d6 Bump certifi from 2024.6.2 to 2024.7.4
Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.6.2 to 2024.7.4.
- [Commits](https://github.com/certifi/python-certifi/compare/2024.06.02...2024.07.04)

---
updated-dependencies:
- dependency-name: certifi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-05 12:45:41 +00:00
Carlo Costino
a6bfd6efce Merge pull request #1167 from GSA/dependabot/pip/botocore-1.34.139
Bump botocore from 1.34.138 to 1.34.139
2024-07-05 08:37:08 -04:00
dependabot[bot]
ef4eee5645 Bump botocore from 1.34.138 to 1.34.139
Bumps [botocore](https://github.com/boto/botocore) from 1.34.138 to 1.34.139.
- [Changelog](https://github.com/boto/botocore/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/botocore/compare/1.34.138...1.34.139)

---
updated-dependencies:
- dependency-name: botocore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-05 12:26:23 +00:00
Carlo Costino
c67077cbff Merge pull request #1166 from GSA/dependabot/pip/boto3-1.34.138
Bump boto3 from 1.34.136 to 1.34.138
2024-07-05 08:23:25 -04:00
dependabot[bot]
f901f6d04a Bump boto3 from 1.34.136 to 1.34.138
Bumps [boto3](https://github.com/boto/boto3) from 1.34.136 to 1.34.138.
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.34.136...1.34.138)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-03 21:50:27 +00:00
Carlo Costino
5c27b62e39 Merge pull request #1078 from GSA/all_users_command
Generate a way to export all Notify.gov user contact information
2024-07-03 16:14:34 -04:00
Carlo Costino
efe45c81ea Merge pull request #1164 from GSA/debug_wash
add debug steps
2024-07-03 14:32:00 -04:00
Carlo Costino
9c39a92545 Merge pull request #1059 from GSA/1006-new-api-failed-and-delivered-messages-7-days
1006/1007/1008 created new API endpoints for front end statistics
2024-07-03 14:09:06 -04:00
Kenneth Kehl
7f33e4445c add debug steps 2024-07-03 10:18:17 -07:00
Carlo Costino
7699b3aab9 Merge pull request #1162 from GSA/debug_wash
fix command syntax
2024-07-03 11:58:49 -04:00
Kenneth Kehl
723b89da51 fix command syntax 2024-07-03 08:44:13 -07:00
Carlo Costino
e6bc071b8d Merge pull request #1160 from GSA/debug_wash
fix tuple
2024-07-03 11:07:48 -04:00
Kenneth Kehl
b436de3b23 fix tuple 2024-07-03 07:49:59 -07:00
Carlo Costino
0867acfb68 Merge pull request #1158 from GSA/dependabot/pip/phonenumbers-8.13.40
Bump phonenumbers from 8.13.39 to 8.13.40
2024-07-02 17:46:53 -04:00
dependabot[bot]
53162de32e Bump phonenumbers from 8.13.39 to 8.13.40
Bumps [phonenumbers](https://github.com/daviddrysdale/python-phonenumbers) from 8.13.39 to 8.13.40.
- [Commits](https://github.com/daviddrysdale/python-phonenumbers/compare/v8.13.39...v8.13.40)

---
updated-dependencies:
- dependency-name: phonenumbers
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-02 21:38:08 +00:00
Carlo Costino
1337796172 Merge pull request #1157 from GSA/dependabot/pip/botocore-1.34.138
Bump botocore from 1.34.136 to 1.34.138
2024-07-02 17:35:18 -04:00
dependabot[bot]
872b4b195c Bump botocore from 1.34.136 to 1.34.138
Bumps [botocore](https://github.com/boto/botocore) from 1.34.136 to 1.34.138.
- [Changelog](https://github.com/boto/botocore/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/botocore/compare/1.34.136...1.34.138)

---
updated-dependencies:
- dependency-name: botocore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-02 21:22:50 +00:00
Carlo Costino
a0e31187dd Merge pull request #1154 from GSA/dependabot/pip/setuptools-70.2.0
Bump setuptools from 70.1.1 to 70.2.0
2024-07-02 17:17:40 -04:00
dependabot[bot]
fe45366bb0 Bump setuptools from 70.1.1 to 70.2.0
Bumps [setuptools](https://github.com/pypa/setuptools) from 70.1.1 to 70.2.0.
- [Release notes](https://github.com/pypa/setuptools/releases)
- [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/setuptools/compare/v70.1.1...v70.2.0)

---
updated-dependencies:
- dependency-name: setuptools
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-02 21:00:47 +00:00
Carlo Costino
5ba070bebf Merge pull request #1153 from GSA/dependabot/pip/moto-5.0.10
Bump moto from 5.0.9 to 5.0.10
2024-07-02 16:58:52 -04:00
Cliff Hill
c368d3d3f2 More import adjustments.
Signed-off-by: Cliff Hill <Clifford.hill@gsa.gov>
2024-07-02 16:00:58 -04:00
Cliff Hill
8708d8ddce Fixin' imports.
Signed-off-by: Cliff Hill <Clifford.hill@gsa.gov>
2024-07-02 15:57:14 -04:00
Cliff Hill
f95d3e0b99 Made changes I requested to be done.
Signed-off-by: Cliff Hill <Clifford.hill@gsa.gov>
2024-07-02 15:38:22 -04:00
Kenneth Kehl
328c211eb6 remove task 2024-07-02 11:55:19 -07:00
Kenneth Kehl
700303829c merge from main 2024-07-02 11:47:34 -07:00
dependabot[bot]
9cacb9cac9 Bump moto from 5.0.9 to 5.0.10
Bumps [moto](https://github.com/getmoto/moto) from 5.0.9 to 5.0.10.
- [Release notes](https://github.com/getmoto/moto/releases)
- [Changelog](https://github.com/getmoto/moto/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getmoto/moto/compare/5.0.9...5.0.10)

---
updated-dependencies:
- dependency-name: moto
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 21:39:32 +00:00
Andrew Shumway
c78a53eb25 Merge branch 'main' of https://github.com/GSA/notifications-api 2024-06-27 08:45:37 -06:00
Kenneth Kehl
8e6a6d42db remove task 2024-06-26 11:59:17 -07:00
Kenneth Kehl
bae4639395 merge from main 2024-06-26 11:40:46 -07:00
Anastasia Gradova
b2e5522d09 Corrected test cases for new stats endpoints 2024-06-24 17:58:37 -06:00
Anastasia Gradova
16555ab0e2 Merge branch 'main' into 1006-new-api-failed-and-delivered-messages-7-days 2024-06-24 16:51:58 -06:00
Anastasia Gradova
47c8964766 fetch_notification_status_for_service_by_month
altered to use NotificationAllTimeView like
the other endpoints
2024-06-21 16:58:09 -06:00
Anastasia Gradova
d3d2610578 correct timedelta for midnight that offest
calculation for today
2024-06-21 14:38:54 -06:00
Kenneth Kehl
0bba77560a merge from main 2024-06-21 08:45:28 -07:00
Anastasia Gradova
966f9b4050 moved generate_date_range to date_util
standardized the SQLAlchemy calls
refactored the endpoints in service/rest.py
2024-06-20 23:12:47 -06:00
Anastasia Gradova
abc0ba9281 Merge branch 'main' into 1006-new-api-failed-and-delivered-messages-7-days 2024-06-20 08:35:12 -06:00
Anastasia Gradova
fd37923294 Updated SQLAlchemy queries and API endpoints
for single database queries to improve
application performance.
2024-06-20 01:04:31 -06:00
Anastasia Gradova
e293f7e3f5 Updated all usage of datetime.utcnow() to
app.utils utc_now() function. Added new endpoint
/service/{{service_id}}/notifications/month
2024-06-14 16:01:04 -06:00
Kenneth Kehl
fae88700aa merge from main 2024-06-14 09:33:20 -07:00
Kenneth Kehl
bff2df514f change it to a task 2024-06-14 09:32:58 -07:00
Kenneth Kehl
9408c9955b merge from main 2024-06-13 09:46:17 -07:00
Andrew Shumway
9c1832cac0 Merge branch 'main' of https://github.com/GSA/notifications-api 2024-06-12 09:30:58 -06:00
Anastasia Gradova
0082ba3dd0 Updated sort order, endpoint defenition, and tests
for REQUESTED attribute
2024-06-06 22:11:24 -06:00
Anastasia Gradova
cd188180ca Added new endpoint for getting statistics for a
service, for a user, by a number of days
2024-06-06 16:37:12 -06:00
Anastasia Gradova
a5055a0cf9 Updated endpoints to use the
NotificationAllTimeView which is a view created
to merge notifiations and notification_history
2024-06-06 16:00:12 -06:00
Anastasia Gradova
227b9a3fc9 Merge branch 'main' into 1006-new-api-failed-and-delivered-messages-7-days 2024-06-05 11:47:55 -06:00
Anastasia Gradova
41f2416216 reversed direction of the day count 2024-06-03 21:38:29 -06:00
Andrew Shumway
50d7b9a7be poetry lock 2024-06-03 15:53:59 -06:00
Andrew Shumway
c2f29ee0dd Merge branch 'main' of https://github.com/GSA/notifications-api 2024-05-31 09:31:39 -06:00
Andrew Shumway
99ee0e21d9 poetry lock 2024-05-29 10:07:23 -06:00
Anastasia Gradova
140e40ebe0 Updated pytest for new return values 2024-05-25 22:14:51 -06:00
Anastasia Gradova
5fbad5bd69 Added endpoints for #1006 and #1007 2024-05-25 20:59:08 -06:00
Anastasia Gradova
5cd68e8081 New endpoints for #1006 and #1007 2024-05-22 13:33:32 -06:00
Andrew Shumway
11cb5d9682 Merge branch 'main' of https://github.com/GSA/notifications-api 2024-05-10 10:26:20 -06:00
Andrew Shumway
1e1a6dffd7 poetry lock 2024-05-10 10:26:10 -06:00
Andrew Shumway
27d6579133 Merge branch 'main' of https://github.com/GSA/notifications-api 2024-05-08 09:00:43 -06:00
Andrew Shumway
a76237b7a9 Merge branch 'main' of https://github.com/GSA/notifications-api 2024-05-02 14:16:36 -06:00
Andrew Shumway
bd5f43d28f poetry lock 2024-04-26 10:16:04 -06:00
Andrew Shumway
482be89519 Merge branch 'main' of https://github.com/GSA/notifications-api 2024-04-25 15:27:49 -06:00
Andrew Shumway
ed61be8736 Poetry lock 2024-04-24 09:30:37 -06:00
Andrew Shumway
cc0c8ddd30 Merge branch 'main' of https://github.com/GSA/notifications-api 2024-04-24 09:30:23 -06:00
Andrew Shumway
da4dbc9c85 Merge branch 'main' of https://github.com/GSA/notifications-api 2024-04-22 10:47:05 -06:00
Andrew Shumway
16a7bee93e poetry lock 2024-04-22 10:47:00 -06:00
Andrew Shumway
9cf048b754 Merge branch 'main' of https://github.com/GSA/notifications-api 2024-04-18 10:15:04 -06:00
Andrew Shumway
c6c05caf44 Merge branch 'main' of https://github.com/GSA/notifications-api 2024-04-16 14:51:15 -06:00
Andrew Shumway
e1382989c3 poetry lock 2024-04-16 14:51:11 -06:00
Kenneth Kehl
7ef70c39b9 show all users 2024-04-02 11:43:01 -07:00
32 changed files with 612 additions and 231 deletions

View File

@@ -1,5 +1,7 @@
import re
import csv
from app.utils import hilite
import botocore
from boto3 import Session
from expiringdict import ExpiringDict
@@ -124,17 +126,28 @@ def extract_phones(job):
def extract_personalisation(job):
print(hilite(f"job type: {type(job)}"))
print(hilite(f"Job? {job}"))
job = job.split("\r\n")
print(hilite(f"job after first split: {job}"))
first_row = job[0]
print(hilite(f"first_row: {first_row}"))
job.pop(0)
first_row = first_row.split(",")
print(hilite(f"first_row again: {first_row}"))
personalisation = {}
job_row = 0
for row in job:
row = row.split(",")
row_csv_module = csv.reader(job)
for row in row_csv_module:
print(hilite(f"row: {row}"))
temp = dict(zip(first_row, row))
print(hilite(f"temp: {temp}"))
personalisation[job_row] = temp
job_row = job_row + 1
print(hilite(f"job_row: {job_row}"))
print(hilite(f"personalisation: {personalisation}"))
return personalisation
@@ -190,6 +203,7 @@ def get_personalisation_from_s3(service_id, job_id, job_row_number):
job = JOBS.get(job_id)
if job is None:
job = get_job_from_s3(service_id, job_id)
print(hilite(f"job at the beginning: {job}"))
JOBS[job_id] = job
incr_jobs_cache_misses()
else:

View File

@@ -18,7 +18,7 @@ from app.dao.notifications_dao import (
from app.delivery import send_to_providers
from app.enums import NotificationStatus
from app.exceptions import NotificationTechnicalFailureException
from app.utils import utc_now
from app.utils import hilite, utc_now
# This is the amount of time to wait after sending an sms message before we check the aws logs and look for delivery
# receipts
@@ -100,7 +100,7 @@ def deliver_sms(self, notification_id):
notification = notifications_dao.get_notification_by_id(notification_id)
ansi_green = "\033[32m"
ansi_reset = "\033[0m"
print(hilite(f"notification inside of deliver_sms: {notification}"))
if not notification:
raise NoResultFound()
if (

View File

@@ -170,6 +170,7 @@ def __total_sending_limits_for_job_exceeded(service, job, job_id):
def save_sms(self, service_id, notification_id, encrypted_notification, sender_id=None):
"""Persist notification to db and place notification in queue to send to sns."""
notification = encryption.decrypt(encrypted_notification)
print(hilite(f"notification at the top of save_sms: {notification}"))
# SerialisedService and SerialisedTemplate classes are
# used here to grab the same service and template from the cache
# to improve performance.

View File

@@ -2,6 +2,7 @@ import os
import re
from time import monotonic
from app.utils import hilite
import botocore
import phonenumbers
from boto3 import client
@@ -54,7 +55,7 @@ class AwsSnsClient(SmsClient):
to = phonenumbers.format_number(
match.number, phonenumbers.PhoneNumberFormat.E164
)
print(hilite(f"to in send_sms: {to}"))
# See documentation
# https://docs.aws.amazon.com/sns/latest/dg/sms_publish-to-phone.html#sms_publish_sdk
attributes = {

View File

@@ -593,10 +593,10 @@ def process_row_from_job(job_id, job_row_number):
@click.option("-f", "--csv_filename", required=True, help="csv file name")
def download_csv_file_by_name(csv_filename):
bucket_name = (current_app.config["CSV_UPLOAD_BUCKET"]["bucket"],)
access_key = (current_app.config["CSV_UPLOAD_BUCKET"]["access_key_id"],)
secret = (current_app.config["CSV_UPLOAD_BUCKET"]["secret_access_key"],)
region = (current_app.config["CSV_UPLOAD_BUCKET"]["region"],)
bucket_name = current_app.config["CSV_UPLOAD_BUCKET"]["bucket"]
access_key = current_app.config["CSV_UPLOAD_BUCKET"]["access_key_id"]
secret = current_app.config["CSV_UPLOAD_BUCKET"]["secret_access_key"]
region = current_app.config["CSV_UPLOAD_BUCKET"]["region"]
print(s3.get_s3_file(bucket_name, csv_filename, access_key, secret, region))
@@ -865,14 +865,12 @@ def promote_user_to_platform_admin(user_email_address):
@notify_command(name="purge-csv-bucket")
def purge_csv_bucket():
bucket_name = (current_app.config["CSV_UPLOAD_BUCKET"]["bucket"],)
access_key = (current_app.config["CSV_UPLOAD_BUCKET"]["access_key_id"],)
secret = (current_app.config["CSV_UPLOAD_BUCKET"]["secret_access_key"],)
region = (current_app.config["CSV_UPLOAD_BUCKET"]["region"],)
bucket_name = current_app.config["CSV_UPLOAD_BUCKET"]["bucket"]
access_key = current_app.config["CSV_UPLOAD_BUCKET"]["access_key_id"]
secret = current_app.config["CSV_UPLOAD_BUCKET"]["secret_access_key"]
region = current_app.config["CSV_UPLOAD_BUCKET"]["region"]
print("ABOUT TO RUN PURGE CSV BUCKET")
s3.purge_bucket(bucket_name, access_key, secret, region)
print("RAN PURGE CSV BUCKET")
"""

View File

@@ -1,3 +1,4 @@
import calendar
from datetime import date, datetime, time, timedelta
from app.utils import utc_now
@@ -66,3 +67,29 @@ def get_calendar_year_for_datetime(start_date):
return year - 1
else:
return year
def get_number_of_days_for_month(year, month):
return calendar.monthrange(year, month)[1]
def generate_date_range(start_date, end_date=None, days=0):
if end_date:
current_date = start_date
while current_date <= end_date:
try:
yield current_date.date()
except ValueError:
pass
current_date += timedelta(days=1)
elif days > 0:
end_date = start_date + timedelta(days=days)
current_date = start_date
while current_date < end_date:
try:
yield current_date.date()
except ValueError:
pass
current_date += timedelta(days=1)
else:
return "An end_date or number of days must be specified"

View File

@@ -84,21 +84,21 @@ def update_fact_notification_status(process_day, notification_type, service_id):
def fetch_notification_status_for_service_by_month(start_date, end_date, service_id):
return (
db.session.query(
func.date_trunc("month", FactNotificationStatus.local_date).label("month"),
FactNotificationStatus.notification_type,
FactNotificationStatus.notification_status,
func.sum(FactNotificationStatus.notification_count).label("count"),
func.date_trunc("month", NotificationAllTimeView.created_at).label("month"),
NotificationAllTimeView.notification_type,
NotificationAllTimeView.status.label("notification_status"),
func.count(NotificationAllTimeView.id).label("count"),
)
.filter(
FactNotificationStatus.service_id == service_id,
FactNotificationStatus.local_date >= start_date,
FactNotificationStatus.local_date < end_date,
FactNotificationStatus.key_type != KeyType.TEST,
NotificationAllTimeView.service_id == service_id,
NotificationAllTimeView.created_at >= start_date,
NotificationAllTimeView.created_at < end_date,
NotificationAllTimeView.key_type != KeyType.TEST,
)
.group_by(
func.date_trunc("month", FactNotificationStatus.local_date).label("month"),
FactNotificationStatus.notification_type,
FactNotificationStatus.notification_status,
func.date_trunc("month", NotificationAllTimeView.created_at).label("month"),
NotificationAllTimeView.notification_type,
NotificationAllTimeView.status,
)
.all()
)

View File

@@ -8,7 +8,7 @@ from sqlalchemy.sql.expression import and_, asc, case, func
from app import db
from app.dao.dao_utils import VersionOptions, autocommit, version_class
from app.dao.date_util import get_current_calendar_year
from app.dao.date_util import generate_date_range, get_current_calendar_year
from app.dao.organization_dao import dao_get_organization_by_email_address
from app.dao.service_sms_sender_dao import insert_service_sms_sender
from app.dao.service_user_dao import dao_get_service_user
@@ -27,6 +27,7 @@ from app.models import (
InvitedUser,
Job,
Notification,
NotificationAllTimeView,
NotificationHistory,
Organization,
Permission,
@@ -40,6 +41,7 @@ from app.models import (
User,
VerifyCode,
)
from app.service import statistics
from app.utils import (
escape_special_characters,
get_archived_db_column_value,
@@ -426,6 +428,61 @@ def dao_fetch_todays_stats_for_service(service_id):
)
def dao_fetch_stats_for_service_from_days(service_id, start_date, end_date):
start_date = get_midnight_in_utc(start_date)
end_date = get_midnight_in_utc(end_date + timedelta(days=1))
return (
db.session.query(
NotificationAllTimeView.notification_type,
NotificationAllTimeView.status,
func.date_trunc("day", NotificationAllTimeView.created_at).label("day"),
func.count(NotificationAllTimeView.id).label("count"),
)
.filter(
NotificationAllTimeView.service_id == service_id,
NotificationAllTimeView.key_type != KeyType.TEST,
NotificationAllTimeView.created_at >= start_date,
NotificationAllTimeView.created_at < end_date,
)
.group_by(
NotificationAllTimeView.notification_type,
NotificationAllTimeView.status,
func.date_trunc("day", NotificationAllTimeView.created_at),
)
.all()
)
def dao_fetch_stats_for_service_from_days_for_user(
service_id, start_date, end_date, user_id
):
start_date = get_midnight_in_utc(start_date)
end_date = get_midnight_in_utc(end_date + timedelta(days=1))
return (
db.session.query(
NotificationAllTimeView.notification_type,
NotificationAllTimeView.status,
func.date_trunc("day", NotificationAllTimeView.created_at).label("day"),
func.count(NotificationAllTimeView.id).label("count"),
)
.filter(
NotificationAllTimeView.service_id == service_id,
NotificationAllTimeView.key_type != KeyType.TEST,
NotificationAllTimeView.created_at >= start_date,
NotificationAllTimeView.created_at < end_date,
NotificationAllTimeView.created_by_id == user_id,
)
.group_by(
NotificationAllTimeView.notification_type,
NotificationAllTimeView.status,
func.date_trunc("day", NotificationAllTimeView.created_at),
)
.all()
)
def dao_fetch_todays_stats_for_all_services(
include_from_test_key=True, only_active=True
):
@@ -607,3 +664,52 @@ def get_live_services_with_organization():
)
return query.all()
def fetch_notification_stats_for_service_by_month_by_user(
start_date, end_date, service_id, user_id
):
return (
db.session.query(
func.date_trunc("month", NotificationAllTimeView.created_at).label("month"),
NotificationAllTimeView.notification_type,
(NotificationAllTimeView.status).label("notification_status"),
func.count(NotificationAllTimeView.id).label("count"),
)
.filter(
NotificationAllTimeView.service_id == service_id,
NotificationAllTimeView.created_at >= start_date,
NotificationAllTimeView.created_at < end_date,
NotificationAllTimeView.key_type != KeyType.TEST,
NotificationAllTimeView.created_by_id == user_id,
)
.group_by(
func.date_trunc("month", NotificationAllTimeView.created_at).label("month"),
NotificationAllTimeView.notification_type,
NotificationAllTimeView.status,
)
.all()
)
def get_specific_days_stats(results, start_date, days=None, end_date=None):
if days is not None and end_date is not None:
raise ValueError("Only set days OR set end_date, not both.")
elif days is not None:
gen_range = generate_date_range(start_date, days=days)
elif end_date is not None:
gen_range = generate_date_range(start_date, end_date)
else:
raise ValueError("Either days or end_date must be set.")
grouped_results = {date: [] for date in gen_range} | {
day.date(): [notification_type, status, day, count]
for notification_type, status, day, count in results
}
stats = {
day.strftime("%Y-%m-%d"): statistics.format_statistics(rows)
for day, rows in grouped_results.items()
}
return stats

View File

@@ -4,7 +4,7 @@ from secrets import randbelow
import sqlalchemy
from flask import current_app
from sqlalchemy import func
from sqlalchemy import func, text
from sqlalchemy.orm import joinedload
from app import db
@@ -244,3 +244,15 @@ def user_can_be_archived(user):
return False
return True
def dao_report_users():
sql = """
select users.name, users.email_address, users.mobile_number, services.name as service_name
from users
inner join user_to_service on users.id=user_to_service.user_id
inner join services on services.id=user_to_service.service_id
where services.name not like '_archived%'
order by services.name asc, users.name asc
"""
return db.session.execute(text(sql))

View File

@@ -86,6 +86,7 @@ def send_sms_to_provider(notification):
notification.job_id,
notification.job_row_number,
)
print(hilite(f"recipient: {recipient}"))
except Exception:
# It is our 2facode, maybe
key = f"2facode-{notification.id}".replace(" ", "")
@@ -108,6 +109,7 @@ def send_sms_to_provider(notification):
"sender": notification.reply_to_text,
"international": notification.international,
}
print(hilite(f"send_sms_kwargs: {send_sms_kwargs}"))
db.session.close() # no commit needed as no changes to objects have been made above
message_id = provider.send_sms(**send_sms_kwargs)

View File

@@ -33,7 +33,7 @@ from app.schemas import (
notifications_filter_schema,
unarchived_template_schema,
)
from app.utils import midnight_n_days_ago, pagination_links
from app.utils import hilite, midnight_n_days_ago, pagination_links
job_blueprint = Blueprint("job", __name__, url_prefix="/service/<uuid:service_id>/job")
@@ -172,6 +172,7 @@ def create_job(service_id):
raise InvalidRequest("Create job is not allowed: service is inactive ", 403)
data = request.get_json()
print(hilite(f"data at the top of create_job: {data}"))
original_file_name = data.get("original_file_name")
data.update({"service": service_id})
try:

View File

@@ -1,6 +1,7 @@
import itertools
from datetime import datetime
from datetime import datetime, timedelta
from botocore.exceptions import ClientError
from flask import Blueprint, current_app, jsonify, request
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm.exc import NoResultFound
@@ -17,7 +18,7 @@ from app.dao.api_key_dao import (
save_model_api_key,
)
from app.dao.dao_utils import dao_rollback, transaction
from app.dao.date_util import get_calendar_year
from app.dao.date_util import get_calendar_year, get_month_start_and_end_date_in_utc
from app.dao.fact_notification_status_dao import (
fetch_monthly_template_usage_for_service,
fetch_notification_status_for_service_by_month,
@@ -63,13 +64,17 @@ from app.dao.services_dao import (
dao_fetch_all_services_by_user,
dao_fetch_live_services_data,
dao_fetch_service_by_id,
dao_fetch_stats_for_service_from_days,
dao_fetch_stats_for_service_from_days_for_user,
dao_fetch_todays_stats_for_all_services,
dao_fetch_todays_stats_for_service,
dao_remove_user_from_service,
dao_resume_service,
dao_suspend_service,
dao_update_service,
fetch_notification_stats_for_service_by_month_by_user,
get_services_by_partial_name,
get_specific_days_stats,
)
from app.dao.templates_dao import dao_get_template_by_id
from app.dao.users_dao import get_user_by_id
@@ -210,6 +215,58 @@ def get_service_notification_statistics(service_id):
)
@service_blueprint.route("/<uuid:service_id>/statistics/<string:start>/<int:days>")
def get_service_notification_statistics_by_day(service_id, start, days):
return jsonify(
data=get_service_statistics_for_specific_days(service_id, start, int(days))
)
def get_service_statistics_for_specific_days(service_id, start, days=1):
# start and end dates needs to be reversed because
# the end date is today and the start is x days in the past
# a day needs to be substracted to allow for today
end_date = datetime.strptime(start, "%Y-%m-%d")
start_date = end_date - timedelta(days=days - 1)
results = dao_fetch_stats_for_service_from_days(service_id, start_date, end_date)
stats = get_specific_days_stats(results, start_date, days=days)
return stats
@service_blueprint.route(
"/<uuid:service_id>/statistics/user/<uuid:user_id>/<string:start>/<int:days>"
)
def get_service_notification_statistics_by_day_by_user(
service_id, user_id, start, days
):
return jsonify(
data=get_service_statistics_for_specific_days_by_user(
service_id, user_id, start, int(days)
)
)
def get_service_statistics_for_specific_days_by_user(
service_id, user_id, start, days=1
):
# start and end dates needs to be reversed because
# the end date is today and the start is x days in the past
# a day needs to be substracted to allow for today
end_date = datetime.strptime(start, "%Y-%m-%d")
start_date = end_date - timedelta(days=days - 1)
results = dao_fetch_stats_for_service_from_days_for_user(
service_id, start_date, end_date, user_id
)
stats = get_specific_days_stats(results, start_date, days=days)
return stats
@service_blueprint.route("", methods=["POST"])
def create_service():
data = request.get_json()
@@ -444,18 +501,44 @@ def get_all_notifications_for_service(service_id):
for notification in pagination.items:
if notification.job_id is not None:
notification.personalisation = get_personalisation_from_s3(
notification.service_id,
notification.job_id,
notification.job_row_number,
)
recipient = get_phone_number_from_s3(
notification.service_id,
notification.job_id,
notification.job_row_number,
)
notification.to = recipient
notification.normalised_to = recipient
try:
notification.personalisation = get_personalisation_from_s3(
notification.service_id,
notification.job_id,
notification.job_row_number,
)
except ClientError as ex:
if ex.response["Error"]["Code"] == "NoSuchKey":
s = notification.service_id
j = notification.job_id
current_app.logger.warning(
f"No personalisation found for s3 file location service: service-{s}-notify/{j}.csv"
)
notification.personalisation = ""
else:
raise ex
try:
recipient = get_phone_number_from_s3(
notification.service_id,
notification.job_id,
notification.job_row_number,
)
notification.to = recipient
notification.normalised_to = recipient
except ClientError as ex:
if ex.response["Error"]["Code"] == "NoSuchKey":
s = notification.service_id
j = notification.job_id
current_app.logger.warning(
f"No phone number found for s3 file location service: service-{s}-notify/{j}.csv"
)
notification.to = ""
notification.normalised_to = ""
else:
raise ex
else:
notification.to = "1"
notification.normalised_to = "1"
@@ -592,6 +675,7 @@ def get_monthly_notification_stats(service_id):
stats = fetch_notification_status_for_service_by_month(
start_date, end_date, service_id
)
statistics.add_monthly_notification_status_stats(data, stats)
now = utc_now()
@@ -604,6 +688,87 @@ def get_monthly_notification_stats(service_id):
return jsonify(data=data)
@service_blueprint.route(
"/<uuid:service_id>/notifications/<uuid:user_id>/monthly", methods=["GET"]
)
def get_monthly_notification_stats_by_user(service_id, user_id):
# check service_id validity
dao_fetch_service_by_id(service_id)
# user = get_user_by_id(user_id=user_id)
try:
year = int(request.args.get("year", "NaN"))
except ValueError:
raise InvalidRequest("Year must be a number", status_code=400)
start_date, end_date = get_calendar_year(year)
data = statistics.create_empty_monthly_notification_status_stats_dict(year)
stats = fetch_notification_stats_for_service_by_month_by_user(
start_date, end_date, service_id, user_id
)
statistics.add_monthly_notification_status_stats(data, stats)
now = utc_now()
if end_date > now:
todays_deltas = fetch_notification_status_for_service_for_day(
now, service_id=service_id
)
statistics.add_monthly_notification_status_stats(data, todays_deltas)
return jsonify(data=data)
@service_blueprint.route(
"/<uuid:service_id>/notifications/<uuid:user_id>/month", methods=["GET"]
)
def get_single_month_notification_stats_by_user(service_id, user_id):
# check service_id validity
dao_fetch_service_by_id(service_id)
try:
month = int(request.args.get("month", "NaN"))
year = int(request.args.get("year", "NaN"))
except ValueError:
raise InvalidRequest(
"Both a month and year are required as numbers", status_code=400
)
month_year = datetime(year, month, 10, 00, 00, 00)
start_date, end_date = get_month_start_and_end_date_in_utc(month_year)
results = dao_fetch_stats_for_service_from_days_for_user(
service_id, start_date, end_date, user_id
)
stats = get_specific_days_stats(results, start_date, end_date=end_date)
return jsonify(stats)
@service_blueprint.route("/<uuid:service_id>/notifications/month", methods=["GET"])
def get_single_month_notification_stats_for_service(service_id):
# check service_id validity
dao_fetch_service_by_id(service_id)
try:
month = int(request.args.get("month", "NaN"))
year = int(request.args.get("year", "NaN"))
except ValueError:
raise InvalidRequest(
"Both a month and year are required as numbers", status_code=400
)
month_year = datetime(year, month, 10, 00, 00, 00)
start_date, end_date = get_month_start_and_end_date_in_utc(month_year)
results = dao_fetch_stats_for_service_from_days(service_id, start_date, end_date)
stats = get_specific_days_stats(results, start_date, end_date=end_date)
return jsonify(stats)
def get_detailed_service(service_id, today_only=False):
service = dao_fetch_service_by_id(service_id)

View File

@@ -113,7 +113,6 @@ def create_empty_monthly_notification_status_stats_dict(year):
def add_monthly_notification_status_stats(data, stats):
for row in stats:
month = row.month.strftime("%Y-%m")
data[month][row.notification_type][row.notification_status] += row.count
data[month][row.notification_type][StatisticsType.REQUESTED] += row.count
return data

View File

@@ -18,6 +18,7 @@ from app.dao.users_dao import (
create_secret_code,
create_user_code,
dao_archive_user,
dao_report_users,
get_login_gov_user,
get_user_and_accounts,
get_user_by_email,
@@ -667,6 +668,12 @@ def update_password(user_id):
return jsonify(data=user.serialize()), 200
@user_blueprint.route("/report-all-users", methods=["GET"])
def report_all_users():
users = dao_report_users()
return jsonify(data=users.serialize()), 200
@user_blueprint.route("/<uuid:user_id>/organizations-and-services", methods=["GET"])
def get_organizations_and_services_for_user(user_id):
user = get_user_and_accounts(user_id)

View File

@@ -505,7 +505,7 @@ flask command purge_functional_test_data -u <functional tests user name prefix>
Running on cloud.gov:
```
cf run-task notify-api "flask command purge_functional_test_data -u <functional tests user name prefix>"
cf run-task notify-api --command "flask command purge_functional_test_data -u <functional tests user name prefix>"
```
@@ -1339,9 +1339,24 @@ In the api logs, search by job_id. Either you will see evidence of the job fail
## Viewing the csv file
If you need to view the questionable csv file, run the following command:
If you need to view the questionable csv file on production, run the following command:
```
cf run-task notify-api "flask command download_csv_file_by_name -f <file location found in admin logs>"
cf run-task notify-api-production --command "flask command download-csv-file-by-name -f <file location found in admin logs>"
```
locally, just do:
```
poetry run flask command download-csv-file-by-name -f <file location in admin logs>
```
## Debug steps
1. Either send a message and capture the csv file name, or get a csv file name from a user
2. Using the log tool at logs.fr.cloud.gov, use filters to limit what you're searching on (cf.app is 'notify-admin-production' for example) and then search with the csv file name in double quotes over the relevant time period (last 5 minutes if you just sent a message, or else whatever time the user sent at)
3. When you find the log line, you should also find the job_id and the s3 file location. Save these somewhere.
4. To get the csv file contents, you can run the command above. This command currently prints to the notify-api log, so after you run the command,
you need to search in notify-api-production for the last 5 minutes with the logs sorted by timestamp. The contents of the csv file unfortunately appear on separate lines so it's very important to sort by time.
5. If you want to see where the message actually failed, search with cf.app is notify-api-production using the job_id that you saved in step #3. If you get far enough, you might see one of the log lines has a message_id. If you see it, you can switch and search on that, which should tell you what happened in AWS (success or failure).

View File

@@ -15,6 +15,7 @@ from alembic import op
from sqlalchemy import text
from app.hashing import hashpw
from app.utils import utc_now
revision = "0025_notify_service_data"
down_revision = "0024_add_research_mode_defaults"
@@ -32,7 +33,7 @@ def upgrade():
"""
conn.execute(
text(user_insert),
{"user_id": user_id, "time_now": datetime.utcnow(), "password": password},
{"user_id": user_id, "time_now": utc_now(), "password": password},
)
service_history_insert = """INSERT INTO services_history (id, name, created_at, active, message_limit, restricted, research_mode, email_from, created_by_id, reply_to_email_address, version)
VALUES (:service_id, 'Notify service', :time_now, True, 1000, False, False, 'testsender@dispostable.com',
@@ -41,7 +42,7 @@ def upgrade():
"""
conn.execute(
text(service_history_insert),
{"service_id": service_id, "time_now": datetime.utcnow(), "user_id": user_id},
{"service_id": service_id, "time_now": utc_now(), "user_id": user_id},
)
service_insert = """INSERT INTO services (id, name, created_at, active, message_limit, restricted, research_mode, email_from, created_by_id, reply_to_email_address, version)
VALUES (:service_id, 'Notify service', :time_now, True, 1000, False, False, 'testsender@dispostable.com',
@@ -49,7 +50,7 @@ def upgrade():
"""
conn.execute(
text(service_insert),
{"service_id": service_id, "time_now": datetime.utcnow(), "user_id": user_id},
{"service_id": service_id, "time_now": utc_now(), "user_id": user_id},
)
user_to_service_insert = """INSERT INTO user_to_service (user_id, service_id) VALUES (:user_id, :service_id)"""
conn.execute(
@@ -74,7 +75,7 @@ def upgrade():
"template_id": uuid.uuid4(),
"template_name": "Notify email verification code",
"template_type": "email",
"time_now": datetime.utcnow(),
"time_now": utc_now(),
"content": email_verification_content,
"service_id": service_id,
"subject": "Confirm GOV.UK Notify registration",
@@ -87,7 +88,7 @@ def upgrade():
"template_id": "ece42649-22a8-4d06-b87f-d52d5d3f0a27",
"template_name": "Notify email verification code",
"template_type": "email",
"time_now": datetime.utcnow(),
"time_now": utc_now(),
"content": email_verification_content,
"service_id": service_id,
"subject": "Confirm GOV.UK Notify registration",
@@ -107,7 +108,7 @@ def upgrade():
"template_id": "4f46df42-f795-4cc4-83bb-65ca312f49cc",
"template_name": "Notify invitation email",
"template_type": "email",
"time_now": datetime.utcnow(),
"time_now": utc_now(),
"content": invitation_content,
"service_id": service_id,
"subject": invitation_subject,
@@ -120,7 +121,7 @@ def upgrade():
"template_id": "4f46df42-f795-4cc4-83bb-65ca312f49cc",
"template_name": "Notify invitation email",
"template_type": "email",
"time_now": datetime.utcnow(),
"time_now": utc_now(),
"content": invitation_content,
"service_id": service_id,
"subject": invitation_subject,
@@ -135,7 +136,7 @@ def upgrade():
"template_id": "36fb0730-6259-4da1-8a80-c8de22ad4246",
"template_name": "Notify SMS verify code",
"template_type": "sms",
"time_now": datetime.utcnow(),
"time_now": utc_now(),
"content": sms_code_content,
"service_id": service_id,
"subject": None,
@@ -149,7 +150,7 @@ def upgrade():
"template_id": "36fb0730-6259-4da1-8a80-c8de22ad4246",
"template_name": "Notify SMS verify code",
"template_type": "sms",
"time_now": datetime.utcnow(),
"time_now": utc_now(),
"content": sms_code_content,
"service_id": service_id,
"subject": None,
@@ -172,7 +173,7 @@ def upgrade():
"template_id": "474e9242-823b-4f99-813d-ed392e7f1201",
"template_name": "Notify password reset email",
"template_type": "email",
"time_now": datetime.utcnow(),
"time_now": utc_now(),
"content": password_reset_content,
"service_id": service_id,
"subject": "Reset your GOV.UK Notify password",
@@ -186,7 +187,7 @@ def upgrade():
"template_id": "474e9242-823b-4f99-813d-ed392e7f1201",
"template_name": "Notify password reset email",
"template_type": "email",
"time_now": datetime.utcnow(),
"time_now": utc_now(),
"content": password_reset_content,
"service_id": service_id,
"subject": "Reset your GOV.UK Notify password",

View File

@@ -11,6 +11,8 @@ from datetime import datetime
from sqlalchemy import text
from app.utils import utc_now
revision = "0028_fix_reg_template_history"
down_revision = "0026_rename_notify_service"
@@ -38,7 +40,7 @@ def upgrade():
"id": "ece42649-22a8-4d06-b87f-d52d5d3f0a27",
"name": "Notify email verification code",
"type": "email",
"time_now": datetime.utcnow(),
"time_now": utc_now(),
"content": email_verification_content,
"service_id": service_id,
"subject": "Confirm GOV.UK Notify registration",

View File

@@ -14,6 +14,8 @@ from alembic import op
from flask import current_app
from sqlalchemy import text
from app.utils import utc_now
revision = "0082_add_go_live_template"
down_revision = "0081_noti_status_as_enum"
@@ -89,7 +91,7 @@ GOV.UK Notify team
"template_id": template_id,
"template_name": template_name,
"template_type": "email",
"time_now": datetime.utcnow(),
"time_now": utc_now(),
"content": template_content,
"notify_service_id": current_app.config["NOTIFY_SERVICE_ID"],
"subject": template_subject,

View File

@@ -9,6 +9,8 @@ Create Date: 2017-08-29 14:09:41.042061
# revision identifiers, used by Alembic.
from sqlalchemy import text
from app.utils import utc_now
revision = "0117_international_sms_notify"
down_revision = "0115_add_inbound_numbers"
@@ -22,7 +24,7 @@ NOTIFY_SERVICE_ID = "d6aa2c68-a2d9-4437-ab19-3ae8eb202553"
def upgrade():
input_params = {
"notify_service_id": NOTIFY_SERVICE_ID,
"datetime_now": datetime.utcnow(),
"datetime_now": utc_now(),
}
conn = op.get_bind()
conn.execute(

View File

@@ -12,6 +12,8 @@ from alembic import op
from flask import current_app
from sqlalchemy import text
from app.utils import utc_now
revision = "0134_add_email_2fa_template"
down_revision = "0133_set_services_sms_prefix"
@@ -44,7 +46,7 @@ def upgrade():
"template_id": template_id,
"template_name": template_name,
"template_type": "email",
"time_now": datetime.utcnow(),
"time_now": utc_now(),
"content": template_content,
"notify_service_id": current_app.config["NOTIFY_SERVICE_ID"],
"subject": template_subject,

View File

@@ -13,6 +13,7 @@ from alembic import op
from sqlalchemy import text
from app.dao.date_util import get_current_calendar_year_start_year
from app.utils import utc_now
revision = "0139_migrate_sms_allowance_data"
down_revision = "0138_sms_sender_nullable"
@@ -34,7 +35,7 @@ def upgrade():
input_params = {
"current_year": current_year,
"default_limit": default_limit,
"time_now": datetime.utcnow(),
"time_now": utc_now(),
}
insert_row_if_not_exist = """
INSERT INTO annual_billing

View File

@@ -12,6 +12,8 @@ from alembic import op
from flask import current_app
from sqlalchemy import text
from app.utils import utc_now
revision = "0171_add_org_invite_template"
down_revision = "0170_hidden_non_nullable"
@@ -53,7 +55,7 @@ def upgrade():
"template_id": template_id,
"template_name": template_name,
"template_type": "email",
"time_now": datetime.utcnow(),
"time_now": utc_now(),
"content": template_content,
"notify_service_id": current_app.config["NOTIFY_SERVICE_ID"],
"subject": template_subject,

View File

@@ -12,6 +12,8 @@ from alembic import op
from flask import current_app
from sqlalchemy import text
from app.utils import utc_now
revision = "0265_add_confirm_edit_templates"
down_revision = "0264_add_folder_permissions_perm"
@@ -57,7 +59,7 @@ def upgrade():
"template_id": email_template_id,
"template_name": email_template_name,
"template_type": "email",
"time_now": datetime.utcnow(),
"time_now": utc_now(),
"content": email_template_content,
"notify_service_id": current_app.config["NOTIFY_SERVICE_ID"],
"subject": email_template_subject,
@@ -78,7 +80,7 @@ def upgrade():
"template_id": mobile_template_id,
"template_name": mobile_template_name,
"template_type": "sms",
"time_now": datetime.utcnow(),
"time_now": utc_now(),
"content": mobile_template_content,
"notify_service_id": current_app.config["NOTIFY_SERVICE_ID"],
"subject": None,

View File

@@ -12,6 +12,8 @@ from alembic import op
from flask import current_app
from sqlalchemy import text
from app.utils import utc_now
revision = "0294_add_verify_reply_to"
down_revision = "0293_drop_complaint_fk"
@@ -58,7 +60,7 @@ def upgrade():
"template_id": email_template_id,
"template_name": email_template_name,
"template_type": "email",
"time_now": datetime.utcnow(),
"time_now": utc_now(),
"content": email_template_content,
"notify_service_id": current_app.config["NOTIFY_SERVICE_ID"],
"subject": email_template_subject,

View File

@@ -12,6 +12,8 @@ from datetime import datetime
from alembic import op
from sqlalchemy import text
from app.utils import utc_now
revision = "0330_broadcast_invite_email"
down_revision = "0329_purge_broadcast_data"
@@ -60,7 +62,7 @@ def upgrade():
input_params = {
"template_id": template_id,
"template_name": broadcast_invitation_template_name,
"time_now": datetime.utcnow(),
"time_now": utc_now(),
"content": broadcast_invitation_content,
"service_id": service_id,
"subject": broadcast_invitation_subject,

View File

@@ -13,6 +13,8 @@ from alembic import op
from flask import current_app
from sqlalchemy import text
from app.utils import utc_now
revision = "0347_add_dvla_volumes_template"
down_revision = "0346_notify_number_sms_sender"
@@ -57,7 +59,7 @@ def upgrade():
"template_id": email_template_id,
"template_name": email_template_name,
"template_type": "email",
"time_now": datetime.utcnow(),
"time_now": utc_now(),
"content": email_template_content,
"notify_service_id": current_app.config["NOTIFY_SERVICE_ID"],
"subject": email_template_subject,

View File

@@ -16,6 +16,7 @@ from alembic import op
from app import db
from app.dao.users_dao import get_user_by_email
from app.models import User
from app.utils import utc_now
revision = "0401_add_e2e_test_user"
down_revision = "0400_add_total_message_limit"
@@ -32,11 +33,11 @@ def upgrade():
"password": password,
"mobile_number": "+12025555555",
"state": "active",
"created_at": datetime.datetime.utcnow(),
"password_changed_at": datetime.datetime.utcnow(),
"created_at": utc_now(),
"password_changed_at": utc_now(),
"failed_login_count": 0,
"platform_admin": "f",
"email_access_validated_at": datetime.datetime.utcnow(),
"email_access_validated_at": utc_now(),
}
conn = op.get_bind()
insert_sql = """

57
poetry.lock generated
View File

@@ -204,17 +204,17 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p
[[package]]
name = "awscli"
version = "1.33.18"
version = "1.33.21"
description = "Universal Command Line Environment for AWS."
optional = false
python-versions = ">=3.8"
files = [
{file = "awscli-1.33.18-py3-none-any.whl", hash = "sha256:4065a0c9ee7bd2281e0b04616242693abbe17cd9d7be966abc7a850d5044226d"},
{file = "awscli-1.33.18.tar.gz", hash = "sha256:800cae2c020dae7e86877e2b53dee637c19acc62de8084bc67e3434ac174ca35"},
{file = "awscli-1.33.21-py3-none-any.whl", hash = "sha256:92e5f8a0f5e3497459f1711ef4044ac1e60ff8d2058e889c63aa6929d5c67459"},
{file = "awscli-1.33.21.tar.gz", hash = "sha256:d0a7209e323c85b28d85cffa9470fff664d5f861bb3b5bda843329e0836f5760"},
]
[package.dependencies]
botocore = "1.34.136"
botocore = "1.34.139"
colorama = ">=0.2.5,<0.4.7"
docutils = ">=0.10,<0.17"
PyYAML = ">=3.10,<6.1"
@@ -403,17 +403,17 @@ files = [
[[package]]
name = "boto3"
version = "1.34.136"
version = "1.34.138"
description = "The AWS SDK for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "boto3-1.34.136-py3-none-any.whl", hash = "sha256:d41037e2c680ab8d6c61a0a4ee6bf1fdd9e857f43996672830a95d62d6f6fa79"},
{file = "boto3-1.34.136.tar.gz", hash = "sha256:0314e6598f59ee0f34eb4e6d1a0f69fa65c146d2b88a6e837a527a9956ec2731"},
{file = "boto3-1.34.138-py3-none-any.whl", hash = "sha256:81518aa95fad71279411fb5c94da4b4a554a5d53fc876faca62b7b5c8737f1cb"},
{file = "boto3-1.34.138.tar.gz", hash = "sha256:f79c15e33eb7706f197d98d828b193cf0891966682ad3ec5e900f6f9e7362e35"},
]
[package.dependencies]
botocore = ">=1.34.136,<1.35.0"
botocore = ">=1.34.138,<1.35.0"
jmespath = ">=0.7.1,<2.0.0"
s3transfer = ">=0.10.0,<0.11.0"
@@ -422,13 +422,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
[[package]]
name = "botocore"
version = "1.34.136"
version = "1.34.139"
description = "Low-level, data-driven core of boto 3."
optional = false
python-versions = ">=3.8"
files = [
{file = "botocore-1.34.136-py3-none-any.whl", hash = "sha256:c63fe9032091fb9e9477706a3ebfa4d0c109b807907051d892ed574f9b573e61"},
{file = "botocore-1.34.136.tar.gz", hash = "sha256:7f7135178692b39143c8f152a618d2a3b71065a317569a7102d2306d4946f42f"},
{file = "botocore-1.34.139-py3-none-any.whl", hash = "sha256:dd1e085d4caa2a4c1b7d83e3bc51416111c8238a35d498e9d3b04f3b63b086ba"},
{file = "botocore-1.34.139.tar.gz", hash = "sha256:df023d8cf8999d574214dad4645cb90f9d2ccd1494f6ee2b57b1ab7522f6be77"},
]
[package.dependencies]
@@ -553,13 +553,13 @@ zstd = ["zstandard (==0.22.0)"]
[[package]]
name = "certifi"
version = "2024.6.2"
version = "2024.7.4"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
files = [
{file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"},
{file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"},
{file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"},
{file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"},
]
[[package]]
@@ -2098,9 +2098,13 @@ files = [
{file = "lxml-5.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196"},
{file = "lxml-5.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b"},
{file = "lxml-5.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8"},
{file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6"},
{file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716"},
{file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c"},
{file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905"},
{file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184"},
{file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6"},
{file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f"},
{file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61"},
{file = "lxml-5.2.2-cp37-cp37m-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"},
{file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"},
@@ -2385,13 +2389,13 @@ files = [
[[package]]
name = "moto"
version = "5.0.9"
version = "5.0.10"
description = ""
optional = false
python-versions = ">=3.8"
files = [
{file = "moto-5.0.9-py2.py3-none-any.whl", hash = "sha256:21a13e02f83d6a18cfcd99949c96abb2e889f4bd51c4c6a3ecc8b78765cb854e"},
{file = "moto-5.0.9.tar.gz", hash = "sha256:eb71f1cba01c70fff1f16086acb24d6d9aeb32830d646d8989f98a29aeae24ba"},
{file = "moto-5.0.10-py2.py3-none-any.whl", hash = "sha256:9ffae2f64cc8fe95b9a12d63ae7268a7d6bea9993b922905b5abd8197d852cd0"},
{file = "moto-5.0.10.tar.gz", hash = "sha256:eff37363221c93ea44f95721ae0ddb56f977fe70437a041b6cc641ee90266279"},
]
[package.dependencies]
@@ -2489,7 +2493,6 @@ files = [
{file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"},
{file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"},
{file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"},
{file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"},
{file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"},
]
@@ -2823,13 +2826,13 @@ ptyprocess = ">=0.5"
[[package]]
name = "phonenumbers"
version = "8.13.39"
version = "8.13.40"
description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
optional = false
python-versions = "*"
files = [
{file = "phonenumbers-8.13.39-py2.py3-none-any.whl", hash = "sha256:3ad2d086fa71e7eef409001b9195ac54bebb0c6e3e752209b558ca192c9229a0"},
{file = "phonenumbers-8.13.39.tar.gz", hash = "sha256:db7ca4970d206b2056231105300753b1a5b229f43416f8c2b3010e63fbb68d77"},
{file = "phonenumbers-8.13.40-py2.py3-none-any.whl", hash = "sha256:9582752c20a1da5ec4449f7f97542bf8a793c8e2fec0ab57f767177bb8fc0b1d"},
{file = "phonenumbers-8.13.40.tar.gz", hash = "sha256:f137c2848b8e83dd064b71881b65680584417efa202177fd330e2f7ff6c68113"},
]
[[package]]
@@ -4017,18 +4020,18 @@ jeepney = ">=0.6"
[[package]]
name = "setuptools"
version = "70.1.1"
version = "70.2.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "setuptools-70.1.1-py3-none-any.whl", hash = "sha256:a58a8fde0541dab0419750bcc521fbdf8585f6e5cb41909df3a472ef7b81ca95"},
{file = "setuptools-70.1.1.tar.gz", hash = "sha256:937a48c7cdb7a21eb53cd7f9b59e525503aa8abaf3584c730dc5f7a5bec3a650"},
{file = "setuptools-70.2.0-py3-none-any.whl", hash = "sha256:b8b8060bb426838fbe942479c90296ce976249451118ef566a5a0b7d8b78fb05"},
{file = "setuptools-70.2.0.tar.gz", hash = "sha256:bd63e505105011b25c3c11f753f7e3b8465ea739efddaccef8f0efac2137bac1"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
name = "shapely"
@@ -4748,4 +4751,4 @@ multidict = ">=4.0"
[metadata]
lock-version = "2.0"
python-versions = "^3.12.2"
content-hash = "d3ca67b44f40fb25b724b8468e07d30901ddced875ffe5d6b6710a17e492b072"
content-hash = "74d41976bb5028dce7b953ee4c2f6108a46215c8e48d2a4b2152d0277a63b395"

View File

@@ -11,8 +11,8 @@ python = "^3.12.2"
alembic = "==1.13.2"
amqp = "==5.2.0"
beautifulsoup4 = "==4.12.3"
boto3 = "^1.34.136"
botocore = "^1.34.136"
boto3 = "^1.34.138"
botocore = "^1.34.139"
cachetools = "==5.3.3"
celery = {version = "==5.4.0", extras = ["redis"]}
certifi = ">=2022.12.7"
@@ -55,7 +55,7 @@ geojson = "^3.1.0"
govuk-bank-holidays = "^0.14"
numpy = "^1.26.4"
ordered-set = "^4.1.0"
phonenumbers = "^8.13.39"
phonenumbers = "^8.13.40"
python-json-logger = "^2.0.7"
pytz = "^2024.1"
regex = "^2024.5.15"
@@ -92,7 +92,7 @@ freezegun = "^1.5.1"
honcho = "*"
isort = "^5.13.2"
jinja2-cli = {version = "==0.8.2", extras = ["yaml"]}
moto = "==5.0.9"
moto = "==5.0.10"
pip-audit = "*"
pre-commit = "^3.7.1"
pytest = "^8.2.2"
@@ -102,7 +102,7 @@ pytest-cov = "^5.0.0"
pytest-xdist = "^3.5.0"
radon = "^6.0.1"
requests-mock = "^1.11.0"
setuptools = "^70.1.1"
setuptools = "^70.2.0"
sqlalchemy-utils = "^0.41.2"
vulture = "^2.10"
detect-secrets = "^1.5.0"

View File

@@ -33,31 +33,44 @@ def test_fetch_notification_status_for_service_by_month(notify_db_session):
service_1 = create_service(service_name="service_1")
service_2 = create_service(service_name="service_2")
create_ft_notification_status(
date(2018, 1, 1), NotificationType.SMS, service_1, count=4
)
create_ft_notification_status(
date(2018, 1, 2), NotificationType.SMS, service_1, count=10
)
create_ft_notification_status(
date(2018, 1, 2),
NotificationType.SMS,
service_1,
notification_status=NotificationStatus.CREATED,
)
create_ft_notification_status(date(2018, 1, 3), NotificationType.EMAIL, service_1)
create_template(service=service_1)
create_template(service=service_1, template_type=TemplateType.EMAIL)
# not the service being tested
create_template(service=service_2)
create_ft_notification_status(date(2018, 2, 2), NotificationType.SMS, service_1)
# loop messages for the month
for x in range(0, 14):
create_notification(
service_1.templates[0],
created_at=datetime(2018, 1, 1, 1, x, 0),
status=NotificationStatus.DELIVERED,
)
create_notification(
service_1.templates[0], created_at=datetime(2018, 1, 1, 1, 1, 0)
)
create_notification(
service_1.templates[1],
created_at=datetime(2018, 1, 1, 1, 1, 0),
status=NotificationStatus.DELIVERED,
)
create_notification(
service_1.templates[0],
created_at=datetime(2018, 2, 1, 1, 1, 0),
status=NotificationStatus.DELIVERED,
)
# not included - too early
create_ft_notification_status(date(2017, 12, 31), NotificationType.SMS, service_1)
# not included - too late
create_ft_notification_status(date(2017, 3, 1), NotificationType.SMS, service_1)
# not included - wrong service
create_ft_notification_status(date(2018, 1, 3), NotificationType.SMS, service_2)
# not included - test keys
create_ft_notification_status(
date(2018, 1, 3), NotificationType.SMS, service_1, key_type=KeyType.TEST
# not the right month
create_notification(
service_1.templates[0],
created_at=datetime(2018, 4, 1, 1, 1, 0),
status=NotificationStatus.DELIVERED,
)
# not the right service
create_notification(
service_2.templates[0],
created_at=datetime(2018, 2, 1, 1, 1, 0),
status=NotificationStatus.DELIVERED,
)
results = sorted(

View File

@@ -301,12 +301,15 @@ def test_add_monthly_notification_status_stats():
data = create_empty_monthly_notification_status_stats_dict(2018)
# this data won't be affected
data["2018-05"][NotificationType.EMAIL][NotificationStatus.SENDING] = 32
data["2018-05"][NotificationType.EMAIL][StatisticsType.REQUESTED] = 32
# this data will get combined with the 8 from row_data
data["2018-05"][NotificationType.SMS][NotificationStatus.SENDING] = 16
data["2018-05"][NotificationType.SMS][StatisticsType.REQUESTED] = 16
add_monthly_notification_status_stats(data, rows)
# first 3 months are empty
assert data == {
"2018-01": {NotificationType.SMS: {}, NotificationType.EMAIL: {}},
"2018-02": {NotificationType.SMS: {}, NotificationType.EMAIL: {}},
@@ -315,12 +318,22 @@ def test_add_monthly_notification_status_stats():
NotificationType.SMS: {
NotificationStatus.SENDING: 1,
NotificationStatus.DELIVERED: 2,
StatisticsType.REQUESTED: 3,
},
NotificationType.EMAIL: {
NotificationStatus.SENDING: 4,
StatisticsType.REQUESTED: 4,
},
NotificationType.EMAIL: {NotificationStatus.SENDING: 4},
},
"2018-05": {
NotificationType.SMS: {NotificationStatus.SENDING: 24},
NotificationType.EMAIL: {NotificationStatus.SENDING: 32},
NotificationType.SMS: {
NotificationStatus.SENDING: 24,
StatisticsType.REQUESTED: 24,
},
NotificationType.EMAIL: {
NotificationStatus.SENDING: 32,
StatisticsType.REQUESTED: 32,
},
},
"2018-06": {NotificationType.SMS: {}, NotificationType.EMAIL: {}},
}

View File

@@ -234,17 +234,36 @@ def test_get_monthly_notification_stats_returns_stats(admin_request, sample_serv
sms_t2 = create_template(sample_service)
email_template = create_template(sample_service, template_type=TemplateType.EMAIL)
create_ft_notification_status(datetime(2016, 6, 1), template=sms_t1)
create_ft_notification_status(datetime(2016, 6, 2), template=sms_t1)
create_ft_notification_status(datetime(2016, 7, 1), template=sms_t1)
create_ft_notification_status(datetime(2016, 7, 1), template=sms_t2)
create_ft_notification_status(
datetime(2016, 7, 1),
template=sms_t1,
notification_status=NotificationStatus.CREATED,
create_notification(
sms_t1,
created_at=datetime(2016, 6, 1, 1, 1, 0),
status=NotificationStatus.DELIVERED,
)
create_notification(
sms_t1,
created_at=datetime(2016, 6, 2, 1, 1, 0),
status=NotificationStatus.DELIVERED,
)
create_notification(
sms_t1,
created_at=datetime(2016, 7, 1, 1, 1, 0),
status=NotificationStatus.DELIVERED,
)
create_notification(
sms_t2,
created_at=datetime(2016, 7, 1, 1, 1, 0),
status=NotificationStatus.DELIVERED,
)
create_notification(
sms_t1,
created_at=datetime(2016, 7, 1, 1, 1, 0),
status=NotificationStatus.CREATED,
)
create_notification(
email_template,
created_at=datetime(2016, 7, 1, 1, 1, 0),
status=NotificationStatus.DELIVERED,
)
create_ft_notification_status(datetime(2016, 7, 1), template=email_template)
response = admin_request.get(
"service.get_monthly_notification_stats",
@@ -256,7 +275,8 @@ def test_get_monthly_notification_stats_returns_stats(admin_request, sample_serv
assert response["data"]["2016-06"] == {
NotificationType.SMS: {
# it combines the two days
NotificationStatus.DELIVERED: 2
NotificationStatus.DELIVERED: 2,
StatisticsType.REQUESTED: 2,
},
NotificationType.EMAIL: {},
}
@@ -265,86 +285,43 @@ def test_get_monthly_notification_stats_returns_stats(admin_request, sample_serv
NotificationType.SMS: {
NotificationStatus.CREATED: 1,
NotificationStatus.DELIVERED: 2,
StatisticsType.REQUESTED: 3,
},
NotificationType.EMAIL: {StatisticsType.DELIVERED: 1},
}
@freeze_time("2016-06-05 12:00:00")
def test_get_monthly_notification_stats_combines_todays_data_and_historic_stats(
admin_request, sample_template
):
create_ft_notification_status(
datetime(2016, 5, 1, 12),
template=sample_template,
count=1,
)
create_ft_notification_status(
datetime(2016, 6, 1, 12),
template=sample_template,
notification_status=NotificationStatus.CREATED,
count=2,
) # noqa
create_notification(
sample_template,
created_at=datetime(2016, 6, 5, 12),
status=NotificationStatus.CREATED,
)
create_notification(
sample_template,
created_at=datetime(2016, 6, 5, 12),
status=NotificationStatus.DELIVERED,
)
# this doesn't get returned in the stats because it is old - it should be in ft_notification_status by now
create_notification(
sample_template,
created_at=datetime(2016, 6, 4, 12),
status=NotificationStatus.SENDING,
)
response = admin_request.get(
"service.get_monthly_notification_stats",
service_id=sample_template.service_id,
year=2016,
)
assert len(response["data"]) == 6 # January to June
assert response["data"]["2016-05"] == {
NotificationType.SMS: {NotificationStatus.DELIVERED: 1},
NotificationType.EMAIL: {},
}
assert response["data"]["2016-06"] == {
NotificationType.SMS: {
# combines the stats from the historic ft_notification_status and the current notifications
NotificationStatus.CREATED: 3,
NotificationStatus.DELIVERED: 1,
NotificationType.EMAIL: {
StatisticsType.DELIVERED: 1,
StatisticsType.REQUESTED: 1,
},
NotificationType.EMAIL: {},
}
def test_get_monthly_notification_stats_ignores_test_keys(
admin_request, sample_service
):
create_ft_notification_status(
datetime(2016, 6, 1),
service=sample_service,
create_template(service=sample_service)
create_notification(
sample_service.templates[0],
created_at=datetime(2016, 6, 1, 1, 1, 0),
key_type=KeyType.NORMAL,
count=1,
status=NotificationStatus.DELIVERED,
)
create_ft_notification_status(
datetime(2016, 6, 1),
service=sample_service,
create_notification(
sample_service.templates[0],
created_at=datetime(2016, 6, 2, 1, 1, 0),
key_type=KeyType.NORMAL,
status=NotificationStatus.DELIVERED,
)
create_notification(
sample_service.templates[0],
created_at=datetime(2016, 6, 1, 1, 1, 0),
key_type=KeyType.TEAM,
count=2,
status=NotificationStatus.DELIVERED,
)
create_ft_notification_status(
datetime(2016, 6, 1),
service=sample_service,
create_notification(
sample_service.templates[0],
created_at=datetime(2016, 6, 1, 1, 1, 0),
key_type=KeyType.TEST,
count=4,
status=NotificationStatus.DELIVERED,
)
response = admin_request.get(
@@ -355,26 +332,27 @@ def test_get_monthly_notification_stats_ignores_test_keys(
assert response["data"]["2016-06"][NotificationType.SMS] == {
NotificationStatus.DELIVERED: 3,
StatisticsType.REQUESTED: 3,
}
def test_get_monthly_notification_stats_checks_dates(admin_request, sample_service):
t = create_template(sample_service)
# create_ft_notification_status(datetime(2016, 3, 31), template=t, notification_status='created')
create_ft_notification_status(
datetime(2016, 4, 2),
template=t,
notification_status=NotificationStatus.SENDING,
create_notification(
t,
created_at=datetime(2016, 4, 2),
status=NotificationStatus.SENDING,
)
create_ft_notification_status(
datetime(2017, 3, 31),
template=t,
notification_status=NotificationStatus.DELIVERED,
create_notification(
t,
created_at=datetime(2017, 3, 31),
status=NotificationStatus.DELIVERED,
)
create_ft_notification_status(
datetime(2017, 4, 11),
template=t,
notification_status=NotificationStatus.PERMANENT_FAILURE,
create_notification(
t,
created_at=datetime(2017, 4, 11),
status=NotificationStatus.PERMANENT_FAILURE,
)
response = admin_request.get(
@@ -386,9 +364,11 @@ def test_get_monthly_notification_stats_checks_dates(admin_request, sample_servi
assert "2017-04" not in response["data"]
assert response["data"]["2016-04"][NotificationType.SMS] == {
NotificationStatus.SENDING: 1,
StatisticsType.REQUESTED: 1,
}
assert response["data"]["2016-04"][NotificationType.SMS] == {
NotificationStatus.SENDING: 1,
StatisticsType.REQUESTED: 1,
}
@@ -399,15 +379,15 @@ def test_get_monthly_notification_stats_only_gets_for_one_service(
templates = [create_template(services[0]), create_template(services[1])]
create_ft_notification_status(
datetime(2016, 6, 1),
template=templates[0],
notification_status=NotificationStatus.CREATED,
create_notification(
templates[0],
created_at=datetime(2016, 6, 1),
status=NotificationStatus.CREATED,
)
create_ft_notification_status(
datetime(2016, 6, 1),
template=templates[1],
notification_status=NotificationStatus.DELIVERED,
create_notification(
templates[1],
created_at=datetime(2016, 6, 1),
status=NotificationStatus.DELIVERED,
)
response = admin_request.get(
@@ -417,6 +397,9 @@ def test_get_monthly_notification_stats_only_gets_for_one_service(
)
assert response["data"]["2016-06"] == {
NotificationType.SMS: {NotificationStatus.CREATED: 1},
NotificationType.SMS: {
NotificationStatus.CREATED: 1,
StatisticsType.REQUESTED: 1,
},
NotificationType.EMAIL: {},
}