improve ActivityLog.get_grouped_used_counts

This commit is contained in:
shibao 2023-06-05 19:22:36 -04:00
parent e713a2e108
commit 8466fcd1f9
11 changed files with 121 additions and 57 deletions

View File

@ -287,26 +287,6 @@ defmodule Cannery.ActivityLog do
end end
end end
@doc """
Returns the number of shot rounds for multiple packs
"""
@spec get_used_counts([Pack.t()], User.t()) ::
%{optional(Pack.id()) => non_neg_integer()}
def get_used_counts(packs, %User{id: user_id}) do
pack_ids =
packs
|> Enum.map(fn %{id: pack_id} -> pack_id end)
Repo.all(
from sr in ShotRecord,
where: sr.pack_id in ^pack_ids,
where: sr.user_id == ^user_id,
group_by: sr.pack_id,
select: {sr.pack_id, sum(sr.count)}
)
|> Map.new()
end
@doc """ @doc """
Returns the last entered shot record date for a pack Returns the last entered shot record date for a pack
""" """
@ -385,31 +365,77 @@ defmodule Cannery.ActivityLog do
defp get_used_count_type_id(query, _nil), do: query defp get_used_count_type_id(query, _nil), do: query
@type get_grouped_used_counts_option ::
{:packs, [Pack.t()] | nil}
| {:types, [Type.t()] | nil}
| {:group_by, :type_id | :pack_id}
@type get_grouped_used_counts_options :: [get_grouped_used_counts_option()]
@doc """ @doc """
Gets the total number of rounds shot for multiple types Gets the total number of rounds shot for multiple types or packs
## Examples ## Examples
iex> get_used_count_for_types(123, %User{id: 123}) iex> get_grouped_used_counts(
...> %User{id: 123},
...> group_by: :type_id,
...> types: [%Type{id: 456, user_id: 123}]
...> )
35 35
""" iex> get_grouped_used_counts(
@spec get_used_count_for_types([Type.t()], User.t()) :: ...> %User{id: 123},
%{optional(Type.id()) => non_neg_integer()} ...> group_by: :pack_id,
def get_used_count_for_types(types, %User{id: user_id}) do ...> packs: [%Pack{id: 456, user_id: 123}]
type_ids = ...> )
types 22
|> Enum.map(fn %Type{id: type_id, user_id: ^user_id} -> type_id end)
Repo.all( """
from p in Pack, @spec get_grouped_used_counts(User.t(), get_grouped_used_counts_options()) ::
left_join: sr in ShotRecord, %{optional(Type.id() | Pack.id()) => non_neg_integer()}
on: p.id == sr.pack_id, def get_grouped_used_counts(%User{id: user_id}, opts) do
where: p.type_id in ^type_ids, from(p in Pack,
where: not (sr.count |> is_nil()), as: :p,
group_by: p.type_id, left_join: sr in ShotRecord,
select: {p.type_id, sum(sr.count)} on: p.id == sr.pack_id,
on: p.user_id == ^user_id,
as: :sr,
where: sr.user_id == ^user_id,
where: not (sr.count |> is_nil())
) )
|> get_grouped_used_counts_group_by(Keyword.fetch!(opts, :group_by))
|> get_grouped_used_counts_types(Keyword.get(opts, :types))
|> get_grouped_used_counts_packs(Keyword.get(opts, :packs))
|> Repo.all()
|> Map.new() |> Map.new()
end end
@spec get_grouped_used_counts_group_by(Queryable.t(), :type_id | :pack_id) :: Queryable.t()
defp get_grouped_used_counts_group_by(query, :type_id) do
query
|> group_by([p: p], p.type_id)
|> select([sr: sr, p: p], {p.type_id, sum(sr.count)})
end
defp get_grouped_used_counts_group_by(query, :pack_id) do
query
|> group_by([sr: sr], sr.pack_id)
|> select([sr: sr], {sr.pack_id, sum(sr.count)})
end
@spec get_grouped_used_counts_types(Queryable.t(), [Type.t()] | nil) :: Queryable.t()
defp get_grouped_used_counts_types(query, types) when types |> is_list() do
type_ids = types |> Enum.map(fn %Type{id: type_id} -> type_id end)
query |> where([p: p], p.type_id in ^type_ids)
end
defp get_grouped_used_counts_types(query, _nil), do: query
@spec get_grouped_used_counts_packs(Queryable.t(), [Pack.t()] | nil) :: Queryable.t()
defp get_grouped_used_counts_packs(query, packs) when packs |> is_list() do
pack_ids = packs |> Enum.map(fn %Pack{id: pack_id} -> pack_id end)
query |> where([p: p], p.id in ^pack_ids)
end
defp get_grouped_used_counts_packs(query, _nil), do: query
end end

