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

refactor: inheritable authoring mixin callbacks for editing & duplication #33756

Conversation

DanielVZ96
Copy link
Contributor

@DanielVZ96 DanielVZ96 commented Nov 21, 2023

Description

Instead of applying duplicated logic after getattr, that logic is incorporated into inheritable methods of the StudioEditable block. Should not affect other roles besides developers.

Supporting information

Resolves #33715

Testing instructions

Courses:

  • Log into CMS and go to Any Course
  • Duplicate any Section
  • Make sure the Section is duplicated with all its children

Libraries:

  • Navigate to the libraries tab
  • Create a new library
  • Try adding, editing and duplicating a text, a video, and a problem component
  • Make sure all student previews are correctly displayed

Deadline

None

@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Nov 21, 2023
@openedx-webhooks
Copy link

openedx-webhooks commented Nov 21, 2023

Thanks for the pull request, @DanielVZ96! 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.

@kdmccormick
Copy link
Member

Thanks @DanielVZ96 ! When you're ready for review, let me know.

@kdmccormick kdmccormick self-requested a review November 21, 2023 04:19
@DanielVZ96 DanielVZ96 force-pushed the dvz/studio-editable-block-callback-refactor branch 2 times, most recently from 6a598c5 to 3b82bbe Compare November 25, 2023 00:07
…uplication

Solves openedx#33715

Instead of applying duplicated logic after getattr, that logic is
incorporated into inheritable methods of the StudioEditable block.

Risks:
- Assumes all cms blocks that are duplicated inherit from
StudioEditableBlock.
@DanielVZ96 DanielVZ96 force-pushed the dvz/studio-editable-block-callback-refactor branch from 49da980 to 5901e25 Compare November 25, 2023 01:03
@@ -296,46 +294,6 @@ def modify_xblock(usage_key, request):
)


class StudioPermissionsService:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved into services for reusability and reduce dependencies too

@@ -969,6 +970,7 @@
XModuleMixin,
EditInfoMixin,
AuthoringMixin,
StudioEditableBlock,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this allowed? this was the only way i could make sure all blocks would have the required callbacks

Copy link
Member

Choose a reason for hiding this comment

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

@DanielVZ96 Great question.

I would rather not add StudioEditableBlock to this list, as that would make render_children and get_preview_view_name available on every XBlock. I'd like to avoid adding methods to the base XBlock API except in the cases where we really need to (like editor_saved, post_editor_saved, studio_post_duplicate).

That said, you are right that we need some way of defining editor_saved, post_editor_saved, and studio_post_duplicate on every block. I was mistaken that we could just add them to StudioEditableBlock. Could I suggest adding them to AuthoringMixin instead, which is already mixed into all CMS blocks?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done! thanks for the suggestion

xmodule/vertical_block.py Outdated Show resolved Hide resolved
@DanielVZ96 DanielVZ96 marked this pull request as ready for review November 25, 2023 04:11
@DanielVZ96
Copy link
Contributor Author

@kdmccormick @Agrendalath ready for review

@DanielVZ96 DanielVZ96 force-pushed the dvz/studio-editable-block-callback-refactor branch from 33778c5 to b4a4c96 Compare November 25, 2023 04:35
Copy link
Member

@kdmccormick kdmccormick left a comment

Choose a reason for hiding this comment

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

Thanks for taking this on @DanielVZ96 .

As I understand it, this is how duplicate_block would now work:

  • duplicate_block calls dest.studio_post_duplicate....
    • by default, that calls dest.handle_children_duplication, which calls util.handle_children_duplication, and returns True.
    • but either dest.studio_post_duplicate or dest.handle_children_duplication could be overridden...
    • and if dest.studio_post_duplicate returns False, then we call util.handle_children_duplication directly.
  • Through all of the above, duplicate_block passes itself in as an argument, so that it can be recursively called on children.

I found that a bit hard to follow. I'd like to find a way to do this that'd yield a smaller stack of calls, and would avoid passing around recursive functions as args.

As an alternative, I think we could have AuthoringMixin define both:

  • studio_duplicate, containing the logic currently in duplicate_block; and
  • studio_post_duplicate, containing the logic currently in util.handle_children_duplication.

(The duplicate_block function itself would just become a thin wrapper around xblock.studio_duplicate.)

In other words, studio_duplicate would handle duplicating just the top-level block itself, and then it would delegate to studio_post_duplicate to handle children. The latter would call child.studio_duplicate(...) in order to recursively handle children, so we wouldn't need the duplication_function arg any more. Classes like LibraryContentBlock could still override studio_post_duplicate for special children handling.

This would eliminate the need for returning a children_handled boolean, and it would eliminate the handle_children_duplication layer.

What do you think?

