Skip to content
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

Add Publish Grading Feature #965

Merged
merged 87 commits into from
Apr 13, 2024

Conversation

emptygx
Copy link
Contributor

@emptygx emptygx commented Jul 7, 2023

To be merged with Frontend #2856

For implementation decisions, see Frontend PR.

Changes

  • Assessment Table: Add new is_grading_published field which determines if students can see their grades.
  • Assessment Config Table: Add new is_grading_auto_published field which determines if grades are auto published for auto graded assessments.
  • Backend changes xp/grading details, e.g. grader, autograding_status, back to default if is_grading_published is false. This prevents students from checking response from server to see their grades even though the Frontend hides it.
  • Notifications Types: Removed autograded and graded types. Added published and unpublished grading types.
  • Notifications now created when grades are published/unpublished
  • Implement relevent functions for publishing/unpublishing grades
  • Admins are able to publish/unpublish all graded submissions
  • Autograder can autopublish grades when all answers have a success autograding_status
  • Updated seed to accommodate changes

emptygx added 5 commits July 3, 2023 11:01
Current submissions will have the is_grading_published field populated
as `true` if all answers have been graded i.e. have a grader_id, while
the rest will default to `false`.

Future submissions will have the field with default value `false` upon
creation.
- Update existing endpoints and queries related to assessment retrieval
- Add new routes and endpoints to handle new publish grading feature
- Add notification for new feature
@emptygx emptygx marked this pull request as ready for review July 12, 2023 09:46
@YaleChen299
Copy link
Contributor

Hi! Please fix the format and CI and tag me again when the PR is ready. Thank you!

@emptygx
Copy link
Contributor Author

emptygx commented Jul 17, 2023

Hi! Please fix the format and CI and tag me again when the PR is ready. Thank you!

@YaleChen299
Hello! Sorry, I have fixed the formatting. Thank you!

@YaleChen299
Copy link
Contributor

Hi! Please fix the format and CI and tag me again when the PR is ready. Thank you!

@YaleChen299 Hello! Sorry, I have fixed the formatting. Thank you!

No worries! Can you check the CI again? I will approve the CI run when I see then.

@coveralls
Copy link

coveralls commented Jul 23, 2023

Coverage Status

coverage: 95.323% (-0.1%) from 95.43%
when pulling b366bc9 on emptygx:add-publish-grading
into d94f60a on source-academy:master.

Copy link
Contributor

@YaleChen299 YaleChen299 left a comment

Choose a reason for hiding this comment

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

Hi @emptygx, I have fixed the formatting and credo issues. However, a few tests are not passing. I suspect that the behaviour of user_total_xp is affected by the behaviour of published/unpublished grades.

What is the expected behaviour for the published/unpublished grades? Is it that users will not see their grades if unpublished?

Could you look into these test cases and fix them? Please document the correct behaviours in terms of the test cases, i.e. for any behaviours that may be affected by the publish grading feature, create at least one positive and one negative test case.

Thank you so much for wrapping up this properly! I think this feature would make the grading activities much more user-friendly!

@@ -298,6 +308,11 @@ defmodule Cadet.Assessments do
|> where([s], s.student_id == ^cr.id)
|> select([s], [:assessment_id, :status])

grading_published_status =
Copy link
Contributor

Choose a reason for hiding this comment

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

this subquery can be merged with the previous one.

@@ -309,11 +324,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),
Copy link
Contributor

Choose a reason for hiding this comment

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

similarly here, this can be merged with the other query.

a
| xp: sa.xp,
| xp: fragment("CASE WHEN ? THEN ? ELSE 0 END", gp.is_grading_published, sa.xp),
Copy link
Contributor

Choose a reason for hiding this comment

The 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).

