Skip to content

Commit

Permalink
Feature/Add ranking information to profile (#195)
Browse files Browse the repository at this point in the history
* Create user_ranking function that returns user scores per each category.

* Create user_ranking action that responds with user scores for each category.

* Create User Scores panel in Profile section.

* Elm formatter
  • Loading branch information
jtkpiotr authored Dec 2, 2017
1 parent 09d8b86 commit 60575ed
Show file tree
Hide file tree
Showing 14 changed files with 224 additions and 32 deletions.
32 changes: 23 additions & 9 deletions aion/lib/aion/ranking.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,38 @@ defmodule Aion.Ranking do

@type category_score_t :: %{category_id: integer, category_name: String.t, scores: (list user_score_t)}
@type user_score_t :: %{user_name: String.t, score: integer}
@type user_category_score_t :: %{category_id: integer, category_name: String.t, score: integer}
@type raw_data_t :: %{category_id: integer, category_name: String.t, user_name: String.t, score: integer}

@spec data :: list category_score_t
def data do
query_data()
@spec general_ranking :: list category_score_t
def general_ranking do
general_ranking_query()
|> Enum.group_by(&%{category_id: &1.category_id, category_name: &1.category_name},
&%{user_name: &1.user_name, score: &1.score})
|> Map.to_list()
|> Enum.map(&Map.put(elem(&1, 0), :scores, elem(&1, 1)))
end

@spec query_data :: list raw_data_t
def query_data do
@spec user_ranking(integer) :: list user_category_score_t
def user_ranking(user_id) do
Repo.all(
from q in User,
join: ucs in UserCategoryScore, on: q.id == ucs.user_id,
join: c in Category, on: c.id == ucs.category_id,
select: %{category_id: c.id, category_name: c.name, user_name: q.name, score: ucs.score}
from [u, ucs, c] in base_query,
where: u.id == ^user_id,
select: %{category_id: c.id, category_name: c.name, score: ucs.score}
)
end

@spec general_ranking_query :: list raw_data_t
def general_ranking_query do
Repo.all(
from [u, ucs, c] in base_query,
select: %{category_id: c.id, category_name: c.name, user_name: u.name, score: ucs.score}
)
end

defp base_query do
from q in User,
join: ucs in UserCategoryScore, on: q.id == ucs.user_id,
join: c in Category, on: c.id == ucs.category_id
end
end
19 changes: 17 additions & 2 deletions aion/test/lib/ranking_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule Aion.RankingTest do
@user1_attrs %{name: "John", email: "[email protected]", password: "test123"}
@user2_attrs %{name: "Michael", email: "[email protected]", password: "test123"}

describe "Ranking.data" do
describe "Ranking.general_ranking/0" do
test "returns player scores for each category" do
user1 = Repo.insert! User.registration_changeset(%User{}, @user1_attrs)
user2 = Repo.insert! User.registration_changeset(%User{}, @user2_attrs)
Expand All @@ -15,10 +15,25 @@ defmodule Aion.RankingTest do
Repo.insert! %UserCategoryScore{user: user1, category: category2, score: 4}
Repo.insert! %UserCategoryScore{user: user2, category: category2, score: 5}

result = Ranking.data()
result = Ranking.general_ranking()

assert result == [%{category_id: category1.id, category_name: "category1", scores: [%{user_name: "John", score: 2}]},
%{category_id: category2.id, category_name: "category2", scores: [%{user_name: "John", score: 4}, %{user_name: "Michael", score: 5}]}]
end
end

describe "Ranking.user_ranking/1" do
test "returns user scores for each category" do
user1 = Repo.insert! User.registration_changeset(%User{}, @user1_attrs)
category1 = Repo.insert! %Category{name: "category1"}
category2 = Repo.insert! %Category{name: "category2"}
Repo.insert! %UserCategoryScore{user: user1, category: category1, score: 2}
Repo.insert! %UserCategoryScore{user: user1, category: category2, score: 4}

result = Ranking.user_ranking(user1.id)

assert result == [%{category_id: category1.id, category_name: "category1", score: 2},
%{category_id: category2.id, category_name: "category2", score: 4}]
end
end
end
9 changes: 7 additions & 2 deletions aion/web/controllers/ranking_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ defmodule Aion.RankingController do

plug(Plug.EnsureAuthenticated, handler: __MODULE__)

def ranking(conn, _params) do
result = Ranking.data()
def general_ranking(conn, _params) do
result = Ranking.general_ranking()
render(conn, "ranking.json", category_scores: result)
end

def user_ranking(conn, _params) do
result = Ranking.user_ranking(Plug.current_resource(conn).id)
render(conn, "user_ranking.json", user_scores: result)
end

def unauthenticated(conn, _params) do
Errors.unauthenticated(conn)
end
Expand Down
9 changes: 6 additions & 3 deletions aion/web/elm/src/General/Models.elm
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ import Ranking.Models exposing (RankingData)
import Toasty
import Toasty.Defaults
import Urls exposing (hostname, websocketUrl)
import User.Models exposing (CurrentUser)
import User.Models exposing (UserData)


type alias Model =
{ user : WebData CurrentUser
{ user : UserData
, authData : AuthData
, rooms : WebData RoomsData
, categories : WebData CategoriesData
Expand Down Expand Up @@ -69,7 +69,10 @@ initialModel flags route location =
False ->
Just flags.token
in
{ user = RemoteData.Loading
{ user =
{ details = RemoteData.Loading
, scores = RemoteData.Loading
}
, authData =
{ loginForm = Forms.initForm loginForm
, registrationForm = Forms.initForm registrationForm
Expand Down
3 changes: 2 additions & 1 deletion aion/web/elm/src/Msgs.elm
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Room.Models exposing (RoomId, RoomsData)
import Time exposing (Time)
import Toasty
import Toasty.Defaults
import User.Models exposing (CurrentUser)
import User.Models exposing (CurrentUser, UserScores)


type Msg
Expand All @@ -24,6 +24,7 @@ type Msg
| OnFetchRanking (WebData Ranking)
| OnFetchCategories (WebData CategoriesData)
| OnFetchCurrentUser (WebData CurrentUser)
| OnFetchUserScores (WebData UserScores)
| OnQuestionCreated (WebData QuestionCreatedData)
| OnCategoryCreated (WebData CategoryCreatedData)
| OnRoomCreated (WebData RoomCreatedData)
Expand Down
27 changes: 23 additions & 4 deletions aion/web/elm/src/Update.elm
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Delay
import Dom exposing (focus)
import Forms
import General.Constants exposing (loginFormMsg, registerFormMsg)
import General.Models exposing (Model, Route(RankingRoute, RoomListRoute, RoomRoute), asEventLogIn, asProgressBarIn)
import General.Models exposing (Model, Route(RankingRoute, UserRoute, RoomListRoute, RoomRoute), asEventLogIn, asProgressBarIn)
import General.Notifications exposing (toastsConfig)
import Json.Decode as Decode
import Json.Encode as Encode
Expand All @@ -33,7 +33,7 @@ import Toasty
import Multiselect
import Socket exposing (initSocket, initializeRoom, leaveRoom, sendAnswer)
import Urls exposing (host, websocketUrl)
import User.Api exposing (fetchCurrentUser)
import User.Api exposing (fetchCurrentUser, fetchUserScores)


updateForm : String -> String -> Forms.Form -> Forms.Form
Expand Down Expand Up @@ -263,7 +263,18 @@ update msg model =
{ newModel | panelData = { oldPanelData | categoryMultiSelect = updatedCategoryMultiselect } } ! []

OnFetchCurrentUser response ->
{ model | user = response } ! []
let
oldUserData =
model.user
in
{ model | user = { oldUserData | details = response } } ! []

OnFetchUserScores response ->
let
oldUserData =
model.user
in
{ model | user = { oldUserData | scores = response } } ! []

OnQuestionCreated response ->
case response of
Expand Down Expand Up @@ -374,6 +385,14 @@ update msg model =
|> withToken model
]

UserRoute ->
{ model | route = newRoute }
! [ afterLeaveCmd
, fetchUserScores
|> withLocation model
|> withToken model
]

_ ->
{ newModel | route = newRoute } ! [ afterLeaveCmd ]

Expand Down Expand Up @@ -439,7 +458,7 @@ update msg model =
model.eventLog

log =
case model.user of
case model.user.details of
RemoteData.Success currentUser ->
{ currentPlayer = currentUser.name
, newPlayer = userJoinedInfo.user
Expand Down
5 changes: 5 additions & 0 deletions aion/web/elm/src/Urls.elm
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ rankingUrl location =
(host location) ++ "api/ranking"


userScoresUrl : Location -> String
userScoresUrl location =
(host location) ++ "api/user_ranking"


loginUrl : Location -> String
loginUrl location =
(host location) ++ "sessions"
Expand Down
31 changes: 28 additions & 3 deletions aion/web/elm/src/User/Api.elm
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import Json.Decode as Decode
import Msgs exposing (Msg)
import Navigation exposing (Location)
import RemoteData
import Urls exposing (host)
import User.Decoders exposing (userDecoder)
import User.Models exposing (CurrentUser)
import Urls exposing (host, userScoresUrl)
import User.Decoders exposing (userDecoder, userScoresDecoder)
import User.Models exposing (CurrentUser, UserScores)
import Auth.Models exposing (Token)


fetchCurrentUserUrl : Location -> String
Expand Down Expand Up @@ -38,3 +39,27 @@ fetchCurrentUser location token =
fetchCurrentUserRequest url token userDecoder
|> RemoteData.sendRequest
|> Cmd.map Msgs.OnFetchCurrentUser


fetchUserScoresRequest : String -> String -> Decode.Decoder UserScores -> Request UserScores
fetchUserScoresRequest url token decoder =
Http.request
{ method = "GET"
, headers = [ Http.header "Authorization" ("Bearer " ++ token) ]
, url = url
, body = Http.emptyBody
, expect = Http.expectJson decoder
, timeout = Nothing
, withCredentials = True
}


fetchUserScores : Location -> Token -> Cmd Msg
fetchUserScores location token =
let
url =
userScoresUrl location
in
fetchUserScoresRequest url token userScoresDecoder
|> RemoteData.sendRequest
|> Cmd.map Msgs.OnFetchUserScores
15 changes: 14 additions & 1 deletion aion/web/elm/src/User/Decoders.elm
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module User.Decoders exposing (..)

import User.Models exposing (CurrentUser)
import User.Models exposing (CurrentUser, UserScores, UserCategoryScore)
import Json.Decode as Decode
import Json.Decode.Pipeline exposing (decode, required)

Expand All @@ -11,3 +11,16 @@ userDecoder =
|> required "name" Decode.string
|> required "id" Decode.int
|> required "email" Decode.string


userScoresDecoder : Decode.Decoder UserScores
userScoresDecoder =
decode UserScores
|> required "categoryScores" (Decode.list (userCategoryScoreDecoder))


userCategoryScoreDecoder : Decode.Decoder UserCategoryScore
userCategoryScoreDecoder =
decode UserCategoryScore
|> required "categoryName" Decode.string
|> required "score" Decode.int
18 changes: 18 additions & 0 deletions aion/web/elm/src/User/Models.elm
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
module User.Models exposing (..)

import RemoteData exposing (WebData)


type alias UserData =
{ details : WebData CurrentUser
, scores : WebData UserScores
}


type alias CurrentUser =
{ name : String
, id : Int
, email : String
}


type alias UserScores =
{ categoryScores : List UserCategoryScore }


type alias UserCategoryScore =
{ categoryName : String
, score : Int
}
Loading

0 comments on commit 60575ed

Please sign in to comment.