Skip to content

Fix certificate generation without persistent grades#29792

Merged
mariajgrimaldi merged 3 commits intoopenedx:masterfrom
raccoongang:dyudyunov/fix-certificate-generation-without-persistent-grades
Apr 8, 2022
Merged

Fix certificate generation without persistent grades#29792
mariajgrimaldi merged 3 commits intoopenedx:masterfrom
raccoongang:dyudyunov/fix-certificate-generation-without-persistent-grades

Conversation

@dyudyunov
Copy link
Copy Markdown
Contributor

@dyudyunov dyudyunov commented Jan 20, 2022

Description

Fix for an error related to endless recursion

Dev Notes:
The student gets the passing grade
-> CourseGradeFactory sends the COURSE_GRADE_NOW_PASSED signal after the grade calculation
-> listen_for_passing_grade receives the signal and calls generate_certificate_task
-> generate_certificate_task calls _generate_regular_certificate_task
-> _generate_regular_certificate_task trying to get grade by calling the _get_course_grade
-> _get_course_grade calls CourseGradeFactory().read() but I have persistent grades off, so actually that ends with CourseGradeFactory().update()
-> CourseGradeFactory().update() sends the COURSE_GRADE_NOW_PASSED
And so on

Related PR to open-release/maple.master:

@openedx-webhooks openedx-webhooks added needs triage open-source-contribution PR author is not from Axim or 2U labels Jan 20, 2022
@openedx-webhooks
Copy link
Copy Markdown

Thanks for the pull request, @dyudyunov! I've created OSPR-6408 to keep track of it in JIRA, where we prioritize reviews. Please note that it may take us up to several weeks or months to complete a review and merge your PR.

Feel free to add as much of the following information to the ticket as you can:

  • supporting documentation
  • Open edX discussion forum threads
  • timeline information ("this must be merged by XX date", and why that is)
  • partner information ("this is a course on edx.org")
  • any other information that can help Product understand the context for the PR

All technical communication about the code itself will be done via the GitHub pull request interface. As a reminder, our process documentation is here.

Please let us know once your PR is ready for our review and all tests are green.

@dyudyunov dyudyunov force-pushed the dyudyunov/fix-certificate-generation-without-persistent-grades branch from 8c1cd1b to f385591 Compare January 20, 2022 12:23
@natabene
Copy link
Copy Markdown
Contributor

@dyudyunov Thank you for your contribution. Is this ready for our review?

@mariajgrimaldi
Copy link
Copy Markdown
Member

Hi there @dyudyunov 😄 I hope you're great!!
I'm curious about the effects of the recursion on the platform 🤔 what did you notice to make these changes?

@dyudyunov
Copy link
Copy Markdown
Contributor Author

Hi @mariajgrimaldi!
How to reproduce the issue:

  1. Be sure that Waffle Swith "certificates.auto_certificate_generation" flag is enabled
  2. Be sure the persistent grades are disabled
  3. Change the course certificate settings to be able to generate the certificate ASAP
  4. Achieve minimal progress for generating certificate (50% by default)

As a result, you'll see the same logs in the loop ending with the max recursion depth error leading to 500 (certificate is not generated)

My fix relates to the certificate generation flow only, so any functionality that depends on the course grade factory was not affected.

@shadinaif
Copy link
Copy Markdown
Contributor

Thank you @dyudyunov . I tried to add inline reviews but there is something stuck with me in this particular PR. I just have one suggestion:

I believe we can remove the try-.catch thing in the new test method

@dyudyunov
Copy link
Copy Markdown
Contributor Author

Thank you @dyudyunov . I tried to add inline reviews but there is something stuck with me in this particular PR. I just have one suggestion:

I believe we can remove the try-.catch thing in the new test method

Hi @shadinaif
I wouldn't want to do that because that is the way to catch the issue fixed in this PR and guarantee it will not appear in the future. I would like to leave it as it is if you don't mind )

@felipemontoya
Copy link
Copy Markdown
Member

Thanks @dyudyunov, this changes fixed the recursion issue I was having when my grades where enough to pass a course and I rendered the progress page.

When I turned Persistent Grades on, everything kept working correctly.

Fix for an error related to endless recursion

Dev Notes:
The student gets the passing grade
-> CourseGradeFactory sends the `COURSE_GRADE_NOW_PASSED` signal after the grade calculation
-> `listen_for_passing_grade` receives the signal and calls `generate_certificate_task`
-> `generate_certificate_task` calls `_generate_regular_certificate_task`
-> `_generate_regular_certificate_task` trying to get grade by calling the `_get_course_grade`
-> `_get_course_grade` calls `CourseGradeFactory().read()` but I have persistent grades off, so actually that ends with `CourseGradeFactory().update()`
-> `CourseGradeFactory().update()` sends the `COURSE_GRADE_NOW_PASSED`
And so on
@dyudyunov dyudyunov force-pushed the dyudyunov/fix-certificate-generation-without-persistent-grades branch from f385591 to 9f8a1cd Compare March 21, 2022 10:38
@dyudyunov
Copy link
Copy Markdown
Contributor Author

Rebased on the latest master, please, review

@dyudyunov dyudyunov force-pushed the dyudyunov/fix-certificate-generation-without-persistent-grades branch from 9f8a1cd to 956c985 Compare March 21, 2022 11:05
@mariajgrimaldi
Copy link
Copy Markdown
Member

