diff --git a/lms/djangoapps/course_api/blocks/transformers/block_depth.py b/lms/djangoapps/course_api/blocks/transformers/block_depth.py new file mode 100644 index 0000000000..9314451ae1 --- /dev/null +++ b/lms/djangoapps/course_api/blocks/transformers/block_depth.py @@ -0,0 +1,63 @@ +""" +Block Depth Transformer +""" +from openedx.core.lib.block_cache.transformer import BlockStructureTransformer + + +class BlockDepthTransformer(BlockStructureTransformer): + """ + Keep track of the depth of each block within the block structure. In case + of multiple paths to a given node (in a DAG), use the shallowest depth. + """ + VERSION = 1 + BLOCK_DEPTH = 'block_depth' + + def __init__(self, requested_depth=None): + self.requested_depth = requested_depth + + @classmethod + def name(cls): + return "blocks_api:block_depth" + + @classmethod + def get_block_depth(cls, block_structure, block_key): + """ + Return the precalculated depth of a block within the block_structure: + + Arguments: + block_structure: a BlockStructure instance + block_key: the key of the block whose depth we want to know + + Returns: + int + """ + return block_structure.get_transformer_block_field( + block_key, + cls, + cls.BLOCK_DEPTH, + ) + + def transform(self, usage_info, block_structure): # pylint: disable=unused-argument + """ + Mutates block_structure based on the given usage_info. + """ + for block_key in block_structure.topological_traversal(): + parents = block_structure.get_parents(block_key) + if parents: + block_depth = min( + self.get_block_depth(block_structure, parent_key) + for parent_key in parents + ) + 1 + else: + block_depth = 0 + block_structure.set_transformer_block_field( + block_key, + self, + self.BLOCK_DEPTH, + block_depth + ) + + if self.requested_depth is not None: + block_structure.remove_block_if( + lambda block_key: self.get_block_depth(block_structure, block_key) > self.requested_depth + ) diff --git a/lms/djangoapps/course_api/blocks/transformers/tests/test_block_depth.py b/lms/djangoapps/course_api/blocks/transformers/tests/test_block_depth.py new file mode 100644 index 0000000000..a4cf85641a --- /dev/null +++ b/lms/djangoapps/course_api/blocks/transformers/tests/test_block_depth.py @@ -0,0 +1,40 @@ +""" +Tests for BlockDepthTransformer. +""" + +# pylint: disable=protected-access + +import ddt +from unittest import TestCase + +from openedx.core.lib.block_cache.tests.test_utils import ChildrenMapTestMixin +from openedx.core.lib.block_cache.block_structure import BlockStructureModulestoreData +from ..block_depth import BlockDepthTransformer + + +@ddt.ddt +class BlockDepthTransformerTestCase(TestCase, ChildrenMapTestMixin): + """ + Test behavior of BlockDepthTransformer + """ + @ddt.data( + (0, [], [], []), + (0, ChildrenMapTestMixin.SIMPLE_CHILDREN_MAP, [[], [], [], [], []], [1, 2, 3, 4]), + (1, ChildrenMapTestMixin.SIMPLE_CHILDREN_MAP, [[1, 2], [], [], [], []], [3, 4]), + (2, ChildrenMapTestMixin.SIMPLE_CHILDREN_MAP, ChildrenMapTestMixin.SIMPLE_CHILDREN_MAP, []), + (3, ChildrenMapTestMixin.SIMPLE_CHILDREN_MAP, ChildrenMapTestMixin.SIMPLE_CHILDREN_MAP, []), + (None, ChildrenMapTestMixin.SIMPLE_CHILDREN_MAP, ChildrenMapTestMixin.SIMPLE_CHILDREN_MAP, []), + + (0, ChildrenMapTestMixin.DAG_CHILDREN_MAP, [[], [], [], [], [], [], []], [1, 2, 3, 4, 5, 6]), + (1, ChildrenMapTestMixin.DAG_CHILDREN_MAP, [[1, 2], [], [], [], [], [], []], [3, 4, 5, 6]), + (2, ChildrenMapTestMixin.DAG_CHILDREN_MAP, [[1, 2], [3], [3, 4], [], [], [], []], [5, 6]), + (3, ChildrenMapTestMixin.DAG_CHILDREN_MAP, ChildrenMapTestMixin.DAG_CHILDREN_MAP, []), + (4, ChildrenMapTestMixin.DAG_CHILDREN_MAP, ChildrenMapTestMixin.DAG_CHILDREN_MAP, []), + (None, ChildrenMapTestMixin.DAG_CHILDREN_MAP, ChildrenMapTestMixin.DAG_CHILDREN_MAP, []), + ) + @ddt.unpack + def test_block_depth(self, block_depth, children_map, transformed_children_map, missing_blocks): + block_structure = self.create_block_structure(BlockStructureModulestoreData, children_map) + BlockDepthTransformer(block_depth).transform(usage_info=None, block_structure=block_structure) + block_structure._prune_unreachable() + self.assert_block_structure(block_structure, transformed_children_map, missing_blocks)