lib/openagents_web/live/assignee_index_live.ex

main at 436d37facc96 · 3 KB

defmodule OpenAgentsWeb.AssigneeIndexLive do
  @moduledoc """
  Renders assignees derived from existing issue data.

  The page is a frequency aggregate over issues, so it moves whenever the
  repository's issues do. It hears that on the issue topic and re-reads
  through the same writable-repository check it mounted under; the message
  carries a repository id and nothing else.

  The aggregate is grouped in Postgres rather than in memory. Loading every
  issue in a repository to count logins is the read `93c3383` removed from the
  homepage, and a page that re-read it per write would pay it far more often
  than a page that only ever read it once.
  """
  use OpenAgentsWeb, :live_view

  alias OpenAgents.Issues
  alias OpenAgents.Repositories
  alias OpenAgentsWeb.LiveRefresh

  def mount(%{"owner" => owner, "repo" => repo}, _session, socket) do
    repository = Repositories.get_writable_by_path!(owner, repo, socket.assigns.current_user)

    if connected?(socket), do: Repositories.subscribe_issues(repository.id)

    {:ok,
     socket
     |> LiveRefresh.init()
     |> assign(:current_scope, socket.assigns[:current_scope])
     |> assign(:owner, owner)
     |> assign(:repo, repo)
     |> assign(:repository, repository)
     |> refresh_panel(:assignees)}
  end

  def handle_info({:issues_changed, repository_id}, socket) do
    if repository_id == socket.assigns.repository.id,
      do: {:noreply, LiveRefresh.mark_stale(socket, :assignees, &refresh_panel/2)},
      else: {:noreply, socket}
  end

  def handle_info(:live_refresh, socket),
    do: {:noreply, LiveRefresh.run(socket, &refresh_panel/2)}

  # Re-read through the same check the mount made: a viewer whose write access
  # was revoked while the page was open stops being handed its rows.
  defp refresh_panel(socket, :assignees) do
    case Repositories.get_visible_repository(
           socket.assigns.repository.id,
           socket.assigns.current_user
         ) do
      nil ->
        assign(socket, :assignees, [])

      repository ->
        if Repositories.writable?(repository, socket.assigns.current_user),
          do: assign(socket, :assignees, Issues.count_issues_by_assignee(repository)),
          else: assign(socket, :assignees, [])
    end
  end

  def render(assigns) do
    ~H"""
    <Layouts.app
      flash={@flash}
      sidebar_sections={assigns[:sidebar_sections]}
      current_scope={@current_scope}
    >
      <h1 class="text-2xl font-bold mb-4">Assignees</h1>

      <%= if @assignees == [] do %>
        <div class="alert" data-variant="info" role="status">
          <.icon name="info-circle" class="size-5" />
          <section>No assignees have been assigned to issues yet.</section>
        </div>
      <% else %>
        <.table id="assignees" rows={@assignees}>
          <:col :let={{login, _count}} label="Assignee">
            <div class="flex items-center gap-2">
              <span class="avatar size-8 font-semibold">
                <span>{login |> String.first() |> String.upcase()}</span>
              </span>
              <span class="font-semibold">{login}</span>
            </div>
          </:col>
          <:col :let={{_login, count}} label="Assigned issues">{count}</:col>
        </.table>
      <% end %>
    </Layouts.app>
    """
  end
end