-
Notifications
You must be signed in to change notification settings - Fork 56
Add Publish Grading Feature #965
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
Changes from 11 commits
8022419
8125ac1
e286902
d174fbd
bc36b60
5dcc425
2153280
1891d72
55c5a36
af683fd
cf98cff
568cc6f
4700a49
0ec5a50
a026ec8
750ba95
f82e43f
e7090f8
5c1b155
788e31e
70ebac5
54cbcfc
cf62185
6b5f0e8
2a05597
9934af4
1097e23
4ce2d6e
9de59c6
98587e6
d046b3b
07f2548
9b33670
e658166
054d89a
2caecd7
dd7742e
f65bc56
1f41b11
8c1387f
7cc1448
44cbab8
89411ad
888a4f7
4298970
147cf4d
a41a894
d2b173b
626b61c
07a63e6
38b3303
ae9b42f
b606905
582a394
ade0e24
f3a0eeb
1abf60b
1ae8d87
5077d70
50a85a9
ddd522d
5360e3a
a33ff69
858c740
f376f1e
a2a23f7
5b91c12
64b5305
21c65f6
596d3c3
340de3a
b2ab4b0
a25f9f7
9d9069c
fd1508b
4ac87af
93d6bd2
bd9abf9
c34d265
365cfb4
ce6ec53
710213c
593708e
d00b686
e355d54
541fac9
b366bc9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -91,6 +91,7 @@ defmodule Cadet.Assessments do | |
submission_xp = | ||
Submission | ||
|> where(student_id: ^cr_id) | ||
|> where(is_grading_published: true) | ||
|> join(:inner, [s], a in Answer, on: s.id == a.submission_id) | ||
|> group_by([s], s.id) | ||
|> select([s, a], %{ | ||
|
@@ -248,6 +249,14 @@ defmodule Cadet.Assessments do | |
Answer | ||
|> join(:inner, [a], s in assoc(a, :submission)) | ||
|> where([_, s], s.student_id == ^course_reg.id) | ||
# change a.xp and a.xp_adjustment to 0 | ||
# if a.is_grading_published is false and return all answers | ||
|> select([a, s], %{ | ||
a | ||
| xp: fragment("CASE WHEN ? THEN ? ELSE 0 END", s.is_grading_published, a.xp), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar comment. I think the hiding logic should be done at the controller layer as other logic might use the xp value. However, I am okay with this logic at the moment since our models are already filled with a lot of controller logic, and this is a bit troublesome to do at the controller level. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found this to be the easier and more straightforward way to filter the data, but if maintainability may be an issue, then doing it at the controller level might be better like you said. I am not too sure how to go about that though and may need some guidance! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that making this filtering at the controller level is a bit more troublesome. I think what we can do is at least separate this filtering from the main subquery selection. If you can make it into a function to be piped through with sematic name like |
||
xp_adjustment: | ||
fragment("CASE WHEN ? THEN ? ELSE 0 END", s.is_grading_published, a.xp_adjustment) | ||
}) | ||
|
||
questions = | ||
Question | ||
|
@@ -298,6 +307,11 @@ defmodule Cadet.Assessments do | |
|> where([s], s.student_id == ^cr.id) | ||
|> select([s], [:assessment_id, :status]) | ||
|
||
grading_published_status = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this subquery can be merged with the previous one. |
||
Submission | ||
|> where([s], s.student_id == ^cr.id) | ||
|> select([s], [:assessment_id, :is_grading_published]) | ||
|
||
assessments = | ||
cr.course_id | ||
|> Query.all_assessments_with_aggregates() | ||
|
@@ -309,11 +323,15 @@ defmodule Cadet.Assessments do | |
on: a.id == sa.assessment_id | ||
) | ||
|> join(:left, [a, _], s in subquery(submission_status), on: a.id == s.assessment_id) | ||
|> select([a, sa, s], %{ | ||
|> join(:left, [a, _, _], gp in subquery(grading_published_status), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. similarly here, this can be merged with the other query. |
||
on: a.id == gp.assessment_id | ||
) | ||
|> select([a, sa, s, gp], %{ | ||
a | ||
| xp: sa.xp, | ||
| xp: fragment("CASE WHEN ? THEN ? ELSE 0 END", gp.is_grading_published, sa.xp), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I personally feel that this hiding logic should be done at the Controller level, before sending the response to the front end, since technically the xp is not zero to the backend's knowledge(this will not affect the current behaviours but this will make the code more maintainable in the future as other feature might need to use this xp value). |
||
graded_count: sa.graded_count, | ||
user_status: s.status | ||
user_status: s.status, | ||
is_grading_published: gp.is_grading_published | ||
}) | ||
|> filter_published_assessments(cr) | ||
|> order_by(:open_at) | ||
|
@@ -817,7 +835,7 @@ defmodule Cadet.Assessments do | |
end) | ||
|> Repo.transaction() | ||
|
||
Cadet.Accounts.Notifications.handle_unsubmit_notifications( | ||
Notifications.handle_unsubmit_notifications( | ||
submission.assessment.id, | ||
Repo.get(CourseRegistration, submission.student_id) | ||
) | ||
|
@@ -844,6 +862,114 @@ defmodule Cadet.Assessments do | |
end | ||
end | ||
|
||
# This function changes the is_grading_published field of the submission to false | ||
# and sends a notification to the student | ||
def unpublish_grading(submission_id, cr = %CourseRegistration{id: course_reg_id, role: role}) | ||
when is_ecto_id(submission_id) do | ||
submission = | ||
Submission | ||
|> join(:inner, [s], a in assoc(s, :assessment)) | ||
|> preload([_, a], assessment: a) | ||
|> Repo.get(submission_id) | ||
|
||
# allows staff to unpublish own assessment | ||
bypass = role in @bypass_closed_roles and submission.student_id == course_reg_id | ||
|
||
with {:submission_found?, true} <- {:submission_found?, is_map(submission)}, | ||
{:allowed_to_unpublish?, true} <- | ||
{:allowed_to_unpublish?, | ||
role == :admin or bypass or | ||
Cadet.Accounts.Query.avenger_of?(cr, submission.student_id)} do | ||
Multi.new() | ||
|> Multi.run( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there is no need to use multi and transaction here since there is only a single update. May II ask what was the reason for this? |
||
:unpublish_grading, | ||
fn _repo, _ -> | ||
submission | ||
|> Submission.changeset(%{is_grading_published: false}) | ||
|> Repo.update() | ||
end | ||
) | ||
|> Repo.transaction() | ||
|
||
Notifications.handle_unpublish_grades_notifications( | ||
submission.assessment.id, | ||
Repo.get(CourseRegistration, submission.student_id) | ||
) | ||
|
||
{:ok, nil} | ||
else | ||
{:submission_found?, false} -> | ||
{:error, {:not_found, "Submission not found"}} | ||
|
||
{:is_open?, false} -> | ||
{:error, {:forbidden, "Assessment not open"}} | ||
|
||
{:allowed_to_unpublish?, false} -> | ||
{:error, {:forbidden, "Only Avenger of student is permitted to unpublish grading"}} | ||
|
||
_ -> | ||
{:error, {:internal_server_error, "Please try again later."}} | ||
end | ||
end | ||
|
||
# This function changes the is_grading_published field of the submission to true | ||
# only if all the answers are graded | ||
# and sends a notification to the student | ||
def publish_grading(submission_id, cr = %CourseRegistration{id: course_reg_id, role: role}) | ||
when is_ecto_id(submission_id) do | ||
submission = | ||
Submission | ||
|> join(:inner, [s], a in assoc(s, :assessment)) | ||
|> preload([_, a], assessment: a) | ||
|> Repo.get(submission_id) | ||
|
||
# allows staff to publish own assessment | ||
bypass = role in @bypass_closed_roles and submission.student_id == course_reg_id | ||
|
||
with {:submission_found?, true} <- {:submission_found?, is_map(submission)}, | ||
{:status, :submitted} <- {:status, submission.status}, | ||
{:allowed_to_publish?, true} <- | ||
{:allowed_to_publish?, | ||
role == :admin or bypass or | ||
Cadet.Accounts.Query.avenger_of?(cr, submission.student_id)} do | ||
Multi.new() | ||
|> Multi.run( | ||
:publish_grading, | ||
fn _repo, _ -> | ||
submission | ||
|> Submission.changeset(%{is_grading_published: true}) | ||
|> Repo.update() | ||
end | ||
) | ||
|> Repo.transaction() | ||
|
||
Notifications.write_notification_when_graded( | ||
submission.id, | ||
:graded | ||
) | ||
|
||
{:ok, nil} | ||
else | ||
{:submission_found?, false} -> | ||
{:error, {:not_found, "Submission not found"}} | ||
|
||
{:is_open?, false} -> | ||
{:error, {:forbidden, "Assessment not open"}} | ||
|
||
{:status, :attempting} -> | ||
{:error, {:bad_request, "Some questions have not been attempted"}} | ||
|
||
{:status, :attempted} -> | ||
{:error, {:bad_request, "Assessment has not been submitted"}} | ||
|
||
{:allowed_to_publish?, false} -> | ||
{:error, {:forbidden, "Only Avenger of student is permitted to publish grading"}} | ||
|
||
_ -> | ||
{:error, {:internal_server_error, "Please try again later."}} | ||
end | ||
end | ||
|
||
@spec update_submission_status_and_xp_bonus(Submission.t()) :: | ||
{:ok, Submission.t()} | {:error, Ecto.Changeset.t()} | ||
defp update_submission_status_and_xp_bonus(submission = %Submission{}) do | ||
|
@@ -1197,6 +1323,7 @@ defmodule Cadet.Assessments do | |
s."xpAdjustment", | ||
s."xpBonus", | ||
s."gradedCount", | ||
s."isGradingPublished", | ||
assts.jsn as assessment, | ||
students.jsn as student, | ||
unsubmitters.jsn as "unsubmittedBy" | ||
|
@@ -1211,7 +1338,8 @@ defmodule Cadet.Assessments do | |
sum(ans.xp) as xp, | ||
sum(ans.xp_adjustment) as "xpAdjustment", | ||
s.xp_bonus as "xpBonus", | ||
count(ans.id) filter (where ans.grader_id is not null) as "gradedCount" | ||
count(ans.id) filter (where ans.grader_id is not null) as "gradedCount", | ||
s.is_grading_published as "isGradingPublished" | ||
from submissions s | ||
left join | ||
answers ans on s.id = ans.submission_id | ||
|
@@ -1367,12 +1495,7 @@ defmodule Cadet.Assessments do | |
{:valid, changeset = %Ecto.Changeset{valid?: true}} <- | ||
{:valid, Answer.grading_changeset(answer, attrs)}, | ||
{:ok, _} <- Repo.update(changeset) do | ||
if is_fully_graded?(answer) and not is_own_submission do | ||
# Every answer in this submission has been graded manually | ||
Notifications.write_notification_when_graded(submission_id, :graded) | ||
else | ||
{:ok, nil} | ||
end | ||
{:ok, nil} | ||
else | ||
{:answer_found?, false} -> | ||
{:error, {:bad_request, "Answer not found or user not permitted to grade."}} | ||
|
Uh oh!
There was an error while loading. Please reload this page.