feat(webhook): time-series histogram stats endpoint + frontend chart

This commit is contained in:
Александр Зимин
2026-05-12 14:30:22 +00:00
parent 91f4310a77
commit 5479142093
11 changed files with 1049 additions and 2 deletions
@@ -58,4 +58,38 @@ public interface WebhookDeliveryRepository extends JpaRepository<WebhookDelivery
/** История доставок одной подписки фильтрованная по status. */
Page<WebhookDelivery> findBySubscriptionIdAndStatusOrderByCreatedAtDesc(
UUID subscriptionId, WebhookDelivery.Status status, Pageable pageable);
/**
* Histogram aggregation для admin UI time-series chart. Группирует deliveries
* подписки по time bucket (hour/day) × status, возвращает count'ы.
*
* <p>{@code bucketUnit} — Postgres date_trunc валидное значение:
* {@code 'hour'} или {@code 'day'} (другие unit'ы service rejects).
* Параметр инъектируется через native query (нельзя bind'ить interval label
* как ?, см. PG docs «String literal type cast»).
*
* <p>Returns rows как {@code Object[]{OffsetDateTime bucketTs, String status,
* Long count}}. Service aggregate'ит в structured DTO с пустыми bucket'ами
* заполненными нулями (continuous timeline).
*
* <p>Index plan: {@code idx_webhook_deliveries_sub_created} (subscription_id,
* created_at). Если будем фильтровать без сабскрипции (всё-про-всё) —
* нужен будет ещё один index, но текущий use case admin-UI всегда per-sub.
*/
@Query(value = """
SELECT date_trunc(:bucketUnit, created_at) AS bucket_ts,
status::text AS status,
COUNT(*) AS cnt
FROM webhook_deliveries
WHERE subscription_id = :subscriptionId
AND created_at >= :fromTs
AND created_at < :toTs
GROUP BY bucket_ts, status
ORDER BY bucket_ts ASC
""", nativeQuery = true)
List<Object[]> aggregateByBucket(
@Param("subscriptionId") UUID subscriptionId,
@Param("bucketUnit") String bucketUnit,
@Param("fromTs") OffsetDateTime fromTs,
@Param("toTs") OffsetDateTime toTs);
}