Skip to content

Commit 2adcd51

Browse files
authored
fix: Auto submission cron job for team assessments bug (#1200)
* Refactor helper function * Remove unused aliases * Refactor helper further * Account for team assessments in autoclose job * Add more extensive tests * Fix format * Fix credo * Fix compile error * Fix empty submission creation for team assessments * refactor: Negate if condition
1 parent b97865b commit 2adcd51

File tree

5 files changed

+186
-42
lines changed

5 files changed

+186
-42
lines changed

lib/cadet/accounts/teams.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ defmodule Cadet.Accounts.Teams do
99
import Ecto.{Changeset, Query}
1010

1111
alias Cadet.Repo
12-
alias Cadet.Accounts.{Team, TeamMember, CourseRegistration, Notification}
13-
alias Cadet.Assessments.{Answer, Assessment, Submission}
12+
alias Cadet.Accounts.{Team, TeamMember, Notification}
13+
alias Cadet.Assessments.{Answer, Submission}
1414

1515
@doc """
1616
Creates a new team and assigns an assessment and team members to it.

lib/cadet/assessments/assessments.ex

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -966,7 +966,17 @@ defmodule Cadet.Assessments do
966966
end
967967
end
968968

969-
defp find_teams(cr_id) do
969+
def is_team_assessment?(assessment_id) when is_ecto_id(assessment_id) do
970+
max_team_size =
971+
Assessment
972+
|> where(id: ^assessment_id)
973+
|> select([a], a.max_team_size)
974+
|> Repo.one()
975+
976+
max_team_size > 1
977+
end
978+
979+
defp find_teams(cr_id) when is_ecto_id(cr_id) do
970980
query =
971981
from(t in Team,
972982
join: tm in assoc(t, :team_members),
@@ -986,28 +996,14 @@ defmodule Cadet.Assessments do
986996
limit: 1
987997
)
988998

989-
assessment_team_size =
990-
Map.get(
991-
Repo.one(
992-
from(a in Assessment,
993-
where: a.id == ^assessment_id,
994-
select: %{max_team_size: a.max_team_size}
995-
)
996-
),
997-
:max_team_size,
998-
0
999-
)
1000-
1001-
case assessment_team_size > 1 do
1002-
true ->
1003-
case Repo.one(query) do
1004-
nil -> {:error, :team_not_found}
1005-
team -> {:ok, team}
1006-
end
1007-
999+
if is_team_assessment?(assessment_id) do
1000+
case Repo.one(query) do
1001+
nil -> {:error, :team_not_found}
1002+
team -> {:ok, team}
1003+
end
1004+
else
10081005
# team is nil for individual assessments
1009-
false ->
1010-
{:ok, nil}
1006+
{:ok, nil}
10111007
end
10121008
end
10131009

lib/cadet/jobs/autograder/grading_job.ex

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ defmodule Cadet.Autograder.GradingJob do
99

1010
require Logger
1111

12+
alias Cadet.Accounts.{Team, TeamMember}
1213
alias Cadet.Assessments
1314
alias Cadet.Assessments.{Answer, Assessment, Question, Submission, SubmissionVotes}
1415
alias Cadet.Autograder.Utilities
@@ -102,13 +103,50 @@ defmodule Cadet.Autograder.GradingJob do
102103
end
103104

104105
defp insert_empty_submission(%{student_id: student_id, assessment: assessment}) do
105-
%Submission{}
106-
|> Submission.changeset(%{
107-
student_id: student_id,
108-
assessment: assessment,
109-
status: :submitted
110-
})
111-
|> Repo.insert!()
106+
if Assessments.is_team_assessment?(assessment.id) do
107+
# Get current team if any
108+
team =
109+
Team
110+
|> where(assessment_id: ^assessment.id)
111+
|> join(:inner, [t], tm in assoc(t, :team_members))
112+
|> where([_, tm], tm.student_id == ^student_id)
113+
|> Repo.one()
114+
115+
if !team do
116+
# Student is not in any team
117+
# Create new team just for the student
118+
team =
119+
%Team{}
120+
|> Team.changeset(%{
121+
assessment_id: assessment.id
122+
})
123+
|> Repo.insert!()
124+
125+
%TeamMember{}
126+
|> TeamMember.changeset(%{
127+
team_id: team.id,
128+
student_id: student_id
129+
})
130+
|> Repo.insert!()
131+
end
132+
133+
%Submission{}
134+
|> Submission.changeset(%{
135+
team_id: team.id,
136+
assessment: assessment,
137+
status: :submitted
138+
})
139+
|> Repo.insert!()
140+
else
141+
# Individual assessment
142+
%Submission{}
143+
|> Submission.changeset(%{
144+
student_id: student_id,
145+
assessment: assessment,
146+
status: :submitted
147+
})
148+
|> Repo.insert!()
149+
end
112150
end
113151

114152
defp update_submission_status_to_submitted(submission = %Submission{status: status}) do

lib/cadet/jobs/autograder/utilities.ex

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ defmodule Cadet.Autograder.Utilities do
88

99
import Ecto.Query
1010

11-
alias Cadet.Accounts.CourseRegistration
11+
alias Cadet.Accounts.{CourseRegistration, TeamMember}
12+
13+
alias Cadet.Assessments
1214
alias Cadet.Assessments.{Answer, Assessment, Question, Submission}
1315

1416
def dispatch_programming_answer(answer = %Answer{}, question = %Question{}, overwrite \\ false) do
@@ -25,7 +27,37 @@ defmodule Cadet.Autograder.Utilities do
2527
})
2628
end
2729

28-
def fetch_submissions(assessment_id, course_id) when is_ecto_id(assessment_id) do
30+
def fetch_submissions(assessment_id, course_id)
31+
when is_ecto_id(assessment_id) and is_ecto_id(course_id) do
32+
if Assessments.is_team_assessment?(assessment_id) do
33+
fetch_team_submissions(assessment_id, course_id)
34+
else
35+
fetch_student_submissions(assessment_id, course_id)
36+
end
37+
end
38+
39+
defp fetch_team_submissions(assessment_id, course_id)
40+
when is_ecto_id(assessment_id) and is_ecto_id(course_id) do
41+
CourseRegistration
42+
|> where(role: "student", course_id: ^course_id)
43+
|> join(
44+
:left,
45+
[cr],
46+
tm in TeamMember,
47+
on: cr.id == tm.student_id
48+
)
49+
|> join(
50+
:left,
51+
[cr, tm],
52+
s in Submission,
53+
on: tm.team_id == s.team_id and s.assessment_id == ^assessment_id
54+
)
55+
|> select([cr, tm, s], %{student_id: cr.id, submission: s})
56+
|> Repo.all()
57+
end
58+
59+
defp fetch_student_submissions(assessment_id, course_id)
60+
when is_ecto_id(assessment_id) and is_ecto_id(course_id) do
2961
CourseRegistration
3062
|> where(role: "student", course_id: ^course_id)
3163
|> join(

test/cadet/jobs/autograder/utilities_test.exs

Lines changed: 87 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,18 +72,58 @@ defmodule Cadet.Autograder.UtilitiesTest do
7272
setup do
7373
course = insert(:course)
7474
config = insert(:assessment_config, %{course: course})
75-
assessment = insert(:assessment, %{is_published: true, course: course, config: config})
75+
76+
# Individual assessment
77+
assessment =
78+
insert(:assessment, %{
79+
is_published: true,
80+
course: course,
81+
config: config,
82+
max_team_size: 1
83+
})
84+
7685
students = insert_list(5, :course_registration, %{role: :student, course: course})
7786
insert(:course_registration, %{course: build(:course), role: :student})
78-
%{students: students, assessment: assessment}
87+
88+
# Team assessment
89+
team_assessment =
90+
insert(:assessment, %{
91+
is_published: true,
92+
course: course,
93+
config: config,
94+
max_team_size: 2
95+
})
96+
97+
team_member1 = insert(:team_member, %{student: Enum.at(students, 0)})
98+
team_member2 = insert(:team_member, %{student: Enum.at(students, 1)})
99+
100+
team1 =
101+
insert(:team, %{assessment: team_assessment, team_members: [team_member1, team_member2]})
102+
103+
team_member3 = insert(:team_member, %{student: Enum.at(students, 2)})
104+
team_member4 = insert(:team_member, %{student: Enum.at(students, 3)})
105+
106+
team2 =
107+
insert(:team, %{assessment: team_assessment, team_members: [team_member3, team_member4]})
108+
109+
%{
110+
individual: %{students: students, assessment: assessment},
111+
team: %{
112+
teams: [team1, team2],
113+
assessment: team_assessment,
114+
teamless: [Enum.at(students, 4)]
115+
}
116+
}
79117
end
80118

81-
test "it returns list of students with matching submissions", %{
82-
students: students,
83-
assessment: assessment
119+
test "it returns list of students with matching submissions for individual assessments", %{
120+
individual: %{students: students, assessment: assessment}
84121
} do
85122
submissions =
86-
Enum.map(students, &insert(:submission, %{assessment: assessment, student: &1}))
123+
Enum.map(
124+
students,
125+
&insert(:submission, %{assessment: assessment, student: &1, team: nil})
126+
)
87127

88128
expected = Enum.map(submissions, &%{student_id: &1.student_id, submission_id: &1.id})
89129

@@ -95,9 +135,8 @@ defmodule Cadet.Autograder.UtilitiesTest do
95135
assert results == expected
96136
end
97137

98-
test "it returns list of students with without matching submissions", %{
99-
students: students,
100-
assessment: assessment
138+
test "it returns list of students without matching submissions for individual assessments", %{
139+
individual: %{students: students, assessment: assessment}
101140
} do
102141
expected_student_ids = Enum.map(students, & &1.id)
103142

@@ -108,6 +147,45 @@ defmodule Cadet.Autograder.UtilitiesTest do
108147

109148
assert results |> Enum.map(& &1.submission) |> Enum.uniq() == [nil]
110149
end
150+
151+
test "it returns list of students both with and without matching submissions for team assessments",
152+
%{
153+
team: %{teams: teams, assessment: assessment, teamless: teamless}
154+
} do
155+
submissions =
156+
Enum.map(
157+
teams,
158+
&%{
159+
team_id: &1.id,
160+
submission: insert(:submission, %{assessment: assessment, team: &1, student: nil})
161+
}
162+
)
163+
164+
expected =
165+
teams
166+
|> Enum.flat_map(& &1.team_members)
167+
|> Enum.map(
168+
&%{
169+
student_id: &1.student_id,
170+
submission_id:
171+
Enum.find(submissions, fn s -> s.team_id == &1.team_id end).submission.id
172+
}
173+
)
174+
175+
expected = expected ++ Enum.map(teamless, &%{student_id: &1.id, submission_id: nil})
176+
177+
results =
178+
assessment.id
179+
|> Utilities.fetch_submissions(assessment.course_id)
180+
|> Enum.map(
181+
&%{
182+
student_id: &1.student_id,
183+
submission_id: if(&1.submission, do: &1.submission.id, else: nil)
184+
}
185+
)
186+
187+
assert results == expected
188+
end
111189
end
112190

113191
defp get_assessments_ids(assessments) do

0 commit comments

Comments
 (0)