What's the best way to track/show an XBlock's version?

Hi everyone,

this is a pretty generic question; please feel free to point me to reference documentation if it exists: what’s the best way for XBlock authors to expose their XBlock version to course staff using the LMS?

I ask because if you author courses on a bunch of Open edX instances and they all have third-party XBlocks installed, it sometimes comes in quite handy for a course author/instructor to be able to tell which version of an XBlock they are working with. For example, they might want to determine whether a feature that just recently landed in an XBlock is available to them.

To be clear, I’m talking about making it possible for course staff to discern which version of an XBlock is in use, not for the administrator of an instance (the latter can always run pip list in the edxapp venv, or refer to their CI configuration).

I’m not sure if we have a generic way of doing that, but first up here is my general preference for tracking a package version:

  1. Use setuptools_scm in setup.py, so that Git vX.Y.Z tags always correspond to X.Y.Z package version numbers.
  2. Just for convenience, have something like this in a package’s __init.py__:
    from importlib.metadata import version
    
    __version__ = version('my-xblock')
    

And then, I thought I’d do (in the XBlock definition):

from . import __version__

class MyXBlock(StudioEditableXBlockMixin, XBlockWithSettingsMixin, XBlock)

    # [...]

    xblock_version = String(
        help=_("This XBlock's installed version"),
        default=__version__,
        scope=Scope.settings
    )

… and then leave xblock_version out of the editable_fields tuple.

That way, if an instructor clicks Staff Debug Info under the XBlock in the LMS, they should see the version that’s being used in the Module Fields list.

So my questions are:

  • Is this a good way of doing it?
  • Are there better or more generic suggestions?
  • Does the XBlock API perhaps already have a feature for version tracking that I am unaware of?
  • Would this be a use case for class tags, so that rather than creating a separate field named xblock_version, I could add a tag by that name that would get listed under the tags list?
  • If so, how do I set class tags? Is it sufficient for my XBlock class to set an attribute named _class_tags, like so:
    class MyXBlock(StudioEditableXBlockMixin, XBlockWithSettingsMixin, XBlock)
        _class_tags = ['tag1', 'tag2', 'tag3']
    
  • Are class tags meant to be scalars? Or is it OK to include a dictionary as a tag, as in
    _class_tags = [{'xblock_version': __version__}]
    

I realise I may be waaay out in left field here, so please feel free to set me straight. :slight_smile:

Cheers,
Florian

1 Like

It’s a good question, and I like your proposal as a band-aid. But I think it will be better to implement a generic solution that allows users or admins to see the installed version of any XBlock, without requiring manual changes to each individual XBlock or its release process.

Here is a script that will programatically display the versions of all installed XBlocks (according to their setup.py). It’s unfortunately quite slow, but maybe we can do something generic like this?

import importlib.metadata
from importlib.util import find_spec
import pathlib

def get_distribution(file_name):
    result = None
    for distribution in importlib.metadata.distributions():
        try:
            relative = (
                pathlib.Path(file_name)
                .relative_to(distribution.locate_file(''))
            )
        except ValueError:
            pass
        else:
            if relative in distribution.files:
                result = distribution
    return result

for xblock_entry_point in importlib.metadata.entry_points()['xblock.v1']:
    xblock_module = xblock_entry_point.value.split(':')[0]
    xblock_file = find_spec(xblock_module).origin
    distribution = get_distribution(xblock_file)
    print(f"{xblock_entry_point.name}: version {distribution.version}")

Note that this script assumes an Open edX version using Python 3.8.

Right, I suppose there’s multiple ways to set an XBlock’s version, and multiple variants of retrieving it, all of which essentially boil down to some use of importlib.metadata.version. But my question was more about what’s a good way to expose an XBlock’s version to a course author, and I’m not sure how a list of all XBlocks installed on the system helps there.

Sure, there’s the sysadmin dashboard (built-in prior to Lilac, now in GitHub - mitodl/edx-sysadmin: A plugin for SysAdmin panel of Open edX) where we could add a summary like this, but as far as I know that’s only visible to users with global Staff permissions, not to per-course Staff users.

I really think this information best fits into the Module Fields list. Is there a clever way of generically creating such a field in the XBlock superclass (so that XBlock authors don’t have to worry about implementing it), while populating it with data according to the subclass package?

Right. I wasn’t necessarily suggesting displaying a list in the sysadmin dashboard, just pointing out that it is possible to get the version of any (or all) XBlocks programmatically today without any changes to the XBlocks themselves. I found that it was non-trivial to go from the XBlock entry point / class name to the version, but I did figure out that approach which seems to work.

I personally don’t think this makes sense as a field, for two reasons - first, because it’s not a data value that needs to be stored on a scoped basis* (like all other fields), and second, because if some XBlock save logic were to persist the current xblock_version field value for a given XBlock, then if a newer version of the XBlock was later installed, that XBlock with the persisted xblock_version field data would still show the old version number.

As you suggest, I do think it would make a lot of sense to modify the “staff debug info” modal dialog in the LMS to display the XBlock version, but I don’t think it should be done as a field. Instead I would suggest changing the code here to fetch the version, and modify the modal dialog’s HTML template to display the version. You could do something similar in Studio if that would be useful.

It’s easy to add a new field to LmsBlockMixin which will make it appear on all blocks, but I don’t think the value can be computed dynamically. And I’m sure that there are clever ways to modify the LMS’s FieldData implementation or the base XBlock class to achieve your suggestion, but I’m not sure if there’s any simple or clean way :slight_smile: .

* If the field were scoped, it should be scoped to [UserScope.NONE, BlockScope.TYPE], which is not a supported combination for XBlock fields.

I think I’ve found a reasonably generic approach here that might work, and that doesn’t define an extra field. What do you think of this?

1 Like

I haven’t tried it out yet, but your approach looks great to me!