mirror of
https://github.com/GSA/notifications-api.git
synced 2025-12-20 15:31:15 -05:00
This stops the pages being slow to load and defers the need to add any pagination. Only the most recent data is likely to be relevant.
194 lines
6.8 KiB
Python
194 lines
6.8 KiB
Python
from datetime import datetime, timedelta
|
|
|
|
from flask import current_app
|
|
from notifications_utils.timezones import convert_utc_to_bst
|
|
from sqlalchemy import asc, desc, func
|
|
|
|
from app import db
|
|
from app.dao.dao_utils import autocommit
|
|
from app.models import (
|
|
SMS_TYPE,
|
|
FactBilling,
|
|
ProviderDetails,
|
|
ProviderDetailsHistory,
|
|
User,
|
|
)
|
|
|
|
|
|
def get_provider_details_by_id(provider_details_id):
|
|
return ProviderDetails.query.get(provider_details_id)
|
|
|
|
|
|
def get_provider_details_by_identifier(identifier):
|
|
return ProviderDetails.query.filter_by(identifier=identifier).one()
|
|
|
|
|
|
def get_alternative_sms_provider(identifier):
|
|
if identifier == 'firetext':
|
|
return 'mmg'
|
|
elif identifier == 'mmg':
|
|
return 'firetext'
|
|
raise ValueError('Unrecognised sms provider {}'.format(identifier))
|
|
|
|
|
|
def dao_get_provider_versions(provider_id):
|
|
return ProviderDetailsHistory.query.filter_by(
|
|
id=provider_id
|
|
).order_by(
|
|
desc(ProviderDetailsHistory.version)
|
|
).limit(
|
|
100 # limit results instead of adding pagination
|
|
).all()
|
|
|
|
|
|
def _adjust_provider_priority(provider, new_priority):
|
|
current_app.logger.info(
|
|
f'Adjusting provider priority - {provider.identifier} going from {provider.priority} to {new_priority}'
|
|
)
|
|
provider.priority = new_priority
|
|
|
|
# Automatic update so set as notify user
|
|
provider.created_by_id = current_app.config['NOTIFY_USER_ID']
|
|
|
|
# update without commit so that both rows can be changed without ending the transaction
|
|
# and releasing the for_update lock
|
|
_update_provider_details_without_commit(provider)
|
|
|
|
|
|
def _get_sms_providers_for_update(time_threshold):
|
|
"""
|
|
Returns a list of providers, while holding a for_update lock on the provider details table, guaranteeing that those
|
|
providers won't change (but can still be read) until you've committed/rolled back your current transaction.
|
|
|
|
if any of the providers have been changed recently, it returns an empty list - it's still your responsiblity to
|
|
release the transaction in that case
|
|
"""
|
|
# get current priority of both providers
|
|
q = ProviderDetails.query.filter(
|
|
ProviderDetails.notification_type == 'sms',
|
|
ProviderDetails.active
|
|
).with_for_update().all()
|
|
|
|
# if something updated recently, don't update again. If the updated_at is null, treat it as min time
|
|
if any((provider.updated_at or datetime.min) > datetime.utcnow() - time_threshold for provider in q):
|
|
current_app.logger.info(f"Not adjusting providers, providers updated less than {time_threshold} ago.")
|
|
return []
|
|
|
|
return q
|
|
|
|
|
|
@autocommit
|
|
def dao_reduce_sms_provider_priority(identifier, *, time_threshold):
|
|
"""
|
|
Will reduce a chosen sms provider's priority, and increase the other provider's priority by 10 points each.
|
|
If either provider has been updated in the last `time_threshold`, then it won't take any action.
|
|
"""
|
|
amount_to_reduce_by = 10
|
|
providers_list = _get_sms_providers_for_update(time_threshold)
|
|
|
|
if len(providers_list) < 2:
|
|
current_app.logger.info("Not adjusting providers, number of active providers is less than 2.")
|
|
return
|
|
|
|
providers = {provider.identifier: provider for provider in providers_list}
|
|
other_identifier = get_alternative_sms_provider(identifier)
|
|
|
|
reduced_provider = providers[identifier]
|
|
increased_provider = providers[other_identifier]
|
|
|
|
# always keep values between 0 and 100
|
|
reduced_provider_priority = max(0, reduced_provider.priority - amount_to_reduce_by)
|
|
increased_provider_priority = min(100, increased_provider.priority + amount_to_reduce_by)
|
|
|
|
_adjust_provider_priority(reduced_provider, reduced_provider_priority)
|
|
_adjust_provider_priority(increased_provider, increased_provider_priority)
|
|
|
|
|
|
@autocommit
|
|
def dao_adjust_provider_priority_back_to_resting_points():
|
|
"""
|
|
Provided that neither SMS provider has been modified in the last hour, move both providers by 10 percentage points
|
|
each towards their defined resting points (set in SMS_PROVIDER_RESTING_POINTS in config.py).
|
|
"""
|
|
amount_to_reduce_by = 10
|
|
time_threshold = timedelta(hours=1)
|
|
|
|
providers = _get_sms_providers_for_update(time_threshold)
|
|
|
|
for provider in providers:
|
|
target = current_app.config['SMS_PROVIDER_RESTING_POINTS'][provider.identifier]
|
|
current = provider.priority
|
|
|
|
if current != target:
|
|
if current > target:
|
|
new_priority = max(target, provider.priority - amount_to_reduce_by)
|
|
else:
|
|
new_priority = min(target, provider.priority + amount_to_reduce_by)
|
|
|
|
_adjust_provider_priority(provider, new_priority)
|
|
|
|
|
|
def get_provider_details_by_notification_type(notification_type, supports_international=False):
|
|
|
|
filters = [ProviderDetails.notification_type == notification_type]
|
|
|
|
if supports_international:
|
|
filters.append(ProviderDetails.supports_international == supports_international)
|
|
|
|
return ProviderDetails.query.filter(*filters).order_by(asc(ProviderDetails.priority)).all()
|
|
|
|
|
|
@autocommit
|
|
def dao_update_provider_details(provider_details):
|
|
_update_provider_details_without_commit(provider_details)
|
|
|
|
|
|
def _update_provider_details_without_commit(provider_details):
|
|
"""
|
|
Doesn't commit, for when you need to control the database transaction manually
|
|
"""
|
|
provider_details.version += 1
|
|
provider_details.updated_at = datetime.utcnow()
|
|
history = ProviderDetailsHistory.from_original(provider_details)
|
|
db.session.add(provider_details)
|
|
db.session.add(history)
|
|
|
|
|
|
def dao_get_provider_stats():
|
|
# this query does not include the current day since the task to populate ft_billing runs overnight
|
|
|
|
current_bst_datetime = convert_utc_to_bst(datetime.utcnow())
|
|
first_day_of_the_month = current_bst_datetime.date().replace(day=1)
|
|
|
|
subquery = db.session.query(
|
|
FactBilling.provider,
|
|
func.sum(FactBilling.billable_units * FactBilling.rate_multiplier).label('current_month_billable_sms')
|
|
).filter(
|
|
FactBilling.notification_type == SMS_TYPE,
|
|
FactBilling.bst_date >= first_day_of_the_month
|
|
).group_by(
|
|
FactBilling.provider
|
|
).subquery()
|
|
|
|
result = db.session.query(
|
|
ProviderDetails.id,
|
|
ProviderDetails.display_name,
|
|
ProviderDetails.identifier,
|
|
ProviderDetails.priority,
|
|
ProviderDetails.notification_type,
|
|
ProviderDetails.active,
|
|
ProviderDetails.updated_at,
|
|
ProviderDetails.supports_international,
|
|
User.name.label('created_by_name'),
|
|
func.coalesce(subquery.c.current_month_billable_sms, 0).label('current_month_billable_sms')
|
|
).outerjoin(
|
|
subquery, ProviderDetails.identifier == subquery.c.provider
|
|
).outerjoin(
|
|
User, ProviderDetails.created_by_id == User.id
|
|
).order_by(
|
|
ProviderDetails.notification_type,
|
|
ProviderDetails.priority,
|
|
).all()
|
|
|
|
return result
|