Skip to content

Commit c02c9f2

Browse files
ShenyiCuiriccqiangelsl
authored
Add Total XP Calculation (#859)
* Add TestApi * Update totalXp GetRequest * Add total xp logic * move totalxp logic into achievements.ex * Update achievements.ex * Change Function Name * Add assessment xp calculation * Remove redundant code * Update incentives view * Fix Bugs - xp calc based on user_id - xp calc based on goal count * Refactor Code - run mix format * Code cleanup - Inlined small functions - Changed function naming related to calculating total xp - Moved total_xp endpoint to user route * Update mix.lock * Fix total_xp query - Calculate total xp through a single query. - Fix variable issues in user_controller * Fix credo formatting errors * Add swagger path for combined_total_xp Fixes some other user route issues * Add normal test for total_xp route Add test * Update user_controller_test.exs * Fix imports * Add more tests for total_xp route * Update test names * Add is_variable_xp: true tests * Remove added @SPEC lines * Tweak documentation * Call swagger_path combined_total_xp in tests Co-authored-by: Richard Qi <55354921+riccqi@users.noreply.github.com> Co-authored-by: angelsl <angelsl@in04.sg>
1 parent d4f733f commit c02c9f2

File tree

4 files changed

+836
-17
lines changed

4 files changed

+836
-17
lines changed

lib/cadet/incentives/achievements.ex

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ defmodule Cadet.Incentives.Achievements do
44
"""
55
use Cadet, [:context, :display]
66

7-
alias Cadet.Incentives.Achievement
7+
alias Cadet.Incentives.{Achievement, GoalProgress}
88

99
import Ecto.Query
1010

11+
require Decimal
12+
1113
@doc """
1214
Returns all achievements.
1315
@@ -21,6 +23,46 @@ defmodule Cadet.Incentives.Achievements do
2123
|> Repo.all()
2224
end
2325

26+
@doc """
27+
Returns a user's total xp from their completed achievements.
28+
"""
29+
def achievements_total_xp(course_id, course_reg_id) when is_ecto_id(course_id) do
30+
xp =
31+
Achievement
32+
|> join(:inner, [a], j in assoc(a, :goals))
33+
|> join(:inner, [_, j], g in assoc(j, :goal))
34+
|> join(:left, [_, _, g], p in GoalProgress,
35+
on: p.goal_uuid == g.uuid and p.course_reg_id == ^course_reg_id
36+
)
37+
|> where([a, j, g, p], a.course_id == ^course_id)
38+
|> group_by([a, j, g, p], a.uuid)
39+
|> having(
40+
[a, j, g, p],
41+
fragment(
42+
"bool_and(?)",
43+
p.completed and p.count == g.target_count and not is_nil(p.course_reg_id)
44+
)
45+
)
46+
# this max is a dummy - simply because a.xp is not under the GROUP BY
47+
|> select([a, j, g, p], %{
48+
xp: fragment("CASE WHEN bool_and(is_variable_xp) THEN SUM(count) ELSE MAX(xp) END")
49+
})
50+
|> subquery()
51+
|> select([s], sum(s.xp))
52+
|> Repo.one()
53+
|> decimal_to_integer()
54+
55+
xp
56+
end
57+
58+
defp decimal_to_integer(decimal) do
59+
if Decimal.is_decimal(decimal) do
60+
Decimal.to_integer(decimal)
61+
else
62+
0
63+
end
64+
end
65+
2466
@spec upsert(map()) :: {:ok, Achievement.t()} | {:error, {:bad_request, String.t()}}
2567
@doc """
2668
Inserts a new achievement, or updates it if it already exists.

lib/cadet_web/controllers/user_controller.ex

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,20 @@ defmodule CadetWeb.UserController do
55

66
use CadetWeb, :controller
77
use PhoenixSwagger
8-
import Cadet.Assessments
9-
alias Cadet.Accounts
8+
alias Cadet.Incentives.Achievements
109
alias Cadet.Accounts.CourseRegistrations
1110

11+
alias Cadet.{Accounts, Assessments}
12+
1213
def index(conn, _) do
1314
user = conn.assigns.current_user
1415
courses = CourseRegistrations.get_courses(conn.assigns.current_user)
1516

1617
if user.latest_viewed_course_id do
1718
latest = CourseRegistrations.get_user_course(user.id, user.latest_viewed_course_id)
18-
xp = user_total_xp(latest)
19-
max_xp = user_max_xp(latest)
20-
story = user_current_story(latest)
19+
xp = Assessments.user_total_xp(latest)
20+
max_xp = Assessments.user_max_xp(latest)
21+
story = Assessments.user_current_story(latest)
2122

2223
render(
2324
conn,
@@ -58,9 +59,9 @@ defmodule CadetWeb.UserController do
5859
end
5960

6061
defp get_course_reg_config(conn, course_reg) do
61-
xp = user_total_xp(course_reg)
62-
max_xp = user_max_xp(course_reg)
63-
story = user_current_story(course_reg)
62+
xp = Assessments.user_total_xp(course_reg)
63+
max_xp = Assessments.user_max_xp(course_reg)
64+
story = Assessments.user_current_story(course_reg)
6465

6566
render(
6667
conn,
@@ -112,8 +113,20 @@ defmodule CadetWeb.UserController do
112113
end
113114
end
114115

116+
def combined_total_xp(conn, _) do
117+
course_id = conn.assigns.course_reg.course_id
118+
user_id = conn.assigns.course_reg.user_id
119+
course_reg_id = conn.assigns.course_reg.id
120+
user_course = CourseRegistrations.get_user_course(user_id, course_id)
121+
122+
total_achievement_xp = Achievements.achievements_total_xp(course_id, course_reg_id)
123+
total_assessment_xp = Assessments.user_total_xp(user_course)
124+
total_xp = total_achievement_xp + total_assessment_xp
125+
json(conn, %{totalXp: total_xp})
126+
end
127+
115128
swagger_path :index do
116-
get("/v2/user")
129+
get("/user")
117130

118131
summary("Get the name, and latest_viewed_course of a user")
119132

@@ -124,7 +137,7 @@ defmodule CadetWeb.UserController do
124137
end
125138

126139
swagger_path :get_latest_viewed do
127-
get("/v2/user/latest_viewed_course")
140+
get("/user/latest_viewed_course")
128141

129142
summary("Get the latest_viewed_course of a user")
130143

@@ -135,7 +148,7 @@ defmodule CadetWeb.UserController do
135148
end
136149

137150
swagger_path :update_latest_viewed do
138-
put("/v2/user/latest_viewed_course")
151+
put("/user/latest_viewed_course")
139152
summary("Update user's latest viewed course")
140153
security([%{JWT: []}])
141154
consumes("application/json")
@@ -148,7 +161,7 @@ defmodule CadetWeb.UserController do
148161
end
149162

150163
swagger_path :update_game_states do
151-
put("/v2/courses/:course_id/user/game_states")
164+
put("/courses/:course_id/user/game_states")
152165
summary("Update user's game states")
153166
security([%{JWT: []}])
154167
consumes("application/json")
@@ -161,13 +174,13 @@ defmodule CadetWeb.UserController do
161174
end
162175

163176
swagger_path :update_research_agreement do
164-
put("/v2/courses/:course_id/user/research_agreement")
177+
put("/courses/:course_id/user/research_agreement")
165178
summary("Update the user's agreement to the anonymized collection of programs for research")
166179
security([%{JWT: []}])
167180
consumes("application/json")
168181

169182
parameters do
170-
course_id(:path, :integer, "the user's course id", required: true)
183+
course_id(:path, :integer, "course ID", required: true)
171184

172185
agreedToResearch(
173186
:body,
@@ -181,6 +194,22 @@ defmodule CadetWeb.UserController do
181194
response(400, "Bad Request")
182195
end
183196

197+
swagger_path :combined_total_xp do
198+
get("/courses/:course_id/user/total_xp")
199+
200+
summary("Get the user's total XP from achievements and assessments")
201+
202+
security([%{JWT: []}])
203+
produces("application/json")
204+
205+
parameters do
206+
course_id(:path, :integer, "course ID", required: true)
207+
end
208+
209+
response(200, "OK", Schema.ref(:TotalXPInfo))
210+
response(401, "Unauthorised")
211+
end
212+
184213
def swagger_definitions do
185214
%{
186215
IndexInfo:
@@ -202,6 +231,15 @@ defmodule CadetWeb.UserController do
202231
)
203232
end
204233
end,
234+
TotalXPInfo:
235+
swagger_schema do
236+
title("User Total XP")
237+
description("the user's total achievement and assessment XP")
238+
239+
properties do
240+
totalXp(:integer, "total XP")
241+
end
242+
end,
205243
LatestViewedInfo:
206244
swagger_schema do
207245
title("Latest viewed course")

lib/cadet_web/router.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ defmodule CadetWeb.Router do
8585
get("/notifications", NotificationsController, :index)
8686
post("/notifications/acknowledge", NotificationsController, :acknowledge)
8787

88+
get("/user/total_xp", UserController, :combined_total_xp)
8889
put("/user/game_states", UserController, :update_game_states)
8990
put("/user/research_agreement", UserController, :update_research_agreement)
9091

0 commit comments

Comments
 (0)