lib/event_store_dashboard/components/stream_modal.ex

defmodule EventStoreDashboard.Components.StreamModal 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(:stream_uuid, :string, required: true)
  attr(:socket, Socket, required: true)

  def render(assigns) do
    stream = fetch_stream(assigns.page.node, assigns.ctx, assigns.stream_uuid)
    snapshot = fetch_snapshot(assigns.page.node, assigns.ctx, assigns.stream_uuid)
    assigns = assign(assigns, stream: stream, snapshot: snapshot)

    ~H"""
    <div>
      <%= if @stream do %>
        <div class="tabular-info">
          <table class="table table-hover tabular-info-table">
            <tbody>
              <tr>
                <td class="border-top-0">Stream</td>
                <td class="border-top-0"><pre>{@stream.stream_uuid}</pre></td>
              </tr>
              <tr>
                <td>ID</td>
                <td><pre>{@stream.stream_id}</pre></td>
              </tr>
              <tr>
                <td>Stream Version</td>
                <td>
                  <EventLink.render
                    socket={@socket}
                    page={@page}
                    ctx={@ctx}
                    stream_uuid={@stream.stream_uuid}
                    event_number={@stream.stream_version}
                  />
                </td>
              </tr>
              <tr>
                <td>Status</td>
                <td><.status_badge status={@stream.status} /></td>
              </tr>
              <tr :if={@snapshot}>
                <td>Snapshot</td>
                <td>
                  <.link
                    patch={snapshot_modal_path(@socket, @page, @ctx, @snapshot.source_uuid)}
                    class="es-modal-trigger"
                  >
                    @{@snapshot.source_version}
                  </.link>
                  <span :if={snapshot_lag(@stream, @snapshot) > 0} class="badge badge-warning ml-2">
                    {snapshot_lag(@stream, @snapshot)} events behind
                  </span>
                </td>
              </tr>
              <tr>
                <td>Created at</td>
                <td><pre>{@stream.created_at}</pre></td>
              </tr>
              <tr :if={@stream.deleted_at}>
                <td>Deleted at</td>
                <td><pre>{@stream.deleted_at}</pre></td>
              </tr>
            </tbody>
          </table>
        </div>

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

  attr(:status, :atom, required: true)

  defp status_badge(%{status: :deleted} = assigns) do
    ~H|<span class="badge badge-danger">deleted</span>|
  end

  defp status_badge(%{status: :created} = assigns) do
    ~H|<span class="badge badge-success">created</span>|
  end

  defp status_badge(assigns) do
    ~H|<span class="badge badge-secondary">unknown</span>|
  end

  defp fetch_stream(node, %Context{} = ctx, stream_uuid) do
    case Repo.fetch_stream(node, ctx, stream_uuid) do
      {:ok, stream} -> stream
      _ -> nil
    end
  end

  defp fetch_snapshot(_node, _ctx, "$all"), do: nil

  defp fetch_snapshot(node, %Context{} = ctx, stream_uuid) do
    case Repo.fetch_snapshot_summary(node, ctx, stream_uuid) do
      {:ok, snapshot} -> snapshot
      _ -> nil
    end
  end

  defp snapshot_lag(stream, snapshot) do
    max((stream.stream_version || 0) - (snapshot.source_version || 0), 0)
  end

  defp snapshot_modal_path(socket, page, %Context{} = ctx, source_uuid) do
    Params.to_live_dashboard_path(socket, page, %Params{
      eventstore: ctx.event_store,
      nav: "snapshots",
      snapshot_uuid: source_uuid,
      stream_modal: nil
    })
  end

  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
end