Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

And/backport tag course #28

Merged
merged 2 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading