From aafb8051944e9b5f25fa9264a42375a8d96d4116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro?= Date: Fri, 14 Jun 2024 01:08:23 +0200 Subject: [PATCH] Display picked items by champion --- .../dimensions/item/item_metadata.ex | 27 ++++++++++++++++++ .../dimensions/item/item_repo.ex | 9 +++++- .../dimensions/item/item_schema.ex | 7 +++-- .../dimensions/player/player_schema.ex | 1 + .../facts/champion_picked_item/repo.ex | 2 ++ .../20240613200312_item_metadata.exs | 9 ++++++ .../20240613212421_item_metadata_index.exs | 11 +++++++ apps/lol_analytics_web/assets/css/app.css | 11 +++++++ .../champion_components/champion_avatar.ex | 1 - .../champion_components/champion_card.ex | 4 ++- .../components/champion_components/items.ex | 23 +++++++++++++++ .../champion_components/summoner_spells.ex | 12 ++++---- .../live/champion_live/show.ex | 19 ++++++------ .../live/champion_live/show.html.heex | 17 +++++++++-- .../live/champion_live/show_mapper.ex | 16 +++++++++++ ...ility-ed9e57067aded442715e9ef16b93f0da.png | Bin 0 -> 8091 bytes 16 files changed, 147 insertions(+), 22 deletions(-) create mode 100644 apps/lol_analytics/lib/lol_analytics/dimensions/item/item_metadata.ex create mode 100644 apps/lol_analytics/priv/repo/migrations/20240613200312_item_metadata.exs create mode 100644 apps/lol_analytics/priv/repo/migrations/20240613212421_item_metadata_index.exs create mode 100644 apps/lol_analytics_web/lib/lol_analytics_web/components/champion_components/items.ex create mode 100644 apps/lol_analytics_web/priv/static/images/lanes/utility-ed9e57067aded442715e9ef16b93f0da.png diff --git a/apps/lol_analytics/lib/lol_analytics/dimensions/item/item_metadata.ex b/apps/lol_analytics/lib/lol_analytics/dimensions/item/item_metadata.ex new file mode 100644 index 0000000..7261ada --- /dev/null +++ b/apps/lol_analytics/lib/lol_analytics/dimensions/item/item_metadata.ex @@ -0,0 +1,27 @@ +defmodule LolAnalytics.Dimensions.Item.ItemMetadata do + alias LolAnalytics.Dimensions.Item.ItemRepo + alias LolAnalytics.Dimensions.Champion.ChampionRepo + @items_data_url "https://ddragon.leagueoflegends.com/cdn/14.11.1/data/en_US/item.json" + + def update_metadata() do + data = get_items() + + data + # |> IO.inspect() + |> Enum.each(&save_metadata/1) + end + + defp get_items() do + with {:ok, resp} <- HTTPoison.get(@items_data_url), + %{"data" => data} <- Poison.decode!(resp.body) do + data + else + _ -> {:error, :get_items_error} + end + end + + defp save_metadata({item_id, metadata}) do + # IO.inspect(item) + ItemRepo.update(item_id, %{metadata: metadata}) + end +end diff --git a/apps/lol_analytics/lib/lol_analytics/dimensions/item/item_repo.ex b/apps/lol_analytics/lib/lol_analytics/dimensions/item/item_repo.ex index 8ab1ec4..0b871e8 100644 --- a/apps/lol_analytics/lib/lol_analytics/dimensions/item/item_repo.ex +++ b/apps/lol_analytics/lib/lol_analytics/dimensions/item/item_repo.ex @@ -10,7 +10,7 @@ defmodule LolAnalytics.Dimensions.Item.ItemRepo do case Repo.one(query) do nil -> item_changeset = ItemSchema.changeset(%ItemSchema{}, %{item_id: item_id}) - Repo.insert(item_changeset) + Repo.insert!(item_changeset) item -> item @@ -20,4 +20,11 @@ defmodule LolAnalytics.Dimensions.Item.ItemRepo do def list_items() do Repo.all(ItemSchema) end + + @spec update(item_id :: String.t(), attrs :: map()) :: any() + def update(item_id, attrs) do + get_or_create(item_id) + |> ItemSchema.changeset(attrs) + |> Repo.update() + end end diff --git a/apps/lol_analytics/lib/lol_analytics/dimensions/item/item_schema.ex b/apps/lol_analytics/lib/lol_analytics/dimensions/item/item_schema.ex index e61a07b..5424ba9 100644 --- a/apps/lol_analytics/lib/lol_analytics/dimensions/item/item_schema.ex +++ b/apps/lol_analytics/lib/lol_analytics/dimensions/item/item_schema.ex @@ -2,14 +2,17 @@ defmodule LolAnalytics.Dimensions.Item.ItemSchema do use Ecto.Schema import Ecto.Changeset + @args [:item_id, :metadata] + schema "dim_item" do field :item_id, :integer + field :metadata, :map timestamps() end def changeset(item = %__MODULE__{}, attrs \\ %{}) do item - |> cast(attrs, [:item_id]) - |> validate_required([:item_id]) + |> cast(attrs, @args) + |> validate_required(@args) end end diff --git a/apps/lol_analytics/lib/lol_analytics/dimensions/player/player_schema.ex b/apps/lol_analytics/lib/lol_analytics/dimensions/player/player_schema.ex index 27ff15c..5e09f74 100644 --- a/apps/lol_analytics/lib/lol_analytics/dimensions/player/player_schema.ex +++ b/apps/lol_analytics/lib/lol_analytics/dimensions/player/player_schema.ex @@ -11,5 +11,6 @@ defmodule LolAnalytics.Dimensions.Player.PlayerSchema do player |> cast(attrs, [:puuid]) |> validate_required([:puuid]) + |> unique_constraint([:puuid]) end end diff --git a/apps/lol_analytics/lib/lol_analytics/facts/champion_picked_item/repo.ex b/apps/lol_analytics/lib/lol_analytics/facts/champion_picked_item/repo.ex index 8b563d6..8da271e 100644 --- a/apps/lol_analytics/lib/lol_analytics/facts/champion_picked_item/repo.ex +++ b/apps/lol_analytics/lib/lol_analytics/facts/champion_picked_item/repo.ex @@ -71,12 +71,14 @@ defmodule LolAnalytics.Facts.ChampionPickedItem.Repo do )", f.is_win ), + metadata: i.metadata, item_id: i.item_id, champion_id: c.champion_id, team_position: f.team_position, total_games: count("*") }, group_by: [ + i.metadata, i.item_id, c.champion_id, f.team_position diff --git a/apps/lol_analytics/priv/repo/migrations/20240613200312_item_metadata.exs b/apps/lol_analytics/priv/repo/migrations/20240613200312_item_metadata.exs new file mode 100644 index 0000000..479861a --- /dev/null +++ b/apps/lol_analytics/priv/repo/migrations/20240613200312_item_metadata.exs @@ -0,0 +1,9 @@ +defmodule LoLAnalytics.Repo.Migrations.ItemMetadata do + use Ecto.Migration + + def change do + alter table("dim_item") do + add :metadata, :map + end + end +end diff --git a/apps/lol_analytics/priv/repo/migrations/20240613212421_item_metadata_index.exs b/apps/lol_analytics/priv/repo/migrations/20240613212421_item_metadata_index.exs new file mode 100644 index 0000000..7773db3 --- /dev/null +++ b/apps/lol_analytics/priv/repo/migrations/20240613212421_item_metadata_index.exs @@ -0,0 +1,11 @@ +defmodule LoLAnalytics.Repo.Migrations.ItemMetadataIndex do + use Ecto.Migration + + def up do + execute("CREATE INDEX dim_item_metadata ON dim_item USING GIN(metadata)") + end + + def down do + execute("DROP INDEX dim_item_metadata") + end +end diff --git a/apps/lol_analytics_web/assets/css/app.css b/apps/lol_analytics_web/assets/css/app.css index 378c8f9..c23224c 100644 --- a/apps/lol_analytics_web/assets/css/app.css +++ b/apps/lol_analytics_web/assets/css/app.css @@ -3,3 +3,14 @@ @import "tailwindcss/utilities"; /* This file is for your main application CSS */ + +.tooltip { + visibility: hidden; + position: absolute; +} + +.has-tooltip:hover .tooltip { + visibility: visible; + z-index: 100; + background-color:lightsteelblue; +} \ No newline at end of file diff --git a/apps/lol_analytics_web/lib/lol_analytics_web/components/champion_components/champion_avatar.ex b/apps/lol_analytics_web/lib/lol_analytics_web/components/champion_components/champion_avatar.ex index d69d1ad..cd13004 100644 --- a/apps/lol_analytics_web/lib/lol_analytics_web/components/champion_components/champion_avatar.ex +++ b/apps/lol_analytics_web/lib/lol_analytics_web/components/champion_components/champion_avatar.ex @@ -9,7 +9,6 @@ defmodule LolAnalyticsWeb.ChampionComponents.ChampionAvatar do ~H"""
champion-icon -

