lib/openagents_web/controllers/project_json.ex

main at 58e6347eeb72 · 3 KB

defmodule OpenAgentsWeb.ProjectJSON do
  @moduledoc """
  Renders GitHub-compatible project and project item JSON.
  """

  def render("index.json", %{projects: projects}) do
    %{projects: Enum.map(projects, &project_json/1)}
  end

  def render("show.json", %{project: project}) do
    project_json(project)
  end

  def render("items.json", %{items: items} = assigns) do
    item_projections = Map.get(assigns, :item_projections, %{})

    %{
      items: Enum.map(items, &item_json(&1, Map.get(item_projections, &1.id)))
    }
  end

  def render("events.json", %{events: events, pagination: pagination}) do
    %{
      events: Enum.map(events, &event_json/1),
      pagination: %{
        page: pagination.page,
        per_page: pagination.per_page,
        total: pagination.total,
        total_pages: total_pages(pagination.total, pagination.per_page)
      }
    }
  end

  def render("fields.json", %{fields: fields}) do
    %{fields: Enum.map(fields, &field_json/1)}
  end

  def render("notes.json", %{notes: notes, page: page, total_count: total_count}) do
    %{
      notes: Enum.map(notes, &note_json/1),
      page: page,
      per_page: OpenAgents.Projects.notes_per_page(),
      total_count: total_count
    }
  end

  def render("note.json", %{note: note}) do
    note_json(note)
  end

  defp project_json(project) do
    %{
      id: project.id,
      number: project.number,
      title: project.title,
      # `description` is the canonical project-context field, and it is
      # Markdown. Nothing renders it server-side for the API; a client renders
      # it the same way it renders an issue body.
      description: project.description,
      owner: project.owner,
      state: project.state,
      # `archived` is orthogonal to `state`: a closed project says the work
      # reached an end, an archived project says the board left the working
      # set. Both are reported so a client never has to infer one from the
      # other.
      archived: OpenAgents.Projects.Project.archived?(project),
      archived_at: project.archived_at,
      created_at: project.inserted_at,
      updated_at: project.updated_at
    }
  end

  defp note_json(note) do
    %{
      id: note.id,
      kind: note.kind,
      body: OpenAgents.Projects.ProjectNote.text(note) || "",
      author: note.author,
      created_at: note.inserted_at,
      updated_at: note.updated_at
    }
  end

  defp item_json(item, item_projection) do
    issue = item.issue
    repository = issue.repository
    base_url = OpenAgentsWeb.Endpoint.url()
    values = get_in(item_projection || %{}, [:values]) || item.values
    promise = get_in(item_projection || %{}, [:promise])

    %{
      id: item.id,
      issue_id: item.issue_id,
      issue: %{
        owner: repository.owner,
        repo: repository.name,
        number: issue.number,
        url:
          "#{base_url}/api/v1/repos/#{repository.owner}/#{repository.name}/issues/#{issue.number}",
        html_url: "#{base_url}/#{repository.owner}/#{repository.name}/issues/#{issue.number}"
      },
      values: values
    }
    |> maybe_put_promise(promise)
  end

  defp maybe_put_promise(json, nil), do: json
  defp maybe_put_promise(json, promise), do: Map.put(json, :openagents, %{promise: promise})

  defp event_json(event) do
    %{
      id: event.id,
      project_item_id: event.project_item_id,
      project_id: event.project_id,
      actor_login: event.actor_login,
      actor_user_id: event.actor_user_id,
      kind: event.kind,
      from_state: event.from_state,
      to_state: event.to_state,
      changes: event.changes,
      occurred_at: event.occurred_at,
      inserted_at: event.inserted_at
    }
  end

  defp total_pages(0, _per_page), do: 1
  defp total_pages(total, per_page), do: ceil(total / per_page)

  defp field_json(field) do
    %{
      id: field.id,
      name: field.name,
      data_type: field.data_type,
      options: field.options
    }
  end
end