defmodule GitGudWeb.OrgLive.Show do
use GitGudWeb, :live_view
alias GitGud.Organizations
alias GitGud.Organizations.Team
alias GitGud.Profiles
alias GitGud.Repositories
alias GitGud.Workflows
@impl true
def mount(%{"handle" => handle}, _session, socket) do
org = Organizations.get_organization_by_handle!(handle)
user = socket.assigns.current_scope && socket.assigns.current_scope.user
case can_view?(org, user) do
false ->
{:ok,
socket
|> put_flash(:error, "You're not authorized to view this organization.")
|> push_navigate(to: ~p"/")}
true ->
repos = visible_repos(org, user)
# Live-update the repo CI badges as runs change anywhere.
if connected?(socket) do
Phoenix.PubSub.subscribe(GitGud.PubSub, Workflows.ci_topic())
end
{:ok,
socket
|> assign(:org, org)
|> assign(:viewer, user)
|> assign(:is_admin?, user && Organizations.org_admin?(org, user))
|> assign(:is_member?, user && Organizations.member?(org, user))
|> assign(:teams, Organizations.list_teams(org))
|> assign(:repos, repos)
|> assign(:ci_status, Workflows.latest_run_status_by_repo(Enum.map(repos, & &1.id)))
|> assign(:pins, Profiles.list_pins(org))
|> assign(:readme, Repositories.get_profile_readme(org, theme: socket.assigns[:editor_theme]))
|> assign_team_form(Team.changeset(%Team{}, %{}))
|> assign(:page_title, "@#{org.handle}")}
end
end
@impl true
def handle_info({:ci_run_changed, _run_id}, socket) do
# A run changed somewhere (topic is global); cheaply refresh this
# org's repo badges. Non-org runs simply don't change the result.
repo_ids = Enum.map(socket.assigns.repos, & &1.id)
{:noreply, assign(socket, :ci_status, Workflows.latest_run_status_by_repo(repo_ids))}
end
def handle_info(_msg, socket), do: {:noreply, socket}
# daisyUI badge class for a repo's latest CI status.
defp ci_badge("success"), do: "badge-success"
defp ci_badge("failure"), do: "badge-error"
defp ci_badge(s) when s in ~w(running queued blocked), do: "badge-warning"
defp ci_badge(_), do: "badge-ghost"
defp can_view?(%{visibility: "public"}, _user), do: true
defp can_view?(%{visibility: "internal"}, %{} = _user), do: true
defp can_view?(%{visibility: "internal"}, nil), do: false
defp can_view?(%{visibility: "private"} = org, %{} = user), do: Organizations.member?(org, user)
defp can_view?(_, _), do: false
defp visible_repos(org, viewer) do
Repositories.list_repositories_for(org)
|> Enum.filter(fn r ->
cond do
r.visibility == "public" -> true
is_nil(viewer) -> false
r.visibility == "internal" -> true
# Private: visible only to org members. The org-level check
# above already passed, but a non-member could still hit a
# public org's page; gate private repos here.
viewer && Organizations.member?(org, viewer) -> true
true -> false
end
end)
end
defp assign_team_form(socket, cs), do: assign(socket, :team_form, to_form(cs))
@impl true
def handle_event("create_team", %{"team" => attrs}, socket) do
case Organizations.create_team(socket.assigns.org, attrs) do
{:ok, _team} ->
{:noreply,
socket
|> assign(:teams, Organizations.list_teams(socket.assigns.org))
|> assign_team_form(Team.changeset(%Team{}, %{}))
|> put_flash(:info, "Team created.")}
{:error, cs} ->
{:noreply, assign_team_form(socket, cs)}
end
end
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash} current_scope={@current_scope}>
<div class="space-y-6">
<header class="flex items-baseline justify-between">
<div class="flex items-start gap-4 min-w-0">
<.avatar name={@org.handle} size="2xl" alt={@org.handle} />
<div>
<h1 class="text-2xl font-semibold">
{GitGud.Organizations.Organization.display(@org)}
</h1>
<p class="text-sm font-mono opacity-60">@{@org.handle}</p>
<p :if={@org.description} class="text-sm opacity-70 mt-1">{@org.description}</p>
<nav :if={@is_admin?} class="text-xs flex gap-3 opacity-80 mt-2">
<.link
navigate={~p"/orgs/#{@org.handle}/settings/profile"}
class="link link-hover"
>
Profile
</.link>
<.link
navigate={~p"/orgs/#{@org.handle}/settings/members"}
class="link link-hover"
>
Members
</.link>
<.link
navigate={~p"/orgs/#{@org.handle}/settings/secrets"}
class="link link-hover"
>
Secrets & variables
</.link>
<.link
navigate={~p"/orgs/#{@org.handle}/settings/runners"}
class="link link-hover"
>
Runners
</.link>
<.link
navigate={~p"/orgs/#{@org.handle}/settings/webhooks"}
class="link link-hover"
>
Webhooks
</.link>
<.link
navigate={~p"/orgs/#{@org.handle}/settings/labels"}
class="link link-hover"
>
Labels
</.link>
</nav>
<nav class="text-xs flex gap-3 opacity-80 mt-2">
<.link navigate={~p"/orgs/#{@org.handle}/actions"} class="link link-hover">
CI dashboard
</.link>
</nav>
</div>
</div>
<.link
:if={@is_admin?}
navigate={~p"/repositories/new"}
class="btn btn-sm btn-primary"
>
New repo
</.link>
</header>
<GitGudWeb.ProfileComponents.details profile={@org} />
<section :if={@pins != []}>
<h2 class="text-lg font-semibold mb-2">Pinned</h2>
<GitGudWeb.PinnedReposGrid.grid pins={@pins} />
</section>
<section :if={@readme}>
<header class="flex items-baseline justify-between mb-2">
<h2 class="text-lg font-semibold">README</h2>
<.link
:if={@is_admin?}
navigate={~p"/r/#{@org.handle}/#{@org.handle}"}
class="text-xs link link-hover opacity-60"
>
Edit in {@org.handle}/{@org.handle} →
</.link>
</header>
<article class="prose max-w-none border border-base-300 rounded p-4">
{Phoenix.HTML.raw(elem(@readme, 1))}
</article>
</section>
<section>
<h2 class="text-lg font-semibold mb-2">Repositories</h2>
<ul class="divide-y divide-base-300 mb-4">
<li class="hidden only:block py-3 opacity-60 text-sm">No repositories yet.</li>
<li
:for={repo <- @repos}
class={["py-2", Repositories.profile_repo?(repo, @org.handle) && "opacity-50 italic"]}
>
<.link
navigate={~p"/r/#{@org.handle}/#{repo.name}"}
class="font-mono link link-hover"
>
{repo.name}
</.link>
<.link
:if={@ci_status[repo.id]}
navigate={~p"/r/#{@org.handle}/#{repo.name}/actions"}
class={["badge badge-xs ml-1 not-italic", ci_badge(@ci_status[repo.id])]}
title={"latest CI run: #{@ci_status[repo.id]}"}
>
{@ci_status[repo.id]}
</.link>
<span
:if={Repositories.profile_repo?(repo, @org.handle)}
class="badge badge-xs badge-ghost ml-1 not-italic"
title="Powers the @{@org.handle} page"
>
profile
</span>
<p :if={repo.description} class="text-sm opacity-70">{repo.description}</p>
</li>
</ul>
</section>
<section>
<h2 class="text-lg font-semibold mb-2">Teams</h2>
<ul class="divide-y divide-base-300 mb-4">
<li class="hidden only:block py-3 opacity-60 text-sm">No teams yet.</li>
<li :for={team <- @teams} class="py-2 font-mono">
{team.name}
<span :if={team.description} class="text-sm opacity-70 ml-2 font-sans">
{team.description}
</span>
</li>
</ul>
<.form
:if={@is_admin?}
for={@team_form}
id="team-form"
phx-submit="create_team"
class="flex items-end gap-2"
>
<.input field={@team_form[:name]} label="New team" required />
<.input field={@team_form[:description]} label="Description" />
<button type="submit" class="btn btn-sm btn-primary">Add</button>
</.form>
</section>
</div>
</Layouts.app>
"""
end
end
9.0 KiB · text
5af55d9