Class: CrmNavbarBroadcaster

Inherits:
Object
  • Object
show all
Defined in:
app/services/crm_navbar_broadcaster.rb

Overview

Broadcasts a refreshed navbar fragment (a single badge or the user's
presence dot) to one user's personal notification stream.

All fragments are user-specific: the SMS badge for user A reflects A's
watch list, not B's. We therefore broadcast to the per-user channel
notifications:#{user.id} (already subscribed by the CRM layout).

To eliminate wasted renders, every broadcast first checks
CrmNavbarLiveUsers.live? — if the user isn't currently sitting on a CRM
page, no template render or Redis publish happens.

This class only does the render + publish. Coalescing (collapsing bursts
of broadcast requests for the same user/badge into one) is the worker's
job; see CrmNavbarRefreshWorker.

Constant Summary collapse

BADGES =

Each badge declares the partial that renders just its fragment and the
DOM id the broadcast targets. Adding a new live navbar element is a
matter of adding a row here and creating the partial.

attributes: is an optional hash passed through to the rendered
<turbo-stream> tag. Use it to opt a fragment into Turbo 8's morph
behavior (method: :morph) when node identity must be preserved
because something else (Bootstrap component, Stimulus value cache,
focus state, ...) holds a reference to it.

{
  # Unified counter for the navbar's Communications offcanvas (sms +
  # voicemail + email). Replaced the per-channel :sms/:voicemail/:email
  # entries when those three dropdowns were merged into one panel; the
  # CrmNavbarFanoutWorker translates incoming sms/voicemail/email events
  # to this badge so writes still trigger a live refresh.
  communications: { partial: 'menus/crm_nav_communications_badge', target: 'crm-nav-communications-badge' },
  pinned:         { partial: 'menus/crm_nav_pinned_badge',         target: 'crm-nav-pinned-badge' },
  # `method: :morph` so the broadcast updates the existing
  # `#my-presence-info` button's attributes/children in place instead of
  # swapping the node out. The button is a Bootstrap Dropdown toggle and
  # Bootstrap caches its menu reference on the toggle node on first
  # construction; a node-level replace mid-transition leaves the cached
  # instance pointing at a detached element and the next click no-ops
  # until the user clicks again. Morph keeps the toggle node — and
  # therefore the cached Dropdown instance — alive.
  presence_dot:   { partial: 'menus/crm_nav_presence_dot',         target: 'my-presence-info', attributes: { method: :morph } }
}.freeze

Class Method Summary collapse

Class Method Details

.refresh(user, badge:) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'app/services/crm_navbar_broadcaster.rb', line 47

def refresh(user, badge:)
  cfg = BADGES.fetch(badge.to_sym)
  return false unless CrmNavbarLiveUsers.live?(user.id)

  Turbo::StreamsChannel.broadcast_replace_to(
    stream_name_for(user),
    target:     cfg[:target],
    partial:    cfg[:partial],
    locals:     { user: user },
    attributes: cfg.fetch(:attributes, {})
  )
  true
end

.stream_name_for(user) ⇒ Object



61
62
63
# File 'app/services/crm_navbar_broadcaster.rb', line 61

def stream_name_for(user)
  "notifications:#{user.id}"
end