<%= @name %>

""" end diff --git a/apps/lol_analytics_web/lib/lol_analytics_web/components/champion_components/champion_card.ex b/apps/lol_analytics_web/lib/lol_analytics_web/components/champion_components/champion_card.ex index 72f6371..ef7d3dc 100644 --- a/apps/lol_analytics_web/lib/lol_analytics_web/components/champion_components/champion_card.ex +++ b/apps/lol_analytics_web/lib/lol_analytics_web/components/champion_components/champion_card.ex @@ -10,8 +10,10 @@ defmodule LolAnalyticsWeb.ChampionComponents.ChampionCard do attr :props, Props, default: %Props{} def champion_card(assigns) do + # IO.inspect(assigns) + ~H""" - <.link patch={"/champions/#{@props.id}"}> + <.link patch={"/champions/#{@props.id}?team-position=#{@props.team_position}"}>
diff --git a/apps/lol_analytics_web/lib/lol_analytics_web/components/champion_components/items.ex b/apps/lol_analytics_web/lib/lol_analytics_web/components/champion_components/items.ex new file mode 100644 index 0000000..2d9c1bb --- /dev/null +++ b/apps/lol_analytics_web/lib/lol_analytics_web/components/champion_components/items.ex @@ -0,0 +1,23 @@ +defmodule LolAnalyticsWeb.ChampionComponents.Items do + use Phoenix.Component + + def items(assigns) do + image = "" + + ~H""" +
+ <%= for item <- assigns.items do %> +
+
+ + <%!--

<%= item.name %>

--%> +

<%= item.win_rate %>%

+

<%= item.wins %>/<%= item.total_games %>

+
+ <%= item.name %> +
+ <% end %> +
+ """ + end +end diff --git a/apps/lol_analytics_web/lib/lol_analytics_web/components/champion_components/summoner_spells.ex b/apps/lol_analytics_web/lib/lol_analytics_web/components/champion_components/summoner_spells.ex index b4cf14b..d453326 100644 --- a/apps/lol_analytics_web/lib/lol_analytics_web/components/champion_components/summoner_spells.ex +++ b/apps/lol_analytics_web/lib/lol_analytics_web/components/champion_components/summoner_spells.ex @@ -6,11 +6,13 @@ defmodule LolAnalyticsWeb.ChampionComponents.SummonerSpells do ~H"""
<%= for spell <- assigns.spells.summoner_spells do %> -
- -

<%= spell.name %>

-

<%= spell.win_rate %>%

-

<%= spell.wins %>/<%= spell.total_games %>

+
+
+ +

<%= spell.win_rate %>%

+

<%= spell.wins %>/<%= spell.total_games %>

+
+
<%= spell.name %>
<% end %>
diff --git a/apps/lol_analytics_web/lib/lol_analytics_web/live/champion_live/show.ex b/apps/lol_analytics_web/lib/lol_analytics_web/live/champion_live/show.ex index 8eee05a..2cd3fac 100644 --- a/apps/lol_analytics_web/lib/lol_analytics_web/live/champion_live/show.ex +++ b/apps/lol_analytics_web/lib/lol_analytics_web/live/champion_live/show.ex @@ -3,6 +3,7 @@ defmodule LoLAnalyticsWeb.ChampionLive.Show do import LolAnalyticsWeb.ChampionComponents.SummonerSpells import LolAnalyticsWeb.ChampionComponents.ChampionAvatar + import LolAnalyticsWeb.ChampionComponents.Items alias LolAnalyticsWeb.ChampionComponents.SummonerSpells.ShowMapper @@ -12,30 +13,30 @@ defmodule LoLAnalyticsWeb.ChampionLive.Show do end @impl true - def handle_params(%{"id" => id}, _, socket) do + def handle_params(%{"id" => id, "team-position" => team_position}, _, socket) do {:noreply, socket |> assign(:page_title, page_title(socket.assigns.live_action)) |> assign(:champion, load_champion_info(id) |> ShowMapper.map_champion()) |> assign(:summoner_spells, %{ summoner_spells: load_summoner_spells(id) |> ShowMapper.map_spells() - })} + }) + |> assign(:items, load_items(id, team_position) |> ShowMapper.map_items())} end defp load_summoner_spells(champion_id) do LolAnalytics.Facts.ChampionPickedSummonerSpell.Repo.get_champion_picked_summoners(champion_id) end - defp load_items(champion_id) do - LolAnalytics.Facts.ChampionPickedItem.Repo.get_champion_picked_items(champion_id) + defp load_items(champion_id, team_position) do + LolAnalytics.Facts.ChampionPickedItem.Repo.get_champion_picked_items( + champion_id, + team_position + ) end defp load_champion_info(champion_id) do - champion = LolAnalytics.Dimensions.Champion.ChampionRepo.get_or_create(champion_id) - - IO.inspect(champion) - - champion + LolAnalytics.Dimensions.Champion.ChampionRepo.get_or_create(champion_id) end defp page_title(:show), do: "Show Champion" diff --git a/apps/lol_analytics_web/lib/lol_analytics_web/live/champion_live/show.html.heex b/apps/lol_analytics_web/lib/lol_analytics_web/live/champion_live/show.html.heex index 13c9e7f..f53193d 100644 --- a/apps/lol_analytics_web/lib/lol_analytics_web/live/champion_live/show.html.heex +++ b/apps/lol_analytics_web/lib/lol_analytics_web/live/champion_live/show.html.heex @@ -1,6 +1,5 @@ <.header> - Champion <%= @champion.id %> - <:actions> + <%= @champion.name %> <.champion_avatar id={@champion.id} name={@champion.name} @@ -8,4 +7,16 @@
-<.summoner_spells spells={@summoner_spells} /> \ No newline at end of file +

Summoner spells

+ +
+ +<.summoner_spells spells={@summoner_spells} /> + +
+ +

Items

+ +
+ +<.items items={@items} /> \ No newline at end of file diff --git a/apps/lol_analytics_web/lib/lol_analytics_web/live/champion_live/show_mapper.ex b/apps/lol_analytics_web/lib/lol_analytics_web/live/champion_live/show_mapper.ex index 0b9ab8e..774fea8 100644 --- a/apps/lol_analytics_web/lib/lol_analytics_web/live/champion_live/show_mapper.ex +++ b/apps/lol_analytics_web/lib/lol_analytics_web/live/champion_live/show_mapper.ex @@ -29,6 +29,22 @@ defmodule LolAnalyticsWeb.ChampionComponents.SummonerSpells.ShowMapper do } end + def map_items(items) do + items + |> Enum.map(&map_item/1) + |> Enum.sort(&(&1.total_games > &2.total_games)) + end + def map_item(item) do + image = item.metadata["image"]["full"] + + %{ + id: item["id"], + win_rate: :erlang.float_to_binary(item.win_rate, decimals: 2), + total_games: item.total_games, + image: "https://ddragon.leagueoflegends.com/cdn/14.11.1/img/item/#{image}", + name: item.metadata["name"], + wins: item.wins + } end end diff --git a/apps/lol_analytics_web/priv/static/images/lanes/utility-ed9e57067aded442715e9ef16b93f0da.png b/apps/lol_analytics_web/priv/static/images/lanes/utility-ed9e57067aded442715e9ef16b93f0da.png new file mode 100644 index 0000000000000000000000000000000000000000..a61e184b0791c41af727d6258f5eb32b3d6519f4 GIT binary patch literal 8091 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 d`-W5)CkCzJg_QF?y}d2R(NNJ