Skip to content

Commit

Permalink
Merge pull request #28 from nelc/and/backport_tag_course
Browse files Browse the repository at this point in the history
And/backport tag course
  • Loading branch information
andrey-canon authored May 21, 2024
2 parents 8942492 + 4e855ee commit 91a8b31
Show file tree
Hide file tree
Showing 21 changed files with 337 additions and 50 deletions.
6 changes: 1 addition & 5 deletions cms/djangoapps/contentstore/tests/test_contentstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
delete_course,
reverse_course_url,
reverse_url,
get_taxonomy_tags_widget_url,
)
from cms.djangoapps.contentstore.views.component import ADVANCED_COMPONENT_TYPES
from common.djangoapps.course_action_state.managers import CourseActionStateItemNotFoundError
Expand Down Expand Up @@ -1381,14 +1380,11 @@ def test_course_overview_view_with_course(self):
self.assertEqual(resp.status_code, 404)
return

taxonomy_tags_widget_url = get_taxonomy_tags_widget_url(course.id)

self.assertContains(
resp,
'<article class="outline outline-complex outline-course" data-locator="{locator}" data-course-key="{course_key}" data-taxonomy-tags-widget-url="{taxonomy_tags_widget_url}" >'.format( # lint-amnesty, pylint: disable=line-too-long
'<article class="outline outline-complex outline-course" data-locator="{locator}" data-course-key="{course_key}">'.format( # lint-amnesty, pylint: disable=line-too-long
locator=str(course.location),
course_key=str(course.id),
taxonomy_tags_widget_url=taxonomy_tags_widget_url,
),
status_code=200,
html=True
Expand Down
29 changes: 22 additions & 7 deletions cms/djangoapps/contentstore/views/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -1375,6 +1375,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
xblock_info["tags"] = tags
if use_tagging_taxonomy_list_page():
xblock_info["taxonomy_tags_widget_url"] = get_taxonomy_tags_widget_url()
xblock_info["course_authoring_url"] = settings.COURSE_AUTHORING_MICROFRONTEND_URL

if course_outline:
if xblock_info['has_explicit_staff_lock']:
Expand All @@ -1393,7 +1394,8 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
# 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['user_partition_info'] = get_visibility_partition_info(xblock, course=course)

Expand Down Expand Up @@ -1641,16 +1643,29 @@ def _xblock_type_and_display_name(xblock):


@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 get_children_tags_count(xblock):
Expand Down
1 change: 1 addition & 0 deletions cms/djangoapps/contentstore/views/preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
'can_edit': can_edit,
'enable_copy_paste': enable_copy_paste,
'can_edit_visibility': context.get('can_edit_visibility', xblock.scope_ids.usage_id.context_key.is_course),
'course_authoring_url': settings.COURSE_AUTHORING_MICROFRONTEND_URL,
'selected_groups_label': selected_groups_label,
'can_add': context.get('can_add', True),
'can_move': context.get('can_move', xblock.scope_ids.usage_id.context_key.is_course),
Expand Down
8 changes: 1 addition & 7 deletions cms/djangoapps/contentstore/views/tests/test_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,15 +258,9 @@ def test_tag_count_in_container_fragment(self, mock_get_object_tag_counts):
self.assertEqual(resp.status_code, 200)
usage_key = self.response_usage_key(resp)

# Get the preview HTML without tags
mock_get_object_tag_counts.return_value = {}
html, __ = self._get_container_preview(root_usage_key)
self.assertIn("wrapper-xblock", html)
self.assertNotIn('data-testid="tag-count-button"', html)

# Get the preview HTML with tags
mock_get_object_tag_counts.return_value = {
str(usage_key): 13
str(usage_key): 13,
}
html, __ = self._get_container_preview(root_usage_key)
self.assertIn("wrapper-xblock", html)
Expand Down
13 changes: 13 additions & 0 deletions cms/static/js/factories/tag_count.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as TagCountView from 'js/views/tag_count';
import * as TagCountModel from 'js/models/tag_count';

// eslint-disable-next-line no-unused-expressions
'use strict';
export default function TagCountFactory(TagCountJson, el) {
var model = new TagCountModel(TagCountJson, {parse: true});
var tagCountView = new TagCountView({el, model});
tagCountView.setupMessageListener();
tagCountView.render();
}

export {TagCountFactory};
13 changes: 13 additions & 0 deletions cms/static/js/models/tag_count.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
define(['backbone', 'underscore'], function(Backbone, _) {
/**
* Model for Tag count view
*/
var TagCountModel = Backbone.Model.extend({
defaults: {
content_id: null,
tags_count: 0,
course_authoring_url: null,
},
});
return TagCountModel;
});
54 changes: 54 additions & 0 deletions cms/static/js/views/course_manage_tags.js
Original file line number Diff line number Diff line change
@@ -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;
}
);
32 changes: 27 additions & 5 deletions cms/static/js/views/course_outline.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
*/
define(['jquery', 'underscore', 'js/views/xblock_outline', 'common/js/components/utils/view_utils', 'js/views/utils/xblock_utils',
'js/models/xblock_outline_info', 'js/views/modals/course_outline_modals', 'js/utils/drag_and_drop',
'js/views/utils/tagging_drawer_utils',],
'js/views/utils/tagging_drawer_utils', 'js/views/tag_count', 'js/models/tag_count'],
function(
$, _, XBlockOutlineView, ViewUtils, XBlockViewUtils,
XBlockOutlineInfo, CourseOutlineModalsFactory, ContentDragger, TaggingDrawerUtils
XBlockOutlineInfo, CourseOutlineModalsFactory, ContentDragger, TaggingDrawerUtils, TagCountView, TagCountModel
) {
var CourseOutlineView = XBlockOutlineView.extend({
// takes XBlockOutlineInfo as a model
Expand All @@ -23,9 +23,33 @@ function(
render: function() {
var renderResult = XBlockOutlineView.prototype.render.call(this);
this.makeContentDraggable(this.el);
this.renderTagCount();
return renderResult;
},

renderTagCount: function() {
const contentId = this.model.get('id');
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: tagCountElem, model: countModel});
tagCountView.setupMessageListener();
tagCountView.render();
tagCountElem.click((event) => {
event.preventDefault();
this.openManageTagsDrawer();
});
},

shouldExpandChildren: function() {
return this.expandedLocators.contains(this.model.get('id'));
},
Expand Down Expand Up @@ -217,10 +241,8 @@ function(
},

openManageTagsDrawer() {
const article = document.querySelector('[data-taxonomy-tags-widget-url]');
const taxonomyTagsWidgetUrl = $(article).attr('data-taxonomy-tags-widget-url');
const taxonomyTagsWidgetUrl = this.model.get('taxonomy_tags_widget_url');
const contentId = this.model.get('id');

TaggingDrawerUtils.openDrawer(taxonomyTagsWidgetUrl, contentId);
},

Expand Down
1 change: 1 addition & 0 deletions cms/static/js/views/pages/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView
el: this.$('.unit-tags'),
model: this.model
});
this.tagListView.setupMessageListener();
this.tagListView.render();

