Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 5 Current »

Legend

  • opaque_key { < settings-scoped field defaults > } { < settings-scoped fields > } { < content-scoped fields > }

    • child_1_opaque_key

    • etc

Scenario

  • We have a course with a library_content block pointing at version 5 of the library, where version 6 is the latest

  • The course overrides the titles (display_name) for X and Y, but uses the default titles of W and Z.

  • It edits the capa content (data) for Y and Z, but uses the upstream content of W and X.

Here’s what the lib looks like:

  • lib:O:L

    • Version 5

      • lb:O:L:problem:libBlockW { display_name: “title W” } { data: “www” }

      • lb:O:L:problem:libBlockX { display_name: “title X” } { data: “xxx” }

      • lb:O:L:problem:libBlockY { display_name: “title Y” } { data: “yyy” }

      • lb:O:L:problem:libBlockZ { display_name: “title Z” } { data: “zzz” }

    • Version 6 {latest}

      • (contents not important)

And here’s what the course tree looks like:

  • course-v1:O+C+R

    • block-v1:O+C+R+type@chapter+block@...

      • block-v1:O+C+R+type@sequence+block@...

        • block-v1:O+C+R+type@vertical+block@...

          • block-v1:O+C+R+type@library_content+block@myLCB { source_library_id: “lib:O:L”, source_library_version: “5” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockW")} { display_name: “title W” } { data: “www” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockX")} { display_name: “title X” } { display_name: “override title X” } { data: “xxx” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockY")} { display_name: “title Y” } { display_name: “override title Y” } { data: “yyy_edit” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockZ")} { display_name: “title Z” } { data: “zzz_edit” }

What does the user see for the blocks under myLCB? Unsurprisingly:

title W

www

override title X

xxx

override title Y

yyy_edit

title Z

zzz_edit

Curveball 1: Import/Export

We export course-v1:O+C+R and then import it into a new course run, course-v1-O+C+R2. Unfortunately, the library’s default settings don’t go into in the export!

Here’s what R2 looks like:

  • course-v1:O+C+R2

    • block-v1:O+C+R2+type@chapter+block@...

      • block-v1:O+C+R2+type@sequence+block@...

        • block-v1:O+C+R2+type@vertical+block@...

          • block-v1:O+C+R2+type@library_content+block@myLCB { source_library_id: “lib:O:L”, source_library_version: “5” }

            • block-v1:O+C+R2+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockW")} { data: “www” }

            • block-v1:O+C+R2+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockX")} { display_name: “override title X” } { data: “xxx” }

            • block-v1:O+C+R2+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockY")} { display_name: “override title Y” } { data: “yyy_edit” }

            • block-v1:O+C+R2+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockZ")} { data: “zzz_edit” }

What does the user see now? Well, wherever a title override isn’t set, they see the the ProblemBlock’s provided default title:

Problem

www

override title X

xxx

override title Y

yyy_edit

Problem

zzz_edit

How do we work around this in V1 libs?