cms/djangoapps/contentstore/utils.py Outdated Show resolved Hide resolved
xmodule/util/duplicate.py Outdated Show resolved Hide resolved
xmodule/library_content_block.py Outdated Show resolved Hide resolved
xmodule/library_content_block.py Outdated Show resolved Hide resolved
xmodule/library_content_block.py Outdated Show resolved Hide resolved
cms/djangoapps/contentstore/utils.py Outdated Show resolved Hide resolved
@kdmccormick
Copy link
Member

Thanks @DanielVZ96 . Your code looks good to me.

Through no fault of your own, we're currently wading through a couple different bugs around Library Content duplication and copying (one that we believe we've fixed, and another the we haven't yet). In light of that, I'd like to hold off on merging this refactoring until both bugs are solidly resolved.

From your end, feel free to consider this "Done", and I can take care of rebasing and merging it when we're ready. Thanks for your patience!

@kdmccormick kdmccormick self-assigned this Dec 11, 2023
@kdmccormick
Copy link
Member

@mphilbrick211 This PR is all set from the author side, but is blocked by some related work. I'll rebase & merge when it's unblocked.

@kdmccormick
Copy link
Member

FYI @bradenmacdonald -- this PR refactors the code that we are thinking of mirroring for copy-paste. In particular, it puts the callbacks into the class hierarchy rather than using getattr. I was going to wait until we land the copy-paste fix before merging this, but feel free to merge this first if you'd like.

