lib/event_store_dashboard/components/subscription_modal.ex

defmodule EventStoreDashboard.Components.SubscriptionModal do
  @moduledoc false

  use Phoenix.Component

  alias EventStoreDashboard.Components.EventLink
  alias EventStoreDashboard.Params
  alias EventStoreDashboard.Repo
  alias EventStoreDashboard.Repo.Context
  alias Phoenix.LiveDashboard.PageBuilder
  alias Phoenix.LiveView.Socket

  attr(:page, PageBuilder, required: true)
  attr(:ctx, Context, required: true)
  attr(:subscription_id, :integer, required: true)
  attr(:socket, Socket, required: true)

  @pending_limit 10

  def render(assigns) do
    subscription = fetch_subscription(assigns.page.node, assigns.ctx, assigns.subscription_id)
    pending = fetch_pending(assigns.page.node, assigns.ctx, subscription)

    assigns =
      assign(assigns,
        subscription: subscription,
        pending: pending,
        pending_limit: @pending_limit
      )

    ~H"""
    <div>
      <%= if @subscription do %>
        <div class="tabular-info">
          <table class="table table-hover tabular-info-table">
            <tbody>
              <tr>
                <td class="border-top-0">ID</td>
                <td class="border-top-0"><pre>{@subscription.subscription_id}</pre></td>
              </tr>
              <tr>
                <td>Name</td>
                <td><pre>{@subscription.subscription_name}</pre></td>
              </tr>
              <tr>
                <td>Stream</td>
                <td>
                  <.link
                    patch={stream_modal_path(@socket, @page, @ctx, @subscription.stream_uuid)}
                    class="es-modal-trigger"
                  >
                    {@subscription.stream_uuid}
                  </.link>
                </td>
              </tr>
              <tr>
                <td>Last seen</td>
                <td>
                  <EventLink.render
                    socket={@socket}
                    page={@page}
                    ctx={@ctx}
                    stream_uuid={@subscription.stream_uuid}
                    event_number={@subscription.last_seen}
                  />
                </td>
              </tr>
              <tr>
                <td>Stream Version</td>
                <td>
                  <EventLink.render
                    socket={@socket}
                    page={@page}
                    ctx={@ctx}
                    stream_uuid={@subscription.stream_uuid}
                    event_number={@subscription.stream_version}
                  />
                </td>
              </tr>
              <tr>
                <td>Lag</td>
                <td><pre>{@subscription.lag}</pre></td>
              </tr>
              <tr>
                <td>Created at</td>
                <td><pre>{@subscription.created_at}</pre></td>
              </tr>
            </tbody>
          </table>
        </div>

        <div :if={@subscription.lag > 0} class="mt-3">
          <h6 class="mb-2">
            Next pending events
            <small class="text-muted">
              (showing up to {@pending_limit} of {@subscription.lag})
            </small>
          </h6>
          <p :if={@pending == []} class="text-muted small mb-0">
            Could not load pending events.
          </p>
          <table :if={@pending != []} class="table table-sm tabular-info-table">
            <thead>
              <tr>
                <th>{position_header(@subscription.stream_uuid)}</th>
                <th>Type</th>
                <th>Created at</th>
              </tr>
            </thead>
            <tbody>
              <tr :for={event <- @pending}>
                <td>
                  <EventLink.render
                    socket={@socket}
                    page={@page}
                    ctx={@ctx}
                    stream_uuid={@subscription.stream_uuid}
                    event_number={event.event_number}
                  />
                </td>
                <td><pre class="mb-0">{event.event_type}</pre></td>
                <td><pre class="mb-0">{event.created_at}</pre></td>
              </tr>
            </tbody>
          </table>
        </div>

        <div class="modal-footer">
          <.link
            patch={view_events_path(@socket, @page, @ctx, @subscription.stream_uuid)}
            class="btn btn-sm btn-primary"
          >
            View events on this stream
          </.link>
        </div>
      <% else %>
        <div class="alert alert-warning" role="alert">
          Subscription not found.
        </div>
      <% end %>
    </div>
    """
  end

  defp fetch_subscription(node, %Context{} = ctx, subscription_id) do
    case Repo.fetch_subscription_by_id(node, ctx, subscription_id) do
      {:ok, subscription} -> subscription
      _ -> nil
    end
  end

  defp fetch_pending(_node, _ctx, nil), do: []
  defp fetch_pending(_node, _ctx, %{lag: 0}), do: []

  defp fetch_pending(node, %Context{} = ctx, subscription) do
    with {:ok, stream} <- Repo.fetch_stream(node, ctx, subscription.stream_uuid),
         {:ok, rows} <-
           Repo.query_events_after_version(
             node,
             ctx,
             stream.stream_id,
             subscription.last_seen || 0,
             @pending_limit
           ) do
      Enum.map(rows, &Repo.row_to_event(&1, ctx))
    else
      _ -> []
    end
  end

  defp position_header("$all"), do: "Position"
  defp position_header(_), do: "Stream Version"

  defp view_events_path(socket, page, %Context{} = ctx, stream_uuid) do
    Params.to_live_dashboard_path(socket, page, %Params{
      eventstore: ctx.event_store,
      nav: "events",
      stream: stream_uuid
    })
  end

  defp stream_modal_path(socket, page, %Context{} = ctx, stream_uuid) do
    Params.to_live_dashboard_path(socket, page, %Params{
      eventstore: ctx.event_store,
      stream_modal: stream_uuid,
      subscription_id: nil
    })
  end
end