We re-load the blocks from the library at the proper version (5). This gives us our defaults back, but it does blow away any content edits :(

R2 would then look like this:

  • course-v1:O+C+R2

    • block-v1:O+C+R2+type@chapter+block@...

      • block-v1:O+C+R2+type@sequence+block@...

        • block-v1:O+C+R2+type@vertical+block@...

          • block-v1:O+C+R2+type@library_content+block@myLCB { source_library_id: “lib:O:L”, source_library_version: “5” }

            • block-v1:O+C+R2+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockW")} { display_name: “title W” } { data: “www” }

            • block-v1:O+C+R2+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockX")} { display_name: “title X” } { display_name: “override title X” } { data: “xxx” }

            • block-v1:O+C+R2+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockY")} { display_name: “title Y” } { display_name: “override title Y” } { data: “yyy” }

            • block-v1:O+C+R2+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockZ")} { display_name: “title Z” } { data: “zzz” }

And the user would see the right titles, but no content edits:

title W

www

override title X

xxx

override title Y

yyy

title Z

zzz

How do we want to work around this in V2 libs?

Well, first of all the current V2 content_libraries API does not support loading blocks for old library versions. We could add support for that, but we’re not sure it’s the right approach.

Instead, we’d rather add defaults to the OLX export, so that the export comes back with defaults intact, and removes the need to re-load library blocks, thus preserving library edits 🎉 The structure is now:

  • course-v1:O+C+R2

    • block-v1:O+C+R2+type@chapter+block@...

      • block-v1:O+C+R2+type@sequence+block@...

        • block-v1:O+C+R2+type@vertical+block@...

          • block-v1:O+C+R2+type@library_content+block@myLCB { source_library_id: “lib:O:L”, source_library_version: “5” }

            • block-v1:O+C+R2+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockW")} { display_name: “title W” } { data: “www” }

            • block-v1:O+C+R2+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockX")} { display_name: “title X” } { display_name: “override title X” } { data: “xxx” }

            • block-v1:O+C+R2+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockY")} { display_name: “title Y” } { display_name: “override title Y” } { data: “yyy_edit” }

            • block-v1:O+C+R2+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockZ")} { display_name: “title Z” } { data: “zzz_edit” }

Yielding:

title W

www

override title X

xxx

override title Y

yyy_edit

title Z

zzz_edit

OPEN QUESTION… how will this work for courses which were exported before the v1/v2 transition?

Curveball 2: Duplication

Back to the original course, let’s say the user duplicates LCB. The standard handling of “duplicate a block” would randomly generating id strings for its children:

  • course-v1:O+C+R

    • block-v1:O+C+R+type@chapter+block@...

      • block-v1:O+C+R+type@sequence+block@...

        • block-v1:O+C+R+type@vertical+block@...

          • block-v1:O+C+R+type@library_content+block@myLCB { source_library_id: “lib:O:L”, source_library_version: “5” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockW")} { display_name: “title W” } { data: “www” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockX")} { display_name: “title X” } { display_name: “override title X” } { data: “xxx” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockY")} { display_name: “title Y” } { display_name: “override title Y” } { data: “yyy_edit” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockZ")} { display_name: “title Z” } { data: “zzz_edit” }

          • block-v1:O+C+R+type@library_content+block@dupeLCB { source_library_id: “lib:O:L”, source_library_version: “5” }

            • block-v1:O+C+R+type@problem+block@deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead1 { display_name: “title W” } { data: “www” }

            • block-v1:O+C+R+type@problem+block@deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead2 { display_name: “title X” } { display_name: “override title X” } { data: “xxx” }

            • block-v1:O+C+R+type@problem+block@deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead3 { display_name: “title Y” } { display_name: “override title Y” } { data: “yyy }

            • block-v1:O+C+R+type@problem+block@deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead4 { display_name: “title Z” } { data: “zzz” }

That would be fine, until the course author updates the library’s source version, at which point the children with randomly-generated IDs would be thrown away in favor of new children with library-block-id-derived IDs. Title overrides and any student state would be lost:

  • course-v1:O+C+R

    • block-v1:O+C+R+type@chapter+block@...

      • block-v1:O+C+R+type@sequence+block@...

        • block-v1:O+C+R+type@vertical+block@...

          • block-v1:O+C+R+type@library_content+block@myLCB { source_library_id: “lib:O:L”, source_library_version: “5” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockW")} { display_name: “title W” } { data: “www” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockX")} { display_name: “title X” } { display_name: “override title X” } { data: “xxx” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockY")} { display_name: “title Y” } { display_name: “override title Y” } { data: “yyy_edit” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockZ")} { display_name: “title Z” } { data: “zzz_edit” }

          • block-v1:O+C+R+type@library_content+block@dupeLCB { source_library_id: “lib:O:L”, source_library_version: “6” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("dupeLCB", "lb:O:L:problem:libBlockW")} { display_name: “title W” } { data: “www” } # R.I.P. STUDENT STATE

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("dupeLCB", "lb:O:L:problem:libBlockX")} { display_name: “title X” } { data: “xxx” } # R.I.P. STUDENT STATE

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("dupeLCB", "lb:O:L:problem:libBlockY")} { display_name: “title Y” } { data: “yyy” } # R.I.P. STUDENT STATE

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("dupeLCB", "lb:O:L:problem:libBlockZ")} { display_name: “title Z” } { data: “zzz” } # R.I.P. STUDENT STATE

How does V1 handle this? Well, it tells the duplication code to not duplicate the library_content block’s children:

  • course-v1:O+C+R

    • block-v1:O+C+R+type@chapter+block@...

      • block-v1:O+C+R+type@sequence+block@...

        • block-v1:O+C+R+type@vertical+block@...

          • block-v1:O+C+R+type@library_content+block@myLCB { source_library_id: “lib:O:L”, source_library_version: “5” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockW")} { display_name: “title W” } { data: “www” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockX")} { display_name: “title X” } { display_name: “override title X” } { data: “xxx” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockY")} { display_name: “title Y” } { display_name: “override title Y” } { data: “yyy_edit” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockZ")} { display_name: “title Z” } { data: “zzz_edit” }

          • block-v1:O+C+R+type@library_content+block@dupeLCB { source_library_id: “lib:O:L”, source_library_version: “5” }

            • (no children yet)