# and return all answers
|> select([a, s], %{
a
| xp: fragment("CASE WHEN ? THEN ? ELSE 0 END", s.is_grading_published, a.xp),
Copy link
Contributor

@YaleChen299 YaleChen299 Jul 24, 2023

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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!

Copy link
Contributor

Choose a reason for hiding this comment

The 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 filter_published_xp, I will be happy to accept that.

@YaleChen299 YaleChen299 force-pushed the add-publish-grading branch from 3ce9105 to cf98cff Compare July 24, 2023 06:22
role == :admin or bypass or
Cadet.Accounts.Query.avenger_of?(cr, submission.student_id)} do
Multi.new()
|> Multi.run(
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

@YaleChen299
Copy link
Contributor

(part of frontend issue #2522)

Currently, the grading states design and business logic cause ambiguity with resubmission of assessments being assumed to be graded immediately, and with grades being displayed when they should not.

This feature aims to solve this issue by introducing a new grading state published and a new backend boolean is_grading_published, allowing explicit publishing of student's grades on the frontend and preventing the fetching of grades if they have yet to be published, hence creating a better logic flow.

This pull request contains changes to be made to the backend to enable this new feature.

Re-thinking about the problem that we are trying to solve, I think we need to auto un-publish the grades when we unsubmit.
The main point of publish grading is to solve resubmitted submissions shown as graded to graders so that grader will not know if the student is graded after resubmission.
This logic is a bit different then whether the grade is publish to the student or not. Please correct me if i am wrong! Thanks!

@emptygx
Copy link
Contributor Author

emptygx commented Jul 24, 2023

Re-thinking about the problem that we are trying to solve, I think we need to auto un-publish the grades when we unsubmit. The main point of publish grading is to solve resubmitted submissions shown as graded to graders so that grader will not know if the student is graded after resubmission. This logic is a bit different then whether the grade is publish to the student or not. Please correct me if i am wrong! Thanks!

Yes, I agree that should be part of the feature, which has already been implemented as part of the logic on the front end, which auto-unpublishes grades whenever an assessment is unsubmitted. Sorry for not mentioning that in the PR description, will make sure to add it into future documentation! Should I add it to the PR description as well?

Oh, thanks for the clarification! In that case what if the unsubmit went through successfully but the unpublish request failed? Will it be better for that to be handled in one single request? @RichDom2185

@YaleChen299 YaleChen299 self-assigned this Jul 25, 2023
@GabrielCWT GabrielCWT dismissed YaleChen299’s stale review April 1, 2024 07:17

Outdated review

@thortol thortol mentioned this pull request Apr 8, 2024
7 tasks
Copy link
Member

@RichDom2185 RichDom2185 left a comment

Choose a reason for hiding this comment

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

Hi, thanks for working on this! Just some comments below, thanks!

lib/cadet/assessments/assessments.ex Outdated Show resolved Hide resolved
Comment on lines +1334 to +1350
answers_query =
Answer
|> group_by([ans], ans.submission_id)
|> select([ans], %{
submission_id: ans.submission_id,
graded_count: filter(count(ans.id), not is_nil(ans.grader_id)),
autograded_count: filter(count(ans.id), ans.autograding_status == :success)
})

question_query =
Question
|> group_by([q], q.assessment_id)
|> join(:inner, [q], a in Assessment, on: q.assessment_id == a.id)
|> select([q, a], %{
assessment_id: q.assessment_id,
question_count: count(q.id)
})
Copy link
Member

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, but since these are subqueries, they'll run first in entirety before any downstream filtering.

So in order to avoid the ~30s query from the joining of the large table, should we filter by course ID here first?

Copy link
Contributor

Choose a reason for hiding this comment

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

I managed to filter the Question table subquery but the Answer table subquery requires quite a few joins to obtain the course_id to filter by. I was wondering if it is worth the multiple joins since I'm not too sure about costs in SQL.

Joins:
Answer -> Question -> Assessment (course_id)

I also thought of combining the queries together since I already use Question -> Assessment but you cant use aggregate functions e.g. filter and count

@@ -258,6 +260,14 @@ defmodule Cadet.Autograder.GradingJob do
)
end

defp grade_submission_question_answer_lists(_, [], [], _, _) do
defp grade_submission_question_answer_lists(submission_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.

Would it be better to query as follows?

submission =
  Submission
  |> join(:inner, [s], a in assoc(s, :assessment))
  |> join(:inner, [_, a], c in assoc(a, :config))
  |> preload([_, a, c], assessment: {a, config: c})
  |> Repo.get(submission_id)
is_grading_auto_published = submission.assessment.config.is_grading_auto_published

|> Answer.autograding_changeset(changes)
|> Repo.update()

submission = Repo.get(Submission, submission_id)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same question as before.

Copy link
Member

@RichDom2185 RichDom2185 left a comment

Choose a reason for hiding this comment

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

LGTM! Let's merge this first to not block the other team's PRs. As to your question, let's discuss it after this is merged.

Thanks for the work!

@RichDom2185 RichDom2185 merged commit 3cf8d7d into source-academy:master Apr 13, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants