@jmakowski@e0d
Is there a way to sync selected library content to multiple courses without having to actually go into each course and click Update Available for individual Library Content course components?
This takes time and we would like to be able to publish component/unit changes from the Library out to those 10 courses from the Library to help speed up time. Having the ability to select specific courses to push the update out would also help, in that maybe only a few courses need to receive the update. Is there a way to do this, or is it scheduled as an upcoming feature?
For example, if we have 10 courses that use the same problem in the Library and we need to make a small change to the problem, currently we’d have to perform the Update Available change and publish it for all 10 courses. This is described in the documentation below.
Any sort of bulk sync or auto sync is not a capability that we have - yet. It’s something we’ve loosely discussed, but not yet prioritized. It’s good to hear your use case for it. Curious, is there an automated component you’d be looking for here? Ie, you select the 10 courses once, and then anytime a change is made to content in that course, it auto-syncs. Or would there need to be a manual process to select those courses for each change, so it becomes more of a semi-manual bulk sync action.
The end-point framework should be mature enough that you could build what you need as a plugin, on potentially a faster timeline than our release cycles. I’d encourage you to connect with @braden to explore this option. It’s something we could discuss integrating into the core later on.
The 10 courses that I mentioned are really just clones of the same content, but they serve different institutions. The reason for this is to keep the gradebook isolated between schools and help with separating analytics. Library Content (beta) bulk or auto sync seems like a win to write once in the library and deploy to many courses.
I do like your recommendation to select the 10 courses one time, then push updates out from the Library Content (beta) components/units when a change is made. Most of the time, we’d prefer the automatic option to the bulk sync.
It may make sense that a course gets added to the auto sync queue whenever it adds the Library Content (beta) component/unit to the course or a new course re-run is created that uses the library content. That way the course developers don’t have to worry about that additional linking step for auto-sync to work. I could see having both options, bulk sync or auto sync, available. Maybe this is a toggleable feature with the library component/unit, and the bulk sync option appears to allow the course developer to decide which courses the update can go out to. I’ll have to talk with our team to see if they would even care about a bulk sync option or not.
Here are the Library Content (beta) component/unit publishing options that may make sense:
Auto Sync
Automatically push content to courses that have included the library component/unit as course Legacy Content (beta) components to the course. Legacy Content (beta) course components (including units) are added to the update queue whenever added to the course or a course re-run is performed. Likewise, when the library component/unit is removed from the course, it’s taken out of the library auto sync update queue.
Bulk Sync
Push content to a specific set of courses that have included the library component/unit. A list of courses_ids and names exists with multi-select available. Everything is selected by default. Uncheck to only allow a few courses to push updates out. When the library component/unit is removed from the course, it’s taken out of the library bulk sync update queue.
Course Pull to Update (default)
Pull changes down from the library component/unit) using the course component Update Available action then Publish the Unit content out whenever content in the library has changed. This mimics the current behavior in the platform.
What have you discussed with bulk or auto sync from the Library Content (beta)?
Have you looked into CCX for your use case? It was designed for the case where you have one master course for content purposes, and an arbitrary number of sub-courses led by “coaches” which are essentially course staff for LMS purposes, but have very limited ability to alter the content (no Studio access, but they can change the scheduling and hide certain content for their sub-course). For the most part, the LMS treats each coach-led course as a separate course (separate enrollment, gradebook, etc.).
It’s definitely not the best-documented feature, but MIT and Pearson use this functionality so @pdpinch or @scottrish might be able to point to better docs or talks about it.
Thanks for this intel Zach, super helpful. I’m going to add these use cases to our backlog. It’s something that will take some UI thought/design as well. At the moment this isn’t on the plan for Verawood, more likely Willow. Is it something you’d be interested in/have the resources to explore building your own plugin for, in the interim?
Dave mentions CCX below. That is a feature you could leverage in current state. However, longer team, we are looking to replace CCX with a more fully-featured Course Template feature. In fact, I’m gathering use cases across the community for that now, so we can get a product proposal kicked off. Feel free to add any use cases relevant to you: Master Course Template - use cases - Google Docs
@dave
Thanks for suggesting CCX. I think that I looked at this long time ago but it never clicked on how to use it. Because Jenny mentioned that this may be replaced with Course Templates, I’m not sure if we should pursue this but we can take a look.
@jmakowski
I’d imagine that we most likely might pursue exploring building out own plugin to build out this Library Content (Beta) syncing to the courses but not sure to what extent this would be yet.
I do see features lacking from the library problem components like the ability to set additional advanced settings like # of submissions, show answer. I was able to test out the new Problem Bank (Beta) for pulling in random library problem components but we typically use this as a quiz and need to lock down show answer and have limited submissions attempts (e.g. 1 attempt).
It’s good to hear that there are multiple ways to build a course out and that other companies are thinking about reuse and saved maintenance time to fix existing course content.
@ztraboo: Yeah, to be clear: Nobody really loves CCX, and we do want to intend to replace it with course templates. I also encourage you to contribute to the course templates use cases doc that Jenna posted. Also, if you’ve already enrolled folks in regular courses, there’s no way to convert those courses to be CCX runs after the fact. All that being said, if there are new courses that you’re planning to run in this matter, CCX exists now and at first glance it seems to meet your requirements without new development work.
@jmakowski@dave
I posted course development improvements mentioned here in the Master Course Template document for our organization Clemson University Center for Workforce Development (CUCWD).
The idea provided by Zach is quite important. One of our partners is now developing 72 different courses. Some units in those courses are the same. For some reason, if they need to correct one of the units, imagine the task they have in their hands…
Regarding CCX, we are developing with eduNext a corporate solution. We hope that these initial developments fit the needs of the community.
We had a similar goal and solved it with a pluggable override of the save_xblock function that triggers a celery task. The task then iterates over all courses, and each xblock within the courses, searching for a select_from_library block that references the library that was updated. If the number of courses grows, I would invest further in enabling easier filtering to identify the courses that need to be updated instead of the brute force approach we’re currently using.
Here’s a rough idea of the code, but please note I have altered it slightly for posting, so please don’t assume it is in working condition.
@shared_task
def propagate_library_updates(xblock_key_string, user_id, xblock_definition_locator):
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from xmodule.modulestore.django import modulestore
store = modulestore()
course_keys = CourseOverview.get_all_course_keys()
for key in course_keys:
try:
update = False
# ... more happens
for block in library_blocks:
if block.source_library_id == xblock_key_string:
for child in block.get_children():
if str(child.definition_locator) == xblock_definition_locator:
update = True
block.sync_from_library(
upgrade_to_latest=True,
filter_fn=block._filter_copy_children,
)
store.publish(block.location, user_id)
elif not update:
for grandchild in child.get_children():
if (
str(grandchild.definition_locator)
== xblock_definition_locator
):
update = True
block.sync_from_library(
upgrade_to_latest=True,
)
store.publish(block.location, user_id)
except Exception:
log.exception(f"Error applying library updates to course {key}")
It is important to note that this celery task, as it is currently defined, can be pretty computing intensive. Further, if many changes are made to libraries in rapid succession (even multiple saves within a single library) it would generate redundant intensive tasks. So investing in smarter handling would be important in order to support this at scale.
@Jeff_Cohen
Thank you for sharing your solution to this and noting that this could be a performance issue.
When publishing a problem component within the Library Content (Beta), I’ve noticed that a modal comes up indicating all course Unit locations where the component is used. I would recommend trying to somehow find this list on the LMS side and traversing this filtered list.
Looks like this Studio API endpoint is called on a library component publish. I’m thinking that in the future that this endpoint could handle the updates of the course Unit page Library Content (Beta) components whenever we publish out with auto-sync enabled. I suppose we’d also need an option to publish the page automatically so that learners can see the updated content.
Example:
- LIBRARY_ORG: CA
- LIBRARY_ID:CL-FAA-ACS-AM-IC-WAB-MOD1
- PROBLEM_ID: 1492183f-a135-4c10-b17a-7be47a8f000c
http://studio.local.openedx.io:8001/api/libraries/v2/blocks/lb:<LIBRARY_ORG>:<LIBRARY_ID>:problem:<PROBLEM_ID>/publish/
Looking further, it appears that this CMS API endpoint is what we most likely want to call to locate all downstream updates for a specific Library Content (Beta) component. I think this will help narrow down where the publishing updates need to go out to in all the courses. This could save you from having to traverse all courses for a given Library Content (Beta) component publishing update.
Example:
- LIBRARY_ORG: CA
- LIBRARY_ID:CL-FAA-ACS-AM-IC-WAB-MOD1
- PROBLEM_ID: 1492183f-a135-4c10-b17a-7be47a8f000c
GET
http://studio.local.openedx.io:8001/api/contentstore/v2/downstreams/?upstream_usage_key=lb:<LIBRARY_ID>:<PROBLEM_ID>:problem:<PROBLEM_ID>&no_page=true
The response to this API call shows you where in the course what block is using the Library Content (Beta) component.
In this example, I used the Library Component (Beta) problem component on the page, and I performed a course re-run to create a new course with similar content. This matches what the Library Content (Beta) publish component list shows.
Two courses include this Library Content (Beta) problem component:
Ah, I should have mentioned that my solution was built for Library v1. We have not yet migrated to the Content Library v2 (Beta) tools, so can’t leverage some of the new support features that have been built for it. This certainly encourages me to plan that migration so we can start to capitalize on the advanced features.
Sorry it’s taken me a while to get back to this thread. I think we had a similar need to you – having a “master” course with “children” for each institution. We wrote a plugin that synchronizes the master courses to the children on publish, based on their re-run relationship – a data model that I didn’t know existed in open edX until we looked into this. It’s working OK so far, although the time between “we want everything synced” to “can we special case this one?” was shorter than I had hoped.
I’m definitely interested in trying to replace this approach with libraries, but we needed something quick and simple, with minimal learning curve for course authors.