Hi there @dyudyunov and @natabene. I'll be reviewing this :)

If True - sends:
COURSE_GRADE_CHANGED signal to listeners and
COURSE_GRADE_NOW_PASSED if learner has passed course or
COURSE_GRADE_NOW_FAILED if learner is now failing course
Copy link
Copy Markdown
Member

@mariajgrimaldi mariajgrimaldi Mar 21, 2022

Choose a reason for hiding this comment

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

Can you help me list the cases where we want to send the signals? At least in this PR

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is probably any place where the CourseGradeFactory().read() used (because it calls the _update() if there is no persistent grade present).

Some examples of the signal receivers:

  • evaluation of any milestone relationships which are attached
    to the course grade;
  • Notifies the Credentials IDA about certain grades it needs for its records, when a grade changes;
  • update minimum grade requirement status;
  • generate a certificate task;
  • etc

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

evaluation of any milestone relationships which are attached
to the course grade;
Notifies the Credentials IDA about certain grades it needs for its records, when a grade changes;
update minimum grade requirement status;
generate a certificate task;
etc

But the whole is to not send the course grade related signals when generating the certificate task right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, it's the main goal to break the recursive grade recalculation when the certificate is generated

COURSE_GRADE_NOW_PASSED.send(
sender=CourseGradeFactory,
user=user,
course_id=course_data.course_key,
Copy link
Copy Markdown
Member

@mariajgrimaldi mariajgrimaldi Mar 21, 2022

Choose a reason for hiding this comment

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

why is using send_grade_signals=False better than sending the course_grade in the signal? and removing course_grade = _get_course_grade(user, course_key, send_grade_signals=False)? 🤔

NOTE: I haven't tested this, I'm just curious if you have

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nice question!
I didn’t try it, but now I want to 🙂
I will give it a try ASAP

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've tried this and can say that:

  • this works perfectly for certificates generated automatically
  • this will not work for certificates generated manually (this case requires to call _get_course_grade 'cause there is no other way to get grades)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

And calling it will lead to sending the signal and checking the certificate status again

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for testing that out!

@mariajgrimaldi
Copy link
Copy Markdown
Member

I believe this was the change that introduced this bug 🤔

@dyudyunov
Copy link
Copy Markdown
Contributor Author

dyudyunov commented Mar 24, 2022

I believe #28196 was the change that introduced this bug 🤔

Yes.
And I kind of understand why it was missing there.
Disabling the Persistent Grades is used rarely and AFAIK will be deprecated in the future

Copy link
Copy Markdown
Member

@mariajgrimaldi mariajgrimaldi left a comment

Choose a reason for hiding this comment

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

This is what I've tested until now with/without persistent grades, and with/without certificates.auto_certificate_generation.

  • Certificate generation through the progress page
  • Using cert_generation command
  • Generation through allowlist
    And this it's working correctly.

I'm OK with this change since it avoids sending the grade signals when getting them in the certificate generation process, as it was before the changes from last year.

@mariajgrimaldi
Copy link
Copy Markdown
Member

I'm interested to know what do you think about this approach? @felipemontoya

We explored another solution besides using the flag to send the grades signals, but it didn't work in all cases. Besides, PR #28196 explicitly added getting the course grade while generating the certificate, where the crucial thing is to get the grade not to send the signals.

Copy link
Copy Markdown
Member

@felipemontoya felipemontoya left a comment

Choose a reason for hiding this comment

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

I think that the approach is solid and it looks like it's working correctly. However I think it can be simplified a little as I left in the inline comment.
If that is not the case I would be fine with merging as is.

else:
COURSE_GRADE_NOW_FAILED.send(
sender=CourseGradeFactory,
if send_course_grade_signals:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It is the case, that every time that send_course_grade_signals is false, should_persist_grades is also false?

I'm thinking that if signals should not be sent when persistent grades is disabled, then we could make the if statement dependant on that variable and avoid creating a new control variable that needs to be accounted for and passed by the generation_handler.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We should send signals 1 time regardless of the persistent grade status.

The flow is:

  • user answers the question in the course;
  • recalculation of course grade is starting;
  • course grade now passing -> send the COURSE_GRADE_NOW_PASSED signal to start certificate generation;

here we should not break the signal sending

  • certificate generation process would like to receive the course grades so it calls the course_grade_factory again

here we should avoid sending the signals

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This makes sense. we should not send the signal within the processing of the same signal.

Since this was the last pending thing, I'll leave my approve

@mariajgrimaldi mariajgrimaldi merged commit 7dc60db into openedx:master Apr 8, 2022
@openedx-webhooks
Copy link
Copy Markdown

@dyudyunov 🎉 Your pull request was merged! Please take a moment to answer a two question survey so we can improve your experience in the future.

@edx-pipeline-bot
Copy link
Copy Markdown
Contributor

EdX Release Notice: This PR has been deployed to the staging environment in preparation for a release to production.

@edx-pipeline-bot
Copy link
Copy Markdown
Contributor

EdX Release Notice: This PR has been deployed to the production environment.

@dyudyunov dyudyunov deleted the dyudyunov/fix-certificate-generation-without-persistent-grades branch April 11, 2022 07:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merged open-source-contribution PR author is not from Axim or 2U

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants