Skip to content

Leaderboard #1238

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open

Leaderboard #1238

wants to merge 29 commits into from

Conversation

tohyzhong
Copy link

@tohyzhong tohyzhong commented Mar 17, 2025

Description

  • Added Overall Leaderboard and Contest Leaderboards
  • XP and contest score fetching from backend
  • Top X Contest Entries to display, Top X Overall leaderboard entries to display
  • contest scores update button in ground control (under voting features)
  • Update assessment workspace Leaderboard logic (based on customised X desired contest entries to display) + csv export fix
  • Automatic XP assignment for contest winners based on XML file (fallback on default value iif XML file does not specify any, put 0 in for rank 1 to not give any XP)
  • Leaderboards are ranked such that the same rank can be given for same score/XP, and we will skip however many ranks that are duplicated. (e.g. if we have two rank 1s, the next will be rank 3)
  • Updated contest winner fetching for assessment workspace leaderboard to align with new Leaderboard page

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update
  • Code quality improvements

How to test

Checklist

  • I have tested this code
  • I have updated the documentation

@tohyzhong tohyzhong self-assigned this Mar 17, 2025
@coveralls
Copy link

Coverage Status

coverage: 91.307% (-2.3%) from 93.605%
when pulling 64e812b on leaderboard
into 41e3ce1 on master.

Copy link
Contributor

@GabrielCWT GabrielCWT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this! Made quite a few comments, do look through them. One issue I have is the lack of pagination, sending over 1000 students in a json for each request seems expensive to me. Also would it be possible to implement some tests? Some errors I caught would've been caught if there were tests.

alias Cadet.Assessments
alias Cadet.Assessments.{Question, Assessment}
alias Cadet.{Assessments, Repo}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if you need to alias Assessments again

@@ -67,6 +71,44 @@ defmodule CadetWeb.AssessmentsController do
end
end

def combined_total_xp_for_all_users(conn, %{"course_id" => course_id}) do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why we require all users? Could we possible paginate this. It feels like it would be an expensive query.

Comment on lines +13 to +16
add(:enable_overall_leaderboard, :boolean, null: false, default: true)
add(:enable_contest_leaderboard, :boolean, null: false, default: true)
add(:top_leaderboard_display, :integer, default: 100)
add(:top_contest_leaderboard_display, :integer, default: 10)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be creating a new migration file with today's date

@@ -25,7 +26,8 @@ defmodule Cadet.Assessments do
alias Cadet.Jobs.Log
alias Cadet.ProgramAnalysis.Lexer
alias Ecto.Multi
alias Cadet.Incentives.Achievements
alias Cadet.Incentives.{Achievements, AchievementToGoal}
alias Ecto.Changeset
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you combine the aliases

having:
fragment(
"bool_and(?)",
p.completed and p.count == g.target_count and not is_nil(p.course_reg_id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if I'm wrong, course_reg_id has a not null constraint so is_nil is redundent.

The rest of them aren't aggregated columns, I think you can put it into the where clause

@@ -1591,7 +1724,8 @@ defmodule Cadet.Assessments do
defp load_contest_voting_entries(
questions,
%CourseRegistration{role: role, course_id: course_id, id: voter_id},
assessment
assessment,
visibleEntries
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be snake cased

Comment on lines 1841 to 1848
# |> where(
# [a],
# fragment(
# "?->>'code' like ?",
# a.answer,
# "%return%"
# )
# )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove uncommented lines

|> join(:left, [a], s in assoc(a, :submission))
|> join(:left, [a, s], student in assoc(s, :student))
|> join(:inner, [a, s, student], student_user in assoc(student, :user))
# |> where([a, s, student], student.role == "student")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove uncommented lines

Comment on lines +97 to +100
contest_id =
Question
|> where(assessment_id: ^assessment_id)
|> Repo.one()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know contests are supposed to have one question, but can we handle the error raised in case for some reason there was more than one question? i.e. returning a proper error message.

Comment on lines +1854 to +1869
|> select([a, s, student, student_user], %{
submission_id: a.submission_id,
code: a.answer["code"],
score: a.relative_score,
name: student_user.name,
username: student_user.username
})

query =
from(sub in subquery(subquery),
select_merge: %{
rank: fragment("RANK() OVER (ORDER BY ? DESC)", sub.score)
}
)

Repo.all(query)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it not possible to avoid the subquery by doing the following? Please do check the correctness of my suggestion :)

select([a, s, student, user], %{
    submission_id: a.submission_id,
    code: a.answer["code"],
    score: a.relative_score,
    name: user.name,
    username: user.username,
    rank: fragment("RANK() OVER (ORDER BY ? DESC)", a.relative_score)
  })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Development

Successfully merging this pull request may close these issues.

4 participants