Skip to content

Conversation

@navinkarkera
Copy link
Contributor

@navinkarkera navinkarkera commented Apr 19, 2025

Description

Sync unit containers from library to courses.

Test instructions:

Run the new integration tests, which should cover most things:

DJANGO_SETTINGS_MODULE=cms.envs.tutor.test pytest cms/djangoapps/contentstore/rest_api/v2/views/tests/test_downstream_sync_integration.py

Manual Test instructions:

Use curl or any other tool to post request, NOTE: below example does not contain csrftoken, studio_session_id etc. which you can add. TIP: use developer console to copy any request as curl request in browser.

curl 'http://studio.local.openedx.io:8001/xblock/' \
  -H 'Accept: application/json, text/plain, */*' \
  -H 'Accept-Language: en-US,en;q=0.6' \
  -H 'Connection: keep-alive' \
  -H 'Content-Type: application/json' \
  --data-raw '{"category":"vertical","parent_locator":"block-v1:OpenedX+DemoX+DemoCourse+type@sequential+block@727232bd02b54af08953c66eb2246d00","library_content_key":"lct:SampleTaxonomyOrg1:lib1:unit:images-57b324"}' \
  --insecure

@openedx-webhooks
Copy link

openedx-webhooks commented Apr 19, 2025

Thanks for the pull request, @navinkarkera!

This repository is currently maintained by @openedx/wg-maintenance-edx-platform.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

Details
Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

notices = []
# Store final children keys to update order of components in unit
children = []
for i in range(len(upstream_children)):
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit for later: we can use enumerate() here.

Comment on lines +538 to +542
if isinstance(upstream_key, LibraryUsageLocatorV2):
lib_block = sync_from_upstream_block(downstream=downstream, user=request.user)
static_file_notices = import_static_assets_for_library_sync(downstream, lib_block, request)
store.update_item(downstream, request.user.id)
else:
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit for later: Probably worth pulling the container sync into a different function and having this one call that.

Copy link
Contributor

@bradenmacdonald bradenmacdonald Apr 24, 2025

Choose a reason for hiding this comment

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

I agree. Also, I want to move all the sync stuff into one upstream_sync app instead of having some key bits of the code randomly in xblock_storage_handlers (?) and the overloaded /xblock/ REST API endpoint.

Comment on lines +576 to +577
# This downstream block was added, or deleted from upstream block.
store.delete_item(child.usage_key, user_id=request.user.id)
Copy link
Contributor

Choose a reason for hiding this comment

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

Clarification: This is the part that (for the MVP) will delete any local additions to a Unit when the next upstream sync happens, right?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, this deletes two different things:

  1. local additions in the course - will get deleted on next sync
  2. blocks deleted upstream

