clearing older methods of the Notifier class. There is now a one-line function, which connects to the Email-server, populates candidates for notifications, creates those notifications one-by-one and sends them. Finally, the database is updated, so a notification is sent only once.

This commit is contained in:
Max Metz 2024-07-30 17:43:17 +02:00
parent f344588f89
commit 8cd6afbb69
2 changed files with 59 additions and 103 deletions

View File

@ -5,12 +5,14 @@ from BreCal.database.sql_queries import SQLQuery
from BreCal.schemas import model from BreCal.schemas import model
from BreCal.brecal_utils.time_handling import difference_to_then from BreCal.brecal_utils.time_handling import difference_to_then
from BreCal.services.email_handling import EmailHandler, create_shipcall_evaluation_notification, send_notification
from BreCal.database.enums import ParticipantwiseTimeDelta
from BreCal.schemas.model import ShipcallType from BreCal.stubs.email_template import get_default_html_email
eta_etd_type_dict = { eta_etd_type_dict = {
ShipcallType.arrival : "Ankunft", model.ShipcallType.arrival : "Ankunft",
ShipcallType.departure : "Abfahrt", model.ShipcallType.departure : "Abfahrt",
ShipcallType.shifting : "Wechselnd" model.ShipcallType.shifting : "Wechselnd"
} }
@ -46,107 +48,39 @@ class Notifier():
- iterate over each remaining shipcall and apply .send_notification - iterate over each remaining shipcall and apply .send_notification
- those which are unsent, shall be sent by the respective type - those which are unsent, shall be sent by the respective type
""" """
raise NotImplementedError("skeleton") # set a threshold, when alarm event notifications become eligible
time_diff_threshold = float(ParticipantwiseTimeDelta.NOTIFICATION)*60 # m minutes, converted to seconds
# get all shipcalls
all_shipcalls = NotImplementedError
shipcalls = [shipcall for shipcall in all_shipcalls if not shipcall.evaluation_notifications_sent]
for shipcall in shipcalls:
notification_list = Notifier.send_notification(shipcall, is_test=is_test)
# #TODO: get all notifications
# #TODO: get matching shipcall (based on shipcall_id)
# #TODO: filter: consider only those, which are not yet sent
# identify necessity
# #TODO: get the 'evaluation_notifications_sent' field from all shipcalls (based on shipcall_id)
# if not -> return
# USE shipcall.evaluation_notifications_sent
# #TODO: those which are unsent, shall be created&sent by the respective type -- Note: consider the is_test argument
# iterate over the list of Notifier.build_notification_type_list
# one might use Notifier.create(..., update_database=True)
# use the History (GetHistory -- by shipcall_id) to identify all subscribed users
# #TODO: update the shipcall dataset ('evaluation_notifications_sent') -- Note: consider the is_test argument
# #TODO_clarify: how to handle the 'evaluation_notifications_sent', when there is no recipient?
return
@staticmethod
def send_notification(shipcall:model.Shipcall, is_test:bool=False)->list[model.Notification]:
"""
Complex-function, which is responsible of creating notification messages, issuing them to users and optionally updating
the database. The requirement is, that the notification is required and passes through an internal set of filters.
Steps:
- get all notifications of shipcall_id
- identify the assigned list of users
- apply all filters. When a filter triggers, exit. If not, create and send a notification.
"""
update_database = False if is_test else True
# #TODO: the concept of old state and new state must be refactored.
# old state: read shipcall_id from notifications and look for the latest finding (if None -> EvaluationType.undefined)
# new state: read shipcall_id from shipcalls and look for the *current* 'evaluation' (-> EvaluationType(value))
# get existing notifications by shipcall_id (list)
existing_notifications = Notifier.get_existing_notifications(shipcall_id=shipcall.id)
old_state = NotImplementedError
new_state = shipcall.evaluation
# get User by querying all History objects of a shipcall_id
users = Notifier.get_users_via_history(shipcall_id=shipcall.id)
# identify necessity
# state-check: Did the 'evaluation' shift to a higher level of severity?
severity_bool = Notifier.check_higher_severity(old_state, new_state)
if not severity_bool:
return None
debug = is_test # if is_test, the Emails will not be issued. Only a print message will be created.
# #TODO: time-based filter. There shall be 'enough' time between the evaluation time and NOW update_database = True if not is_test else False # if_test, the database will not be updated.
evaluation_time = shipcall.evaluation_time time_diff_threshold = time_diff_threshold if not is_test else 0.0 # 0.0 delay when is_test is set.
# latency_bool = #TODO_DIFFERENCE_FROM_NOW_TO_EVALUATION_TIME____THIS_METHOD_ALREADY_EXISTS(evaluation_time)
# careful: what is True, what is False?
# if latency_booL:
# return None
notification_list = [] email_handler = EmailHandler(mail_server='w01d5503.kasserver.com', mail_port=465, mail_address="max.metz@scope-sorting.com")
for user in users: pwd = b'gAAAAABmqJlkXbtJTL1tFiyQNHhF_Y7sgtVI0xEx07ybwbX70Ro1Vp73CLDq49eFSYG-1SswIDQ2JBSORYlWaR-Vh2kIwPHy_lX8SxkySrRvBRzkyZP5x0I='
notification = Notifier.create(
shipcall.id,
old_state,
new_state,
user,
update_database=update_database,
is_test=is_test
)
notification_list.append(notification)
return notification_list
@staticmethod
def publish(shipcall_id, old_state, new_state, user, update_database:bool=False)->typing.Optional[model.Notification]:
"""
Complex-function, which creates, sends and documents a notification. It serves as a convenience function.
The method does not apply internal filters to identify, whether a notification should be created in the first place.
options: try:
update_database: bool. # login in advance, so the email handler uses a shared connection. It disconnects only once at the end of the call.
# #TODO: instead of update_database, one may also use is_test email_handler.login(interactive=False, pwd=pwd)
"""
# 1.) create
# ... = Notifier.create(shipcall_id, old_state, new_state, user) # e.g., might return a dictionary of dict[model.NotificationType, str], where str is the message
# 2.) send # get candidates: find all eligible shipcalls, where the evaluation state is yellow or red & the notifications are not yet sent
# ... = Notifier.send(...) # should contain internal 'logistics', which user the respective handlers to send notifications eligible_shipcalls = Notifier.get_eligible_shipcalls()
# find all notifications, which belong to the shipcall ids of the eligible_shipcall list
# a time_diff_threshold is used to block those notifications, which are still fairly novel
eligible_notifications = Notifier.get_eligible_notifications(eligible_shipcalls, time_diff_threshold)
for notification in eligible_notifications:
# get all users, which are attached to the shipcall (uses the History dataset)
users = Notifier.get_users_via_history(notification.shipcall_id)
# filter: only consider the users, which have subscribed to the notification type
users = [user for user in users if Notifier.check_user_is_subscribed_to_notification_type(user,notification_type=notification.type)]
# obtain the mail address of each respective user
Notifier.create_and_send_email_notification(email_handler, pwd, users, notification, update_database=update_database, debug=debug)
finally:
email_handler.close()
# 3.) document (mysql database)
# if update_database
# ... = Notifier.document(...)
raise NotImplementedError("skeleton")
return return
@staticmethod @staticmethod
@ -422,7 +356,7 @@ class Notifier():
# use ship & shipcall data models to prepare the body # use ship & shipcall data models to prepare the body
ship_name = ship.name ship_name = ship.name
eta_etd = Notifier.create_etaetd_string(shipcall.eta, shipcall.etd) eta_etd = Notifier.create_etaetd_string(shipcall.eta, shipcall.etd)
eta_etd_type = eta_etd_type_dict[ShipcallType(shipcall.type)] eta_etd_type = eta_etd_type_dict[model.ShipcallType(shipcall.type)]
evaluation_message = shipcall.evaluation_message evaluation_message = shipcall.evaluation_message
return (ship_name, evaluation_message, eta_etd, eta_etd_type) return (ship_name, evaluation_message, eta_etd, eta_etd_type)
@ -435,6 +369,25 @@ class Notifier():
schemas = execute_sql_query_standalone(query=query, param=schemaModel, command_type="execute") schemas = execute_sql_query_standalone(query=query, param=schemaModel, command_type="execute")
return return
@staticmethod
def create_and_send_email_notification(email_handler:EmailHandler, pwd:bytes, users:list[model.User], notification:model.Notification, update_database:bool=True, debug:bool=False):
email_tgts = [user.user_email for user in users if user.user_email is not None]
ship_name, evaluation_message, eta_etd, eta_etd_type = Notifier.prepare_notification_body(notification)
content = get_default_html_email()
files = [] # optional attachments
msg_multipart,msg_content = create_shipcall_evaluation_notification(
email_handler, ship_name, evaluation_message, eta_etd, eta_etd_type, content, files=files
)
# send the messages via smtlib's SSL functions
send_notification(email_handler, email_tgts, msg_multipart, pwd, debug=debug)
if update_database:
Notifier.shipcall_put_update_evaluation_notifications_sent_flag(notification)
return
@staticmethod @staticmethod
def check_user_is_subscribed_to_notification_type(user,notification_type): def check_user_is_subscribed_to_notification_type(user,notification_type):

View File

@ -287,7 +287,9 @@ def create_shipcall_evaluation_notification(email_handler, ship_name:str, evalua
return (msg_multipart,content) return (msg_multipart,content)
def send_notification(email_handler, email_tgts, msg, pwd, debug=False): def send_notification(email_handler, email_tgts, msg, pwd, debug=False):
email_handler.login(interactive=False, pwd=pwd) already_logged_in = email_handler.check_login()
if not already_logged_in:
email_handler.login(interactive=False, pwd=pwd)
try: try:
assert email_handler.check_login() assert email_handler.check_login()
@ -297,5 +299,6 @@ def send_notification(email_handler, email_tgts, msg, pwd, debug=False):
print(f"(send_notification INFO): debugging state. Would have sent an Email to: {email_tgts}") print(f"(send_notification INFO): debugging state. Would have sent an Email to: {email_tgts}")
finally: finally:
email_handler.close() if not already_logged_in:
email_handler.close()
return return