diff --git a/lib/algora/bounties/bounties.ex b/lib/algora/bounties/bounties.ex index 6e3054570..e1453f662 100644 --- a/lib/algora/bounties/bounties.ex +++ b/lib/algora/bounties/bounties.ex @@ -1331,17 +1331,42 @@ defmodule Algora.Bounties do |> join(:inner, [b], o in assoc(b, :owner), as: :o) |> join(:left, [t: t], r in assoc(t, :repository), as: :r) |> join(:left, [r: r], ro in assoc(r, :user), as: :ro) + |> join(:left, [t: t], a in subquery(attempt_activity_query()), on: a.ticket_id == t.id, as: :attempt_activity) + |> join(:left, [t: t], c in subquery(claim_activity_query()), on: c.ticket_id == t.id, as: :claim_activity) |> where([b], not is_nil(b.amount)) |> where([b], b.status != :cancelled) |> apply_criteria(criteria) end + defp attempt_activity_query do + from a in Attempt, + where: a.status == :active, + group_by: a.ticket_id, + select: %{ + ticket_id: a.ticket_id, + active_attempt_count: count(a.id), + last_attempt_at: max(a.inserted_at) + } + end + + defp claim_activity_query do + from c in Claim, + where: c.status != :cancelled, + where: c.type == :pull_request, + group_by: c.target_id, + select: %{ + ticket_id: c.target_id, + pull_request_count: count(c.group_id, :distinct), + last_claim_at: max(c.inserted_at) + } + end + def list_bounties_with(base_query, criteria \\ []) do base_query |> list_bounties_query(criteria) # TODO: sort by b.paid_at if criteria[:status] == :paid |> order_by([b], desc: b.inserted_at, desc: b.id) - |> select([b, o: o, t: t, ro: ro, r: r], %{ + |> select([b, o: o, t: t, ro: ro, r: r, attempt_activity: a, claim_activity: c], %{ id: b.id, inserted_at: b.inserted_at, amount: b.amount, @@ -1373,6 +1398,19 @@ defmodule Algora.Bounties do provider_login: ro.provider_login, avatar_url: ro.avatar_url } + }, + activity: %{ + active_attempt_count: type(fragment("COALESCE(?, 0)", a.active_attempt_count), :integer), + pull_request_count: type(fragment("COALESCE(?, 0)", c.pull_request_count), :integer), + last_activity_at: + fragment( + "GREATEST(?, COALESCE(?, ?), COALESCE(?, ?))", + b.inserted_at, + a.last_attempt_at, + b.inserted_at, + c.last_claim_at, + b.inserted_at + ) } }) |> Repo.all() diff --git a/lib/algora_web/components/bounties.ex b/lib/algora_web/components/bounties.ex index 2d6e6adb2..346d8e7f4 100644 --- a/lib/algora_web/components/bounties.ex +++ b/lib/algora_web/components/bounties.ex @@ -12,6 +12,7 @@ defmodule AlgoraWeb.Components.Bounties do
@@ -44,4 +66,27 @@ defmodule AlgoraWeb.Components.Bounties do """ end + + defp bounty_activity(%{activity: activity}) when is_map(activity) do + %{ + active_attempt_count: Map.get(activity, :active_attempt_count, 0), + pull_request_count: Map.get(activity, :pull_request_count, 0), + last_activity_at: Map.get(activity, :last_activity_at) + } + end + + defp bounty_activity(bounty) do + %{ + active_attempt_count: 0, + pull_request_count: 0, + last_activity_at: Map.get(bounty, :inserted_at) + } + end + + defp active_bounty?(%{active_attempt_count: attempts, pull_request_count: prs}) do + attempts > 0 or prs > 0 + end + + defp pr_label(1), do: "PR" + defp pr_label(_count), do: "PRs" end diff --git a/test/algora/bounties_test.exs b/test/algora/bounties_test.exs index 5e61eab8f..f4950bce1 100644 --- a/test/algora/bounties_test.exs +++ b/test/algora/bounties_test.exs @@ -658,6 +658,74 @@ defmodule Algora.BountiesTest do assert Enum.any?(bounties, &(&1.status == :paid)) refute Enum.any?(bounties, &(&1.status == :cancelled)) end + + test "includes active attempt and pull request activity", %{ticket: ticket} do + bounty = + insert!(:bounty, + status: :open, + ticket: ticket, + owner: insert!(:user), + inserted_at: ~U[2024-01-01 12:00:00Z] + ) + + insert!(:attempt, + ticket: ticket, + user: insert!(:user), + status: :active, + inserted_at: ~U[2024-01-02 12:00:00Z] + ) + + insert!(:attempt, + ticket: ticket, + user: insert!(:user), + status: :inactive, + inserted_at: ~U[2024-01-03 12:00:00Z] + ) + + pull_request = insert!(:ticket, type: :pull_request, number: 101, repository: ticket.repository) + + insert!(:claim, + target: ticket, + source: pull_request, + user: insert!(:user), + status: :pending, + group_id: "group-101", + inserted_at: ~U[2024-01-04 12:00:00Z] + ) + + insert!(:claim, + target: ticket, + source: pull_request, + user: insert!(:user), + status: :pending, + group_id: "group-101", + inserted_at: ~U[2024-01-05 12:00:00Z] + ) + + insert!(:claim, + target: ticket, + source: insert!(:ticket, type: :pull_request, number: 102, repository: ticket.repository), + user: insert!(:user), + status: :cancelled, + group_id: "group-102", + inserted_at: ~U[2024-01-06 12:00:00Z] + ) + + insert!(:claim, + target: ticket, + user: insert!(:user), + type: :review, + status: :pending, + group_id: "group-review", + inserted_at: ~U[2024-01-07 12:00:00Z] + ) + + [listed_bounty] = Bounties.list_bounties(id: bounty.id) + + assert listed_bounty.activity.active_attempt_count == 1 + assert listed_bounty.activity.pull_request_count == 1 + assert NaiveDateTime.truncate(listed_bounty.activity.last_activity_at, :second) == ~N[2024-01-05 12:00:00] + end end describe "list_claims/1" do