add activity log with order timeline and global feed
All checks were successful
deploy / deploy (push) Successful in 4m22s

Single activity_log table powering two views: chronological timeline
on each order detail page (replacing the old fulfilment card) and a
global feed at /admin/activity with tabs, category filters, search,
and pagination. Real-time via PubSub — new entries appear instantly,
nav badge updates across all admin pages.

Instrumented across all event points: Stripe webhooks, order notifier,
submission worker, fulfilment status worker, product sync worker, and
Oban exhausted-job telemetry. Contextual action buttons (retry
submission, retry sync, dismiss) with Oban unique constraints to
prevent double-enqueue. 90-day pruning via cron.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-03-01 15:09:08 +00:00
parent b235219aee
commit 580a7203c9
23 changed files with 1716 additions and 54 deletions

View File

@@ -5,7 +5,7 @@ defmodule BerrypodWeb.AdminLayoutHook do
"""
import Phoenix.Component
alias Berrypod.Settings
alias Berrypod.{ActivityLog, Settings}
alias Berrypod.Theme.{CSSCache, CSSGenerator}
def on_mount(:assign_current_path, _params, _session, socket) do
@@ -22,6 +22,8 @@ defmodule BerrypodWeb.AdminLayoutHook do
css
end
if Phoenix.LiveView.connected?(socket), do: ActivityLog.subscribe()
socket =
socket
|> assign(:current_path, "")
@@ -29,6 +31,7 @@ defmodule BerrypodWeb.AdminLayoutHook do
|> assign(:email_configured, Berrypod.Mailer.email_configured?())
|> assign(:theme_settings, theme_settings)
|> assign(:generated_css, generated_css)
|> assign(:attention_count, ActivityLog.count_needing_attention())
|> Phoenix.LiveView.attach_hook(:set_current_path, :handle_params, fn _params,
uri,
socket ->
@@ -37,6 +40,13 @@ defmodule BerrypodWeb.AdminLayoutHook do
|> assign(:current_path, URI.parse(uri).path)
|> assign(:site_live, Settings.site_live?())}
end)
|> Phoenix.LiveView.attach_hook(:update_attention_count, :handle_info, fn
{:new_activity, _entry}, socket ->
{:cont, assign(socket, :attention_count, ActivityLog.count_needing_attention())}
_other, socket ->
{:cont, socket}
end)
{:cont, socket}
end