Deliverable: A lightweight functionality that enables authors to reuse components, units, subsections and sections by sharing directly from one course to another. The copy/paste functionality will also work for static pages. For this first MVP, the content is copied as a one-time action, and any future edits made to either copy won’t be synced. However, it is possible to edit the component independently in its new environment.
We’re going to lean on the common copy-paste metaphor, by introducing the idea of a Studio copy/paste. You can copy content at various levels of granularity (components, units, subsections, sections), and then paste it into a different location within the same course, or into an entirely different course.
Copy button that lives alongside existing buttons for duplicate, move, manage access and delete. This would be made available at the Component, Unit, Subsection, and Section levels. The UI includes a new dropdown menu from a three-dot icon in the right heading section of components, units, subsections and sections.
[DECISION 2/15/23 - We will keep both the duplicate and the copy as independent features for now, due to nuanced differences in their scope, ie duplicate only works within a course, and copy works across courses, and duplicate allows authors to interact immediately with the duplicated content. Additionally, duplicate is a well-used function and one we don’t want to remove from current authoring flows. The concern about confusion is addressed with the UI decision to build a dropdown menu that allows both functions to coexist.] Note: The overlap between Copy and Duplicate is likely to confuse users. It’s possible that we could also replace the Duplicate functionality with this.
Authors are alerted that their copy is in progress, and when their copy is complete, with toast notifications.
Context-specific paste button(s). You can’t paste a Section into a Unit, so these would have to only be usable when the appropriate type of thing is in the Clipboard.
Paste preview via a “What’s in my clipboard” hover display that informs authors about the title of the copied item, the item type, and the source with a link back to the source.
“Paste complete” toast notifications
Paste error messages that display at the top of the appropriate page in the course outline and function similarly to how errors are displayed on the edX registration page (page automatically scrolls to the top to display the alert).
Warning modals that provide authors with instructions for files they may need to update manually, including the exact files in question. These modals appear as soon as the paste button is clicked (i.e. before the paste action has actually begun), and the alert in the second screenshot appears after the paste has taken place.
Additional Scope details:
The initial scope of this is confined to courses, but we should be able to use these conventions in libraries in the future.
The copy-paste convention means that there would be no expectation that you could edit it in some shareable space.
The isolated scope of this approach means that it could co-exist with later libraries' functionality without confusing users.
For now, limit the clipboard to one item per user.
At some point after the MVP, this may be expanded to hold multiple items (e.g. the five most recently copied things) for power users. This overlaps with the "I'm building my course and I have three different kinds of things I use" scenario.
Items that are copied may be pasted multiple times.
An item in the clipboard is overwritten when a new copy is made.
Alternative UX Considered
An earlier concept involved using a set of menus that would allow you to select the destination course/location an item would be copied into, drilling down until you got to the location you wanted:
The Clipboard approach is preferred we believe it provides a simpler and more intuitive interface, and we don’t have to deal with the complexity of filtering for people who have access to thousands of courses.
Static Asset Dependencies
XBlock content often has static asset dependencies (e.g. images, subtitles, etc.). Our approach:
Static asset dependencies are a first class concept within the new APIs / data models.
Assets that are missing from the current course will be copied into it.
Assets with a matching name in the new course will not be overwritten by copied assets, even if the assets differ in their contents.
Pluggable extension mechanism where XBlocks can define how to find their dependent assets.
Explicit vs. implicit static asset files
Some static assets are explicitly uploaded and tracked in Studio’s Files and Uploads page, but some are implicitly managed by individual XBlocks, such as manually uploaded SRT files. I believe ORA2 also has something along these lines, though I forget the specifics. We have to copy both explicit and implicit asset files in order to not break content, but how do we report that in the preview? Some possible approaches:
Proposed Solution: Only report the things that are in Files and Uploads.
Alternative: Display all assets being copied, including the ones local to specific XBlocks.
Alternative: Display both items but explicitly separate them in the presentation to the user.
I’m proposing we go with #1 because the assets that are local to a given XBlock can’t actually have any conflicts, so we don’t need to talk about them. We can conceptually think of them as a part of the XBlock in that sense, and it will reduce confusion for the course author.
Note that I’m just making up vocabulary on the fly here, and “explicit vs. implicit static assets” aren’t really a thing that Studio talks about. Studio generally uses the language of “Files” for the explicitly uploaded ones.
Special Case: python_lib.zip
Problem: How do we handle ProblemBlocks that depend on libraries that are bundled in a course’s python_lib.zip file?
Sometimes courses will put custom grading libraries like mitx-grading-library here, but there’s only one of these for the course. We can’t just overwrite, copying under a new name wouldn’t work, and merging could get tricky (esp. if there are multiple versions of the same libraries).
This might be a case where we just have to call out the limitation and flag it in the problem somewhere?
Not frequently used, but it is used by some of our big power users.
Content Incompatibility and Reconciliation
The content being copied may not be fully compatible with the course’s metadata. For example:
Content groups on content may not match those configured for the course
Some subset of XBlock content might not be enabled for this course (via advanced settings)
Inline forums settings may not make sense
Start and due dates will likely need to be adjusted
Exam related settings, proctoring
Grading policy (assignment type)
Note that these are not mutually exclusive, and we may choose some mix of approaches depending on the specific situation.
Add APIs to support reconciliation/adjustment of values before they’re inserted into a course.
Fall back to reasonable default settings, e.g. if the content group doesn’t exist for the course, we could set that value to be blank.
Adjust the course settings, either automatically or with some kind of confirmation, e.g. add XBlock types to the course’s advanced settings.
Block pasting entirely if requirements are not met.
Paste with minimal checking, but surface incompatibilities via Studio’s editing interface so that users can be made aware of the issues and correct them. TODO: Investigate what happens today in Studio’s interface when content is imported with some of these incompatibilities.
No matter what, we will need some extensible API for different content types to implement to enable things like determining which assets they need. We can create an EnsureDestinationCompatibility API that will allow content types to reconfigure themselves automatically. The API itself shouldn’t directly use the XBlock runtime.
For the initial MVP, allow incompatible items to be pasted, and surface the errors in the Studio UI.
This is easier to implement.
XBlocks already have a validation API (example: ContentLibraryBlock when the source library is missing).
This approach makes it easier to handle situations where we’re pasting a whole Section’s worth of content, where there may be many things to resolve.
A Section might be so large that it takes a prohibitive amount of time to serialize into OLX in order to be copied. This needs to be tested.
If it turns out that it is too slow to run synchronously, the API may need to be adjusted to run in celery tasks. This would increase the scope of the project.
Milestones will be created so that Component and Unit level copy/paste can be delivered independently of Subsection and Section level copy/paste.
Copy, Modify/Delete, and Paste
Say a piece of content was copied and is in the clipboard for a long period of time (days or even weeks). Since that time, modifications have been made to the original content or the static assets that have been referenced by that content.
Proposal: We completely copy the OLX and associated assets at the time the “Copy” button is pressed.
This is a bit more expensive than holding onto references, but it’s intuitive and it guards against content being deleted between copying and pasting.
This does mean that any links we show as part of the preview (e.g. “this is the location we copied this thing from” could break–courses could be removed, content could be deleted, etc.) So we should be prepared for that.
We should display some sort of indication of when the copy was made in whatever paste-preview-like functionality we create. Pasting is usually going to happen almost immediately after copying, but the copied item might stick around in the clipboard for a long time after that. It will also help authors to figure out whether something in the clipboard is outdated (i.e. they should go get a more recent version of it).
Library Content Blocks
LibraryContentBlock is complex and ends up pulling copies of all the content it references into the Modulestore for that course. I’m not sure if this will Just Work if we copy the OLX for the LibraryContentBlock across courses, or if there’s other loading that needs to happen.
A new clipboard app would hold the data models, probably in edx-platform, but possibly in openedx-learning?
We could also store the clipboard entirely on the client-side (local storage), and have it interact with new endpoints.
Approach A: API to Get OLX+assets; + API to Insert OLX + assets + adapt configuration
This may enable future partial-import functionality and other features.
Approach B: Copy Item API that works across courses
Both client-centric approaches may make it more cumbersome to do server-side plugin-based validation if conflict resolution is complex or asynchronous.
There might be a more granular concept lurking underneath this, one that would be used by both clipboard and future features like template-assisted authoring or partial import.
At the very least, we should separate the simple storage of content + metadata from the complexities and rules associated with adjusting the content for use in a course.
There should be pluggable APIs associated with Copy/Pasting different kinds of content. But we’re not currently planning to make an interface for placement of the Paste button (i.e. that space is not going to be a place where we have a bunch of plugin-powered buttons for new features–at least not yet).
Clipboard contents would be stored as serialized OLX, with additional supporting metadata around whatever assets would need to be copied.
We should also store preview-related metadata in the data models, so they can be queried directly for display to the user without parsing OLX.
Large pieces of content (like sections) might be slow to copy–we should test, but it’s possible the copy-process in general needs to run as a celery task. Pasting has a similar issue. There is already a “duplicate” button for Sections and Subsections today, but that can be implemented at a lower level, while this API would be using serialized OLX.
Doing it at the ModuleStore layer would be more performant, but would make it less portable over the long term (e.g. Blockstore, ModuleStore, library implementations, potential to import from things that are not on the same instance, etc.)
We may want to add an attribute in the pasted OLX that shows where the content was copied from (e.g. the usage key of the original).
Keeping XBlock dependencies out of the new clipboard app may be difficult, but we should strive for it if possible.
Presenting the clipboard in Studio may also pose implementation challenges if it has to appear in both MFEs and the older Studio interface. (We may side-step this for a while with a less ideal UI that adds buttons to the existing interface.)
Copying content within the same course might have ambiguous desired behavior.
If we copy-paste an inline discussion block, do we keep the same discussion_id (i.e. point it to the same conversation)? What if we copy a whole Unit that the discussion block is in?
This might be a differentiator between copy-paste and a future “build-from-template” kind of functionality.
There are other concepts that are sort of technically adjacent to this one. We won’t necessarily implement these using the same foundation, but we should try to keep them in the back of our minds while building this in case there are easy wins:
Copy-Paste to and from Libraries (hence the preference to store the clipboard in OLX).
Partial Import (e.g. Add two sections to this course.)
Clipboards that hold multiple items (e.g. the last five copied things)
Functionality to clear single items, or the entire clipboard
Templated content, e.g. “Problem + Question” as a Unit type, or a “Case Study” Unit that has four Components in a specific order.
M1: Basic Component Copy-Paste Proof-of-Concept
Copy-paste support only for Components
UI buttons for copying and pasting
Should handle pasting both within the same course as well as across courses
Feature flag toggle to enable/disable the display of this feature
M1 gets the very basic functionality out there in a way that’s testable by users, but is not really releasable to a production environment.
M2: Releasable Component Copy-Paste with Asset Dependencies
Add support for copying static asset dependencies along with individual Components.
This must be implemented in a way that is pluggable.
A bit of prior art from LabXchange work might be useful as a starting point, e.g. see the OLX REST API which provides a list of the static assets used by the given components/OLX.
Must be smart enough to detect when an asset already exists in the destination location (matching filename).
Should display in the Paste preview.
New mixin XBlock field to track the usage key of the original it was copied from (not editable or displayed).
Update Studio docs.
M2 gets us to something that’s useful enough to release to end users, even if it has obvious gaps in functionality and some edge cases where it might be incompatible (e.g. content group settings).
M3: Unit Support
Extend copy-paste support and UI to the Unit level.
Copy and Paste buttons in the Outline screen
Copy and Paste buttons in the Unit edit screen
Conflicts for incompatible content groups and disabled XBlock types should be visible to users, even if they must be resolved manually (this might already exist to satisfy the import use case).
Update Studio docs.
M3 gets us to something broadly releasable to users.
M4: Subsection Support
Extend copy-paste support and UI to the Subsection level
Copy and Paste buttons in the Outline screen
This may require asynchronous operation for both copying and pasting. If so, it could significantly increase the implementation scope.
This may involve a reconciliation process around exam/assignment settings and scheduling of some sort, or we could just blindly copy those and make sure that the conflicts are visibly surfaced in the pasted content so that they can be manually resolved.
Update Studio docs.
M4 gets to some of the really big copy-paste functionality for moving stuff between courses, but it also represents more unknowns and possibilities for scope creep.
M5: Section Support
Extend copy-paste support and UI to the Section level
Copy and Paste buttons in the Outline screen
Update Studio docs
M5 would be the last piece, though there might be other experience fine-tuning work that gets moved here as well.
2-3 weeks for basic implementation of M1
Another 2-3 weeks for beta testing and feedback
If the async nature of the larger pieces (Sections, Subsections) threatens to blow out the scope, we could start with a smaller MVP that focuses on Components and Units first.