lib/event_store_dashboard/components/snapshots_table.ex

defmodule EventStoreDashboard.Components.SnapshotsTable do
  @moduledoc false

  use Phoenix.LiveComponent

  import Phoenix.LiveDashboard.PageBuilder

  alias EventStoreDashboard.Components.{EventLink, Pagination, SnapshotModal, TableParams}
  alias EventStoreDashboard.Repo
  alias EventStoreDashboard.Repo.Context

  @page_param :snapshots_page
  @sort_columns ~w(source_uuid source_type source_version created_at)

  @impl true
  def update(assigns, socket) do
    result =
      paginate_snapshots(
        assigns.ctx,
        assigns.page.node,
        assigns.page_number,
        assigns.page.params
      )

    {:ok,
     socket
     |> assign(assigns)
     |> assign(page_param: @page_param, result: result)}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <div>
      <.live_table
        id="event_store_snapshots_table"
        page={@page}
        title="Snapshots"
        default_sort_by={:created_at}
        rows_name="snapshots"
        row_attrs={&row_attrs/1}
        row_fetcher={&fetch_rows(&1, &2, @result)}
      >
        <:col field={:source_uuid} header="Stream" sortable={:asc} />
        <:col field={:source_type} header="Type" sortable={:asc} />
        <:col field={:source_version} header="Stream Version" sortable={:asc} :let={row}>
          <EventLink.render
            socket={@socket}
            page={@page}
            ctx={@ctx}
            stream_uuid={row[:source_uuid]}
            event_number={row[:source_version]}
            stop_propagation
          />
        </:col>
        <:col field={:created_at} header="Created at" sortable={:desc} />
      </.live_table>
      <Pagination.render
        id="snapshots-pagination"
        param={@page_param}
        page_number={@page_number}
        total_pages={@result.total_pages}
        socket={@socket}
        page={@page}
      />
      <.live_modal
        :if={@snapshot_uuid}
        id="snapshot-modal"
        title="Snapshot"
        return_to={modal_return_to(@socket, @page)}
      >
        <SnapshotModal.render
          page={@page}
          ctx={@ctx}
          source_uuid={@snapshot_uuid}
          socket={@socket}
        />
      </.live_modal>
    </div>
    """
  end

  defp paginate_snapshots(nil, _node, _page_number, _url_params),
    do: %{entries: [], total_entries: 0, total_pages: 0}

  defp paginate_snapshots(%Context{} = ctx, node, page_number, url_params) do
    sort_by = TableParams.parse_sort_by(url_params, @sort_columns, :created_at)
    sort_dir = TableParams.parse_sort_dir(url_params, :desc)
    limit = TableParams.parse_limit(url_params)

    search_term = url_params |> TableParams.parse_search() |> like_pattern()
    offset = (page_number - 1) * limit

    with {:ok, total_entries} <- Repo.count_snapshots(node, ctx, search_term),
         {:ok, rows} <-
           Repo.query_snapshots(
             node,
             ctx,
             sort_by,
             sort_dir,
             search_term,
             limit,
             offset
           ) do
      total_pages = if total_entries == 0, do: 0, else: div(total_entries - 1, limit) + 1

      %{
        entries: Enum.map(rows, &Repo.row_to_snapshot_summary/1),
        total_entries: total_entries,
        total_pages: total_pages
      }
    else
      _ -> %{entries: [], total_entries: 0, total_pages: 0}
    end
  end

  defp like_pattern(nil), do: nil
  defp like_pattern(term), do: "%" <> term <> "%"

  defp fetch_rows(_params, _node, result), do: {result.entries, result.total_entries}

  defp row_attrs(row) do
    [
      {"phx-click", "show_snapshot"},
      {"phx-value-source", row.source_uuid},
      {"phx-page-loading", true}
    ]
  end

  defp modal_return_to(socket, page) do
    live_dashboard_path(socket, page, snapshot_uuid: nil)
  end
end