feat(trackers): user filters for Gitea, webhook polling cleanup, dashboard navigability
- Gitea: NotificationTracker now exposes sender allowlist / blocklist filters via MultiEntitySelect, populated from Gitea /users/search merged with past EventLog senders so the picker is useful before the first webhook arrives. - Webhook providers (gitea, planka, webhook): stop scheduling interval polling jobs on tracker create/update/startup; hide the "every Xs" indicator in the tracker list since there is no polling. - Dashboard: stat cards are now <a> links that route to providers, trackers, targets, command-trackers, or scroll to the events panel. Provider deck rows highlight the target provider on click. - Command trackers / command configs: auto-reselect the right config when the provider type changes (matches notification-tracker behavior). - Migration: drop legacy batch_duration column from notification_tracker — the field is gone from the model but its NOT NULL constraint blocked inserts on older DBs. - Docs: refresh entity-relationships.md with current NotificationTracker fields (filters, adaptive_max_skip, default_*_config_id).
This commit is contained in:
@@ -378,6 +378,8 @@ async def _load_tracker_jobs() -> None:
|
||||
|
||||
tz = await _load_app_timezone()
|
||||
|
||||
from notify_bridge_core.providers.capabilities import get_capabilities
|
||||
|
||||
for tracker in trackers:
|
||||
job_id = f"tracker_{tracker.id}"
|
||||
if scheduler.get_job(job_id):
|
||||
@@ -386,6 +388,18 @@ async def _load_tracker_jobs() -> None:
|
||||
ptype = provider_types.get(tracker.provider_id, "")
|
||||
filters = tracker.filters or {}
|
||||
|
||||
# Webhook-based providers receive events via inbound HTTP — there is
|
||||
# nothing to poll. Scheduling an interval job for them just wakes up
|
||||
# check_tracker every scan_interval seconds to immediately return,
|
||||
# wasting CPU and DB queries for no work.
|
||||
caps = get_capabilities(ptype) if ptype else None
|
||||
if caps and caps.webhook_based:
|
||||
_LOGGER.debug(
|
||||
"Skipping interval scheduling for webhook tracker %d (%s, type=%s)",
|
||||
tracker.id, tracker.name, ptype,
|
||||
)
|
||||
continue
|
||||
|
||||
# Scheduler providers can use cron triggers
|
||||
if ptype == "scheduler" and filters.get("schedule_type") == "cron":
|
||||
cron_expr = filters.get("cron_expression", "")
|
||||
@@ -450,6 +464,29 @@ def _add_cron_job(
|
||||
)
|
||||
|
||||
|
||||
async def _is_webhook_tracker(tracker_id: int) -> bool:
|
||||
"""Return True iff the tracker's provider type is webhook-based.
|
||||
|
||||
Looks up provider type once via the capabilities registry. Used by
|
||||
``schedule_tracker`` to short-circuit interval scheduling.
|
||||
"""
|
||||
from sqlmodel import select
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
from notify_bridge_core.providers.capabilities import get_capabilities
|
||||
from ..database.engine import get_engine
|
||||
from ..database.models import NotificationTracker, ServiceProvider as ServiceProviderModel
|
||||
|
||||
async with AsyncSession(get_engine()) as session:
|
||||
tracker = await session.get(NotificationTracker, tracker_id)
|
||||
if tracker is None:
|
||||
return False
|
||||
provider = await session.get(ServiceProviderModel, tracker.provider_id)
|
||||
if provider is None:
|
||||
return False
|
||||
caps = get_capabilities(provider.type)
|
||||
return bool(caps and caps.webhook_based)
|
||||
|
||||
|
||||
async def schedule_tracker(
|
||||
tracker_id: int,
|
||||
interval: int,
|
||||
@@ -461,6 +498,10 @@ async def schedule_tracker(
|
||||
``adaptive_max_skip`` mirrors the DB column and is registered with the
|
||||
adaptive module-state so tick-time skip decisions don't re-query the DB.
|
||||
Pass ``None`` or ``0`` to disable back-off for the tracker.
|
||||
|
||||
Webhook-based providers receive events via inbound HTTP and have nothing
|
||||
to poll, so this no-ops for them — preventing scan_interval from creating
|
||||
useless wakeups via the API create/update path.
|
||||
"""
|
||||
scheduler = get_scheduler()
|
||||
job_id = f"tracker_{tracker_id}"
|
||||
@@ -474,6 +515,13 @@ async def schedule_tracker(
|
||||
if scheduler.get_job(job_id):
|
||||
scheduler.remove_job(job_id)
|
||||
|
||||
# Webhook-based providers don't poll — skip job creation entirely.
|
||||
if await _is_webhook_tracker(tracker_id):
|
||||
_LOGGER.debug(
|
||||
"Skipping interval scheduling for webhook tracker %d", tracker_id,
|
||||
)
|
||||
return
|
||||
|
||||
if cron_expression:
|
||||
try:
|
||||
tz = await _load_app_timezone()
|
||||
|
||||
Reference in New Issue
Block a user