"""
syncable_fields = _get_synchronizable_fields(upstream, downstream)
customizable_fields = set(downstream.get_customizable_fields().keys())
isVideoBlock = downstream.usage_key.block_type == "video"
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: I'm surprised our linting didn't complain about camel-cased var here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Fixed. But yeah, that's suspicious....

Comment on lines +130 to +132
# Note: if we expect that an upstream may not be set at all (i.e. we're just inspecting a random
# unit that may be a regular course unit), we don't want to log this, so set log_error=False then.
if log_error:
Copy link
Contributor

Choose a reason for hiding this comment

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

Simply viewing a regular unit in Studio (even before this PR) was resulting in a ton of these warnings:

upstream_sync.py:133 - Tried to inspect an unsupported, broken, or missing downstream->upstream link: 'block-v1:BradenX+CP200+23+type@vertical+block@9d1d7e5c0b4a42958f7e4174879ec1e2'->'None'
... cms.lib.xblock.upstream_sync.NoUpstream: Content is not linked to a Content Library.

So I updated the code to suppress the logging in general. Most things don't have an upstream so it's expected.

Copy link
Contributor

@ormsbee ormsbee left a comment

Choose a reason for hiding this comment

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

I have a lot of nits for follow-ups, but the only blocking concern I have in the short term is the part in the models.py file where it looks like we're always saying that there are changes:

https://github.com/openedx/edx-platform/blob/4a69f30e229ad4ff44126bdada5a31ba3736fd19/cms/djangoapps/contentstore/models.py#L247-L256

@bradenmacdonald: If you've determined that this still gives us the correct behavior, then I'd really like to see some comment explaining what's going on. It's either a bug or it's really confusing in its current state.

@ormsbee ormsbee dismissed their stale review April 24, 2025 18:08

Because I misunderstood the comment.

Copy link
Contributor

@ormsbee ormsbee left a comment

Choose a reason for hiding this comment

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

I have a number of nits for follow-up, but nothing blocking.

@bradenmacdonald
Copy link
Contributor

bradenmacdonald commented Apr 24, 2025

@ormsbee

If you've determined that this still gives us the correct behavior, then I'd really like to see some comment explaining what's going on. It's either a bug or it's really confusing in its current state.

I didn't really get into the ComponentLink / link tracking personally while working on this PR. It's kind of an ancillary index just used to quickly retrieve all the library content in the course, but doesn't directly affect the core upstream sync system.

But what I believe is going on here is this function is ultimately only ever called during XBLOCK_CREATED/XBLOCK_UPDATED signal handlers (where downstream has changed). So there are always changes of some sort when this is called.

The code is very confusing but I think what it's trying to say is this:

  1. Currently, whenever you modify a downstream XBlock, it sets the ComponentLink "last modified at" time to the time the XBlock was modified, even if you just modified a random field like max_attempts which is excluded from syncing.
  2. Ideally, in the future, whenever you modify a downstream XBlock, the code will change the ComponentLink "last modified at" time only if something relevant to the upstream sync was modified, like the upstream source was changed, or the sync was dismissed (skip the update for this particular version), or ????????? .

@bradenmacdonald
Copy link
Contributor

Thanks everyone who collaborated on this PR, @navinkarkera @pomegranited @ormsbee !

@navinkarkera can you please work on a follow-up PR(s) for some of the cleanups that @ormsbee has identified?

@bradenmacdonald bradenmacdonald merged commit 1cd73d1 into openedx:master Apr 24, 2025
49 checks passed
@github-project-automation github-project-automation bot moved this from Waiting on Author to Done in Contributions Apr 24, 2025
@bradenmacdonald bradenmacdonald deleted the navin/fal-4077/link-unit branch April 24, 2025 18:41
@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.

@navinkarkera
Copy link
Contributor Author

navinkarkera commented Apr 25, 2025

@ormsbee

If you've determined that this still gives us the correct behavior, then I'd really like to see some comment explaining what's going on. It's either a bug or it's really confusing in its current state.

I didn't really get into the ComponentLink / link tracking personally while working on this PR. It's kind of an ancillary index just used to quickly retrieve all the library content in the course, but doesn't directly affect the core upstream sync system.

But what I believe is going on here is this function is ultimately only ever called during XBLOCK_CREATED/XBLOCK_UPDATED signal handlers (where downstream has changed). So there are always changes of some sort when this is called.

The code is very confusing but I think what it's trying to say is this:

1. Currently, whenever you modify a downstream XBlock, it sets the `ComponentLink` "last modified at" time to the time the XBlock was modified, even if you just modified a random field like `max_attempts` which is excluded from syncing.

2. Ideally, in the future, whenever you modify a downstream XBlock, the code will change the `ComponentLink` "last modified at" time _only if_ something relevant to the upstream sync was modified, like the `upstream` source was changed, or the sync was dismissed (skip the update for this particular version), or ????????? .

@bradenmacdonald @ormsbee Sorry for this very confusing piece of code. You guessed it right @bradenmacdonald. At first, has_changes was set to False before the loop: 711d6aa#diff-47a27860758e70cabb21ef00cb29fb90efa277773d45546adb679ba05587f3bfR178-R185

But while working on the frontend, I came across the requirement of sorting the links based on recently modified downstream blocks. XBlocks do not store modified dates so I updated this part to update the links modified time any time downstream block is updated and use it as base for sorting the links in frontend.

@navinkarkera
Copy link
Contributor Author

@bradenmacdonald @ormsbee Created #36599 to address comments.

@ormsbee
Copy link
Contributor

ormsbee commented Apr 25, 2025

XBlocks do not store modified dates

If you need this from the modulestore, can you call block.edited_on from the EditInfoMixin?

https://github.com/openedx/edx-platform/blob/e5f7a027e4ff1695454664923128ff4f4426ad91/xmodule/modulestore/edit_info.py#L22-L27

@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.

@navinkarkera
Copy link
Contributor Author

@ormsbee Nice! That resolves it: ac708ab

@navinkarkera
Copy link
Contributor Author

Created openedx/frontend-app-authoring#1865 to compliment above change in frontend.

UsamaSadiq pushed a commit that referenced this pull request May 14, 2025
* feat: library unit sync
* feat: create component link only for component xblocks
* feat: container link model
* feat: update downstream api views
* feat: delete extra components in container on sync (not working)
* fix: duplicate definitions of LibraryXBlockMetadata
* test: add a new integration test suite for syncing
* feat: partially implement container+child syncing
* fix: blockserializer wasn't always serializing all HTML block fields
* feat: handle reorder, addition and deletion of components in sync

Updates children components of unit in course based on upstream unit,
deletes removed component, adds new ones and updates order as per
upstream.

* feat: return unit upstreamInfo and disallow edits to units in courses that are sourced from a library (#773)
* feat: Add upstream_info to unit
* feat: disallow edits to units in courses that are sourced from a library (#774)

---------

Co-authored-by: Jillian Vogel <jill@opencraft.com>
Co-authored-by: Rômulo Penido <romulo.penido@gmail.com>

* docs: capitalization of XBlock

Co-authored-by: David Ormsbee <dave@axim.org>

* refactor: (minor) change python property name to reflect type better

* fix: lots of "Tried to inspect a missing...upstream link" warnings

when viewing a unit in Studio

* docs: mention potential REST API for future refactor

* fix: check if upstream actually exists before making unit read-only

* chore: fix camel-case var

* fix: test failure when mocked XBlock doesn't have UpstreamSyncMixin

---------

Co-authored-by: Braden MacDonald <braden@opencraft.com>
Co-authored-by: Chris Chávez <xnpiochv@gmail.com>
Co-authored-by: Jillian Vogel <jill@opencraft.com>
Co-authored-by: Rômulo Penido <romulo.penido@gmail.com>
Co-authored-by: Braden MacDonald <mail@bradenm.com>
Co-authored-by: David Ormsbee <dave@axim.org>
marlonkeating pushed a commit that referenced this pull request Jul 15, 2025
* feat: library unit sync
* feat: create component link only for component xblocks
* feat: container link model
* feat: update downstream api views
* feat: delete extra components in container on sync (not working)
* fix: duplicate definitions of LibraryXBlockMetadata
* test: add a new integration test suite for syncing
* feat: partially implement container+child syncing
* fix: blockserializer wasn't always serializing all HTML block fields
* feat: handle reorder, addition and deletion of components in sync

Updates children components of unit in course based on upstream unit,
deletes removed component, adds new ones and updates order as per
upstream.

* feat: return unit upstreamInfo and disallow edits to units in courses that are sourced from a library (#773)
* feat: Add upstream_info to unit
* feat: disallow edits to units in courses that are sourced from a library (#774)

---------

Co-authored-by: Jillian Vogel <jill@opencraft.com>
Co-authored-by: Rômulo Penido <romulo.penido@gmail.com>

* docs: capitalization of XBlock

Co-authored-by: David Ormsbee <dave@axim.org>

* refactor: (minor) change python property name to reflect type better

* fix: lots of "Tried to inspect a missing...upstream link" warnings

when viewing a unit in Studio

* docs: mention potential REST API for future refactor

* fix: check if upstream actually exists before making unit read-only

* chore: fix camel-case var

* fix: test failure when mocked XBlock doesn't have UpstreamSyncMixin

---------

Co-authored-by: Braden MacDonald <braden@opencraft.com>
Co-authored-by: Chris Chávez <xnpiochv@gmail.com>
Co-authored-by: Jillian Vogel <jill@opencraft.com>
Co-authored-by: Rômulo Penido <romulo.penido@gmail.com>
Co-authored-by: Braden MacDonald <mail@bradenm.com>
Co-authored-by: David Ormsbee <dave@axim.org>
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

Archived in project

Development

Successfully merging this pull request may close these issues.

7 participants