View File

@ -267,7 +267,7 @@ defmodule Cannery.Ammo do
@spec get_historical_count_for_types([Type.t()], User.t()) :: @spec get_historical_count_for_types([Type.t()], User.t()) ::
%{optional(Type.id()) => non_neg_integer()} %{optional(Type.id()) => non_neg_integer()}
def get_historical_count_for_types(types, %User{id: user_id} = user) do def get_historical_count_for_types(types, %User{id: user_id} = user) do
used_counts = types |> ActivityLog.get_used_count_for_types(user) used_counts = ActivityLog.get_grouped_used_counts(user, types: types, group_by: :type_id)
round_counts = types |> get_round_count_for_types(user) round_counts = types |> get_round_count_for_types(user)
types types
@ -840,7 +840,8 @@ defmodule Cannery.Ammo do
@spec get_original_counts([Pack.t()], User.t()) :: @spec get_original_counts([Pack.t()], User.t()) ::
%{optional(Pack.id()) => non_neg_integer()} %{optional(Pack.id()) => non_neg_integer()}
def get_original_counts(packs, %User{id: user_id} = current_user) do def get_original_counts(packs, %User{id: user_id} = current_user) do
used_counts = ActivityLog.get_used_counts(packs, current_user) used_counts =
ActivityLog.get_grouped_used_counts(current_user, packs: packs, group_by: :pack_id)
packs packs
|> Map.new(fn %Pack{id: pack_id, count: count, user_id: ^user_id} -> |> Map.new(fn %Pack{id: pack_id, count: count, user_id: ^user_id} ->

View File

@ -158,7 +158,7 @@ defmodule CanneryWeb.Components.TypeTableComponent do
[used_counts, historical_round_counts, historical_pack_counts, used_pack_counts] = [used_counts, historical_round_counts, historical_pack_counts, used_pack_counts] =
if show_used do if show_used do
[ [
types |> ActivityLog.get_used_count_for_types(current_user), ActivityLog.get_grouped_used_counts(current_user, types: types, group_by: :type_id),
types |> Ammo.get_historical_count_for_types(current_user), types |> Ammo.get_historical_count_for_types(current_user),
Ammo.get_grouped_packs_count(current_user, Ammo.get_grouped_packs_count(current_user,
types: types, types: types,

View File

@ -4,7 +4,10 @@ defmodule CanneryWeb.ExportController do
def export(%{assigns: %{current_user: current_user}} = conn, %{"mode" => "json"}) do def export(%{assigns: %{current_user: current_user}} = conn, %{"mode" => "json"}) do
types = Ammo.list_types(current_user) types = Ammo.list_types(current_user)
used_counts = types |> ActivityLog.get_used_count_for_types(current_user)
used_counts =
ActivityLog.get_grouped_used_counts(current_user, types: types, group_by: :type_id)
round_counts = types |> Ammo.get_round_count_for_types(current_user) round_counts = types |> Ammo.get_round_count_for_types(current_user)
pack_counts = Ammo.get_grouped_packs_count(current_user, types: types, group_by: :type_id) pack_counts = Ammo.get_grouped_packs_count(current_user, types: types, group_by: :type_id)
@ -29,7 +32,10 @@ defmodule CanneryWeb.ExportController do
end) end)
packs = Ammo.list_packs(current_user, show_used: true) packs = Ammo.list_packs(current_user, show_used: true)
used_counts = packs |> ActivityLog.get_used_counts(current_user)
used_counts =
ActivityLog.get_grouped_used_counts(current_user, packs: packs, group_by: :pack_id)
original_counts = packs |> Ammo.get_original_counts(current_user) original_counts = packs |> Ammo.get_original_counts(current_user)
cprs = packs |> Ammo.get_cprs(current_user) cprs = packs |> Ammo.get_cprs(current_user)
percentages_remaining = packs |> Ammo.get_percentages_remaining(current_user) percentages_remaining = packs |> Ammo.get_percentages_remaining(current_user)

View File

@ -170,7 +170,7 @@ msgstr ""
"Ungültige Nummer an Kopien. Muss zwischen 1 and %{max} liegen. War " "Ungültige Nummer an Kopien. Muss zwischen 1 and %{max} liegen. War "
"%{multiplier}" "%{multiplier}"
#: lib/cannery/ammo.ex:979 #: lib/cannery/ammo.ex:980
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Invalid multiplier" msgid "Invalid multiplier"
msgstr "" msgstr ""

View File

@ -153,7 +153,7 @@ msgstr ""
msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}" msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}"
msgstr "" msgstr ""
#: lib/cannery/ammo.ex:979 #: lib/cannery/ammo.ex:980
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Invalid multiplier" msgid "Invalid multiplier"
msgstr "" msgstr ""

View File

@ -152,7 +152,7 @@ msgstr ""
msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}" msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}"
msgstr "" msgstr ""
#: lib/cannery/ammo.ex:979 #: lib/cannery/ammo.ex:980
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Invalid multiplier" msgid "Invalid multiplier"
msgstr "" msgstr ""

View File

@ -168,7 +168,7 @@ msgstr "No se ha podido procesar el número de copias"
msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}" msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}"
msgstr "Número inválido de copias, debe ser entre 1 y %{max}. Fue %{multiplier" msgstr "Número inválido de copias, debe ser entre 1 y %{max}. Fue %{multiplier"
#: lib/cannery/ammo.ex:979 #: lib/cannery/ammo.ex:980
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Invalid multiplier" msgid "Invalid multiplier"
msgstr "Multiplicador inválido" msgstr "Multiplicador inválido"

View File

@ -169,7 +169,7 @@ msgstr "Impossible d'analyser le nombre de copies"
msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}" msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}"
msgstr "Nombre de copies invalide, doit être 1 et %{max}. Été %{multiplier}" msgstr "Nombre de copies invalide, doit être 1 et %{max}. Été %{multiplier}"
#: lib/cannery/ammo.ex:979 #: lib/cannery/ammo.ex:980
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Invalid multiplier" msgid "Invalid multiplier"
msgstr "Multiplicateur invalide" msgstr "Multiplicateur invalide"