By default, is a no-op. Can be overriden in subclasses.
"""

def studio_duplicate(
Copy link
Contributor

Choose a reason for hiding this comment

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

This function feels very much like runtime code to me. What's the rationale for putting it into an XBlock mixin rather than the runtime? It seems like the goal here is to make studio_duplicate "inheritable" but I'm not sure that's a good thing, nor does it seem to be used?

And what about duplicating in v2 content libraries - are we going to have a NewRuntimeAuthoringMixin that implements studio_duplicate and studio_post_duplicate for the new/blockstore/learning-core runtime?

I guess my assumption has been that methods on XBlocks or their mixins generally only interact with the core XBlock API, the runtime, and/or runtime services. Any code that's specific to modulestore should be abstracted behind a runtime method or a service. I know we haven't always followed this pattern consistently, but I thought we had been moving in that direction.

Copy link
Contributor

Choose a reason for hiding this comment

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

^ Note: This is more a question than a request for changes. And probably @kdmccormick and @Agrendalath are the ones best positioned to answer :)

Copy link
Member

Choose a reason for hiding this comment

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

@bradenmacdonald,

I guess my assumption has been that methods on XBlocks or their mixins generally only interact with the core XBlock API, the runtime, and/or runtime services. Any code that's specific to modulestore should be abstracted behind a runtime method or a service. I know we haven't always followed this pattern consistently, but I thought we had been moving in that direction.

Yeah, that makes sense to me. This PR is part of a bigger GitHub issue (#33640), which I haven't been tracking closely enough to answer this, though. @kdmccormick definitely has a better idea about the planned follow-ups.

Copy link
Member

Choose a reason for hiding this comment

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

Good point @bradenmacdonald . This does seem like runtime capability rather than a block capability. Until recently I didn't really grasp the distinction between those two :P

@DanielVZ96 and @Agrendalath , when I wrote up the issue for this refactoring, I thought it'd be pretty straightfoward and self-contained. In reality, it seems like it's wrapped up in the complexities of Content Libraries V2 and copy-paste. I should have warned you folks of that when you originally opened your draft PR; that's my bad. I need to spend the next couple of work days sorting out our plan for duplication, copy-paste, and import for library_content blocks, and then I'll come back with a recommendation of what to do here. Thanks for your responsiveness and patience so far.

def studio_post_duplicate(
self,
source_block,
store,
Copy link
Contributor

Choose a reason for hiding this comment

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

If we're changing the API of studio_post_duplicate, I would love to find a way to remove store, since it's specific to modulestore. The store parameter wasn't actually used prior to this PR.

Passing user also seems a bit redundant as the user is available via the user service, as an ID via self.scope_ids.user_id, and via self.runtime.user (non-standard but works in all Open edX runtimes).

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 agree, i really wanted to not inject the store dependency but this was the only way i figured i could keep the exact same behavior/code as before. The API is very recent and I doubt anyone uses it considering it's also internal.

I'll wait until @kdmccormick comes back to see what we can do to improve this API.

@DanielVZ96
Copy link
Contributor Author

@kdmccormick hey, do you have any updates so we can move this forward?

@kdmccormick
Copy link
Member

I'll come back and re-review this once #34066 and openedx/XBlock#718 merge, which I think should take about a week.

@DanielVZ96
Copy link
Contributor Author

@kdmccormick hey, just to be sure, one of the PRs is merged, but the other one is stale since like 3 weeks ago. do you have an updated eta?

@kdmccormick
Copy link
Member

@DanielVZ96 I just managed to merge the second one today--phew.

@bradenmacdonald Once we store the upstreams and defaults of LibraryContentBlock children, we should be able to copy-paste and duplicate LibraryContentBlocks just like any other XBlock. So, it seems like we would no longer need a studio_duplicate hook--we could just delete it after implementing the linked ADR, right?

If that sounds right to you, do you agree that this PR should leave studio_duplicate alone, but still add declarations for the editor_saved and post_editor_saved hooks?

@bradenmacdonald
Copy link
Contributor

bradenmacdonald commented Mar 20, 2024

@kdmccormick We [will] still need the XBLOCK_DUPLICATED event for duplicating the tags, since we won't be storing the tags in the OLX, but we won't need to call any method like studio_post_duplicate on the XBlock instances themselves.

I'm in favor of:

  • Leaving studio_duplicate alone - I don't think that sort of code belongs in the XBlock but rather the runtime, where it already was.
  • Possibly removing the studio_post_duplicate hook in a future PR if there's no remaining use case for it.
  • Keep the new declarations for the editor_saved and post_editor_saved hooks that this PR is adding (assuming there are good use cases for them).

@bradenmacdonald
Copy link
Contributor

@kdmccormick What's left before we can get this PR merged?

@kdmccormick
Copy link
Member

Leaving studio_duplicate alone - I don't think that sort of code belongs in the XBlock but rather the runtime, where it already was.

Agreed, please revert the studio_duplicate changes from this PR. Sorry for sending you that direction @DanielVZ96 , that was poor architectural advice on my part.

Possibly removing the studio_post_duplicate hook in a future PR if there's no remaining use case for it.

I believe there will be no remaining use cases once this ADR is implemented, so it would be good to then remove the hook. I'll add that to the running list of content libraries cleanup items.

Keep the new declarations for the editor_saved and post_editor_saved hooks that this PR is adding (assuming there are good use cases for them).

Agreed. These are a nice improvement.

@DanielVZ96 DanielVZ96 force-pushed the dvz/studio-editable-block-callback-refactor branch from 136e945 to e94fa52 Compare July 7, 2024 14:47
@DanielVZ96 DanielVZ96 force-pushed the dvz/studio-editable-block-callback-refactor branch from 46b952f to 5312b5e Compare July 8, 2024 07:35
@@ -47,7 +48,6 @@
from cms.djangoapps.contentstore.xblock_storage_handlers.view_handlers import (
handle_xblock,
create_xblock_info,
load_services_for_studio,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's an indirect import, should be going directly to contentstore.utils

@@ -45,7 +45,7 @@
wrap_xblock_aside
)

from ..utils import get_visibility_partition_info, StudioPermissionsService
from ..utils import StudioPermissionsService, get_visibility_partition_info
Copy link
Contributor Author

Choose a reason for hiding this comment

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

from formatter

@DanielVZ96
Copy link
Contributor Author

DanielVZ96 commented Jul 8, 2024

@kdmccormick @bradenmacdonald I think this is finally ready for one last review

@bradenmacdonald
Copy link
Contributor

@DanielVZ96 Those changes look good to me! Do we have an XBlock that's ready to use these new APIs? I just don't want to merge without a concrete use case.

Copy link
Member

@kdmccormick kdmccormick left a comment

Choose a reason for hiding this comment

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

Looks great! I have not tested, but I trust that you have.

@kdmccormick
Copy link
Member

@bradenmacdonald editor_saved is already used by SplitTestBlock and VideoBlock. post_editor_saved is used by the (legacy) LibraryContentBlock. This PR just formalizes the methods into the CMS XBlock inheritance hierarchy (well, kinda-- it's as formal as we can get with the XBlockMixin system).

For what it's worth, I don't see this as a public API, just a formalization of an internal edx-platform API.

Copy link
Contributor

@bradenmacdonald bradenmacdonald left a comment

Choose a reason for hiding this comment

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

OK, thanks for the explanation @kdmccormick

@bradenmacdonald bradenmacdonald merged commit 78b691b into openedx:master Jul 25, 2024
49 checks passed
@openedx-webhooks
Copy link

@DanielVZ96 🎉 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
Contributor

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

@edx-pipeline-bot
Copy link
Contributor

2U Release Notice: This PR has been deployed to the edX production environment.

1 similar comment
@edx-pipeline-bot
Copy link
Contributor

2U Release Notice: This PR has been deployed to the edX production environment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
open-source-contribution PR author is not from Axim or 2U
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Refactor StudioEditableBlock's callbacks for editing & duplication
6 participants