MishkaCmsCoreWeb. Live. Admin. Components. ActivityFeed
(Mishka CMS Core v0.0.1)
Copy Markdown
View Source
Real-time activity feed driven by a pluggable list of sources, rendered as a collapsed "Logs" toggle over a System Logs panel.
Source adapters implement MishkaCmsCore.Activity.Behaviours.Source. Each adapter
knows its own topics, broadcasts, entry shape, and initial fetch — this
component just composes them.
Usage
Wire a page with setup/2 and render it with feed/1; see setup/2 for the
options and the ordering rule. A page with a collapsible panel of its own pairs
the two with close_panel/2, so only one of them is open at a time.
Behaviour
- The panel is collapsed on load. The toggle shows/hides it client-side with
Phoenix.LiveView.JS(a fade/slide transition), and pushes to the server so the first open can fetch. - History is fetched on the FIRST open, not on mount — a page carrying a feed pays nothing for it until asked. Skeleton rows stand in while the fetch runs.
- The panel does not page. It shows one window of
:limitrows and links out to the full activity page, which pages properly — asking a reader to manage a growing preview and a full list at once was the worse of the two. - Live broadcasts arriving before the first open are buffered and merged into
that first fetch — sources with no history (
RuntimeCompile) only ever appear live, so dropping them on the opening fetch would lose them.
Parent responsibilities
The component runs in the parent LiveView's process, so PubSub messages
arrive at the parent's mailbox. setup/2 handles this; a parent driving the
component directly must:
- On
mount, subscribe totopics_for/2,3. - Forward incoming
%Phoenix.Socket.Broadcast{}messages to this component viasend_update/3, under:broadcast. Usehandles?/2to filter.
See also: MishkaCmsCore.Activity.Sources.PaperTrail,
MishkaCmsCore.Activity.Sources.Oban.
Summary
Functions
JS that closes the feed's panel: the client-side hide, plus the push that tells the feed its panel is shut so its toggle stops reading open.
A failed fetch still opens the feed to whatever arrives live.
Closes the panel without regard to what it was doing, so a page that closed it client-side to make
room for its own panel can say so. Unlike "toggle_logs" this is not a flip: a page fires it on
every one of its own opens, whether the feed was open or not.
True when at least one source claims ownership of the broadcast. Use in
the parent's handle_info/2 to decide whether to forward.
One-call wiring for a parent LiveView. Subscribes to the activity topics for
opts[:resources] and attaches a handle_info hook that forwards matching
broadcasts to this component — the page's own handle_info clauses are left
untouched (resource notifications pass through so the table still updates;
Oban broadcasts are consumed here so they never reach an unmatched clause).
PubSub topics the parent should subscribe to so the feed receives live
updates. Pass the same current_user, sources, and resources you
pass to the component itself.
Functions
JS that closes the feed's panel: the client-side hide, plus the push that tells the feed its panel is shut so its toggle stops reading open.
A page with a panel of its own chains this onto that panel's toggle, and passes the reverse to
feed/1's :on_toggle, so opening either panel closes the other. Both halves are unconditional:
hiding an already-hidden panel does nothing, so neither toggle has to know which panel is open.
<ActivityFeed.feed setup={@activity_feed} on_toggle={close_issues()} />
defp toggle_issues(setup) do
%JS{}
|> ActivityFeed.close_panel(setup)
|> JS.toggle(to: "#issues-panel")
|> JS.push("toggle_issues")
endTakes the @activity_feed map from setup/2, whose :id names both the panel and the feed's own
root element — the push is aimed at that root, so it reaches the component rather than the page.
Renders the feed from the @activity_feed map produced by setup/2. The
scope (resources/sources) is fixed by setup/2 so the subscription and
the display always agree; only :limit may be overridden per call.
<ActivityFeed.feed setup={@activity_feed} />
<ActivityFeed.feed setup={@activity_feed} limit={10} />Attributes
setup(:map) (required)limit(:integer) - Defaults tonil.on_toggle(Phoenix.LiveView.JS) - JS to run when the feed's toggle is clicked, ahead of the panel's own transition. A page with a panel of its own passes the JS closing that panel, so only one is ever open;close_panel/2is the mirror image, for that panel's toggle to close the feed.Defaults to
%Phoenix.LiveView.JS{ops: []}.
Slots
toggles- Buttons that sit beside the feed's own toggle, on its row. A page with its own panel puts its toggle here rather than beside the feed: the feed is full width so its panel is, and wrapping it in a column to make room would narrow the panel too.
A failed fetch still opens the feed to whatever arrives live.
The history could not be read, but broadcasts keep coming, and they are the only entries this feed
will ever have. Leaving loaded? false would buffer every one of them into :pending while the
header went on counting them — a list that says "3 entries" and shows none.
Closes the panel without regard to what it was doing, so a page that closed it client-side to make
room for its own panel can say so. Unlike "toggle_logs" this is not a flip: a page fires it on
every one of its own opens, whether the feed was open or not.
True when at least one source claims ownership of the broadcast. Use in
the parent's handle_info/2 to decide whether to forward.
One-call wiring for a parent LiveView. Subscribes to the activity topics for
opts[:resources] and attaches a handle_info hook that forwards matching
broadcasts to this component — the page's own handle_info clauses are left
untouched (resource notifications pass through so the table still updates;
Oban broadcasts are consumed here so they never reach an unmatched clause).
Because that hook halts the Oban and runtime-compile broadcasts, any hook of
the page's own that needs them (RuntimeStatusLive.setup/2,
PreviewHelpers.setup/1, a site-health hook) must be attached BEFORE this one.
Call it in mount/3 after current_user is assigned, then render the feed
with feed/1, passing the single @activity_feed assign it sets:
socket =
ActivityFeed.setup(socket,
resources: [MishkaCmsCore.Runtime.Component],
feed_id: "components-activity"
)
# in the template:
<ActivityFeed.feed setup={@activity_feed} />Options
:resources— modules to scope to; omit for a site-wide feed.:feed_id— this component's id (default"activity-feed").:sources— source adapters (default: all built-in — PaperTrail, EventLog, Oban, RuntimeCompile, RuntimeDraft).:limit— how many rows the panel shows (default5). A template may override it per call via<ActivityFeed.feed setup={...} limit={10} />.
PubSub topics the parent should subscribe to so the feed receives live
updates. Pass the same current_user, sources, and resources you
pass to the component itself.