fix(): fixed bugs with cache invalidation, notifications and guardrails
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.11 on 2026-02-24 09:40
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('notifications', '0003_notificationrule_rate_limit_max_notifications_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='notificationlog',
|
||||
name='event_type',
|
||||
field=models.CharField(choices=[('experiment_started', 'Experiment started'), ('experiment_paused', 'Experiment paused'), ('experiment_resumed', 'Experiment resumed'), ('experiment_completed', 'Experiment completed'), ('guardrail_triggered', 'Guardrail triggered'), ('review_requested', 'Review requested'), ('review_approved', 'Review approved'), ('review_rejected', 'Review rejected')], max_length=30, verbose_name='event type'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notificationrule',
|
||||
name='event_type',
|
||||
field=models.CharField(choices=[('experiment_started', 'Experiment started'), ('experiment_paused', 'Experiment paused'), ('experiment_resumed', 'Experiment resumed'), ('experiment_completed', 'Experiment completed'), ('guardrail_triggered', 'Guardrail triggered'), ('review_requested', 'Review requested'), ('review_approved', 'Review approved'), ('review_rejected', 'Review rejected')], max_length=30, verbose_name='event type'),
|
||||
),
|
||||
]
|
||||
@@ -14,6 +14,7 @@ class ChannelType(models.TextChoices):
|
||||
class NotificationEventType(models.TextChoices):
|
||||
EXPERIMENT_STARTED = "experiment_started", _("Experiment started")
|
||||
EXPERIMENT_PAUSED = "experiment_paused", _("Experiment paused")
|
||||
EXPERIMENT_RESUMED = "experiment_resumed", _("Experiment resumed")
|
||||
EXPERIMENT_COMPLETED = "experiment_completed", _("Experiment completed")
|
||||
GUARDRAIL_TRIGGERED = "guardrail_triggered", _("Guardrail triggered")
|
||||
REVIEW_REQUESTED = "review_requested", _("Review requested")
|
||||
|
||||
@@ -200,15 +200,24 @@ def _build_event_key(
|
||||
return f"{event_type}:{payload.experiment_id}:{bucket}"
|
||||
|
||||
|
||||
def _escape_markdown(text: str) -> str:
|
||||
for ch in r"\_*[]()~`>#+-=|{}.!":
|
||||
text = text.replace(ch, f"\\{ch}")
|
||||
return text
|
||||
|
||||
|
||||
def _send_telegram(config: dict[str, Any], payload: dict[str, Any]) -> None:
|
||||
bot_token = config.get("bot_token", "")
|
||||
chat_id = config.get("chat_id", "")
|
||||
if not bot_token or not chat_id:
|
||||
raise ValueError("Telegram config requires 'bot_token' and 'chat_id'.")
|
||||
|
||||
text = f"*{payload['title']}*\n\n{payload['body']}"
|
||||
title = _escape_markdown(payload["title"])
|
||||
body = _escape_markdown(payload["body"])
|
||||
text = f"*{title}*\n\n{body}"
|
||||
if payload.get("experiment_name"):
|
||||
text += f"\n\nExperiment: {payload['experiment_name']}"
|
||||
name = _escape_markdown(payload["experiment_name"])
|
||||
text += f"\n\nExperiment: {name}"
|
||||
|
||||
api_url = config.get(
|
||||
"api_url",
|
||||
@@ -219,7 +228,7 @@ def _send_telegram(config: dict[str, Any], payload: dict[str, Any]) -> None:
|
||||
json={
|
||||
"chat_id": chat_id,
|
||||
"text": text,
|
||||
"parse_mode": "Markdown",
|
||||
"parse_mode": "MarkdownV2",
|
||||
},
|
||||
timeout=10,
|
||||
)
|
||||
@@ -249,7 +258,7 @@ def _send_smtp(config: dict[str, Any], payload: dict[str, Any]) -> None:
|
||||
def flush_pending_notifications() -> dict[str, int]:
|
||||
pending = NotificationLog.objects.filter(
|
||||
status=NotificationStatus.PENDING,
|
||||
).select_related("channel")
|
||||
).select_related("channel").order_by("created_at")
|
||||
|
||||
senders = {
|
||||
ChannelType.TELEGRAM: _send_telegram,
|
||||
|
||||
@@ -238,6 +238,27 @@ class NotificationEnqueueTest(TestCase):
|
||||
)
|
||||
self.assertEqual(len(logs), 0)
|
||||
|
||||
def test_enqueue_experiment_resumed(self) -> None:
|
||||
rule_create(
|
||||
event_type=NotificationEventType.EXPERIMENT_RESUMED,
|
||||
channel=self.channel,
|
||||
)
|
||||
logs = notification_enqueue(
|
||||
NotificationEventType.EXPERIMENT_RESUMED,
|
||||
NotificationPayload(
|
||||
title="Experiment Resumed",
|
||||
body=f"Experiment '{self.experiment.name}' - experiment resumed.",
|
||||
event_type=NotificationEventType.EXPERIMENT_RESUMED,
|
||||
experiment_id=str(self.experiment.pk),
|
||||
experiment_name=self.experiment.name,
|
||||
),
|
||||
)
|
||||
self.assertEqual(len(logs), 1)
|
||||
self.assertEqual(
|
||||
logs[0].event_type, NotificationEventType.EXPERIMENT_RESUMED
|
||||
)
|
||||
self.assertEqual(logs[0].status, NotificationStatus.PENDING)
|
||||
|
||||
|
||||
class FlushNotificationsTest(TestCase):
|
||||
@override
|
||||
|
||||
Reference in New Issue
Block a user