perf: add indexed notification summaries and widget

This commit is contained in:
2026-07-29 14:16:29 +02:00
parent 992d2ca533
commit e32ba3663b
9 changed files with 312 additions and 36 deletions

View File

@@ -19,6 +19,14 @@ class NotificationMessage(Base, TimestampMixin):
__table_args__ = (
Index("ix_notification_messages_tenant_status", "tenant_id", "status"),
Index("ix_notification_messages_tenant_recipient", "tenant_id", "recipient_type", "recipient_id"),
Index(
"ix_notification_messages_summary",
"tenant_id",
"recipient_id",
"deleted_at",
"status",
"read_at",
),
Index("ix_notification_messages_source", "tenant_id", "source_module", "source_resource_type", "source_resource_id"),
Index("ix_notification_messages_due", "tenant_id", "status", "not_before_at"),
)

View File

@@ -104,6 +104,13 @@ manifest = ModuleManifest(
),
),
view_surfaces=(
ViewSurface(
id="notifications.widget.summary",
module_id=MODULE_ID,
kind="section",
label="Notification summary widget",
order=30,
),
ViewSurface(
id="notifications.settings.preferences",
module_id=MODULE_ID,

View File

@@ -0,0 +1,29 @@
"""Add notification summary covering index.
Revision ID: 7a8b9c0d1e2f
Revises: 6f7a8b9c0d1e
"""
from alembic import op
revision = "7a8b9c0d1e2f"
down_revision = "6f7a8b9c0d1e"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.create_index(
"ix_notification_messages_summary",
"notification_messages",
["tenant_id", "recipient_id", "deleted_at", "status", "read_at"],
unique=False,
)
def downgrade() -> None:
op.drop_index(
"ix_notification_messages_summary",
table_name="notification_messages",
)

View File

@@ -6,7 +6,7 @@ from email.message import EmailMessage
from pathlib import Path
from typing import Any
from sqlalchemy import event, or_
from sqlalchemy import and_, case, event, func, or_
from sqlalchemy.orm import Session
from govoplan_core.core.notifications import NotificationDispatchRequest
@@ -200,21 +200,75 @@ def notification_summary(
user_id: str | None = None,
recipient_ids: tuple[str, ...] | None = None,
) -> dict[str, int | bool]:
base_query = session.query(NotificationMessage).filter(
filters = [
NotificationMessage.tenant_id == tenant_id,
NotificationMessage.deleted_at.is_(None),
)
]
if recipient_ids is not None:
base_query = base_query.filter(NotificationMessage.recipient_id.in_(recipient_ids))
active_query = base_query.filter(NotificationMessage.status.notin_(["cancelled", "skipped"]))
filters.append(NotificationMessage.recipient_id.in_(recipient_ids))
active = NotificationMessage.status.notin_(["cancelled", "skipped"])
total, unread, pending, failed = (
session.query(
func.count(NotificationMessage.id),
func.coalesce(
func.sum(
case(
(
and_(
active,
NotificationMessage.read_at.is_(None),
),
1,
),
else_=0,
)
),
0,
),
func.coalesce(
func.sum(
case(
(
and_(
active,
NotificationMessage.status.in_(
sorted(PENDING_STATUSES)
),
),
1,
),
else_=0,
)
),
0,
),
func.coalesce(
func.sum(
case(
(
and_(
active,
NotificationMessage.status == "failed",
),
1,
),
else_=0,
)
),
0,
),
)
.filter(*filters)
.one()
)
show_unread_badge = True
if user_id:
show_unread_badge = get_notification_preferences(session, tenant_id=tenant_id, user_id=user_id).show_unread_badge
return {
"total": base_query.count(),
"unread": active_query.filter(NotificationMessage.read_at.is_(None)).count(),
"pending": active_query.filter(NotificationMessage.status.in_(sorted(PENDING_STATUSES))).count(),
"failed": active_query.filter(NotificationMessage.status == "failed").count(),
"total": int(total or 0),
"unread": int(unread or 0),
"pending": int(pending or 0),
"failed": int(failed or 0),
"show_unread_badge": show_unread_badge,
}