-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Add block completion value as optional field in course_blocks.api. #16674
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 block completion value as optional field in course_blocks.api. #16674
Conversation
|
Thanks for the pull request, @tomaszgy! It looks like you're a member of a company that does contract work for edX. If you're doing this work as part of a paid contract with edX, you should talk to edX about who will review this pull request. If this work is not part of a paid contract with edX, then you should ensure that there is an OSPR issue to track this work in JIRA, so that we don't lose track of your pull request. |
bradenmacdonald
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this is on the right track :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, please go ahead and do that now :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we just call this "completion" ? Then it will match the request parameter, and we can use the same field for storing aggregate completions in the future once we implement that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I recommend putting trailing commas on lines like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try using BlockCompletion.objects.submit_completion(...) instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! When I used map_into_course in the transform() method as you suggested in OC-3114, the problem is solved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tomaszgy Great - but even still, I think we should still be using submit_completion(...) here, so that BlockCompletion objects get created consistently using that method everywhere in the codebase. You may also need to use this decorator on this test method, to enable the completion API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
|
@tomaszgy You've still got a quality issue: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tomaszgy Adding this new transformer to BlocksAPITransformer causes it to generate its data every time a course's block structure is created, which results in one additional MySQL query per course when using the course blocks API. That is why a number of tests are failing.
Since we generally won't need to read this data and it doesn't affect block visibility or anything, I think it would be better to only execute this transformer when its field has been explicitly requested, which would involve adding logic here to only add this new transformer when completion is in requested_fields (or pass requested_fields to this class's init method and keep the logic here).
@nasthagiri does that make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
1902951 to
465e0a6
Compare
|
jenkins run quality |
jcdyer
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code looks good, with a couple of minor issues mentioned. When I try to access the API, though, I get a 500 error: "TransformerException at /api/courses/v1/blocks/: The following requested transformers are not registered: set(['blocks_api:completion'])"
Do I need to configure something?
| include_special_exams = False | ||
| include_completion = requested_fields is not None and 'completion' in requested_fields | ||
| if requested_fields is not None and 'special_exam_info' in requested_fields: | ||
| include_special_exams = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: There are multiple lines here checking if requested_fields is not None. Also, include_completion is injected into the middle of the logic for checking special_exams (L54 & LL57-58). Consider changing LL54-58 to
if requested_fields is None:
requested_fields = []
include_completion = 'completion' in requested_fields
include_special_exams = 'special_exam_info' in requested_fields
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
| COMPLETION = 'completion' | ||
|
|
||
| def __init__(self): | ||
| pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you deliberately trying to suppress the behavior of the superclass's __init__() method, or is this just vestigial code? This could either use an explanatory comment, or should be deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well spotted :-) Actually superclass doesn't define __init__ and inherits itself from object but I'll add a call to superclass's constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're not doing anything else, just delete the definition altogether. If you leave it out, it will search the superclasses automatically, just like for any other method.
| * completion: (float or None) The level of completion of the block. | ||
| Its value can vary between 0.0 and 1.0 or be equal to None | ||
| if block is not completable. | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should mention that the value is only included if "completion" included in requested_fields. See graded or format below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
|
Hey there! Thanks for these changes. Overall, they look great. When you have a moment, please read the Transformer Version Updates section of the wiki. Essentially, if you can merge these changes in 2 phases: initially, setting the Also, you'll want to update setup.py to include the new Transformer. |
| SupportedFieldType( | ||
| BlockCompletionTransformer.COMPLETION, | ||
| BlockCompletionTransformer, | ||
| 'completion' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Future note to self (and others): It would be great if we can make this pluggable, so new Transformers don't need to manually update this list - and can be automatically extracted from registered transformers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nasthagiri I remember implementing a registry for something once, and I found two basic ways of doing it: The first is by creating a registry class with a register() decorator function:
class Registry(object):
def __init__(self):
self.registered = []
def register(self, registrant):
self.registered.append(registrant)
return transformer
transformers = Registry()
@transformers.register
class MyTransformer(BlockTransformer):
pass
The other uses a metaclass defined on the superclass. Then when the class gets constructed, it is automatically registered in the metaclass. Django models use this method.
The major differences are:
- that the decorator is visible to the programmer, while the metaclass is hidden.
- programmers can opt-out of the decorator by not including it, but not the metaclass (unless the metaclass explicitly provides a hook, like writing):
class Registerable(Registry):
register = False
The downside of allowing programmers to opt out (or more accurately opt not to opt-in) is that you also make it possible to forget to opt-in. - that decorators are easier to read and write than metaclasses.
| # create ordered list of transformers, adding BlocksAPITransformer at end. | ||
| transformers = BlockStructureTransformers() | ||
| include_special_exams = False | ||
| include_completion = requested_fields is not None and 'completion' in requested_fields |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Future note to self (and others): If there are any great ideas of making this automatically pluggable as well, welcoming ideas/contributions.
f487dc0 to
d79a9ea
Compare
|
@jcdyer and @nasthagiri - thanks for your reviews! I've just addressed your comments. Please have a look and if all's good I'll squash the commits. @jcdyer, thanks for spotting @nasthagiri, I've removed for now |
|
Thanks @tomaszgy
We hope that works for your team. Thanks. |
|
@tomaszgy There are a bunch of test failures. Do you know what's causing them? |
|
@nasthagiri, thanks for the scenario! @jcdyer, indeed, there're lots of tests failing. Many of them (e.g. this one) fail due to |
|
@tomaszgy thanks for bringing up the exception issue. Can you change that particular block_structure code to remove the check for READ_VERSION=0? You are running into it since you are the first to introduce a new transformer since we designed the cache-invalidation strategy. We used that strategy for updating existing transformers, but not (yet) for introducing a new one. Thanks. |
|
Thanks for the suggestion, @nasthagiri! I've just removed the check. |
|
@nasthagiri one of the bokchoy tests ( In the meantime I opened Second PR (#16859) with CC @jcdyer |
|
jenkins run bokchoy |
|
Thanks @mtyaka! :) |
Signed-off-by: Tomasz Gargas <tomasz@opencraft.com>
…ansformer. Signed-off-by: Tomasz Gargas <tomasz@opencraft.com>
db3940d to
d4edf33
Compare
|
@nasthagiri Just a status update: I've squashed and rebased this PR, now bokchoy tests pass, too. If there's anything else I should change, please let me know. If all's good, please feel free to merge it :) Second PR with |
|
@nasthagiri, thanks! Just rebased Second PR to make it ready for merge. |
|
Devops ticket created: https://openedx.atlassian.net/browse/DEVOPS-6904 |
|
EdX Release Notice: This PR has been deployed to the staging environment in preparation for a release to production on Thursday, December 14, 2017. |
|
Updating here: We've run the generate_course_blocks command on Stage. I've verified there are no exceptions raised by the new transformer. |
|
EdX Release Notice: This PR has been deployed to the production environment. |
|
Update: The management command was run on production after yesterday's release. We're good to move forward with the next PR. |
Allow
completioninrequested_fieldsThis PR extends Course Blocks API to allow user to pass
completionintorequested_fields. API returns block level completion data for each block (0.0 - 1.0 or None if not completable).JIRA tickets: Implements OC-3114.
Discussions: This PR is a part of the implementation of the Completion API.
Dependencies: It updates version
xblockin requirements to1.1.1.Sandboxes URLs:
LMS: https://pr16674.sandbox.opencraft.hosting
Studio: https://studio-pr16674.sandbox.opencraft.hosting/
Merge deadline: TBD
Testing instructions:
I
lms/djangoapps/course_api/blocks/transformers/tests/test_block_completion.pywhich is a part of this PR.II
BlockCompletionas in Block completion model #16047:completionvalues:http://localhost:18000/api/courses/v1/blocks/?course_id=course-v1:edX%2BDemoX%2BDemo_Course&all_blocks=true&requested_fields=completion&depth=10
(Make sure the id in the
course_idis the same ascourse_keyin point 2)Author notes and concerns: None
Reviewers
Settings
As
setup.pyfile has been changed to register new transformer you'll need to dopython setup.py installinside your container.EDIT:
Rebased on master from 1902951aeb3c0e049e221683e8dc11985102a7ee.
Squashed and then rebased from db3940d onto master.
Signed-off-by: Tomasz Gargas tomasz@opencraft.com