this.unitOutlineView = new UnitOutlineView({
Expand Down
77 changes: 77 additions & 0 deletions cms/static/js/views/pages/container_subviews.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,83 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
}
},

setupMessageListener: function () {
window.addEventListener(
"message", (event) => {
// Listen any message from Manage tags drawer.
var data = event.data;
var courseAuthoringUrl = this.model.get("course_authoring_url")
if (event.origin == courseAuthoringUrl
&& data.includes('[Manage tags drawer] Tags updated:')) {
// This message arrives when there is a change in the tag list.
// The message contains the new list of tags.
let jsonData = data.replace(/\[Manage tags drawer\] Tags updated: /g, "");
jsonData = JSON.parse(jsonData);
if (jsonData.contentId == this.model.id) {
this.model.set('tags', this.buildTaxonomyTree(jsonData));
this.render();
}
}
},
);
},

buildTaxonomyTree: function(data) {
// TODO We can use this function for the initial request of tags
// and avoid to use two functions (see get_unit_tags on contentstore/views/component.py)

var taxonomyList = [];
var totalCount = 0;
var actualId = 0;
data.taxonomies.forEach((taxonomy) => {
// Build a tag tree for each taxonomy
var rootTagsValues = [];
var tags = {};
taxonomy.tags.forEach((tag) => {
// Creates the tags for all the lineage of this tag
for (let i = tag.lineage.length - 1; i >= 0; i--){
var tagValue = tag.lineage[i]
var tagProcessedBefore = tags.hasOwnProperty(tagValue);
if (!tagProcessedBefore) {
tags[tagValue] = {
id: actualId,
value: tagValue,
children: [],
}
actualId++;
if (i == 0) {
rootTagsValues.push(tagValue);
}
}
if (i !== tag.lineage.length - 1) {
// Add a child into the children list
tags[tagValue].children.push(tags[tag.lineage[i + 1]])
}
if (tagProcessedBefore) {
// Break this loop if the tag has been processed before,
// we don't need to process lineage again to avoid duplicates.
break;
}
}
})

var tagCount = Object.keys(tags).length;
// Add the tree to the taxonomy list
taxonomyList.push({
id: taxonomy.taxonomyId,
value: taxonomy.name,
tags: rootTagsValues.map(rootValue => tags[rootValue]),
count: tagCount,
});
totalCount += tagCount;
});

return {
count: totalCount,
taxonomies: taxonomyList,
};
},

handleKeyDownOnHeader: function(event) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
Expand Down
14 changes: 12 additions & 2 deletions cms/static/js/views/pages/course_outline.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
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'],
'common/js/components/views/feedback_notification', 'js/views/course_highlights_enable', 'js/views/course_manage_tags'],
function($, _, gettext, BasePage, XBlockViewUtils, CourseOutlineView, ViewUtils, AlertView, NoteView,
CourseHighlightsEnableView
CourseHighlightsEnableView,
CourseManageTagsView
) {
'use strict';
var expandedLocators, CourseOutlinePage;
Expand Down Expand Up @@ -93,6 +94,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();
}

this.outlineView = new this.outlineViewClass({
el: this.$('.outline'),
model: this.model,
Expand Down
Loading

0 comments on commit 91a8b31

Please sign in to comment.