Then, it reloads the blocks from the library. This avoids the student-state-loss risk described above, but it does, annoyingly, blow away any content edits. This behavior is essentially the same as what happens when you import a V1 library content block.

  • course-v1:O+C+R

    • block-v1:O+C+R+type@chapter+block@...

      • block-v1:O+C+R+type@sequence+block@...

        • block-v1:O+C+R+type@vertical+block@...

          • block-v1:O+C+R+type@library_content+block@myLCB { source_library_id: “lib:O:L”, source_library_version: “5” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockW")} { display_name: “title W” } { data: “www” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockX")} { display_name: “title X” } { display_name: “override title X” } { data: “xxx” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockY")} { display_name: “title Y” } { display_name: “override title Y” } { data: “yyy_edit” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockZ")} { display_name: “title Z” } { data: “zzz_edit” }

          • block-v1:O+C+R+type@library_content+block@dupeLCB { source_library_id: “lib:O:L”, source_library_version: “5” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("dupeLCB", "lb:O:L:problem:libBlockW")} { display_name: “title W” } { data: “www” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("dupeLCB", "lb:O:L:problem:libBlockX")} { display_name: “title X” } { display_name: “override title X” } { data: “xxx” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("dupeLCB", "lb:O:L:problem:libBlockY")} { display_name: “title Y” } { display_name: “override title Y” } { data: “yyy }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("dupeLCB", "lb:O:L:problem:libBlockZ")} { display_name: “title Z” } { data: “zzz” }

For V2, it’d be really nice if we could duplicate settings and content and correctly derive the content IDs:

  • course-v1:O+C+R

    • block-v1:O+C+R+type@chapter+block@...

      • block-v1:O+C+R+type@sequence+block@...

        • block-v1:O+C+R+type@vertical+block@...

          • block-v1:O+C+R+type@library_content+block@myLCB { source_library_id: “lib:O:L”, source_library_version: “5” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockW")} { display_name: “title W” } { data: “www” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockX")} { display_name: “title X” } { display_name: “override title X” } { data: “xxx” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockY")} { display_name: “title Y” } { display_name: “override title Y” } { data: “yyy_edit” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("myLCB", "lb:O:L:problem:libBlockZ")} { display_name: “title Z” } { data: “zzz_edit” }

          • block-v1:O+C+R+type@library_content+block@dupeLCB { source_library_id: “lib:O:L”, source_library_version: “5” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("dupeLCB", "lb:O:L:problem:libBlockW")} { display_name: “title W” } { data: “www” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("dupeLCB", "lb:O:L:problem:libBlockX")} { display_name: “title X” } { display_name: “override title X” } { data: “xxx” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("dupeLCB", "lb:O:L:problem:libBlockY")} { display_name: “title Y” } { display_name: “override title Y” } { data: “yyy_edit” }

            • block-v1:O+C+R+type@problem+block@{DERIVE_ID("dupeLCB", "lb:O:L:problem:libBlockZ")} { display_name: “title Z” } { data: “zzz_edit” }

OPEN QUESTION… how do we do this? Well, if we knew the right derived keys, we could set them ourselves! But that requires knowing what the original source library block usage keys were.

Two different ideas for figuring out the old usage keys

  1. Add a field to LibraryContentBlock that maps the DERIVE_ID(…) output back to the original library id. Essentially:

    • class LibraryContentBlock(XBlock):
          ...
          # Values are: {
          #     DERIVE_ID("dupeLCB", "lb:O:L:problem:libBlockW"): "lb:O:L:problem:libBlockW",
          #     DERIVE_ID("dupeLCB", "lb:O:L:problem:libBlockX"): "lb:O:L:problem:libBlockW",
          #     DERIVE_ID("dupeLCB", "lb:O:L:problem:libBlockY"): "lb:O:L:problem:libBlockW",
          #     DERIVE_ID("dupeLCB", "lb:O:L:problem:libBlockZ"): "lb:O:L:problem:libBlockW",
          # }
          child_usage_keys_to_library_usage_keys = Dict(...)
          ...
    • We would need to gracefully handle old blocks which don’t have this field set.

  2. Upon duplication of a library_content block, load up the blocks from the old version of the library, run DERIVE_KEY on each of those, and compare the results to the library content block’s children:

    • child_usage_keys_to_library_usage_keys: dict[UsageKey, UsageKey] = {}
      for source_lib_block_key in get_library_block_usage_keys(library=lcb.source_library_id, version=lcb.source_library_version):
          child_usage_keys_to_library_usage_keys = DERIVE_KEY(lcb.usage_key.block_id, source_lib_block_key)

TODO

  • No labels