View File

@ -168,7 +168,7 @@ msgstr ""
msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}" msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}"
msgstr "" msgstr ""
#: lib/cannery/ammo.ex:979 #: lib/cannery/ammo.ex:980
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Invalid multiplier" msgid "Invalid multiplier"
msgstr "" msgstr ""

View File

@ -208,7 +208,7 @@ defmodule Cannery.ActivityLogTest do
assert 0 = ActivityLog.get_used_count(current_user, pack_id: another_pack.id) assert 0 = ActivityLog.get_used_count(current_user, pack_id: another_pack.id)
end end
test "get_used_counts/2 returns accurate used counts", %{ test "get_grouped_used_counts/2 returns accurate used counts for packs", %{
pack: %{id: pack_id} = pack, pack: %{id: pack_id} = pack,
type: type, type: type,
container: container, container: container,
@ -217,20 +217,41 @@ defmodule Cannery.ActivityLogTest do
{1, [%{id: another_pack_id} = another_pack]} = pack_fixture(type, container, current_user) {1, [%{id: another_pack_id} = another_pack]} = pack_fixture(type, container, current_user)
assert %{pack_id => 5} == assert %{pack_id => 5} ==
[pack, another_pack] |> ActivityLog.get_used_counts(current_user) ActivityLog.get_grouped_used_counts(current_user,
packs: [pack, another_pack],
group_by: :pack_id
)
shot_record_fixture(%{count: 5}, current_user, another_pack) shot_record_fixture(%{count: 5}, current_user, another_pack)
used_counts = [pack, another_pack] |> ActivityLog.get_used_counts(current_user)
used_counts =
ActivityLog.get_grouped_used_counts(current_user,
packs: [pack, another_pack],
group_by: :pack_id
)
assert %{^pack_id => 5} = used_counts assert %{^pack_id => 5} = used_counts
assert %{^another_pack_id => 5} = used_counts assert %{^another_pack_id => 5} = used_counts
shot_record_fixture(%{count: 15}, current_user, pack) shot_record_fixture(%{count: 15}, current_user, pack)
used_counts = [pack, another_pack] |> ActivityLog.get_used_counts(current_user)
used_counts =
ActivityLog.get_grouped_used_counts(current_user,
packs: [pack, another_pack],
group_by: :pack_id
)
assert %{^pack_id => 20} = used_counts assert %{^pack_id => 20} = used_counts
assert %{^another_pack_id => 5} = used_counts assert %{^another_pack_id => 5} = used_counts
shot_record_fixture(%{count: 10}, current_user, pack) shot_record_fixture(%{count: 10}, current_user, pack)
used_counts = [pack, another_pack] |> ActivityLog.get_used_counts(current_user)
used_counts =
ActivityLog.get_grouped_used_counts(current_user,
packs: [pack, another_pack],
group_by: :pack_id
)
assert %{^pack_id => 30} = used_counts assert %{^pack_id => 30} = used_counts
assert %{^another_pack_id => 5} = used_counts assert %{^another_pack_id => 5} = used_counts
end end
@ -317,13 +338,19 @@ defmodule Cannery.ActivityLogTest do
{1, [pack]} = pack_fixture(another_type, container, current_user) {1, [pack]} = pack_fixture(another_type, container, current_user)
assert %{type_id => 5} == assert %{type_id => 5} ==
[type, another_type] ActivityLog.get_grouped_used_counts(current_user,
|> ActivityLog.get_used_count_for_types(current_user) types: [type, another_type],
group_by: :type_id
)
# use generated pack # use generated pack
shot_record_fixture(%{count: 5}, current_user, pack) shot_record_fixture(%{count: 5}, current_user, pack)
used_counts = [type, another_type] |> ActivityLog.get_used_count_for_types(current_user) used_counts =
ActivityLog.get_grouped_used_counts(current_user,
types: [type, another_type],
group_by: :type_id
)
assert %{^type_id => 5} = used_counts assert %{^type_id => 5} = used_counts
assert %{^another_type_id => 5} = used_counts assert %{^another_type_id => 5} = used_counts
@ -331,7 +358,11 @@ defmodule Cannery.ActivityLogTest do
# use generated pack again # use generated pack again
shot_record_fixture(%{count: 1}, current_user, pack) shot_record_fixture(%{count: 1}, current_user, pack)
used_counts = [type, another_type] |> ActivityLog.get_used_count_for_types(current_user) used_counts =
ActivityLog.get_grouped_used_counts(current_user,
types: [type, another_type],
group_by: :type_id
)
assert %{^type_id => 5} = used_counts assert %{^type_id => 5} = used_counts
assert %{^another_type_id => 6} = used_counts assert %{^another_type_id => 6} = used_counts