From ed379926c23fb4ef61cf9ebb679a5bb908281ee4 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Thu, 16 Nov 2023 10:03:13 -0800 Subject: [PATCH] feat: display tag counts on course outline page (feature flagged) (#33696) * feat: display tag counts on outline * fix: taxonomies should default to allow_multiple=True * fix: only load counts once per request * chore: version bump for openedx-learning --- .../xblock_storage_handlers/view_handlers.py | 16 +++++++++ cms/templates/js/course-outline.underscore | 5 +-- .../core/djangoapps/content_tagging/api.py | 2 +- .../content_tagging/tests/test_api.py | 33 +++++++++---------- .../content_tagging/tests/test_rules.py | 2 +- requirements/constraints.txt | 2 +- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 10 files changed, 41 insertions(+), 27 deletions(-) diff --git a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py index 4ed0073bef..dca25695a2 100644 --- a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py +++ b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py @@ -24,6 +24,7 @@ from django.utils.translation import gettext as _ from edx_django_utils.plugins import pluggable_override from openedx_events.content_authoring.data import DuplicatedXBlockData from openedx_events.content_authoring.signals import XBLOCK_DUPLICATED +from openedx_tagging.core.tagging import api as tagging_api from edx_proctoring.api import ( does_backend_support_onboarding, get_exam_by_content_id, @@ -54,6 +55,7 @@ from openedx.core.djangoapps.bookmarks import api as bookmarks_api from openedx.core.djangoapps.discussions.models import DiscussionsConfiguration from openedx.core.djangoapps.video_config.toggles import PUBLIC_VIDEO_SHARE from openedx.core.lib.gating import api as gating_api +from openedx.core.lib.cache_utils import request_cached from openedx.core.toggles import ENTRANCE_EXAMS from xmodule.course_block import ( DEFAULT_START_DATE, @@ -1400,6 +1402,7 @@ def create_xblock_info( # lint-amnesty, pylint: disable=too-many-statements # If the ENABLE_TAGGING_TAXONOMY_LIST_PAGE feature flag is enabled, we show the "Manage Tags" options if use_tagging_taxonomy_list_page(): xblock_info["use_tagging_taxonomy_list_page"] = True + xblock_info["tag_counts_by_unit"] = _get_course_unit_tags(xblock.location.context_key) xblock_info[ "has_partition_group_components" @@ -1414,6 +1417,19 @@ def create_xblock_info( # lint-amnesty, pylint: disable=too-many-statements return xblock_info +@request_cached() +def _get_course_unit_tags(course_key) -> dict: + """ + Get the count of tags that are applied to each unit (vertical) in this course, as a dict. + """ + if not course_key.is_course: + return {} # Unsupported key type, e.g. a library + # Create a pattern to match the IDs of the units, e.g. "block-v1:org+course+run+type@vertical+block@*" + vertical_key = course_key.make_usage_key('vertical', 'x') + unit_key_pattern = str(vertical_key).rsplit("@", 1)[0] + "@*" + return tagging_api.get_object_tag_counts(unit_key_pattern) + + def _was_xblock_ever_exam_linked_with_external(course, xblock): """ Determine whether this XBlock is or was ever configured as an external proctored exam. diff --git a/cms/templates/js/course-outline.underscore b/cms/templates/js/course-outline.underscore index 8bf74b07af..baf802a291 100644 --- a/cms/templates/js/course-outline.underscore +++ b/cms/templates/js/course-outline.underscore @@ -7,6 +7,7 @@ var hasPartitionGroups = xblockInfo.get('has_partition_group_components'); var userPartitionInfo = xblockInfo.get('user_partition_info'); var selectedGroupsLabel = userPartitionInfo['selected_groups_label']; var selectedPartitionIndex = userPartitionInfo['selected_partition_index']; +var tagsCount = (xblockInfo.get('tag_counts_by_unit') || {})[xblockInfo.get('id')] || 0; var statusMessages = []; var messageType; @@ -170,11 +171,11 @@ if (is_proctored_exam) { <% } %> - <% if (xblockInfo.isVertical() && typeof useTaggingTaxonomyListPage !== "undefined" && useTaggingTaxonomyListPage) { %> + <% if (xblockInfo.isVertical() && typeof useTaggingTaxonomyListPage !== "undefined" && useTaggingTaxonomyListPage && tagsCount > 0) { %>
  • - ? + <%- tagsCount %> <%- gettext('Manage Tags') %>
  • diff --git a/openedx/core/djangoapps/content_tagging/api.py b/openedx/core/djangoapps/content_tagging/api.py index 88cdf0d03e..1f33cc1c84 100644 --- a/openedx/core/djangoapps/content_tagging/api.py +++ b/openedx/core/djangoapps/content_tagging/api.py @@ -18,7 +18,7 @@ def create_taxonomy( name: str, description: str | None = None, enabled=True, - allow_multiple=False, + allow_multiple=True, allow_free_text=False, orgs: list[Organization] | None = None, ) -> Taxonomy: diff --git a/openedx/core/djangoapps/content_tagging/tests/test_api.py b/openedx/core/djangoapps/content_tagging/tests/test_api.py index c086396ee2..8dc6117376 100644 --- a/openedx/core/djangoapps/content_tagging/tests/test_api.py +++ b/openedx/core/djangoapps/content_tagging/tests/test_api.py @@ -20,28 +20,23 @@ class TestTaxonomyMixin: # Taxonomies self.taxonomy_disabled = api.create_taxonomy( name="Learning Objectives", - enabled=False, + # We will disable this taxonomy below, after we have used it to tag some objects. + # Note: "disabled" taxonomies are not a supported nor user-exposed feature at the moment, so it's not + # actually that important to test them. ) api.set_taxonomy_orgs(self.taxonomy_disabled, orgs=[self.org1, self.org2]) - self.taxonomy_all_orgs = api.create_taxonomy( - name="Content Types", - enabled=True, - ) + + self.taxonomy_all_orgs = api.create_taxonomy(name="Content Types") api.set_taxonomy_orgs(self.taxonomy_all_orgs, all_orgs=True) - self.taxonomy_both_orgs = api.create_taxonomy( - name="OpenedX/Axim Content Types", - enabled=True, - ) + + self.taxonomy_both_orgs = api.create_taxonomy(name="OpenedX/Axim Content Types") api.set_taxonomy_orgs(self.taxonomy_both_orgs, orgs=[self.org1, self.org2]) - self.taxonomy_one_org = api.create_taxonomy( - name="OpenedX Content Types", - enabled=True, - ) + + self.taxonomy_one_org = api.create_taxonomy(name="OpenedX Content Types") api.set_taxonomy_orgs(self.taxonomy_one_org, orgs=[self.org1]) - self.taxonomy_no_orgs = api.create_taxonomy( - name="No orgs", - enabled=True, - ) + + self.taxonomy_no_orgs = api.create_taxonomy(name="No orgs") + # Tags self.tag_disabled = Tag.objects.create( taxonomy=self.taxonomy_disabled, @@ -100,6 +95,9 @@ class TestTaxonomyMixin: taxonomy=self.taxonomy_disabled, tags=[self.tag_disabled.value], )[0] + self.taxonomy_disabled.enabled = False + self.taxonomy_disabled.save() + self.disabled_course_tag.refresh_from_db() # Update its cached .taxonomy @ddt.ddt @@ -184,7 +182,6 @@ class TestAPITaxonomy(TestTaxonomyMixin, TestCase): assert valid_tags[0].id == object_tag.id @ddt.data( - ("taxonomy_disabled", "disabled_course_tag"), ("taxonomy_all_orgs", "all_orgs_course_tag"), ("taxonomy_all_orgs", "all_orgs_block_tag"), ("taxonomy_both_orgs", "both_orgs_course_tag"), diff --git a/openedx/core/djangoapps/content_tagging/tests/test_rules.py b/openedx/core/djangoapps/content_tagging/tests/test_rules.py index 9c1187ab91..d0aba1f91f 100644 --- a/openedx/core/djangoapps/content_tagging/tests/test_rules.py +++ b/openedx/core/djangoapps/content_tagging/tests/test_rules.py @@ -599,7 +599,7 @@ class TestRulesTaxonomy(TestTaxonomyMixin, TestCase): def test_view_object_tag_diabled(self): """ - Noboty can view a ObjectTag from a disable taxonomy + Nobody can view a ObjectTag from a disabled taxonomy """ perm = "oel_tagging.view_objecttag" assert self.superuser.has_perm(perm, self.disabled_course_tag) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index ce0abac9ef..cdffa7de14 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -121,7 +121,7 @@ libsass==0.10.0 click==8.1.6 # pinning this version to avoid updates while the library is being developed -openedx-learning==0.3.3 +openedx-learning==0.3.4 # lti-consumer-xblock 9.6.2 contains a breaking change that makes # existing custom parameter configurations unusable. diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 381e65cdbf..8bc597dc1c 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -802,7 +802,7 @@ openedx-filters==1.6.0 # via # -r requirements/edx/kernel.in # lti-consumer-xblock -openedx-learning==0.3.3 +openedx-learning==0.3.4 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index a0469b417c..3dd5071f99 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -1330,7 +1330,7 @@ openedx-filters==1.6.0 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # lti-consumer-xblock -openedx-learning==0.3.3 +openedx-learning==0.3.4 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index dae7893119..74f0aac9b5 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -960,7 +960,7 @@ openedx-filters==1.6.0 # via # -r requirements/edx/base.txt # lti-consumer-xblock -openedx-learning==0.3.3 +openedx-learning==0.3.4 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 78513772cb..8c0234d392 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -1005,7 +1005,7 @@ openedx-filters==1.6.0 # via # -r requirements/edx/base.txt # lti-consumer-xblock -openedx-learning==0.3.3 +openedx-learning==0.3.4 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt