diff --git a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py index aac29de20aa9..de3dbc55e54f 100644 --- a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py +++ b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py @@ -1243,7 +1243,8 @@ 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["course_tags_count"] = _get_course_tags_count(course.id) + xblock_info["tag_counts_by_block"] = _get_course_block_tags(xblock.location.context_key) xblock_info[ "has_partition_group_components" @@ -1263,16 +1264,29 @@ def create_xblock_info( # lint-amnesty, pylint: disable=too-many-statements @request_cached() -def _get_course_unit_tags(course_key) -> dict: +def _get_course_tags_count(course_key) -> dict: """ - Get the count of tags that are applied to each unit (vertical) in this course, as a dict. + Get the count of tags that are applied to the course as a dict: {course_key: tags_count} + """ + if not course_key.is_course: + return {} # Unsupported key type + + return get_object_tag_counts(str(course_key), count_implicit=True) + + +@request_cached() +def _get_course_block_tags(course_key) -> dict: + """ + Get the count of tags that are applied to each block 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 get_object_tag_counts(unit_key_pattern, count_implicit=True) + + # Create a pattern to match the IDs of all blocks, e.g. "block-v1:org+course+run+type@*" + catch_all_key = course_key.make_usage_key("*", "x") + catch_all_key_pattern = str(catch_all_key).rsplit("@*", 1)[0] + "@*" + + return get_object_tag_counts(catch_all_key_pattern, count_implicit=True) def _was_xblock_ever_exam_linked_with_external(course, xblock): diff --git a/cms/static/js/views/course_manage_tags.js b/cms/static/js/views/course_manage_tags.js new file mode 100644 index 000000000000..b276069b345d --- /dev/null +++ b/cms/static/js/views/course_manage_tags.js @@ -0,0 +1,54 @@ +define([ + 'jquery', 'underscore', 'backbone', 'js/utils/templates', + 'edx-ui-toolkit/js/utils/html-utils', 'js/views/utils/tagging_drawer_utils', + 'js/views/tag_count', 'js/models/tag_count'], +function( + $, _, Backbone, TemplateUtils, HtmlUtils, TaggingDrawerUtils, TagCountView, TagCountModel +) { + 'use strict'; + + var CourseManageTagsView = Backbone.View.extend({ + events: { + 'click .manage-tags-button': 'openManageTagsDrawer', + }, + + initialize: function() { + this.template = TemplateUtils.loadTemplate('course-manage-tags'); + this.courseId = course.id; + }, + + openManageTagsDrawer: function(event) { + const taxonomyTagsWidgetUrl = this.model.get('taxonomy_tags_widget_url'); + const contentId = this.courseId; + TaggingDrawerUtils.openDrawer(taxonomyTagsWidgetUrl, contentId); + }, + + renderTagCount: function() { + const contentId = this.courseId; + const tagCountsForCourse = this.model.get('course_tags_count'); + const tagsCount = tagCountsForCourse !== undefined ? tagCountsForCourse[contentId] : 0; + var countModel = new TagCountModel({ + content_id: contentId, + tags_count: tagsCount, + course_authoring_url: this.model.get('course_authoring_url'), + }, {parse: true}); + var tagCountView = new TagCountView({el: this.$('.tag-count'), model: countModel}); + tagCountView.setupMessageListener(); + tagCountView.render(); + this.$('.tag-count').click((event) => { + event.preventDefault(); + this.openManageTagsDrawer(); + }); + }, + + render: function() { + var html = this.template(this.model.attributes); + HtmlUtils.setHtml(this.$el, HtmlUtils.HTML(html)); + this.renderTagCount(); + return this; + } + }); + + return CourseManageTagsView; +} +); diff --git a/cms/static/js/views/course_outline.js b/cms/static/js/views/course_outline.js index 8319e42eb3ea..b0e89127aa28 100644 --- a/cms/static/js/views/course_outline.js +++ b/cms/static/js/views/course_outline.js @@ -34,17 +34,22 @@ function( renderTagCount: function() { const contentId = this.model.get('id'); - const tagCountsByUnit = this.model.get('tag_counts_by_unit') - const tagsCount = tagCountsByUnit !== undefined ? tagCountsByUnit[contentId] : 0 + const tagCountsByBlock = this.model.get('tag_counts_by_block') + // Skip the course block since that is handled elsewhere in course_manage_tags + if (contentId.includes('@course')) { + return + } + const tagsCount = tagCountsByBlock !== undefined ? tagCountsByBlock[contentId] : 0 + const tagCountElem = this.$(`.tag-count[data-locator="${contentId}"]`); var countModel = new TagCountModel({ content_id: contentId, tags_count: tagsCount, course_authoring_url: this.model.get('course_authoring_url'), }, {parse: true}); - var tagCountView = new TagCountView({el: this.$('.tag-count'), model: countModel}); + var tagCountView = new TagCountView({el: tagCountElem, model: countModel}); tagCountView.setupMessageListener(); tagCountView.render(); - this.$('.tag-count').click((event) => { + tagCountElem.click((event) => { event.preventDefault(); this.openManageTagsDrawer(); }); diff --git a/cms/static/js/views/pages/course_outline.js b/cms/static/js/views/pages/course_outline.js index dd1c13eda9fc..627a21e563e5 100644 --- a/cms/static/js/views/pages/course_outline.js +++ b/cms/static/js/views/pages/course_outline.js @@ -4,10 +4,12 @@ define([ 'jquery', 'underscore', 'gettext', 'js/views/pages/base_page', 'js/views/utils/xblock_utils', 'js/views/course_outline', 'common/js/components/utils/view_utils', 'common/js/components/views/feedback_alert', - 'common/js/components/views/feedback_notification', 'js/views/course_highlights_enable', 'js/views/course_video_sharing_enable'], + 'common/js/components/views/feedback_notification', 'js/views/course_highlights_enable', 'js/views/course_video_sharing_enable', + 'js/views/course_manage_tags'], function($, _, gettext, BasePage, XBlockViewUtils, CourseOutlineView, ViewUtils, AlertView, NoteView, CourseHighlightsEnableView, - CourseVideoSharingEnableView + CourseVideoSharingEnableView, + CourseManageTagsView ) { 'use strict'; @@ -135,6 +137,15 @@ function($, _, gettext, BasePage, XBlockViewUtils, CourseOutlineView, ViewUtils, this.highlightsEnableView.render(); } + // if tagging enabled + if (this.model.get('use_tagging_taxonomy_list_page')) { + this.courseManageTagsView = new CourseManageTagsView({ + el: this.$('.status-manage-tags'), + model: this.model + }); + this.courseManageTagsView.render(); + } + // if video sharing enable if (this.model.get('video_sharing_enabled')) { this.videoSharingEnableView = new CourseVideoSharingEnableView({ diff --git a/cms/static/sass/views/_outline.scss b/cms/static/sass/views/_outline.scss index 480335211392..469939cc8d97 100644 --- a/cms/static/sass/views/_outline.scss +++ b/cms/static/sass/views/_outline.scss @@ -185,6 +185,7 @@ .status-release, .status-highlights-enabled, + .status-manage-tags, .status-video-sharing-enabled, .status-studio-frontend { @extend %t-copy-base; @@ -202,6 +203,7 @@ } .status-highlights-enabled, + .status-manage-tags, .status-video-sharing-enabled { vertical-align: top; } @@ -209,9 +211,12 @@ .status-release-label, .status-release-value, .status-highlights-enabled-label, + .status-course-manage-tags-label, .status-video-sharing-enabled-label, .status-highlights-enabled-value, + .status-course-manage-tags-value, .status-highlights-enabled-info, + .status-course-manage-tags-info, .status-video-sharing-enabled-info, .status-actions { display: inline-block; @@ -221,6 +226,7 @@ .status-release-value, .status-highlights-enabled-value, + .status-course-manage-tags-value, .status-video-sharing-enabled-value { @extend %t-strong; @@ -228,6 +234,7 @@ } .status-highlights-enabled-info, + .status-course-manage-tags-info, .status-video-sharing-enabled-info { font-size: smaller; margin-left: $baseline / 2; diff --git a/cms/templates/course_outline.html b/cms/templates/course_outline.html index f1e426212bce..16d9ccbd4ca5 100644 --- a/cms/templates/course_outline.html +++ b/cms/templates/course_outline.html @@ -29,7 +29,7 @@ <%block name="header_extras"> -% for template_name in ['course-outline', 'xblock-string-field-editor', 'basic-modal', 'modal-button', 'course-outline-modal', 'due-date-editor', 'self-paced-due-date-editor', 'release-date-editor', 'grading-editor', 'publish-editor', 'staff-lock-editor', 'unit-access-editor', 'discussion-editor', 'content-visibility-editor', 'verification-access-editor', 'timed-examination-preference-editor', 'access-editor', 'settings-modal-tabs', 'show-correctness-editor', 'highlights-editor', 'highlights-enable-editor', 'course-highlights-enable', 'course-video-sharing-enable', 'summary-configuration-editor', 'tag-count', 'subsection-share-link-modal-tabs', 'full-page-share-link-editor', 'embed-link-share-link-editor']: +% for template_name in ['course-outline', 'xblock-string-field-editor', 'basic-modal', 'modal-button', 'course-outline-modal', 'due-date-editor', 'self-paced-due-date-editor', 'release-date-editor', 'grading-editor', 'publish-editor', 'staff-lock-editor', 'unit-access-editor', 'discussion-editor', 'content-visibility-editor', 'verification-access-editor', 'timed-examination-preference-editor', 'access-editor', 'settings-modal-tabs', 'show-correctness-editor', 'highlights-editor', 'highlights-enable-editor', 'course-highlights-enable', 'course-manage-tags', 'course-video-sharing-enable', 'summary-configuration-editor', 'tag-count', 'subsection-share-link-modal-tabs', 'full-page-share-link-editor', 'embed-link-share-link-editor']: @@ -269,6 +269,7 @@