Compare commits
38 Commits
deploy
..
d07e604cbe
| Author | SHA1 | Date | |
|---|---|---|---|
| d07e604cbe | |||
| 75ec6a49e9 | |||
| c3de834f57 | |||
| a5cb6dc299 | |||
| 56af93b14e | |||
| 45f72cb836 | |||
| 656edf30a7 | |||
| aafb805194 | |||
| bd8148f3e2 | |||
| 4823e68700 | |||
| 4371701ca9 | |||
| 60bf927c67 | |||
| f79f0db862 | |||
| 7e30db3864 | |||
| 0987290dca | |||
| 4e9d98afe2 | |||
| ce825b75b1 | |||
| 0c72f96e16 | |||
| fb4fe9d487 | |||
| b5e58c4c25 | |||
| b611b296c3 | |||
| 16437b5deb | |||
| 33bcbece88 | |||
| 74a086c855 | |||
| 192748bb1d | |||
| d9dee3af7b | |||
| 2b51aace6e | |||
| 3ef7861f22 | |||
| 968c2634b7 | |||
| 6dd8eea3d3 | |||
| fe3d040978 | |||
| 9b955641d0 | |||
| 77b292e47e | |||
| 22b79f5376 | |||
| 520c234a94 | |||
| dacb9ad8fc | |||
| 6053cfcdac | |||
| 5ce7ce0542 |
@@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
# A sample docker-setup hook
|
||||
#
|
||||
# Sets up a Docker network which can then be used by the application’s containers
|
||||
|
||||
ssh user@example.com docker network create kamal
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# A sample post-deploy hook
|
||||
#
|
||||
# These environment variables are available:
|
||||
# KAMAL_RECORDED_AT
|
||||
# KAMAL_PERFORMER
|
||||
# KAMAL_VERSION
|
||||
# KAMAL_HOSTS
|
||||
# KAMAL_ROLE (if set)
|
||||
# KAMAL_DESTINATION (if set)
|
||||
# KAMAL_RUNTIME
|
||||
|
||||
echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds"
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Rebooted Traefik on $KAMAL_HOSTS"
|
||||
@@ -1,51 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# A sample pre-build hook
|
||||
#
|
||||
# Checks:
|
||||
# 1. We have a clean checkout
|
||||
# 2. A remote is configured
|
||||
# 3. The branch has been pushed to the remote
|
||||
# 4. The version we are deploying matches the remote
|
||||
#
|
||||
# These environment variables are available:
|
||||
# KAMAL_RECORDED_AT
|
||||
# KAMAL_PERFORMER
|
||||
# KAMAL_VERSION
|
||||
# KAMAL_HOSTS
|
||||
# KAMAL_ROLE (if set)
|
||||
# KAMAL_DESTINATION (if set)
|
||||
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "Git checkout is not clean, aborting..." >&2
|
||||
git status --porcelain >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
first_remote=$(git remote)
|
||||
|
||||
if [ -z "$first_remote" ]; then
|
||||
echo "No git remote set, aborting..." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
current_branch=$(git branch --show-current)
|
||||
|
||||
if [ -z "$current_branch" ]; then
|
||||
echo "Not on a git branch, aborting..." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1)
|
||||
|
||||
if [ -z "$remote_head" ]; then
|
||||
echo "Branch not pushed to remote, aborting..." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$KAMAL_VERSION" != "$remote_head" ]; then
|
||||
echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
# A sample pre-connect check
|
||||
#
|
||||
# Warms DNS before connecting to hosts in parallel
|
||||
#
|
||||
# These environment variables are available:
|
||||
# KAMAL_RECORDED_AT
|
||||
# KAMAL_PERFORMER
|
||||
# KAMAL_VERSION
|
||||
# KAMAL_HOSTS
|
||||
# KAMAL_ROLE (if set)
|
||||
# KAMAL_DESTINATION (if set)
|
||||
# KAMAL_RUNTIME
|
||||
|
||||
hosts = ENV["KAMAL_HOSTS"].split(",")
|
||||
results = nil
|
||||
max = 3
|
||||
|
||||
elapsed = Benchmark.realtime do
|
||||
results = hosts.map do |host|
|
||||
Thread.new do
|
||||
tries = 1
|
||||
|
||||
begin
|
||||
Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME)
|
||||
rescue SocketError
|
||||
if tries < max
|
||||
puts "Retrying DNS warmup: #{host}"
|
||||
tries += 1
|
||||
sleep rand
|
||||
retry
|
||||
else
|
||||
puts "DNS warmup failed: #{host}"
|
||||
host
|
||||
end
|
||||
end
|
||||
|
||||
tries
|
||||
end
|
||||
end.map(&:value)
|
||||
end
|
||||
|
||||
retries = results.sum - hosts.size
|
||||
nopes = results.count { |r| r == max }
|
||||
|
||||
puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ]
|
||||
@@ -1,109 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
# A sample pre-deploy hook
|
||||
#
|
||||
# Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds.
|
||||
#
|
||||
# Fails unless the combined status is "success"
|
||||
#
|
||||
# These environment variables are available:
|
||||
# KAMAL_RECORDED_AT
|
||||
# KAMAL_PERFORMER
|
||||
# KAMAL_VERSION
|
||||
# KAMAL_HOSTS
|
||||
# KAMAL_COMMAND
|
||||
# KAMAL_SUBCOMMAND
|
||||
# KAMAL_ROLE (if set)
|
||||
# KAMAL_DESTINATION (if set)
|
||||
|
||||
# Only check the build status for production deployments
|
||||
if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production"
|
||||
exit 0
|
||||
end
|
||||
|
||||
require "bundler/inline"
|
||||
|
||||
# true = install gems so this is fast on repeat invocations
|
||||
gemfile(true, quiet: true) do
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "octokit"
|
||||
gem "faraday-retry"
|
||||
end
|
||||
|
||||
MAX_ATTEMPTS = 72
|
||||
ATTEMPTS_GAP = 10
|
||||
|
||||
def exit_with_error(message)
|
||||
$stderr.puts message
|
||||
exit 1
|
||||
end
|
||||
|
||||
class GithubStatusChecks
|
||||
attr_reader :remote_url, :git_sha, :github_client, :combined_status
|
||||
|
||||
def initialize
|
||||
@remote_url = `git config --get remote.origin.url`.strip.delete_prefix("https://github.com/")
|
||||
@git_sha = `git rev-parse HEAD`.strip
|
||||
@github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"])
|
||||
refresh!
|
||||
end
|
||||
|
||||
def refresh!
|
||||
@combined_status = github_client.combined_status(remote_url, git_sha)
|
||||
end
|
||||
|
||||
def state
|
||||
combined_status[:state]
|
||||
end
|
||||
|
||||
def first_status_url
|
||||
first_status = combined_status[:statuses].find { |status| status[:state] == state }
|
||||
first_status && first_status[:target_url]
|
||||
end
|
||||
|
||||
def complete_count
|
||||
combined_status[:statuses].count { |status| status[:state] != "pending"}
|
||||
end
|
||||
|
||||
def total_count
|
||||
combined_status[:statuses].count
|
||||
end
|
||||
|
||||
def current_status
|
||||
if total_count > 0
|
||||
"Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..."
|
||||
else
|
||||
"Build not started..."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
$stdout.sync = true
|
||||
|
||||
puts "Checking build status..."
|
||||
attempts = 0
|
||||
checks = GithubStatusChecks.new
|
||||
|
||||
begin
|
||||
loop do
|
||||
case checks.state
|
||||
when "success"
|
||||
puts "Checks passed, see #{checks.first_status_url}"
|
||||
exit 0
|
||||
when "failure"
|
||||
exit_with_error "Checks failed, see #{checks.first_status_url}"
|
||||
when "pending"
|
||||
attempts += 1
|
||||
end
|
||||
|
||||
exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS
|
||||
|
||||
puts checks.current_status
|
||||
sleep(ATTEMPTS_GAP)
|
||||
checks.refresh!
|
||||
end
|
||||
rescue Octokit::NotFound
|
||||
exit_with_error "Build status could not be found"
|
||||
end
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Rebooting Traefik on $KAMAL_HOSTS..."
|
||||
@@ -1,3 +0,0 @@
|
||||
defmodule LolAnalytics.Analyzer do
|
||||
@callback analyze(:url, path :: String.t()) :: :ok
|
||||
end
|
||||
@@ -1,50 +0,0 @@
|
||||
defmodule LolAnalytics.Analyzer.ChampionAnalyzer do
|
||||
alias Hex.HTTP
|
||||
@behaviour LolAnalytics.Analyzer
|
||||
|
||||
def analyze_all_matches do
|
||||
Storage.MatchStorage.S3MatchStorage.list_files("ranked")
|
||||
|> Enum.map(& &1.key)
|
||||
|> Enum.each(fn path ->
|
||||
LolAnalytics.Analyzer.ChampionAnalyzer.analyze(:url, "http://localhost:9000/ranked/#{path}")
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
iex> LolAnalytics.Analyzer.ChampionAnalyzer.analyze(:url, "http://localhost:9000/ranked/14.9.580.2108/EUW1_6923309745.json")
|
||||
"""
|
||||
@impl true
|
||||
@spec analyze(atom(), String.t()) :: :ok
|
||||
def analyze(:url, path) do
|
||||
data = HTTPoison.get!(path)
|
||||
analyze(:data, data.body)
|
||||
:ok
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec analyze(atom(), any()) :: list(LoLAPI.Model.Participant.t())
|
||||
def analyze(:data, data) do
|
||||
decoded_match = Poison.decode!(data, as: %LoLAPI.Model.MatchResponse{})
|
||||
participants = decoded_match.info.participants
|
||||
version = extract_game_version(decoded_match)
|
||||
|
||||
participants
|
||||
|> Enum.each(fn participant = %LoLAPI.Model.Participant{} ->
|
||||
if participant.teamPosition != "" do
|
||||
LolAnalytics.ChampionWinRate.ChampionWinRateRepo.add_champion_win_rate(
|
||||
participant.championId,
|
||||
version,
|
||||
participant.teamPosition,
|
||||
participant.win
|
||||
)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp extract_game_version(game_data) do
|
||||
game_data.info.gameVersion
|
||||
|> String.split(".")
|
||||
|> Enum.take(2)
|
||||
|> Enum.join(".")
|
||||
end
|
||||
end
|
||||
@@ -46,7 +46,7 @@ defmodule LolAnalytics.ChampionWinRate.ChampionWinRateRepo do
|
||||
Repo.all(ChampionWinRateSchema)
|
||||
end
|
||||
|
||||
def get_champion_win_rate(champion_id, patch) do
|
||||
def get_champion_win_rate(champion_id, _patch) do
|
||||
champion_query =
|
||||
from cwr in LolAnalytics.ChampionWinRate.ChampionWinRateSchema,
|
||||
where: cwr.champion_id == ^champion_id
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
defmodule LolAnalytics.Dimensions.Champion.ChampionMetadata do
|
||||
alias LolAnalytics.Dimensions.Champion.ChampionRepo
|
||||
@champions_data_url "https://ddragon.leagueoflegends.com/cdn/14.11.1/data/en_US/champion.json"
|
||||
|
||||
def update_metadata() do
|
||||
{:ok, %{"data" => data}} = get_champions()
|
||||
|
||||
data
|
||||
|> Enum.each(&save_metadata/1)
|
||||
end
|
||||
|
||||
defp get_champions() do
|
||||
with {:ok, resp} <- HTTPoison.get(@champions_data_url),
|
||||
data <- Poison.decode(resp.body) do
|
||||
data
|
||||
else
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp save_metadata({_champion, info}) do
|
||||
%{
|
||||
"image" => %{
|
||||
"full" => full_image
|
||||
},
|
||||
"name" => name,
|
||||
"key" => key_string
|
||||
} = info
|
||||
|
||||
attrs = %{
|
||||
image: full_image,
|
||||
name: name
|
||||
}
|
||||
|
||||
{champion_id, _} = Integer.parse(key_string)
|
||||
|
||||
ChampionRepo.update(champion_id, attrs)
|
||||
|
||||
info
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
defmodule LolAnalytics.Dimensions.Champion.ChampionRepo do
|
||||
alias LoLAnalytics.Repo
|
||||
alias LolAnalytics.Dimensions.Champion.ChampionSchema
|
||||
|
||||
@spec get_or_create(String.t()) :: struct()
|
||||
def get_or_create(champion_id) do
|
||||
champion = Repo.get_by(ChampionSchema, champion_id: champion_id)
|
||||
|
||||
case champion do
|
||||
nil ->
|
||||
changeset = ChampionSchema.changeset(%ChampionSchema{}, %{champion_id: champion_id})
|
||||
Repo.insert(changeset)
|
||||
|
||||
champion ->
|
||||
champion
|
||||
end
|
||||
end
|
||||
|
||||
def update(champion_id, attrs) do
|
||||
get_or_create(champion_id)
|
||||
|> ChampionSchema.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@spec list_champions() :: any()
|
||||
def list_champions() do
|
||||
Repo.all(ChampionSchema)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
defmodule LolAnalytics.Dimensions.Champion.ChampionSchema do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
schema "dim_champion" do
|
||||
field :champion_id, :integer
|
||||
field :name, :string
|
||||
field :image, :string
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def changeset(champion = %__MODULE__{}, attrs \\ %{}) do
|
||||
champion
|
||||
|> cast(attrs, [:champion_id, :name, :image])
|
||||
|> validate_required([:champion_id])
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
@@ -0,0 +1,30 @@
|
||||
defmodule LolAnalytics.Dimensions.Item.ItemRepo do
|
||||
alias LolAnalytics.Dimensions.Item.ItemSchema
|
||||
alias LoLAnalytics.Repo
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
def get_or_create(item_id) do
|
||||
query = from i in ItemSchema, where: i.item_id == ^item_id
|
||||
|
||||
case Repo.one(query) do
|
||||
nil ->
|
||||
item_changeset = ItemSchema.changeset(%ItemSchema{}, %{item_id: item_id})
|
||||
Repo.insert!(item_changeset)
|
||||
|
||||
item ->
|
||||
item
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
@@ -0,0 +1,18 @@
|
||||
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, @args)
|
||||
|> validate_required(@args)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
defmodule LolAnalytics.Dimensions.Match.MatchRepo do
|
||||
alias LolAnalytics.Dimensions.Match.MatchSchema
|
||||
alias LoLAnalytics.Repo
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
@spec get_or_create(String.t()) :: %MatchSchema{}
|
||||
def get_or_create(match_id) do
|
||||
query = from m in MatchSchema, where: m.match_id == ^match_id
|
||||
match = Repo.one(query)
|
||||
|
||||
case match do
|
||||
nil ->
|
||||
match_changeset =
|
||||
MatchSchema.changeset(
|
||||
%MatchSchema{},
|
||||
%{match_id: match_id}
|
||||
)
|
||||
|
||||
Repo.insert(match_changeset)
|
||||
|
||||
match ->
|
||||
match
|
||||
end
|
||||
end
|
||||
|
||||
def list_matches() do
|
||||
Repo.all(MatchSchema)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
defmodule LolAnalytics.Dimensions.Match.MatchSchema do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
schema "dim_match" do
|
||||
field :match_id, :string
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def changeset(match = %__MODULE__{}, attrs \\ %{}) do
|
||||
match
|
||||
|> cast(attrs, [:match_id])
|
||||
|> validate_required([:match_id])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
defmodule LolAnalytics.Dimensions.Patch.PatchRepo do
|
||||
alias LolAnalytics.Dimensions.Patch.PatchSchema
|
||||
alias LoLAnalytics.Repo
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
def get_or_create(patch_number) do
|
||||
query = from p in PatchSchema, where: p.patch_number == ^patch_number
|
||||
|
||||
case Repo.one(query) do
|
||||
nil ->
|
||||
patch_changeset =
|
||||
PatchSchema.changeset(
|
||||
%PatchSchema{},
|
||||
%{patch_number: patch_number}
|
||||
)
|
||||
|
||||
Repo.insert(patch_changeset)
|
||||
|
||||
patch ->
|
||||
patch
|
||||
end
|
||||
end
|
||||
|
||||
def list_patches() do
|
||||
Repo.all(PatchSchema)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
defmodule LolAnalytics.Dimensions.Patch.PatchSchema do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
schema "dim_patch" do
|
||||
field :patch_number, :string
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def changeset(patch = %__MODULE__{}, attrs \\ %{}) do
|
||||
patch
|
||||
|> cast(attrs, [:patch_number])
|
||||
|> validate_required([:patch_number])
|
||||
|> unique_constraint([:patch_number])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
defmodule LolAnalytics.Dimensions.Player.PlayerRepo do
|
||||
import Ecto.Query
|
||||
|
||||
alias LolAnalytics.Dimensions.Player.PlayerSchema
|
||||
alias LoLAnalytics.Repo
|
||||
|
||||
def get_or_create(puuid) do
|
||||
query = from p in PlayerSchema, where: p.puuid == ^puuid
|
||||
|
||||
case Repo.one(query) do
|
||||
nil ->
|
||||
player_changeset =
|
||||
PlayerSchema.changeset(
|
||||
%PlayerSchema{},
|
||||
%{puuid: puuid}
|
||||
)
|
||||
|
||||
Repo.insert(player_changeset)
|
||||
|
||||
player ->
|
||||
player
|
||||
end
|
||||
end
|
||||
|
||||
def list_players() do
|
||||
Repo.all(PlayerSchema)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
defmodule LolAnalytics.Dimensions.Player.PlayerSchema do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
schema "dim_player" do
|
||||
field :puuid, :string
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def changeset(player = %__MODULE__{}, attrs \\ %{}) do
|
||||
player
|
||||
|> cast(attrs, [:puuid])
|
||||
|> validate_required([:puuid])
|
||||
|> unique_constraint([:puuid])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
defmodule LolAnalytics.Dimensions.SummonerSpell.SummonerSpellMetadata do
|
||||
alias LolAnalytics.Dimensions.SummonerSpell.SummonerSpellRepo
|
||||
@spells_url "https://ddragon.leagueoflegends.com/cdn/14.11.1/data/en_US/summoner.json"
|
||||
|
||||
def update_metadata() do
|
||||
get_spells()
|
||||
|> Enum.each(&save_metadata/1)
|
||||
end
|
||||
|
||||
defp get_spells() do
|
||||
case HTTPoison.get(@spells_url) do
|
||||
{:ok, resp} ->
|
||||
Poison.decode!(resp.body)["data"]
|
||||
end
|
||||
end
|
||||
|
||||
defp save_metadata({name, metadata}) do
|
||||
%{"key" => spell_id} = metadata
|
||||
|
||||
SummonerSpellRepo.update(spell_id, %{metadata: metadata})
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,37 @@
|
||||
defmodule LolAnalytics.Dimensions.SummonerSpell.SummonerSpellRepo do
|
||||
import Ecto.Query
|
||||
|
||||
alias LolAnalytics.Dimensions.SummonerSpell.SummonerSpellSchema
|
||||
alias LoLAnalytics.Repo
|
||||
|
||||
@spec get_or_create(String.t()) :: any()
|
||||
def get_or_create(spell_id) do
|
||||
query = from s in SummonerSpellSchema, where: s.spell_id == ^spell_id
|
||||
spell = Repo.one(query)
|
||||
|
||||
case spell do
|
||||
nil ->
|
||||
spell_changeset =
|
||||
SummonerSpellSchema.changeset(
|
||||
%SummonerSpellSchema{},
|
||||
%{spell_id: spell_id}
|
||||
)
|
||||
|
||||
Repo.insert(spell_changeset)
|
||||
|
||||
spell ->
|
||||
spell
|
||||
end
|
||||
end
|
||||
|
||||
@spec update(spell_id :: String.t(), attrs :: map()) :: any()
|
||||
def update(spell_id, attrs) do
|
||||
get_or_create(spell_id)
|
||||
|> SummonerSpellSchema.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def list_spells() do
|
||||
Repo.all(SummonerSpellSchema)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
defmodule LolAnalytics.Dimensions.SummonerSpell.SummonerSpellSchema do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
schema "dim_summoner_spell" do
|
||||
field :spell_id, :integer
|
||||
field :metadata, :map
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def changeset(summoner_spell = %__MODULE__{}, attrs) do
|
||||
summoner_spell
|
||||
|> cast(attrs, [:spell_id, :metadata])
|
||||
|> validate_required([:spell_id])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,63 @@
|
||||
defmodule LolAnalytics.Facts.ChampionPickedItem.FactProcessor do
|
||||
alias LolAnalytics.Facts.ChampionPickedItem.Repo
|
||||
require Logger
|
||||
@behaviour LolAnalytics.Facts.FactBehaviour
|
||||
|
||||
@doc """
|
||||
|
||||
iex> LolAnalytics.Facts.ChampionPickedItem.FactProcessor.process_game_at_url("http://192.168.1.55:9000/ranked/14.3.558.106/EUW1_6803789466.json")
|
||||
"""
|
||||
@impl true
|
||||
def process_game_at_url(url) do
|
||||
case HTTPoison.get(url) do
|
||||
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
|
||||
IO.inspect(url)
|
||||
process_game_data(body)
|
||||
|
||||
{:error, %HTTPoison.Error{reason: reason}} ->
|
||||
{:error, reason}
|
||||
|
||||
_ ->
|
||||
{:error, "Could not fetch data from #{url}"}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_game_data(data) do
|
||||
decoded_match = Poison.decode!(data, as: %LoLAPI.Model.MatchResponse{})
|
||||
participants = decoded_match.info.participants
|
||||
version = extract_game_version(decoded_match)
|
||||
|
||||
Logger.info("Processing ChampionPickedItem for match #{decoded_match.metadata.matchId}")
|
||||
|
||||
participants
|
||||
|> Enum.each(fn participant = %LoLAPI.Model.Participant{} ->
|
||||
if participant.teamPosition != "" do
|
||||
[:item0, :item1, :item2, :item3, :item4, :item5, :item6]
|
||||
|> Enum.with_index()
|
||||
|> Enum.each(fn {item_key, index} ->
|
||||
item_key = Map.get(participant, item_key)
|
||||
|
||||
Repo.insert(%{
|
||||
champion_id: participant.championId,
|
||||
match_id: decoded_match.metadata.matchId,
|
||||
is_win: participant.win,
|
||||
item_id: item_key,
|
||||
slot_number: index,
|
||||
game_length_seconds: decoded_match.info.gameDuration,
|
||||
queue_id: decoded_match.info.queueId,
|
||||
puuid: participant.puuid,
|
||||
team_position: participant.teamPosition,
|
||||
patch_number: version
|
||||
})
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp extract_game_version(game_data) do
|
||||
game_data.info.gameVersion
|
||||
|> String.split(".")
|
||||
|> Enum.take(2)
|
||||
|> Enum.join(".")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,89 @@
|
||||
defmodule LolAnalytics.Facts.ChampionPickedItem.Repo do
|
||||
import Ecto.Query
|
||||
|
||||
alias LolAnalytics.Dimensions.Item.ItemSchema
|
||||
alias LolAnalytics.Dimensions.Champion.ChampionSchema
|
||||
alias LolAnalytics.Facts.ChampionPickedItem.Schema
|
||||
alias LolAnalytics.Dimensions.Item.ItemRepo
|
||||
alias LolAnalytics.Dimensions.Player.PlayerRepo
|
||||
alias LolAnalytics.Dimensions.Champion.ChampionRepo
|
||||
alias LolAnalytics.Dimensions.Match.MatchRepo
|
||||
alias LoLAnalytics.Repo
|
||||
|
||||
@doc """
|
||||
Inserts a new entry for the campion_picked_item fact.
|
||||
|
||||
Example:
|
||||
|
||||
iex> item_picked = %{champion_id: 90, item_id: 1, match_id: "123", patch_number: "14.9", puuid: "1223", slot_number: 1, queue_id: 420, team_position: "JUNGLE", slot_number: 1, is_win: true}
|
||||
iex> LolAnalytics.Facts.ChampionPickedItem.Repo.insert(item_picked)
|
||||
"""
|
||||
@spec insert(%{
|
||||
:champion_id => integer(),
|
||||
:item_id => integer(),
|
||||
:match_id => binary(),
|
||||
:patch_number => String.t(),
|
||||
:puuid => String.t(),
|
||||
:is_win => boolean(),
|
||||
:queue_id => integer(),
|
||||
:team_position => String.t(),
|
||||
:slot_number => integer()
|
||||
}) :: {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}
|
||||
def insert(attrs) do
|
||||
_match = MatchRepo.get_or_create(attrs.match_id)
|
||||
_champion = ChampionRepo.get_or_create(attrs.champion_id)
|
||||
_player = PlayerRepo.get_or_create(attrs.puuid)
|
||||
_patch = PlayerRepo.get_or_create(attrs.patch_number)
|
||||
_item_id = ItemRepo.get_or_create(attrs.item_id)
|
||||
|
||||
prev =
|
||||
from(f in Schema,
|
||||
where:
|
||||
f.match_id == ^attrs.match_id and
|
||||
f.puuid == ^attrs.puuid and
|
||||
f.item_id == ^attrs.item_id and
|
||||
f.slot_number == ^attrs.slot_number
|
||||
)
|
||||
|> Repo.one()
|
||||
|
||||
Schema.changeset(prev || %Schema{}, attrs)
|
||||
|> Repo.insert_or_update()
|
||||
end
|
||||
|
||||
@spec get_champion_picked_items(String.t(), String.t()) :: list()
|
||||
def get_champion_picked_items(champion_id, team_position) do
|
||||
query =
|
||||
from f in Schema,
|
||||
where:
|
||||
f.champion_id == ^champion_id and
|
||||
f.team_position == ^team_position and
|
||||
f.item_id != 0,
|
||||
join: c in ChampionSchema,
|
||||
on: c.champion_id == f.champion_id,
|
||||
join: i in ItemSchema,
|
||||
on: i.item_id == f.item_id,
|
||||
select: %{
|
||||
wins: fragment("count(CASE WHEN ? THEN 1 END)", f.is_win),
|
||||
win_rate:
|
||||
fragment(
|
||||
"
|
||||
((cast(count(CASE WHEN ? THEN 1 END) as float) / cast(count(*) as float)) * 100.0
|
||||
)",
|
||||
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
|
||||
]
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
defmodule LolAnalytics.Facts.ChampionPickedItem.Schema do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@casting_args [
|
||||
:champion_id,
|
||||
:item_id,
|
||||
:match_id,
|
||||
:is_win,
|
||||
:queue_id,
|
||||
:patch_number,
|
||||
:team_position,
|
||||
:puuid,
|
||||
:slot_number
|
||||
]
|
||||
|
||||
schema "fact_champion_picked_item" do
|
||||
field :champion_id, :integer
|
||||
field :item_id, :integer
|
||||
field :match_id, :string
|
||||
field :is_win, :boolean
|
||||
field :queue_id, :integer
|
||||
field :patch_number, :string
|
||||
field :team_position, :string
|
||||
field :puuid, :string
|
||||
field :slot_number, :integer
|
||||
end
|
||||
|
||||
def changeset(fact, attrs) do
|
||||
fact
|
||||
|> cast(attrs, @casting_args)
|
||||
|> validate_required(@casting_args)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,69 @@
|
||||
defmodule LolAnalytics.Facts.ChampionPickedSummonerSpell.FactProcessor do
|
||||
@behaviour LolAnalytics.Facts.FactBehaviour
|
||||
|
||||
require Logger
|
||||
|
||||
alias LolAnalytics.Facts.ChampionPickedSummonerSpell
|
||||
|
||||
@impl true
|
||||
@spec process_game_at_url(String.t()) :: any()
|
||||
def process_game_at_url(url) do
|
||||
case HTTPoison.get(url) do
|
||||
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
|
||||
process_game_data(body)
|
||||
|
||||
{:error, %HTTPoison.Error{reason: reason}} ->
|
||||
{:error, reason}
|
||||
|
||||
_ ->
|
||||
{:error, "Could not fetch data from #{url}"}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_game_data(data) do
|
||||
decoded_match = Poison.decode!(data, as: %LoLAPI.Model.MatchResponse{})
|
||||
participants = decoded_match.info.participants
|
||||
version = extract_game_version(decoded_match)
|
||||
|
||||
Logger.info("Processing ChampionPickedSummoner for match #{decoded_match.metadata.matchId}")
|
||||
|
||||
participants
|
||||
|> Enum.each(fn participant = %LoLAPI.Model.Participant{} ->
|
||||
if participant.teamPosition != "" do
|
||||
attrs_spell_1 = %{
|
||||
champion_id: participant.championId,
|
||||
match_id: decoded_match.metadata.matchId,
|
||||
is_win: participant.win,
|
||||
summoner_spell_id: participant.summoner1Id,
|
||||
game_length_seconds: decoded_match.info.gameDuration,
|
||||
queue_id: decoded_match.info.queueId,
|
||||
puuid: participant.puuid,
|
||||
team_position: participant.teamPosition,
|
||||
patch_number: version
|
||||
}
|
||||
|
||||
attrs_spell_2 = %{
|
||||
champion_id: participant.championId,
|
||||
match_id: decoded_match.metadata.matchId,
|
||||
is_win: participant.win,
|
||||
summoner_spell_id: participant.summoner2Id,
|
||||
game_length_seconds: decoded_match.info.gameDuration,
|
||||
queue_id: decoded_match.info.queueId,
|
||||
puuid: participant.puuid,
|
||||
team_position: participant.teamPosition,
|
||||
patch_number: version
|
||||
}
|
||||
|
||||
ChampionPickedSummonerSpell.Repo.insert(attrs_spell_1)
|
||||
ChampionPickedSummonerSpell.Repo.insert(attrs_spell_2)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp extract_game_version(game_data) do
|
||||
game_data.info.gameVersion
|
||||
|> String.split(".")
|
||||
|> Enum.take(2)
|
||||
|> Enum.join(".")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,81 @@
|
||||
defmodule LolAnalytics.Facts.ChampionPickedSummonerSpell.Repo do
|
||||
import Ecto.Query
|
||||
|
||||
alias LolAnalytics.Dimensions.SummonerSpell.SummonerSpellSchema
|
||||
alias LolAnalytics.Dimensions.SummonerSpell.SummonerSpellRepo
|
||||
alias LolAnalytics.Dimensions.Champion.ChampionSchema
|
||||
|
||||
alias LolAnalytics.Facts.ChampionPickedSummonerSpell.Schema
|
||||
|
||||
alias LolAnalytics.Dimensions.Player.PlayerRepo
|
||||
alias LolAnalytics.Dimensions.Champion.ChampionRepo
|
||||
alias LolAnalytics.Dimensions.Match.MatchRepo
|
||||
|
||||
alias LoLAnalytics.Repo
|
||||
|
||||
@type insert_attrs :: %{
|
||||
match_id: String.t(),
|
||||
champion_id: String.t(),
|
||||
puuid: String.t(),
|
||||
summoner_spell_id: :integer
|
||||
}
|
||||
|
||||
@spec insert(insert_attrs()) :: any()
|
||||
def insert(attrs) do
|
||||
_match = MatchRepo.get_or_create(attrs.match_id)
|
||||
_champion = ChampionRepo.get_or_create(attrs.champion_id)
|
||||
_player = PlayerRepo.get_or_create(attrs.puuid)
|
||||
_spell = SummonerSpellRepo.get_or_create(attrs.summoner_spell_id)
|
||||
|
||||
prev =
|
||||
from(f in Schema,
|
||||
where:
|
||||
f.match_id == ^attrs.match_id and
|
||||
f.champion_id == ^attrs.champion_id and
|
||||
f.summoner_spell_id ==
|
||||
^attrs.summoner_spell_id
|
||||
)
|
||||
|> Repo.one()
|
||||
|
||||
Schema.changeset(prev || %Schema{}, attrs)
|
||||
|> Repo.insert_or_update()
|
||||
end
|
||||
|
||||
def get_champion_picked_summoners(champion_id, team_position \\ "MIDDLE") do
|
||||
query =
|
||||
from f in Schema,
|
||||
where: f.champion_id == ^champion_id and f.team_position == ^team_position,
|
||||
join: c in ChampionSchema,
|
||||
on: c.champion_id == f.champion_id,
|
||||
join: s in SummonerSpellSchema,
|
||||
on: s.spell_id == f.summoner_spell_id,
|
||||
select: %{
|
||||
wins: fragment("count(CASE WHEN ? THEN 1 END)", f.is_win),
|
||||
win_rate:
|
||||
fragment(
|
||||
"
|
||||
((cast(count(CASE WHEN ? THEN 1 END) as float) / cast(count(*) as float)) * 100.0
|
||||
)",
|
||||
f.is_win
|
||||
),
|
||||
spell_id: f.summoner_spell_id,
|
||||
metadata: s.metadata,
|
||||
champion_id: c.champion_id,
|
||||
team_position: f.team_position,
|
||||
total_games: count("*")
|
||||
},
|
||||
group_by: [
|
||||
f.champion_id,
|
||||
f.summoner_spell_id,
|
||||
s.metadata,
|
||||
c.champion_id,
|
||||
f.team_position
|
||||
]
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
def list_facts() do
|
||||
Repo.all(Schema)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
defmodule LolAnalytics.Facts.ChampionPickedSummonerSpell.Schema do
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
@params [
|
||||
:champion_id,
|
||||
:summoner_spell_id,
|
||||
:match_id,
|
||||
:is_win,
|
||||
:game_length_seconds,
|
||||
:queue_id,
|
||||
:team_position,
|
||||
:puuid
|
||||
]
|
||||
|
||||
schema "fact_champion_picked_summoner_spell" do
|
||||
field :champion_id, :integer
|
||||
field :summoner_spell_id, :integer
|
||||
field :match_id, :string
|
||||
field :is_win, :boolean
|
||||
field :game_length_seconds, :integer
|
||||
field :queue_id, :integer
|
||||
field :team_position, :string
|
||||
field :puuid, :string
|
||||
end
|
||||
|
||||
def changeset(fact = %__MODULE__{}, attrs \\ %{}) do
|
||||
fact
|
||||
|> cast(attrs, @params)
|
||||
|> validate_required(@params)
|
||||
|> unique_constraint([:puuid, :match_id, :summoner_spell_id])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,91 @@
|
||||
defmodule LolAnalytics.Facts.ChampionPlayedGame.Repo do
|
||||
import Ecto.Query
|
||||
|
||||
alias LolAnalytics.Dimensions.Patch.PatchRepo
|
||||
alias LolAnalytics.Dimensions.Champion.ChampionSchema
|
||||
alias LolAnalytics.Dimensions.Player.PlayerRepo
|
||||
alias LolAnalytics.Dimensions.Champion.ChampionRepo
|
||||
alias LolAnalytics.Dimensions.Match.MatchRepo
|
||||
alias LolAnalytics.Facts.ChampionPlayedGame.Schema
|
||||
alias LoLAnalytics.Repo
|
||||
|
||||
def insert(attrs) do
|
||||
_match = MatchRepo.get_or_create(attrs.match_id)
|
||||
_champion = ChampionRepo.get_or_create(attrs.champion_id)
|
||||
_player = PlayerRepo.get_or_create(attrs.puuid)
|
||||
_patch = PatchRepo.get_or_create(attrs.patch_number)
|
||||
|
||||
prev =
|
||||
from(f in Schema,
|
||||
where:
|
||||
f.match_id == ^attrs.match_id and
|
||||
f.champion_id == ^attrs.champion_id and f.queue_id == ^attrs.queue_id
|
||||
)
|
||||
|> Repo.one()
|
||||
|
||||
changeset = Schema.changeset(prev || %Schema{}, attrs)
|
||||
|
||||
Repo.update(changeset)
|
||||
end
|
||||
|
||||
def list_played_matches() do
|
||||
Repo.all(Schema)
|
||||
end
|
||||
|
||||
def get_win_rates do
|
||||
query =
|
||||
from m in Schema,
|
||||
join: c in ChampionSchema,
|
||||
on: c.champion_id == m.champion_id,
|
||||
select: %{
|
||||
wins: fragment("count(CASE WHEN ? THEN 1 END)", m.is_win),
|
||||
win_rate:
|
||||
fragment(
|
||||
"
|
||||
((cast(count(CASE WHEN ? THEN 1 END) as float) / cast(count(*) as float)) * 100.0
|
||||
)",
|
||||
m.is_win
|
||||
),
|
||||
id: m.champion_id,
|
||||
name: c.name,
|
||||
image: c.image,
|
||||
team_position: m.team_position,
|
||||
total_games: count("*")
|
||||
},
|
||||
group_by: [m.champion_id, c.image, c.name, m.team_position],
|
||||
having: count("*") > 100
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
def get_win_rates_by_patch(champion_id, team_position) do
|
||||
query =
|
||||
from m in Schema,
|
||||
where: m.team_position == ^team_position,
|
||||
join: c in ChampionSchema,
|
||||
on: c.champion_id == m.champion_id,
|
||||
select: %{
|
||||
wins: fragment("count(CASE WHEN ? THEN 1 END)", m.is_win),
|
||||
win_rate:
|
||||
fragment(
|
||||
"
|
||||
((cast(count(CASE WHEN ? THEN 1 END) as float) / cast(count(*) as float)) * 100.0
|
||||
)",
|
||||
m.is_win
|
||||
),
|
||||
id: m.champion_id,
|
||||
patch_number: m.patch_number,
|
||||
name: c.name,
|
||||
image: c.image,
|
||||
team_position: m.team_position,
|
||||
total_games: count("*")
|
||||
},
|
||||
where: c.champion_id == ^champion_id,
|
||||
group_by: [m.champion_id, c.image, c.name, m.team_position, m.patch_number]
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
def get_win_rates_by_roles() do
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,35 @@
|
||||
defmodule LolAnalytics.Facts.ChampionPlayedGame.Schema do
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
@casting_attrs [
|
||||
:champion_id,
|
||||
:match_id,
|
||||
:patch_number,
|
||||
:is_win,
|
||||
:game_length_seconds,
|
||||
:team_position,
|
||||
:puuid,
|
||||
:queue_id
|
||||
]
|
||||
|
||||
schema "fact_champion_played_game" do
|
||||
field :champion_id, :integer
|
||||
field :match_id, :string
|
||||
field :patch_number, :string
|
||||
field :is_win, :boolean
|
||||
field :game_length_seconds, :integer
|
||||
field :team_position, :string
|
||||
field :puuid, :string
|
||||
field :queue_id, :integer
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def changeset(fact = %__MODULE__{}, attrs \\ %{}) do
|
||||
fact
|
||||
|> cast(attrs, @casting_attrs)
|
||||
|> validate_required(@casting_attrs)
|
||||
|> unique_constraint([:id, :champion_id, :queue_id])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,53 @@
|
||||
defmodule LolAnalytics.Facts.ChampionPlayedGame.FactProcessor do
|
||||
require Logger
|
||||
|
||||
@behaviour LolAnalytics.Facts.FactBehaviour
|
||||
|
||||
@impl true
|
||||
@spec process_game_at_url(String.t()) :: none()
|
||||
def process_game_at_url(url) do
|
||||
case HTTPoison.get(url) do
|
||||
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
|
||||
process_game_data(body)
|
||||
|
||||
{:error, %HTTPoison.Error{reason: reason}} ->
|
||||
{:error, reason}
|
||||
|
||||
_ ->
|
||||
{:error, "Could not fetch data from #{url}"}
|
||||
end
|
||||
end
|
||||
|
||||
def process_game_data(data) do
|
||||
decoded_match = Poison.decode!(data, as: %LoLAPI.Model.MatchResponse{})
|
||||
participants = decoded_match.info.participants
|
||||
version = extract_game_version(decoded_match)
|
||||
|
||||
Logger.info("Processing ChampionPlayedMatch for #{decoded_match.metadata.matchId}")
|
||||
|
||||
participants
|
||||
|> Enum.each(fn participant = %LoLAPI.Model.Participant{} ->
|
||||
if participant.teamPosition != "" do
|
||||
attrs = %{
|
||||
champion_id: participant.championId,
|
||||
match_id: decoded_match.metadata.matchId,
|
||||
is_win: participant.win,
|
||||
game_length_seconds: decoded_match.info.gameDuration,
|
||||
queue_id: decoded_match.info.queueId,
|
||||
puuid: participant.puuid,
|
||||
team_position: participant.teamPosition,
|
||||
patch_number: version
|
||||
}
|
||||
|
||||
LolAnalytics.Facts.ChampionPlayedGame.Repo.insert(attrs)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp extract_game_version(game_data) do
|
||||
game_data.info.gameVersion
|
||||
|> String.split(".")
|
||||
|> Enum.take(2)
|
||||
|> Enum.join(".")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,3 @@
|
||||
defmodule LolAnalytics.Facts.FactBehaviour do
|
||||
@callback process_game_at_url(String.t()) :: any()
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
defmodule LolAnalytics.Facts.FactsRunner do
|
||||
alias LolAnalytics.Facts
|
||||
|
||||
def analyze_all_matches do
|
||||
Storage.MatchStorage.S3MatchStorage.stream_files("ranked")
|
||||
|> peach(fn %{key: path} ->
|
||||
get_facts()
|
||||
|> Enum.each(fn fact_runner ->
|
||||
apply(fact_runner, ["http://192.168.1.55:9000/ranked/#{path}"])
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
def analyze_match() do
|
||||
end
|
||||
|
||||
def get_facts() do
|
||||
[
|
||||
# &Facts.ChampionPickedSummonerSpell.FactProcessor.process_game_at_url/1,
|
||||
# &Facts.ChampionPlayedGame.FactProcessor.process_game_at_url/1,
|
||||
&Facts.ChampionPickedItem.FactProcessor.process_game_at_url/1
|
||||
]
|
||||
end
|
||||
|
||||
def peach(enum, fun, concurrency \\ System.schedulers_online(), timeout \\ :infinity) do
|
||||
Task.async_stream(enum, &fun.(&1), max_concurrency: concurrency, timeout: timeout)
|
||||
|> Stream.each(fn {:ok, val} -> val end)
|
||||
|> Enum.to_list()
|
||||
end
|
||||
end
|
||||
@@ -13,7 +13,7 @@ defmodule LolAnalytics.Match.MatchRepo do
|
||||
LoLAnalytics.Repo.one(query)
|
||||
end
|
||||
|
||||
@spec get_match(String.t()) :: %LolAnalytics.Match.MatchSchema{}
|
||||
@spec get_match(String.t()) :: %LolAnalytics.Match.MatchSchema{} | nil
|
||||
def get_match(match_id) do
|
||||
query = from m in MatchSchema, where: m.match_id == ^match_id
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ defmodule LoLAnalytics.MixProject do
|
||||
{:postgrex, ">= 0.0.0"},
|
||||
{:jason, "~> 1.2"},
|
||||
{:lol_api, in_umbrella: true},
|
||||
{:storage, in_umbrella: true},
|
||||
{:httpoison, "~> 2.2"},
|
||||
{:poison, "~> 5.0"}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
defmodule LoLAnalytics.Repo.Migrations.AnalyticsTables do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table("dim_champion") do
|
||||
add :champion_id, :integer, primary_key: true, null: false
|
||||
timestamps()
|
||||
end
|
||||
|
||||
create index("dim_champion", [:champion_id], unique: true)
|
||||
|
||||
create table("dim_item") do
|
||||
add :item_id, :integer, primary_key: true, null: false
|
||||
timestamps()
|
||||
end
|
||||
|
||||
create index("dim_item", [:item_id], unique: true)
|
||||
|
||||
create table("dim_match") do
|
||||
add :match_id, :string, primary_key: true, null: false
|
||||
timestamps()
|
||||
end
|
||||
|
||||
create index("dim_match", [:match_id], unique: true)
|
||||
|
||||
create table("dim_patch") do
|
||||
add :patch_number, :string, primary_key: true, null: false
|
||||
timestamps()
|
||||
end
|
||||
|
||||
create index("dim_patch", [:patch_number], unique: true)
|
||||
|
||||
create table("dim_player") do
|
||||
add :puuid, :string, primary_key: true, null: false
|
||||
timestamps()
|
||||
end
|
||||
|
||||
create index("dim_player", [:puuid], unique: true)
|
||||
|
||||
create table("dim_summoner_spell") do
|
||||
add :spell_id, :integer, primary_key: true, null: false
|
||||
timestamps()
|
||||
end
|
||||
|
||||
create index("dim_summoner_spell", [:spell_id], unique: true)
|
||||
|
||||
create table("fact_champion_played_game") do
|
||||
add :champion_id, references("dim_champion", column: :champion_id, type: :integer)
|
||||
add :match_id, references("dim_match", column: :match_id, type: :string)
|
||||
add :is_win, :boolean
|
||||
add :game_length_seconds, :integer
|
||||
add :queue_id, :integer
|
||||
add :patch_number, references("dim_patch", column: :patch_number, type: :string)
|
||||
add :team_position, :string
|
||||
add :puuid, references("dim_player", column: :puuid, type: :string)
|
||||
timestamps()
|
||||
end
|
||||
|
||||
create index("fact_champion_played_game", [:id, :champion_id, :queue_id])
|
||||
create index("fact_champion_played_game", [:puuid, :match_id], unique: true)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
defmodule LoLAnalytics.Repo.Migrations.ChampionDimName do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table("dim_champion") do
|
||||
add :name, :string
|
||||
add :image, :string
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
defmodule LoLAnalytics.Repo.Migrations.SummonerSpellWinRate do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table("fact_champion_picked_summoner_spell") do
|
||||
add :champion_id, references("dim_champion", column: :champion_id, type: :integer)
|
||||
add :summoner_spell_id, references("dim_summoner_spell", column: :spell_id, type: :integer)
|
||||
add :match_id, references("dim_match", column: :match_id, type: :string)
|
||||
add :is_win, :boolean
|
||||
add :game_length_seconds, :integer
|
||||
add :queue_id, :integer
|
||||
add :team_position, :string
|
||||
add :puuid, references("dim_player", column: :puuid, type: :string)
|
||||
add :patch_number, references("dim_patch", column: :patch_number, type: :string)
|
||||
end
|
||||
|
||||
create index(
|
||||
"fact_champion_picked_summoner_spell",
|
||||
[:puuid, :match_id, :summoner_spell_id],
|
||||
unique: true
|
||||
)
|
||||
|
||||
create index("fact_champion_picked_summoner_spell", [
|
||||
:is_win,
|
||||
:team_position,
|
||||
:queue_id,
|
||||
:summoner_spell_id,
|
||||
:patch_number
|
||||
])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
defmodule LoLAnalytics.Repo.Migrations.SummonerSpellMetadata do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table("dim_summoner_spell") do
|
||||
add :metadata, :map
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
defmodule LoLAnalytics.Repo.Migrations.SummonerSpellMetadataIndex do
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
execute("CREATE INDEX dim_summoner_spell_metadata ON dim_summoner_spell USING GIN(metadata)")
|
||||
end
|
||||
|
||||
def down do
|
||||
execute("DROP INDEX dim_summoner_spell_metadata")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
defmodule LoLAnalytics.Repo.Migrations.ChampionPickedItem do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table("fact_champion_picked_item") do
|
||||
add :champion_id, references("dim_champion", column: :champion_id, type: :integer)
|
||||
add :item_id, references("dim_item", column: :item_id, type: :integer)
|
||||
add :slot_number, :integer
|
||||
add :match_id, references("dim_match", column: :match_id, type: :string)
|
||||
add :is_win, :boolean
|
||||
add :game_length_seconds, :integer
|
||||
add :queue_id, :integer
|
||||
add :patch_number, references("dim_patch", column: :patch_number, type: :string)
|
||||
add :team_position, :string
|
||||
add :puuid, references("dim_player", column: :puuid, type: :string)
|
||||
end
|
||||
|
||||
create index("fact_champion_picked_item", [:champion_id])
|
||||
create index("fact_champion_picked_item", [:puuid, :match_id, :slot_number, :item_id], unique: true)
|
||||
create index("fact_champion_picked_item", [
|
||||
:item_id,
|
||||
:queue_id,
|
||||
:is_win,
|
||||
:patch_number,
|
||||
:team_position
|
||||
])
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -3,3 +3,16 @@
|
||||
@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;
|
||||
border: 1px solid #333;
|
||||
background-color: whitesmoke;
|
||||
border-radius: 12px;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
defmodule LolAnalyticsWeb.ChampionComponents.ChampionAvatar do
|
||||
use Phoenix.Component
|
||||
|
||||
attr :id, :integer, required: true
|
||||
attr :image, :string, required: true
|
||||
attr :name, :string, required: true
|
||||
|
||||
def champion_avatar(assigns) do
|
||||
~H"""
|
||||
<div class="flex flex-col w-40">
|
||||
<img src={@image} alt="champion-icon" />
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
defmodule LolAnalyticsWeb.ChampionComponents.ChampionCard.Props do
|
||||
defstruct [:id, :win_rate, :image, :name, :team_position, :wins, :total_games]
|
||||
end
|
||||
|
||||
defmodule LolAnalyticsWeb.ChampionComponents.ChampionCard do
|
||||
use Phoenix.Component
|
||||
|
||||
alias LolAnalyticsWeb.ChampionComponents.ChampionCard.Props
|
||||
|
||||
attr :props, Props, default: %Props{}
|
||||
|
||||
def champion_card(assigns) do
|
||||
# IO.inspect(assigns)
|
||||
|
||||
~H"""
|
||||
<.link patch={"/champions/#{@props.id}?team-position=#{@props.team_position}"}>
|
||||
<div class="flex flex-col rounded-xl bg-clip-border overflow-hidden bg-gray-200">
|
||||
<div class="flex flex-col flex-col-reverse">
|
||||
<div class="flex w-auto px-4 py-1 opacity-80 gap-2 absolute z-10 align-bottom bg-black">
|
||||
<img src={team_position_image(@props.team_position)} class="w-5 h-5" />
|
||||
<h3 class="text-white"><%= @props.name %></h3>
|
||||
</div>
|
||||
<img src={"https://ddragon.leagueoflegends.com/cdn/14.11.1/img/champion/#{@props.image}"} />
|
||||
</div>
|
||||
<div class="py-1" />
|
||||
<div class="pl-2">
|
||||
<h3>Win rate: <%= @props.win_rate %></h3>
|
||||
<h3>Wins: <%= @props.wins %> / <%= @props.total_games %></h3>
|
||||
</div>
|
||||
<div class="py-1" />
|
||||
</div>
|
||||
</.link>
|
||||
"""
|
||||
end
|
||||
|
||||
defp team_position_image("BOTTOM"), do: "/images/lanes/bot.png"
|
||||
defp team_position_image("MIDDLE"), do: "/images/lanes/mid.png"
|
||||
defp team_position_image("TOP"), do: "/images/lanes/top.png"
|
||||
defp team_position_image("JUNGLE"), do: "/images/lanes/jungle.png"
|
||||
defp team_position_image("UTILITY"), do: "/images/lanes/utility.png"
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
defmodule LolAnalyticsWeb.ChampionComponents.Items do
|
||||
use Phoenix.Component
|
||||
|
||||
def items(assigns) do
|
||||
image = ""
|
||||
|
||||
~H"""
|
||||
<div class="flex flex-wrap flex-wrap gap-4">
|
||||
<%= for item <- assigns.items do %>
|
||||
<div class="has-tooltip">
|
||||
<div clas="flex flex-col gap-1 p-4">
|
||||
<img src={item.image} />
|
||||
<%!-- <p><%= item.name %></p> --%>
|
||||
<p><%= item.win_rate %>%</p>
|
||||
<p><%= item.wins %>/<%= item.total_games %></p>
|
||||
</div>
|
||||
<span class="tooltip -mt-8 py-2 px-4 rounded-xl"><%= item.name %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
defmodule LolAnalyticsWeb.ChampionComponents.SummonerSpells do
|
||||
alias LolAnalyticsWeb.ChampionComponents.SummonerSpells.Props
|
||||
use Phoenix.Component
|
||||
|
||||
def summoner_spells(assigns) do
|
||||
~H"""
|
||||
<div class="flex flex-wrap flex-wrap gap-4">
|
||||
<%= for spell <- assigns.spells.summoner_spells do %>
|
||||
<div class="has-tooltip">
|
||||
<div clas="flex flex-col gap-1">
|
||||
<img src={spell.image} />
|
||||
<p><%= spell.win_rate %>%</p>
|
||||
<p><%= spell.wins %>/<%= spell.total_games %></p>
|
||||
</div>
|
||||
<div class="tooltip -my-8 px-4 py-2 rounded-xl">
|
||||
<p><%= spell.name %></p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
||||
@@ -302,6 +302,9 @@ defmodule LoLAnalyticsWeb.CoreComponents do
|
||||
end
|
||||
|
||||
def input(%{type: "checkbox"} = assigns) do
|
||||
IO.puts(">>>>")
|
||||
IO.inspect(assigns)
|
||||
|
||||
assigns =
|
||||
assign_new(assigns, :checked, fn ->
|
||||
Phoenix.HTML.Form.normalize_value("checkbox", assigns[:value])
|
||||
@@ -315,8 +318,8 @@ defmodule LoLAnalyticsWeb.CoreComponents do
|
||||
type="checkbox"
|
||||
id={@id}
|
||||
name={@name}
|
||||
value="true"
|
||||
checked={@checked}
|
||||
value="false"
|
||||
checked={false}
|
||||
class="rounded border-zinc-300 text-zinc-900 focus:ring-0"
|
||||
{@rest}
|
||||
/>
|
||||
|
||||
@@ -2,25 +2,16 @@
|
||||
<div class="flex items-center justify-between border-b border-zinc-100 py-3 text-sm">
|
||||
<div class="flex items-center gap-4">
|
||||
<a href="/">
|
||||
<img src={~p"/images/logo.svg"} width="36" />
|
||||
LoL Analytics
|
||||
</a>
|
||||
<p class="bg-brand/5 text-brand rounded-full px-2 font-medium leading-6">
|
||||
v<%= Application.spec(:phoenix, :vsn) %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 font-semibold leading-6 text-zinc-900">
|
||||
<a href="https://twitter.com/elixirphoenix" class="hover:text-zinc-700">
|
||||
@elixirphoenix
|
||||
</a>
|
||||
<a href="https://github.com/phoenixframework/phoenix" class="hover:text-zinc-700">
|
||||
GitHub
|
||||
</a>
|
||||
<a
|
||||
href="https://hexdocs.pm/phoenix/overview.html"
|
||||
class="rounded-lg bg-zinc-100 px-2 py-1 hover:bg-zinc-200/80"
|
||||
>
|
||||
Get Started <span aria-hidden="true">→</span>
|
||||
</a>
|
||||
<.link patch="/champions">
|
||||
Champions
|
||||
</.link>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -29,4 +20,4 @@
|
||||
<.flash_group flash={@flash} />
|
||||
<%= @inner_content %>
|
||||
</div>
|
||||
</main>
|
||||
</main>
|
||||
@@ -1,17 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="[scrollbar-gutter:stable]">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="csrf-token" content={get_csrf_token()} />
|
||||
<.live_title suffix=" · Phoenix Framework">
|
||||
<%= assigns[:page_title] || "LoLAnalytics" %>
|
||||
</.live_title>
|
||||
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
||||
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-white antialiased">
|
||||
<%= @inner_content %>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="csrf-token" content={get_csrf_token()} />
|
||||
<.live_title suffix=" · Phoenix Framework">
|
||||
<%= assigns[:page_title] || "LoLAnalytics" %>
|
||||
</.live_title>
|
||||
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
||||
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="bg-white antialiased">
|
||||
<%= @inner_content %>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,28 @@
|
||||
defmodule LolAnalyticsWeb.Loader do
|
||||
use Phoenix.Component
|
||||
|
||||
def loader(assigns) do
|
||||
~H"""
|
||||
<div role="status" class="flex flex-col mx-auto w-max justify-center items-center gap-4">
|
||||
<span class="text-3xl">Loading</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="w-16 h-16 text-gray-300 animate-spin dark:text-gray-300 fill-orange-600"
|
||||
viewBox="0 0 100 101"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
||||
fill="currentFill"
|
||||
/>
|
||||
</svg>
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,3 @@
|
||||
defmodule LolAnalyticsWeb.ChampionLive.ChampionSummary do
|
||||
defstruct [:id, :win_rate, :image, :name, :team_position, :wins, :total_games]
|
||||
end
|
||||
@@ -0,0 +1,57 @@
|
||||
defmodule LolAnalyticsWeb.ChampionLive.Components.ChampionFilters do
|
||||
use LoLAnalyticsWeb, :live_component
|
||||
|
||||
@roles_definition [
|
||||
%{title: "All", value: "all"},
|
||||
%{title: "Top", value: "TOP"},
|
||||
%{title: "Jungle", value: "JUNGLE"},
|
||||
%{title: "Mid", value: "MIDDLE"},
|
||||
%{title: "Bot", value: "BOTTOM"},
|
||||
%{title: "Support", value: "UTILITY"}
|
||||
]
|
||||
|
||||
def on_role_selected(role) do
|
||||
send_update(LolAnalyticsWeb.ChampionLive, role)
|
||||
end
|
||||
|
||||
def mount(socket) do
|
||||
socket =
|
||||
assign(socket, :roles, @roles_definition)
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
attr :selectedrole, :string, required: true
|
||||
attr :roles, :list, default: []
|
||||
|
||||
def render(assigns) do
|
||||
selected_class =
|
||||
"px-8 py-2 flex flex-row gap-2 align-middle rounded-full border-gray-200 border border-orange-600 cursor-pointer"
|
||||
|
||||
~H"""
|
||||
<div>
|
||||
<div class="flex flex-row overflow-x-scroll sm:overflow-x-auto justify-between">
|
||||
<%= for role <- @roles do %>
|
||||
<%= if (assigns.selectedrole == role.value) do %>
|
||||
<div phx-click="filter" phx-value-role={role.title} class={selected_class}>
|
||||
<p><%= role.title %></p>
|
||||
</div>
|
||||
<% else %>
|
||||
<div
|
||||
phx-click="filter"
|
||||
phx-value-role={role.value}
|
||||
class="px-8 py-2 flex flex-row gap-2 align-middle cursor-pointer"
|
||||
>
|
||||
<p><%= role.title %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
defmodule LolAnalyticsWeb.ChampionLive.Components.ChampionFilters.EventHandler do
|
||||
@callback role_selected() :: none()
|
||||
end
|
||||
@@ -0,0 +1,103 @@
|
||||
defmodule LoLAnalyticsWeb.ChampionLive.Index do
|
||||
use LoLAnalyticsWeb, :live_view
|
||||
|
||||
import LolAnalyticsWeb.ChampionComponents.ChampionCard
|
||||
import LolAnalyticsWeb.Loader
|
||||
import Phoenix.VerifiedRoutes
|
||||
|
||||
alias LolAnalyticsWeb.ChampionLive.Mapper
|
||||
alias LolAnalyticsWeb.ChampionLive.Components.ChampionFilters
|
||||
|
||||
@behaviour LolAnalyticsWeb.ChampionFilters.EventHandler
|
||||
|
||||
@impl true
|
||||
def mount(params, _session, socket) do
|
||||
role =
|
||||
case params["role"] do
|
||||
nil -> "all"
|
||||
role -> role
|
||||
end
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:selected_role, role)
|
||||
|> assign(:champions, %{status: :loading})
|
||||
|> load_champs(role)
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
def handle_params(params, _uri, socket) do
|
||||
case params["role"] do
|
||||
role -> {:noreply, assign(socket, selected_role: role)}
|
||||
_ -> {:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("filter", %{"role" => selected_role} = params, socket) do
|
||||
{:reply, %{},
|
||||
socket
|
||||
|> push_navigate(to: ~p"/champions?#{params}")
|
||||
|> assign(:champions, %{status: :loading})
|
||||
|> load_champs(selected_role)
|
||||
|> assign(:selected_role, selected_role)}
|
||||
end
|
||||
|
||||
defp load_champs(socket, selected_role) do
|
||||
socket
|
||||
|> start_async(
|
||||
:get_champs,
|
||||
fn ->
|
||||
LolAnalytics.Facts.ChampionPlayedGame.Repo.get_win_rates()
|
||||
|> filter_champs(selected_role)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
def handle_async(:get_champs, {:ok, champs}, socket) do
|
||||
{:noreply, assign(socket, :champions, %{status: :data, data: champs})}
|
||||
end
|
||||
|
||||
def render_champions(assigns) do
|
||||
case assigns.champions do
|
||||
%{status: :loading} ->
|
||||
~H"""
|
||||
<.loader />
|
||||
"""
|
||||
|
||||
%{status: :data, data: champions} ->
|
||||
~H"""
|
||||
<div id="champions" class="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
||||
<%= for champion <- champions do %>
|
||||
<.champion_card props={champion} />
|
||||
<% end %>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
defp filter_champs(champs, selected_role) do
|
||||
champs =
|
||||
LolAnalytics.Facts.ChampionPlayedGame.Repo.get_win_rates()
|
||||
|> Enum.filter((&filter_role/1).(selected_role))
|
||||
|> Mapper.map_champs()
|
||||
|> Enum.sort(&(&1.win_rate >= &2.win_rate))
|
||||
end
|
||||
|
||||
defp filter_role(role) do
|
||||
fn champ ->
|
||||
champ.team_position == role || role == "all"
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(params, _url, socket) do
|
||||
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
|
||||
end
|
||||
|
||||
defp apply_action(socket, :index, _params) do
|
||||
socket
|
||||
|> assign(:page_title, "Listing Champions")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
<.header>
|
||||
Champions
|
||||
</.header>
|
||||
|
||||
<div class="px-2">
|
||||
<h1 class="text-l font-semibold">Filters</h1>
|
||||
|
||||
<div class="flex flex-col py-2 gap-2">
|
||||
<p>Roles</p>
|
||||
<.live_component module={ChampionFilters} id="role-filters" selectedrole={@selected_role || "all" } />
|
||||
</div>
|
||||
|
||||
<div class="h-4"></div>
|
||||
|
||||
<.render_champions champions={@champions} />
|
||||
</div>
|
||||
@@ -0,0 +1,13 @@
|
||||
defmodule LolAnalyticsWeb.ChampionLive.Mapper do
|
||||
alias LolAnalyticsWeb.ChampionLive.ChampionSummary
|
||||
|
||||
def map_champs(champs) do
|
||||
champs
|
||||
|> Enum.map(fn champ ->
|
||||
%{
|
||||
champ
|
||||
| win_rate: :erlang.float_to_binary(champ.win_rate, decimals: 2)
|
||||
}
|
||||
end)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,55 @@
|
||||
defmodule LoLAnalyticsWeb.ChampionLive.Show do
|
||||
use LoLAnalyticsWeb, :live_view
|
||||
|
||||
import LolAnalyticsWeb.ChampionComponents.SummonerSpells
|
||||
import LolAnalyticsWeb.ChampionComponents.ChampionAvatar
|
||||
import LolAnalyticsWeb.ChampionComponents.Items
|
||||
|
||||
alias LolAnalyticsWeb.ChampionComponents.SummonerSpells.ShowMapper
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
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, team_position) |> ShowMapper.map_spells()
|
||||
})
|
||||
|> load_items(id, team_position)}
|
||||
end
|
||||
|
||||
defp load_summoner_spells(champion_id, team_position) do
|
||||
LolAnalytics.Facts.ChampionPickedSummonerSpell.Repo.get_champion_picked_summoners(
|
||||
champion_id,
|
||||
team_position
|
||||
)
|
||||
end
|
||||
|
||||
defp load_items(socket, champion_id, team_position) do
|
||||
items =
|
||||
LolAnalytics.Facts.ChampionPickedItem.Repo.get_champion_picked_items(
|
||||
champion_id,
|
||||
team_position
|
||||
)
|
||||
|
||||
all_items_mapped = items |> ShowMapper.map_items() |> Enum.take(30)
|
||||
boots = items |> ShowMapper.extract_boots()
|
||||
|
||||
socket
|
||||
|> assign(:items, all_items_mapped)
|
||||
|> assign(:boots, boots)
|
||||
end
|
||||
|
||||
defp load_champion_info(champion_id) do
|
||||
LolAnalytics.Dimensions.Champion.ChampionRepo.get_or_create(champion_id)
|
||||
end
|
||||
|
||||
defp page_title(:show), do: "Show Champion"
|
||||
defp page_title(:edit), do: "Edit Champion"
|
||||
end
|
||||
@@ -0,0 +1,32 @@
|
||||
<.header>
|
||||
<%= @champion.name %>
|
||||
</.header>
|
||||
|
||||
<.champion_avatar id={@champion.id} name={@champion.name}
|
||||
image={"https://ddragon.leagueoflegends.com/cdn/14.11.1/img/champion/#{@champion.image}"} />
|
||||
|
||||
<div class="my-4" />
|
||||
|
||||
<h2 class="text-2xl">Summoner spells</h2>
|
||||
|
||||
<div class="my-2" />
|
||||
|
||||
<.summoner_spells spells={@summoner_spells} />
|
||||
|
||||
<div class="my-4" />
|
||||
|
||||
<h2 class="text-2xl">Items</h2>
|
||||
|
||||
<h2 class="text-xl">Boots</h2>
|
||||
|
||||
<div class="my-2" />
|
||||
|
||||
<.items items={@boots} />
|
||||
|
||||
<div class="my-4" />
|
||||
|
||||
<h2 class="text-xl">Popular items</h2>
|
||||
|
||||
<div class="my-2" />
|
||||
|
||||
<.items items={@items} />
|
||||
@@ -0,0 +1,60 @@
|
||||
defmodule LolAnalyticsWeb.ChampionComponents.SummonerSpells.ShowMapper do
|
||||
alias LolAnalyticsWeb.ChampionComponents.SummonerSpells.SummonerSpell
|
||||
|
||||
@spec map_champion(%LolAnalytics.Dimensions.Champion.ChampionSchema{}) :: map()
|
||||
def map_champion(champion) do
|
||||
%{
|
||||
id: champion.champion_id,
|
||||
name: champion.name,
|
||||
image: champion.image
|
||||
}
|
||||
end
|
||||
|
||||
def map_spells(items) do
|
||||
items
|
||||
|> Enum.map(&map_spell/1)
|
||||
|> Enum.sort(&(&1.total_games > &2.total_games))
|
||||
end
|
||||
|
||||
def map_spell(spell) do
|
||||
image = spell.metadata["image"]["full"]
|
||||
|
||||
%{
|
||||
id: spell["id"],
|
||||
win_rate: :erlang.float_to_binary(spell.win_rate, decimals: 2),
|
||||
total_games: spell.total_games,
|
||||
image: "https://ddragon.leagueoflegends.com/cdn/14.11.1/img/spell/#{image}",
|
||||
name: spell.metadata["name"],
|
||||
wins: spell.wins
|
||||
}
|
||||
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
|
||||
|
||||
def extract_boots(items) do
|
||||
items
|
||||
|> Enum.filter(fn item ->
|
||||
tags = item.metadata["tags"]
|
||||
tags |> Enum.any?(&String.equivalent?(&1, "Boots"))
|
||||
end)
|
||||
|> Enum.map(&map_item/1)
|
||||
|> Enum.sort(&(&1.total_games > &2.total_games))
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,27 @@
|
||||
defmodule LoLAnalyticsWeb.RoleLive.FormComponent do
|
||||
use LoLAnalyticsWeb, :live_component
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div>
|
||||
<.header>
|
||||
<%= @title %>
|
||||
<:subtitle>Use this form to manage role records in your database.</:subtitle>
|
||||
</.header>
|
||||
|
||||
<.simple_form
|
||||
for={@form}
|
||||
id="role-form"
|
||||
phx-target={@myself}
|
||||
phx-change="validate"
|
||||
phx-submit="save"
|
||||
>
|
||||
<:actions>
|
||||
<.button phx-disable-with="Saving...">Save Role</.button>
|
||||
</:actions>
|
||||
</.simple_form>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
defmodule LoLAnalyticsWeb.RoleLive.Index do
|
||||
use LoLAnalyticsWeb, :live_view
|
||||
|
||||
@roles ["ALL", "TOP", "MIDDLE", "JUNGLE", "UTILITY", "BOTTOM"]
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok, stream(socket, :roles, @roles)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(params, _url, socket) do
|
||||
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
|
||||
end
|
||||
|
||||
defp apply_action(socket, :index, _params) do
|
||||
socket
|
||||
|> assign(:page_title, "Listing Roles")
|
||||
|> assign(:role, nil)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({LoLAnalyticsWeb.RoleLive.FormComponent, {:saved, role}}, socket) do
|
||||
{:noreply, stream_insert(socket, :roles, role)}
|
||||
end
|
||||
|
||||
# @impl true
|
||||
# def handle_event("delete", %{"id" => id}, socket) do
|
||||
# role = Accounts.get_role!(id)
|
||||
# {:ok, _} = Accounts.delete_role(role)
|
||||
|
||||
# {:noreply, stream_delete(socket, :roles, role)}
|
||||
# end
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
<.header>
|
||||
Listing Roles
|
||||
<:actions></:actions>
|
||||
</.header>
|
||||
@@ -0,0 +1,18 @@
|
||||
defmodule LoLAnalyticsWeb.RoleLive.Show do
|
||||
use LoLAnalyticsWeb, :live_view
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(%{"id" => _id}, _, socket) do
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:page_title, page_title(socket.assigns.live_action))}
|
||||
end
|
||||
|
||||
defp page_title(:show), do: "Show Role"
|
||||
defp page_title(:edit), do: "Edit Role"
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
<.header>
|
||||
Role <%= @role.id %>
|
||||
<:subtitle>This is a role record from your database.</:subtitle>
|
||||
<:actions></:actions>
|
||||
</.header>
|
||||
@@ -17,7 +17,13 @@ defmodule LoLAnalyticsWeb.Router do
|
||||
scope "/", LoLAnalyticsWeb do
|
||||
pipe_through :browser
|
||||
|
||||
get "/", PageController, :home
|
||||
live "/", ChampionLive.Index, :index
|
||||
live "/champions", ChampionLive.Index, :index
|
||||
live "/champions/new", ChampionLive.Index, :new
|
||||
live "/champions/:id/edit", ChampionLive.Index, :edit
|
||||
|
||||
live "/champions/:id", ChampionLive.Show, :show
|
||||
live "/champions/:id/show/edit", ChampionLive.Show, :edit
|
||||
end
|
||||
|
||||
# Other scopes may use custom stacks.
|
||||
|
||||
|
After Width: | Height: | Size: 152 B |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
@@ -0,0 +1,5 @@
|
||||
# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
|
||||
#
|
||||
# To ban all spiders from the entire site uncomment the next two lines:
|
||||
# User-agent: *
|
||||
# Disallow: /
|
||||
@@ -0,0 +1,108 @@
|
||||
defmodule LoLAnalyticsWeb.ChampionLiveTest do
|
||||
use LoLAnalyticsWeb.ConnCase
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
import LoLAnalytics.AccountsFixtures
|
||||
|
||||
@create_attrs %{}
|
||||
@update_attrs %{}
|
||||
@invalid_attrs %{}
|
||||
|
||||
defp create_champion(_) do
|
||||
champion = champion_fixture()
|
||||
%{champion: champion}
|
||||
end
|
||||
|
||||
describe "Index" do
|
||||
setup [:create_champion]
|
||||
|
||||
test "lists all champions", %{conn: conn} do
|
||||
{:ok, _index_live, html} = live(conn, ~p"/champions")
|
||||
|
||||
assert html =~ "Listing Champions"
|
||||
end
|
||||
|
||||
test "saves new champion", %{conn: conn} do
|
||||
{:ok, index_live, _html} = live(conn, ~p"/champions")
|
||||
|
||||
assert index_live |> element("a", "New Champion") |> render_click() =~
|
||||
"New Champion"
|
||||
|
||||
assert_patch(index_live, ~p"/champions/new")
|
||||
|
||||
assert index_live
|
||||
|> form("#champion-form", champion: @invalid_attrs)
|
||||
|> render_change() =~ "can't be blank"
|
||||
|
||||
assert index_live
|
||||
|> form("#champion-form", champion: @create_attrs)
|
||||
|> render_submit()
|
||||
|
||||
assert_patch(index_live, ~p"/champions")
|
||||
|
||||
html = render(index_live)
|
||||
assert html =~ "Champion created successfully"
|
||||
end
|
||||
|
||||
test "updates champion in listing", %{conn: conn, champion: champion} do
|
||||
{:ok, index_live, _html} = live(conn, ~p"/champions")
|
||||
|
||||
assert index_live |> element("#champions-#{champion.id} a", "Edit") |> render_click() =~
|
||||
"Edit Champion"
|
||||
|
||||
assert_patch(index_live, ~p"/champions/#{champion}/edit")
|
||||
|
||||
assert index_live
|
||||
|> form("#champion-form", champion: @invalid_attrs)
|
||||
|> render_change() =~ "can't be blank"
|
||||
|
||||
assert index_live
|
||||
|> form("#champion-form", champion: @update_attrs)
|
||||
|> render_submit()
|
||||
|
||||
assert_patch(index_live, ~p"/champions")
|
||||
|
||||
html = render(index_live)
|
||||
assert html =~ "Champion updated successfully"
|
||||
end
|
||||
|
||||
test "deletes champion in listing", %{conn: conn, champion: champion} do
|
||||
{:ok, index_live, _html} = live(conn, ~p"/champions")
|
||||
|
||||
assert index_live |> element("#champions-#{champion.id} a", "Delete") |> render_click()
|
||||
refute has_element?(index_live, "#champions-#{champion.id}")
|
||||
end
|
||||
end
|
||||
|
||||
describe "Show" do
|
||||
setup [:create_champion]
|
||||
|
||||
test "displays champion", %{conn: conn, champion: champion} do
|
||||
{:ok, _show_live, html} = live(conn, ~p"/champions/#{champion}")
|
||||
|
||||
assert html =~ "Show Champion"
|
||||
end
|
||||
|
||||
test "updates champion within modal", %{conn: conn, champion: champion} do
|
||||
{:ok, show_live, _html} = live(conn, ~p"/champions/#{champion}")
|
||||
|
||||
assert show_live |> element("a", "Edit") |> render_click() =~
|
||||
"Edit Champion"
|
||||
|
||||
assert_patch(show_live, ~p"/champions/#{champion}/show/edit")
|
||||
|
||||
assert show_live
|
||||
|> form("#champion-form", champion: @invalid_attrs)
|
||||
|> render_change() =~ "can't be blank"
|
||||
|
||||
assert show_live
|
||||
|> form("#champion-form", champion: @update_attrs)
|
||||
|> render_submit()
|
||||
|
||||
assert_patch(show_live, ~p"/champions/#{champion}")
|
||||
|
||||
html = render(show_live)
|
||||
assert html =~ "Champion updated successfully"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,108 @@
|
||||
defmodule LoLAnalyticsWeb.RoleLiveTest do
|
||||
use LoLAnalyticsWeb.ConnCase
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
import LoLAnalytics.AccountsFixtures
|
||||
|
||||
@create_attrs %{}
|
||||
@update_attrs %{}
|
||||
@invalid_attrs %{}
|
||||
|
||||
defp create_role(_) do
|
||||
role = role_fixture()
|
||||
%{role: role}
|
||||
end
|
||||
|
||||
describe "Index" do
|
||||
setup [:create_role]
|
||||
|
||||
test "lists all roles", %{conn: conn} do
|
||||
{:ok, _index_live, html} = live(conn, ~p"/roles")
|
||||
|
||||
assert html =~ "Listing Roles"
|
||||
end
|
||||
|
||||
test "saves new role", %{conn: conn} do
|
||||
{:ok, index_live, _html} = live(conn, ~p"/roles")
|
||||
|
||||
assert index_live |> element("a", "New Role") |> render_click() =~
|
||||
"New Role"
|
||||
|
||||
assert_patch(index_live, ~p"/roles/new")
|
||||
|
||||
assert index_live
|
||||
|> form("#role-form", role: @invalid_attrs)
|
||||
|> render_change() =~ "can't be blank"
|
||||
|
||||
assert index_live
|
||||
|> form("#role-form", role: @create_attrs)
|
||||
|> render_submit()
|
||||
|
||||
assert_patch(index_live, ~p"/roles")
|
||||
|
||||
html = render(index_live)
|
||||
assert html =~ "Role created successfully"
|
||||
end
|
||||
|
||||
test "updates role in listing", %{conn: conn, role: role} do
|
||||
{:ok, index_live, _html} = live(conn, ~p"/roles")
|
||||
|
||||
assert index_live |> element("#roles-#{role.id} a", "Edit") |> render_click() =~
|
||||
"Edit Role"
|
||||
|
||||
assert_patch(index_live, ~p"/roles/#{role}/edit")
|
||||
|
||||
assert index_live
|
||||
|> form("#role-form", role: @invalid_attrs)
|
||||
|> render_change() =~ "can't be blank"
|
||||
|
||||
assert index_live
|
||||
|> form("#role-form", role: @update_attrs)
|
||||
|> render_submit()
|
||||
|
||||
assert_patch(index_live, ~p"/roles")
|
||||
|
||||
html = render(index_live)
|
||||
assert html =~ "Role updated successfully"
|
||||
end
|
||||
|
||||
test "deletes role in listing", %{conn: conn, role: role} do
|
||||
{:ok, index_live, _html} = live(conn, ~p"/roles")
|
||||
|
||||
assert index_live |> element("#roles-#{role.id} a", "Delete") |> render_click()
|
||||
refute has_element?(index_live, "#roles-#{role.id}")
|
||||
end
|
||||
end
|
||||
|
||||
describe "Show" do
|
||||
setup [:create_role]
|
||||
|
||||
test "displays role", %{conn: conn, role: role} do
|
||||
{:ok, _show_live, html} = live(conn, ~p"/roles/#{role}")
|
||||
|
||||
assert html =~ "Show Role"
|
||||
end
|
||||
|
||||
test "updates role within modal", %{conn: conn, role: role} do
|
||||
{:ok, show_live, _html} = live(conn, ~p"/roles/#{role}")
|
||||
|
||||
assert show_live |> element("a", "Edit") |> render_click() =~
|
||||
"Edit Role"
|
||||
|
||||
assert_patch(show_live, ~p"/roles/#{role}/show/edit")
|
||||
|
||||
assert show_live
|
||||
|> form("#role-form", role: @invalid_attrs)
|
||||
|> render_change() =~ "can't be blank"
|
||||
|
||||
assert show_live
|
||||
|> form("#role-form", role: @update_attrs)
|
||||
|> render_submit()
|
||||
|
||||
assert_patch(show_live, ~p"/roles/#{role}")
|
||||
|
||||
html = render(show_live)
|
||||
assert html =~ "Role updated successfully"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -18,7 +18,7 @@ defmodule LoLAPI.AccountApi do
|
||||
200 ->
|
||||
{:ok, Poison.decode(response.body)}
|
||||
|
||||
code ->
|
||||
_code ->
|
||||
Logger.error("Error getting puuid from player #{name} \##{tag}")
|
||||
{:err, response.status_code}
|
||||
end
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
defmodule Scrapper.MatchClassifier do
|
||||
require Logger
|
||||
|
||||
@spec classify_match(%LoLAPI.Model.MatchResponse{}) :: nil
|
||||
def classify_match(match = %LoLAPI.Model.MatchResponse{}) do
|
||||
classify_match_by_queue(match.info.queueId)
|
||||
end
|
||||
|
||||
@spec classify_match_by_queue(String.t()) :: nil
|
||||
def classify_match_by_queue("420") do
|
||||
matches = Storage.MatchStorage.S3MatchStorage.list_files("matches")
|
||||
total_matches = Enum.count(matches)
|
||||
|
||||
matches
|
||||
|> Enum.with_index(fn match, index -> {match, index} end)
|
||||
|> Scrapper.Parallel.peach(fn {match, index} ->
|
||||
def stream_classify_matches_by_queue(queue \\ 420, bucket \\ "ranked") do
|
||||
Storage.MatchStorage.S3MatchStorage.stream_files("matches")
|
||||
|> Stream.each(fn match ->
|
||||
%{key: json_file} = match
|
||||
[key | _] = String.split(json_file, ".")
|
||||
|
||||
@@ -25,15 +15,12 @@ defmodule Scrapper.MatchClassifier do
|
||||
%{"info" => %{"gameVersion" => gameVersion, "queueId" => queueId}} =
|
||||
Poison.decode!(response.body)
|
||||
|
||||
if queueId == 420 do
|
||||
Storage.MatchStorage.S3MatchStorage.store_match(key, response.body, "ranked", gameVersion)
|
||||
Logger.info("Match at #{index} of #{total_matches} is classified")
|
||||
if queueId == queue do
|
||||
Storage.MatchStorage.S3MatchStorage.store_match(key, response.body, bucket, gameVersion)
|
||||
Logger.info("Match #{key} processed")
|
||||
end
|
||||
|
||||
match
|
||||
end)
|
||||
end
|
||||
|
||||
def classify_match_by_queue(_) do
|
||||
end
|
||||
end
|
||||
|
||||
@@ -44,7 +44,16 @@ defmodule Scrapper.Processor.MatchProcessor do
|
||||
|
||||
def process_resp({:ok, raw_match}, match_id) do
|
||||
decoded_match = Poison.decode!(raw_match, as: %LoLAPI.Model.MatchResponse{})
|
||||
match_url = Storage.MatchStorage.S3MatchStorage.store_match(match_id, raw_match, "matches")
|
||||
|
||||
match_url =
|
||||
case decoded_match.info.queueId do
|
||||
420 ->
|
||||
Storage.MatchStorage.S3MatchStorage.store_match(match_id, raw_match, "ranked", decoded_match.info.gameVersion)
|
||||
|
||||
queue_id ->
|
||||
Storage.MatchStorage.S3MatchStorage.store_match(match_id, raw_match, queue_id)
|
||||
end
|
||||
|
||||
match = LolAnalytics.Match.MatchRepo.get_match(match_id)
|
||||
|
||||
case match do
|
||||
@@ -66,6 +75,6 @@ defmodule Scrapper.Processor.MatchProcessor do
|
||||
end)
|
||||
end
|
||||
|
||||
def process_resp({:err, code}, match_id) do
|
||||
def process_resp({:err, _code}, _match_id) do
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
defmodule Scrapper.Processor.PlayerProcessor do
|
||||
alias Calendar.ISO
|
||||
use Broadway
|
||||
|
||||
def start_link(_opts) do
|
||||
|
||||
@@ -19,7 +19,7 @@ defmodule Scrapper.Queue.MatchQueue do
|
||||
LolAnalytics.Match.MatchRepo.get_match(match_id)
|
||||
|> case do
|
||||
nil -> GenServer.call(__MODULE__, {:queue_match, match_id})
|
||||
match -> :already_processed
|
||||
_match -> :already_processed
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
defmodule Storage.MatchStorage do
|
||||
@callback stream_files(String.t()) :: Enumerable.t()
|
||||
@callback get_match(String.t()) :: {:ok, Scrapper.Data.Match.t()} | {:error, :not_found}
|
||||
@callback save_match(String.t(), Scrapper.Data.Match.t()) :: :ok
|
||||
@callback list_matches() :: [map()]
|
||||
@callback store_match(match_id :: String.t(), match :: map(), path :: String.t()) :: String.t()
|
||||
@callback store_match(match_id :: String.t(), match_data :: String.t(), path :: String.t()) ::
|
||||
String.t()
|
||||
@callback store_match(
|
||||
match_id :: String.t(),
|
||||
match_data :: String.t(),
|
||||
bucket :: String.t(),
|
||||
path :: String.t()
|
||||
) ::
|
||||
String.t()
|
||||
end
|
||||
|
||||
@@ -2,43 +2,14 @@ defmodule Storage.MatchStorage.S3MatchStorage do
|
||||
require Logger
|
||||
@behaviour Storage.MatchStorage
|
||||
|
||||
def get_match(match_id) do
|
||||
""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Lists all files at the given path.
|
||||
|
||||
iex > Storage.MatchStorage.S3MatchStorage.list_files("matches")
|
||||
"""
|
||||
@impl true
|
||||
def list_files(path) do
|
||||
{:ok, %{:body => %{:contents => contents, next_continuation_token: next_continuation_token}}} =
|
||||
ExAws.S3.list_objects_v2(path)
|
||||
|> ExAws.request()
|
||||
|
||||
if next_continuation_token do
|
||||
list_files(path, contents, next_continuation_token)
|
||||
# |> Enum.map(fn %{key: key} -> key end)
|
||||
else
|
||||
contents
|
||||
# |> Enum.map(fn %{key: key} -> key end)
|
||||
end
|
||||
def get_match(_match_id) do
|
||||
end
|
||||
|
||||
@spec list_files(String.t(), list(String.t()), String.t()) :: list(String.t())
|
||||
defp list_files(path, acc, continuation_token) do
|
||||
resp =
|
||||
{:ok,
|
||||
%{:body => %{:contents => contents, next_continuation_token: next_continuation_token}}} =
|
||||
ExAws.S3.list_objects_v2(path, continuation_token: continuation_token)
|
||||
|> ExAws.request()
|
||||
|
||||
if next_continuation_token == "" do
|
||||
acc ++ contents
|
||||
else
|
||||
list_files(path, acc ++ contents, next_continuation_token)
|
||||
end
|
||||
@impl true
|
||||
def stream_files(path) do
|
||||
ExAws.S3.list_objects_v2(path)
|
||||
|> ExAws.stream!()
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
# Name of your application. Used to uniquely configure containers.
|
||||
service: my-app
|
||||
|
||||
# Name of the container image.
|
||||
image: user/my-app
|
||||
|
||||
# Deploy to these servers.
|
||||
servers:
|
||||
- 192.168.0.1
|
||||
|
||||
# Credentials for your image host.
|
||||
registry:
|
||||
# Specify the registry server, if you're not using Docker Hub
|
||||
# server: registry.digitalocean.com / ghcr.io / ...
|
||||
username: my-user
|
||||
|
||||
# Always use an access token rather than real password when possible.
|
||||
password:
|
||||
- KAMAL_REGISTRY_PASSWORD
|
||||
|
||||
# Inject ENV variables into containers (secrets come from .env).
|
||||
# Remember to run `kamal env push` after making changes!
|
||||
# env:
|
||||
# clear:
|
||||
# DB_HOST: 192.168.0.2
|
||||
# secret:
|
||||
# - RAILS_MASTER_KEY
|
||||
|
||||
# Use a different ssh user than root
|
||||
# ssh:
|
||||
# user: app
|
||||
|
||||
# Configure builder setup.
|
||||
# builder:
|
||||
# args:
|
||||
# RUBY_VERSION: 3.2.0
|
||||
# secrets:
|
||||
# - GITHUB_TOKEN
|
||||
# remote:
|
||||
# arch: amd64
|
||||
# host: ssh://app@192.168.0.1
|
||||
|
||||
# Use accessory services (secrets come from .env).
|
||||
# accessories:
|
||||
# db:
|
||||
# image: mysql:8.0
|
||||
# host: 192.168.0.2
|
||||
# port: 3306
|
||||
# env:
|
||||
# clear:
|
||||
# MYSQL_ROOT_HOST: '%'
|
||||
# secret:
|
||||
# - MYSQL_ROOT_PASSWORD
|
||||
# files:
|
||||
# - config/mysql/production.cnf:/etc/mysql/my.cnf
|
||||
# - db/production.sql:/docker-entrypoint-initdb.d/setup.sql
|
||||
# directories:
|
||||
# - data:/var/lib/mysql
|
||||
# redis:
|
||||
# image: redis:7.0
|
||||
# host: 192.168.0.2
|
||||
# port: 6379
|
||||
# directories:
|
||||
# - data:/data
|
||||
|
||||
# Configure custom arguments for Traefik. Be sure to reboot traefik when you modify it.
|
||||
# traefik:
|
||||
# args:
|
||||
# accesslog: true
|
||||
# accesslog.format: json
|
||||
|
||||
# Configure a custom healthcheck (default is /up on port 3000)
|
||||
# healthcheck:
|
||||
# path: /healthz
|
||||
# port: 4000
|
||||
|
||||
# Bridge fingerprinted assets, like JS and CSS, between versions to avoid
|
||||
# hitting 404 on in-flight requests. Combines all files from new and old
|
||||
# version inside the asset_path.
|
||||
#
|
||||
# If your app is using the Sprockets gem, ensure it sets `config.assets.manifest`.
|
||||
# See https://github.com/basecamp/kamal/issues/626 for details
|
||||
#
|
||||
# asset_path: /rails/public/assets
|
||||
|
||||
# Configure rolling deploys by setting a wait time between batches of restarts.
|
||||
# boot:
|
||||
# limit: 10 # Can also specify as a percentage of total hosts, such as "25%"
|
||||
# wait: 2
|
||||
|
||||
# Configure the role used to determine the primary_host. This host takes
|
||||
# deploy locks, runs health checks during the deploy, and follow logs, etc.
|
||||
#
|
||||
# Caution: there's no support for role renaming yet, so be careful to cleanup
|
||||
# the previous role on the deployed hosts.
|
||||
# primary_role: web
|
||||
|
||||
# Controls if we abort when see a role with no hosts. Disabling this may be
|
||||
# useful for more complex deploy configurations.
|
||||
#
|
||||
# allow_empty_roles: false
|
||||
@@ -8,7 +8,7 @@ import Config
|
||||
config :lol_analytics_web, LoLAnalyticsWeb.Endpoint,
|
||||
server: true,
|
||||
http: [ip: {0, 0, 0, 0}, port: 4000],
|
||||
url: [host: "lol-analytics.kaizer.cloud", port: 80],
|
||||
url: [host: "localhost", port: 80],
|
||||
cache_static_manifest: "priv/static/cache_manifest.json"
|
||||
|
||||
# Do not print debug messages in production
|
||||
|
||||
@@ -6,7 +6,7 @@ import Config
|
||||
# and secrets from environment variables or elsewhere. Do not define
|
||||
# any compile-time configuration in here, as it won't be applied.
|
||||
# The block below contains prod specific runtime configuration.
|
||||
if config_env() == :prod do
|
||||
if config_env() == :prod || true do
|
||||
database_url =
|
||||
System.get_env("DATABASE_URL") ||
|
||||
raise """
|
||||
|
||||
@@ -11,7 +11,7 @@ config :lol_analytics, LoLAnalytics.Repo,
|
||||
hostname: "localhost",
|
||||
database: "lol_analytics_test#{System.get_env("MIX_TEST_PARTITION")}",
|
||||
pool: Ecto.Adapters.SQL.Sandbox,
|
||||
pool_size: System.schedulers_online() * 2
|
||||
pool_size: System.schedulers_online() * 4
|
||||
|
||||
# We don't run a server during test. If one is required,
|
||||
# you can enable the server option below.
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
db:
|
||||
image: postgres:latest
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_DB: postgres
|
||||
ports:
|
||||
- "5432:5432"
|
||||
rabbitmq:
|
||||
image: rabbitmq:3-management-alpine
|
||||
container_name: 'rabbitmq'
|
||||
ports:
|
||||
- "15671:15671"
|
||||
- "15672:15672"
|
||||
- "15691:15691"
|
||||
- "15692:15692"
|
||||
- "25672:25672"
|
||||
- "4639:4639"
|
||||
- "5671:5671"
|
||||
- "5672:5672"
|
||||
volumes:
|
||||
- ~/.docker-conf/rabbitmq/data/:/var/lib/rabbitmq/
|
||||
- ~/.docker-conf/rabbitmq/log/:/var/log/rabbitmq
|
||||
networks:
|
||||
- rabbitmq_go_net
|
||||
volumes:
|
||||
postgres-data:
|
||||
|
||||
|
||||
networks:
|
||||
rabbitmq_go_net:
|
||||
driver: bridge
|
||||
@@ -0,0 +1,22 @@
|
||||
```
|
||||
SELECT
|
||||
(cast(count(CASE WHEN is_win THEN 1 END) as float) / cast(count(*) as float)) * 100.0 as win_rate,
|
||||
count(CASE WHEN is_win THEN 1 END) as games_won,
|
||||
count(*) as total_games,
|
||||
champion_id
|
||||
FROM fact_champion_played_game
|
||||
GROUP BY champion_id
|
||||
ORDER BY win_rate desc;
|
||||
```
|
||||
|
||||
```
|
||||
SELECT
|
||||
(cast(count(CASE WHEN is_win THEN 1 END) as float) / cast(count(*) as float)) * 100.0 as win_rate,
|
||||
count(CASE WHEN is_win THEN 1 END) as games_won,
|
||||
count(*) as total_games,
|
||||
champion_id,
|
||||
team_position
|
||||
FROM fact_champion_played_game
|
||||
GROUP BY champion_id, team_position
|
||||
ORDER BY win_rate desc;
|
||||
```
|
||||