SIENTIAPDE-1172

feat: enhance email and slot manager activities with new filtering and caching functionalities

- Updated Email class to improve attachment handling and logging.
- Refactored Formatters class to process notifications more effectively by grouping them based on their configurations.
- Introduced new methods in SlotManager for filtering notification alerts and storing notification cache.
- Enhanced Alerts workflow to integrate new filtering and caching activities.
- Added tests for new functionalities in SlotManager and Formatters classes to ensure reliability.
This commit is contained in:
vitor-aignosi
2025-07-28 15:07:09 -03:00
parent f2cb0f057d
commit ab657d0fda
10 changed files with 786 additions and 33 deletions

View File

@@ -1,9 +1,8 @@
from email import encoders
from email.mime.base import MIMEBase
import traceback
from temporalio import workflow, activity
with workflow.unsafe.imports_passed_through():
import traceback
import smtplib
from typing import Any
from sientia_do.temporal.activities.base import BaseActivity
@@ -12,6 +11,8 @@ with workflow.unsafe.imports_passed_through():
from orchestrator.utils.email_builder import EmailBuilder
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
class Email(BaseActivity):
@@ -24,7 +25,6 @@ class Email(BaseActivity):
self.sender_email = sender_email
self.sender_password = sender_password
self.port = port
self.logger = logger
if self.sender_password:
self.server = smtplib.SMTP_SSL(smpt_server, port)
@@ -68,7 +68,7 @@ class Email(BaseActivity):
Attaches a list of attachments to an email message.
Args:
attachments (List[Dict]): A list of dictionaries where each dictionary contains
the keys 'attachment_id', 'trigger', and 'timestamp' representing the attachment details.
the keys 'filename' and 'content' representing the attachment details.
msg (MIMEMultipart): The email message object to which the attachments will be added.
Returns:
MIMEMultipart: The email message object with the attachments added.
@@ -124,7 +124,11 @@ class Email(BaseActivity):
msg['Subject'] = f"SIENTIA™ {mail_type}"
msg = self.handle_attachments(
[notification['attachment_content']
[
{
"filename": f"{notification['trigger']}_{notification['notification_id']}.txt",
"content": notification['attachment_content']
}
for notification in group_config['notifications']
if notification['attachment_content']],
msg)

View File

@@ -508,28 +508,30 @@ class Formatters(BaseActivity):
for group_name, group_config in receiver_groups.items():
notification_id = group_config['notification_id']
trigger = group_config['trigger']
for notification in group_config['notifications']:
key = f"{notification_id}:{trigger}"
notification_id = notification['notification_id']
trigger = notification['trigger']
if key not in data:
data[key] = {
'status': group_config['status'],
'timestamp': group_config['timestamp'],
'groups': [group_name],
'message': group_config['message'],
'level': group_config['level'],
'notification_id': notification_id,
'block': group_config['block'],
'schedule': trigger,
'pipeline': group_config['pipeline'],
'project': group_config['project'],
'model_name': group_config['model_name'],
'model_id': group_config['model_id'],
'mail_type': mail_type
}
else:
data[key]['groups'].append(group_name)
key = f"{notification_id}:{trigger}"
if key not in data:
data[key] = {
'status': group_config['status'],
'timestamp': notification['timestamp'],
'groups': [group_name],
'message': notification['message'],
'level': notification['level'],
'notification_id': notification_id,
'block': notification['block'],
'schedule': trigger,
'pipeline': notification['pipeline'],
'project': notification['project'],
'model_name': notification['model_name'],
'model_id': notification['model_id'],
'mail_type': mail_type
}
else:
data[key]['groups'].append(group_name)
return DataFrame(list(data.values())).to_dict()

View File

@@ -1,4 +1,4 @@
from pandas import DataFrame
from temporalio import activity, workflow
with workflow.unsafe.imports_passed_through():
@@ -9,6 +9,9 @@ with workflow.unsafe.imports_passed_through():
from sientia_do.temporal.activities.redis_base import Redis
from sientia_do.notifications.handlers import CoreNotificationHandler as NotificationHandler
from sientia_do.notifications.models import NotificationLevel
from orchestrator.utils.patterns import DEFAULT_DATE_FORMAT
from pandas import DataFrame
from datetime import datetime, timedelta
class SlotManager(Redis):
@@ -263,3 +266,74 @@ class SlotManager(Redis):
raise e
return last_data_timestamp
@activity.defn(name="filter_notification_alerts")
async def filter_notification_alerts(self, input_data: dict[str, Any]) -> dict[str, Any]:
"""
Filter notification alerts
"""
metadata = input_data['metadata']
notification_package = input_data['notification_package']
sending_configs = input_data['sending_configs']
notification_ttl = input_data['notification_ttl']
self.info("Filtering notification alerts...", metadata=metadata)
receiver_groups = {}
for receiver_group in sending_configs:
group_name = receiver_group['group_name']
receiver_groups[group_name] = {
**receiver_group,
"notifications": []
}
receiver_groups[group_name]['notifications'] = []
ignore_list = receiver_group.get('ignore', [])
for notification in notification_package:
alert_type = "do_nothing"
notification_id = notification['notification_id']
# Check if notification was recently sent
key = f"{notification['trigger']}:{notification_id}"
last_sent = self.get(key)
if last_sent is None:
alert_type = "core_alerts"
else:
last_sent = datetime.strptime(
last_sent, DEFAULT_DATE_FORMAT)
# Check if "notification_ttl" seconds has passed since last sent
if (datetime.now() - last_sent) > timedelta(seconds=notification_ttl):
alert_type = "persistent_alerts"
# Check if this group must be notified
if alert_type in receiver_group['contents'] and notification_id not in ignore_list:
receiver_groups[group_name]["notifications"].append(
notification)
return receiver_groups
@activity.defn(name="store_notification_cache")
async def store_notification_cache(self, input_data: dict[str, Any]) -> dict[str, Any]:
"""
Store notification cache
"""
metadata = input_data['metadata']
log_report = DataFrame(input_data['log_report'])
sent_ttl = input_data['sent_ttl']
self.info("Storing notification cache...", metadata=metadata)
now = datetime.now().strftime(DEFAULT_DATE_FORMAT)
for index, row in log_report.iterrows():
status = row['status']
if status == 'sent':
key = f"{row['schedule']}:{row['notification_id']}"
self.set(key, now, ttl=sent_ttl)
return log_report