lib/openagents_web/live/issue_new_live.ex

436d37facc96 · 4 KB

defmodule OpenAgentsWeb.IssueNewLive do
  @moduledoc """
  Renders a form to create a new issue.

  Filing an issue needs an identity, not a membership: any signed-in person
  can open one on a public repository. Labels and milestones are triage
  decisions, so their pickers appear only for repository members with write
  access.
  """
  use OpenAgentsWeb, :live_view

  alias OpenAgents.Issues
  alias OpenAgents.Issues.Issue
  alias OpenAgents.Labels
  alias OpenAgents.Milestones
  alias OpenAgents.Repositories

  def mount(%{"owner" => owner, "repo" => repo}, _session, socket) do
    user = socket.assigns.current_user

    repository =
      try do
        Repositories.get_visible_by_path!(owner, repo, user)
      rescue
        Ecto.NoResultsError -> nil
      end

    if is_nil(repository) or not Repositories.issue_participant?(repository, user) do
      {:ok,
       socket
       |> assign(:current_scope, socket.assigns[:current_scope])
       |> put_flash(:error, "You cannot open an issue on that repository.")
       |> redirect(to: ~p"/")}
    else
      changeset =
        Issues.change_issue(repository, %Issue{}, %{"title" => "", "body" => ""})

      can_write = Repositories.writable?(repository, user)

      {:ok,
       socket
       |> assign(:current_scope, socket.assigns[:current_scope])
       |> assign(:owner, owner)
       |> assign(:repo, repo)
       |> assign(:repository, repository)
       |> assign(:can_write, can_write)
       |> assign(:form, to_form(changeset))
       |> assign(
         :milestone_options,
         if(can_write,
           do: Enum.map(Milestones.list_milestones(repository), &{&1.title, &1.number}),
           else: []
         )
       )
       |> assign(
         :label_options,
         if(can_write,
           do: Enum.map(Labels.list_labels(repository), &{&1.name, &1.name}),
           else: []
         )
       )}
    end
  end

  def handle_event("save", %{"issue" => issue_params}, socket) do
    repository = socket.assigns.repository
    user = socket.assigns.current_user
    can_participate = Repositories.issue_participant?(repository, user)
    can_write = Repositories.writable?(repository, user)
    socket = assign(socket, :can_write, can_write)

    if can_participate do
      title = issue_params["title"]
      body = issue_params["body"]
      milestone = if(can_write, do: issue_params["milestone"] || "", else: "")
      labels = if(can_write, do: issue_params["labels"] || [], else: [])

      case Issues.create_issue(repository, %{"title" => title, "body" => body}, user) do
        {:ok, issue} ->
          issue = apply_metadata(issue, labels, milestone, user)

          {:noreply,
           socket
           |> put_flash(:info, "Issue created")
           |> push_navigate(
             to: ~p"/#{socket.assigns.owner}/#{socket.assigns.repo}/issues/#{issue.number}"
           )}

        {:error, %Ecto.Changeset{} = changeset} ->
          {:noreply, assign(socket, :form, to_form(changeset))}
      end
    else
      {:noreply, put_flash(socket, :error, "You can no longer open an issue here.")}
    end
  end

  def handle_event(_unsupported_event, _params, socket) do
    {:noreply, put_flash(socket, :error, "That issue action is not available.")}
  end

  defp apply_metadata(issue, labels, milestone, author) do
    labels = List.wrap(labels) |> Enum.reject(&(&1 == ""))

    issue =
      if labels != [] do
        case Issues.add_labels(issue, labels, author) do
          {:ok, updated} -> updated
          _error -> issue
        end
      else
        issue
      end

    if milestone != "" do
      milestone_number = String.to_integer(milestone)

      case Issues.set_milestone(issue, milestone_number) do
        {:ok, updated} -> updated
        _error -> issue
      end
    else
      issue
    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">New issue</h1>

      <.form
        for={@form}
        id="new-issue-form"
        phx-submit="save"
        class="card !m-0 space-y-4"
      >
        <.input field={@form[:title]} label="Title" required />
        <.input field={@form[:body]} type="textarea" label="Body" />
        <.input
          :if={@can_write}
          field={@form[:milestone]}
          type="select"
          label="Milestone"
          options={@milestone_options}
          prompt="Select a milestone"
        />
        <.input
          :if={@can_write}
          field={@form[:labels]}
          type="select"
          label="Labels"
          options={@label_options}
          multiple
        />
        <footer class="flex justify-end gap-2">
          <.link navigate={~p"/#{@owner}/#{@repo}/issues"} class="btn" data-variant="ghost">
            Cancel
          </.link>
          <.button type="submit" variant={:primary}>Create issue</.button>
        </footer>
      </.form>
    </Layouts.app>
    """
  end
end