[BB-10108] Refactor Branching XBlock to include Ren'Py like features#10
[BB-10108] Refactor Branching XBlock to include Ren'Py like features#10
Conversation
Needed as we are scoring per choice and cyclic nodes can result in score loops making highest score unbound
Using topo+dp to find max path sum
samuelallan72
left a comment
There was a problem hiding this comment.
Thanks @pkulkark ! This is a pretty large PR, so I've made a first pass, and left several code review comments inline. I haven't done a deep review of code logic yet, or played around too much with the UI.
Also a comment on UI/UX:
Saving when there's an issue with the main background image is confusing - the error message may be hidden because it's in the body rather than a popup or next to the save button, and the studio still displays the saving animation in the lower right corner:
This is confusing, as you are thinking that it's just taking a long time to save, when in fact validation has failed and you need to scroll down to view the error:
Maybe these errors too need to go in the bottom bar or as a popup message?
branching_xblock/branching_xblock.py
Outdated
| return node | ||
|
|
||
| @staticmethod | ||
| def _coerce_choice_score(raw_score): |
There was a problem hiding this comment.
Rather than coercing the score everywhere, can we simply validate it in studio_submit (or a function only called from that), and trust the data structures after that?
In general I see a lot of noise working with data in the backend, due to all the validating and normalising happening. It would be nice to validate and normalise in one place, where it's submitted from the user (ie. studio_submit), and then treat it as known data types everywhere else.
If we need validation for the case where the xblock is imported from olx, then perhaps we can use a standard validation method to validate everything - like validate or validate_field_data - ref https://docs.openedx.org/projects/xblock/en/latest/xblock-utils/index.html or https://docs.openedx.org/projects/xblock/en/latest/xblock.html#xblock.core.XBlock.validate . I'm not sure exactly how these interact, but either way, if we can move all validation related things to the boundaries, that will leave things cleaner in the rest of the code.
branching_xblock/branching_xblock.py
Outdated
| help="Whether the background image is decorative" | ||
| ) | ||
|
|
||
| max_score = Float( |
There was a problem hiding this comment.
Score fields appear to be treated as integers everywhere else, so can these be integer fields instead?
branching_xblock/branching_xblock.py
Outdated
| return "Final grade range must end at 100." | ||
| return None | ||
|
|
||
| def _normalize_grade_ranges(self, raw_grade_ranges, strict=False): |
There was a problem hiding this comment.
This overlaps with _validate_grade_ranges above. Can we simply drop this and use something like:
if error := _validate_grade_ranges(raw_ranges):
raise ValidationError(error)
else:
self.grade_ranges = raw_ranges
I'm not sure why we need so much normalising of values throughout? Can we just validate, and ensure the frontend editor always submits the expected types of values?
| new_id = f"node-{uuid.uuid4().hex[:6]}" | ||
| else: | ||
| new_id = old_id | ||
| new_id = f"node-{uuid.uuid4().hex[:6]}" if old_id.startswith('temp-') or not old_id else old_id |
There was a problem hiding this comment.
It would be interesting to explore letting the frontend provide unique ids for the nodes, to avoid this node id re-assigning and mapping.
a31f6f6 to
69cfac0
Compare
717966f to
aaf609b
Compare
- move studio save validation to a single backend validation path - return structured field/global/node errors for UI rendering - remove frontend normalization/coercion and rely on backend responses
aaf609b to
aa3e3e7
Compare
Good catch. I've fixed this now. |
|
Thanks @pkulkark, I see your responses ; let me know when it's ready for another review pass. |
branching_xblock/branching_xblock.py
Outdated
|
|
||
| def _normalize_scenario_nodes(self) -> None: | ||
| """ | ||
| Normalize stored scenario node payloads once per nodes object. |
There was a problem hiding this comment.
Could you expand these docs with information about why this is required, what it actually does, and a note about potentially removing it in the future when we no longer need to normalise old data?
Also, maybe we can run the normalising in the init method, so we don't need to remember to use it in the various view methods?
There was a problem hiding this comment.
I've added more information in the doc strings - let me know if that's good enough.
I don't think we should move the normalising in an init method, because we'd risk invalid stored values being auto-corrected during learner requests, which hides data issues instead of surfacing them through authoring validation. Keeping normalization in Studio/save flow will avoid that. Alternative option is to drop the whole normalizing and assume the old saved values wouldn't be needed to be preserved but i'm a bit hesitant to do that since this xblock rolls in the old branching xblock too.
There was a problem hiding this comment.
Hmm if it's in init, then any invalid stored values will be automatically corrected whenever they're saved next. Currently, existing components may be in a slightly broken state after upgrade, until they are edited and re-saved in studio.
Alternative option is to drop the whole normalizing and assume the old saved values wouldn't be needed to be preserved but i'm a bit hesitant to do that since this xblock rolls in the old branching xblock too.
Is this definitely to be a in-place update to the branching xblock? If not, and if we don't need to have backward compatibility, then perhaps a better option might be to change the xblock label (to register it as a different/new xblock) and maybe even the package name, and drop normalising altogether.
There was a problem hiding this comment.
Yes it was part of the requirement to have this as an in-place update.
|
@pkulkark I'm still seeing the red general error feedback text within the main content:
I'm not sure where that should go though, as it's not in the designs. |
samuelallan72
left a comment
There was a problem hiding this comment.
@pkulkark in general, it works as expected functionally, thanks! I flagged a few things though - please see my inline comments. :)
b191692 to
16a805a
Compare
16a805a to
855077c
Compare
c58cc0d to
ab9c996
Compare
faf8448 to
c1540a9
Compare
samuelallan72
left a comment
There was a problem hiding this comment.
@pkulkark everything looks pretty good to me. 👍 I have left a couple of comments/suggestions, but otherwise feel free to response as you see fit and merge.
This was a very large change, so I may have missed things.
- I tested this: read the code, tested manually on the sandbox
- I read through the code
- I checked for accessibility issues (my screenreader wasn't working, so I didn't test that. I did test keyboard controls though.)
- Includes documentation
Co-authored-by: Samuel Allan <samuel.allan@fastmail.com>
4e989aa to
f5c81e1
Compare
f5c81e1 to
2f3b7a0
Compare



Description
This PR refactors Branching XBlock to introduce Ren’Py-like branching/storytelling behavior, with updated authoring and learner UX aligned to the design reference in Figma: Figma design file.
Key updates included:
left_image_url,right_image_url, overlay text, background image settings).Supporting Information
OpenCraft Internal Jira task: BB-10108
Testing Instructions
Additional notes from author
mainboth changed the sameenable_hints→enable_reset_activityareas. Although 3 related commits frommainwere cherry-picked here (ab2770c,8e5d707,c05093e), Git still reported conflicts because cherry-picks copy changes but not commit ancestry. To reconcile both ancestry and overlapping edits,origin/mainwas merged into this branch and conflicts were resolved in merge commitfebfb7b.