Merge pull request #14803 from edx/aj/TNL-6743_GroupConfigEnrollmentPartition
Aj/tnl 6743 group config enrollment partition
This commit is contained in:
@@ -8,15 +8,16 @@ from util.db import generate_int_id, MYSQL_MAX_INT
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from contentstore.utils import reverse_usage_url
|
||||
from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_user_partition
|
||||
from xmodule.partitions.partitions import UserPartition, MINIMUM_STATIC_PARTITION_ID
|
||||
from xmodule.partitions.partitions_service import get_all_partitions_for_course
|
||||
from xmodule.split_test_module import get_split_user_partitions
|
||||
from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_user_partition
|
||||
|
||||
MINIMUM_GROUP_ID = MINIMUM_STATIC_PARTITION_ID
|
||||
|
||||
RANDOM_SCHEME = "random"
|
||||
COHORT_SCHEME = "cohort"
|
||||
ENROLLMENT_SCHEME = "enrollment_track"
|
||||
|
||||
CONTENT_GROUP_CONFIGURATION_DESCRIPTION = _(
|
||||
'The groups in this configuration can be mapped to cohorts in the Instructor Dashboard.'
|
||||
@@ -187,21 +188,10 @@ class GroupConfiguration(object):
|
||||
return usage_info
|
||||
|
||||
@staticmethod
|
||||
def get_content_groups_usage_info(store, course):
|
||||
"""
|
||||
Get usage information for content groups.
|
||||
"""
|
||||
items = store.get_items(course.id, settings={'group_access': {'$exists': True}}, include_orphans=False)
|
||||
|
||||
return GroupConfiguration._get_content_groups_usage_info(course, items)
|
||||
|
||||
@staticmethod
|
||||
def _get_content_groups_usage_info(course, items):
|
||||
def get_partitions_usage_info(store, course):
|
||||
"""
|
||||
Returns all units names and their urls.
|
||||
|
||||
This will return only groups for the cohort user partition.
|
||||
|
||||
Returns:
|
||||
{'group_id':
|
||||
[
|
||||
@@ -216,8 +206,10 @@ class GroupConfiguration(object):
|
||||
],
|
||||
}
|
||||
"""
|
||||
items = store.get_items(course.id, settings={'group_access': {'$exists': True}}, include_orphans=False)
|
||||
|
||||
usage_info = {}
|
||||
for item, group_id in GroupConfiguration._iterate_items_and_content_group_ids(course, items):
|
||||
for item, group_id in GroupConfiguration._iterate_items_and_group_ids(course, items):
|
||||
if group_id not in usage_info:
|
||||
usage_info[group_id] = []
|
||||
|
||||
@@ -267,7 +259,7 @@ class GroupConfiguration(object):
|
||||
}
|
||||
"""
|
||||
usage_info = {}
|
||||
for item, group_id in GroupConfiguration._iterate_items_and_content_group_ids(course, items):
|
||||
for item, group_id in GroupConfiguration._iterate_items_and_group_ids(course, items):
|
||||
if group_id not in usage_info:
|
||||
usage_info[group_id] = []
|
||||
|
||||
@@ -282,22 +274,23 @@ class GroupConfiguration(object):
|
||||
return usage_info
|
||||
|
||||
@staticmethod
|
||||
def _iterate_items_and_content_group_ids(course, items):
|
||||
def _iterate_items_and_group_ids(course, items):
|
||||
"""
|
||||
Iterate through items and content group IDs in a course.
|
||||
Iterate through items and group IDs in a course.
|
||||
|
||||
This will yield group IDs *only* for cohort user partitions.
|
||||
This will yield group IDs for all user partitions except those with a scheme of random.
|
||||
|
||||
Yields: tuple of (item, group_id)
|
||||
"""
|
||||
content_group_configuration = get_cohorted_user_partition(course)
|
||||
if content_group_configuration is not None:
|
||||
for item in items:
|
||||
if hasattr(item, 'group_access') and item.group_access:
|
||||
group_ids = item.group_access.get(content_group_configuration.id, [])
|
||||
all_partitions = get_all_partitions_for_course(course)
|
||||
for config in all_partitions:
|
||||
if config is not None and config.scheme.name != RANDOM_SCHEME:
|
||||
for item in items:
|
||||
if hasattr(item, 'group_access') and item.group_access:
|
||||
group_ids = item.group_access.get(config.id, [])
|
||||
|
||||
for group_id in group_ids:
|
||||
yield item, group_id
|
||||
for group_id in group_ids:
|
||||
yield item, group_id
|
||||
|
||||
@staticmethod
|
||||
def update_usage_info(store, course, configuration):
|
||||
@@ -319,23 +312,23 @@ class GroupConfiguration(object):
|
||||
configuration_json['usage'] = usage_information.get(configuration.id, [])
|
||||
elif configuration.scheme.name == COHORT_SCHEME:
|
||||
# In case if scheme is "cohort"
|
||||
configuration_json = GroupConfiguration.update_content_group_usage_info(store, course, configuration)
|
||||
configuration_json = GroupConfiguration.update_partition_usage_info(store, course, configuration)
|
||||
return configuration_json
|
||||
|
||||
@staticmethod
|
||||
def update_content_group_usage_info(store, course, configuration):
|
||||
def update_partition_usage_info(store, course, configuration):
|
||||
"""
|
||||
Update usage information for particular Content Group Configuration.
|
||||
Update usage information for particular Partition Configuration.
|
||||
|
||||
Returns json of particular content group configuration updated with usage information.
|
||||
Returns json of particular partition configuration updated with usage information.
|
||||
"""
|
||||
usage_info = GroupConfiguration.get_content_groups_usage_info(store, course)
|
||||
content_group_configuration = configuration.to_json()
|
||||
usage_info = GroupConfiguration.get_partitions_usage_info(store, course)
|
||||
partition_configuration = configuration.to_json()
|
||||
|
||||
for group in content_group_configuration['groups']:
|
||||
for group in partition_configuration['groups']:
|
||||
group['usage'] = usage_info.get(group['id'], [])
|
||||
|
||||
return content_group_configuration
|
||||
return partition_configuration
|
||||
|
||||
@staticmethod
|
||||
def get_or_create_content_group(store, course):
|
||||
@@ -357,9 +350,27 @@ class GroupConfiguration(object):
|
||||
)
|
||||
return content_group_configuration.to_json()
|
||||
|
||||
content_group_configuration = GroupConfiguration.update_content_group_usage_info(
|
||||
content_group_configuration = GroupConfiguration.update_partition_usage_info(
|
||||
store,
|
||||
course,
|
||||
content_group_configuration
|
||||
)
|
||||
return content_group_configuration
|
||||
|
||||
@staticmethod
|
||||
def get_all_user_partition_details(store, course):
|
||||
"""
|
||||
Returns all the available partitions with updated usage information
|
||||
|
||||
:return: list of all partitions available with details
|
||||
"""
|
||||
all_partitions = get_all_partitions_for_course(course)
|
||||
all_updated_partitions = []
|
||||
for partition in all_partitions:
|
||||
configuration = GroupConfiguration.update_partition_usage_info(
|
||||
store,
|
||||
course,
|
||||
partition
|
||||
)
|
||||
all_updated_partitions.append(configuration)
|
||||
return all_updated_partitions
|
||||
|
||||
@@ -377,7 +377,7 @@ class CoursewareSearchIndexer(SearchIndexerBase):
|
||||
@classmethod
|
||||
def fetch_group_usage(cls, modulestore, structure):
|
||||
groups_usage_dict = {}
|
||||
groups_usage_info = GroupConfiguration.get_content_groups_usage_info(modulestore, structure).items()
|
||||
groups_usage_info = GroupConfiguration.get_partitions_usage_info(modulestore, structure).items()
|
||||
groups_usage_info.extend(
|
||||
GroupConfiguration.get_content_groups_items_usage_info(
|
||||
modulestore,
|
||||
|
||||
@@ -30,6 +30,7 @@ from .library import LIBRARIES_ENABLED, get_library_creator_status
|
||||
from ccx_keys.locator import CCXLocator
|
||||
from contentstore.course_group_config import (
|
||||
COHORT_SCHEME,
|
||||
ENROLLMENT_SCHEME,
|
||||
GroupConfiguration,
|
||||
GroupConfigurationsValidationError,
|
||||
RANDOM_SCHEME,
|
||||
@@ -99,7 +100,6 @@ from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError, DuplicateCourseError
|
||||
from xmodule.tabs import CourseTab, CourseTabList, InvalidTabsException
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
__all__ = ['course_info_handler', 'course_handler', 'course_listing',
|
||||
@@ -1473,7 +1473,7 @@ def remove_content_or_experiment_group(request, store, course, configuration, gr
|
||||
return JsonResponse(status=404)
|
||||
|
||||
group_id = int(group_id)
|
||||
usages = GroupConfiguration.get_content_groups_usage_info(store, course)
|
||||
usages = GroupConfiguration.get_partitions_usage_info(store, course)
|
||||
used = group_id in usages
|
||||
|
||||
if used:
|
||||
@@ -1521,7 +1521,24 @@ def group_configurations_list_handler(request, course_key_string):
|
||||
else:
|
||||
experiment_group_configurations = None
|
||||
|
||||
content_group_configuration = GroupConfiguration.get_or_create_content_group(store, course)
|
||||
all_partitions = GroupConfiguration.get_all_user_partition_details(store, course)
|
||||
should_show_enrollment_track = False
|
||||
group_schemes = []
|
||||
for partition in all_partitions:
|
||||
group_schemes.append(partition['scheme'])
|
||||
if partition['scheme'] == ENROLLMENT_SCHEME:
|
||||
enrollment_track_configuration = partition
|
||||
should_show_enrollment_track = len(enrollment_track_configuration['groups']) > 1
|
||||
|
||||
# Remove the enrollment track partition and add it to the front of the list if it should be shown.
|
||||
all_partitions.remove(partition)
|
||||
if should_show_enrollment_track:
|
||||
all_partitions.insert(0, partition)
|
||||
|
||||
# Add empty content group if there is no COHORT User Partition in the list.
|
||||
# This will add ability to add new groups in the view.
|
||||
if COHORT_SCHEME not in group_schemes:
|
||||
all_partitions.append(GroupConfiguration.get_or_create_content_group(store, course))
|
||||
|
||||
return render_to_response('group_configurations.html', {
|
||||
'context_course': course,
|
||||
@@ -1529,7 +1546,8 @@ def group_configurations_list_handler(request, course_key_string):
|
||||
'course_outline_url': course_outline_url,
|
||||
'experiment_group_configurations': experiment_group_configurations,
|
||||
'should_show_experiment_groups': should_show_experiment_groups,
|
||||
'content_group_configuration': content_group_configuration
|
||||
'all_group_configurations': all_partitions,
|
||||
'should_show_enrollment_track': should_show_enrollment_track
|
||||
})
|
||||
elif "application/json" in request.META.get('HTTP_ACCEPT'):
|
||||
if request.method == 'POST':
|
||||
|
||||
@@ -613,6 +613,15 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
|
||||
def setUp(self):
|
||||
super(GroupConfigurationsUsageInfoTestCase, self).setUp()
|
||||
|
||||
def _get_user_partition(self, scheme):
|
||||
"""
|
||||
Returns the first user partition with the specified scheme.
|
||||
"""
|
||||
for group in GroupConfiguration.get_all_user_partition_details(self.store, self.course):
|
||||
if group['scheme'] == scheme:
|
||||
return group
|
||||
return None
|
||||
|
||||
def _get_expected_content_group(self, usage_for_group):
|
||||
"""
|
||||
Returns the expected configuration with particular usage.
|
||||
@@ -637,7 +646,7 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
|
||||
Test that right data structure will be created if content group is not used.
|
||||
"""
|
||||
self._add_user_partitions(scheme_id='cohort')
|
||||
actual = GroupConfiguration.get_or_create_content_group(self.store, self.course)
|
||||
actual = self._get_user_partition('cohort')
|
||||
expected = self._get_expected_content_group(usage_for_group=[])
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
@@ -650,7 +659,7 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
|
||||
cid=0, group_id=1, name_suffix='0', special_characters=u"JOSÉ ANDRÉS"
|
||||
)
|
||||
|
||||
actual = GroupConfiguration.get_or_create_content_group(self.store, self.course)
|
||||
actual = self._get_user_partition('cohort')
|
||||
expected = self._get_expected_content_group(
|
||||
usage_for_group=[
|
||||
{
|
||||
@@ -669,7 +678,7 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
|
||||
self._add_user_partitions(count=1, scheme_id='cohort')
|
||||
vertical, __ = self._create_problem_with_content_group(cid=0, group_id=1, name_suffix='0')
|
||||
|
||||
actual = GroupConfiguration.get_or_create_content_group(self.store, self.course)
|
||||
actual = self._get_user_partition('cohort')
|
||||
|
||||
expected = self._get_expected_content_group(usage_for_group=[
|
||||
{
|
||||
@@ -706,7 +715,7 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
|
||||
expected = self._get_expected_content_group(usage_for_group=[])
|
||||
|
||||
# Get the actual content group information
|
||||
actual = GroupConfiguration.get_or_create_content_group(self.store, self.course)
|
||||
actual = self._get_user_partition('cohort')
|
||||
|
||||
# Assert that actual content group information is same as expected one.
|
||||
self.assertEqual(actual, expected)
|
||||
@@ -720,7 +729,7 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
|
||||
vertical, __ = self._create_problem_with_content_group(cid=0, group_id=1, name_suffix='0')
|
||||
vertical1, __ = self._create_problem_with_content_group(cid=0, group_id=1, name_suffix='1')
|
||||
|
||||
actual = GroupConfiguration.get_or_create_content_group(self.store, self.course)
|
||||
actual = self._get_user_partition('cohort')
|
||||
|
||||
expected = self._get_expected_content_group(usage_for_group=[
|
||||
{
|
||||
@@ -927,7 +936,7 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
|
||||
|
||||
# This used to cause an exception since the code assumed that
|
||||
# only one partition would be available.
|
||||
actual = GroupConfiguration.get_content_groups_usage_info(self.store, self.course)
|
||||
actual = GroupConfiguration.get_partitions_usage_info(self.store, self.course)
|
||||
self.assertEqual(actual.keys(), [0])
|
||||
|
||||
actual = GroupConfiguration.get_content_groups_items_usage_info(self.store, self.course)
|
||||
|
||||
@@ -372,7 +372,7 @@ class GetItemTest(ItemTest):
|
||||
self.assertEqual(result["user_partitions"], [
|
||||
{
|
||||
"id": ENROLLMENT_TRACK_PARTITION_ID,
|
||||
"name": "Enrollment Tracks",
|
||||
"name": "Enrollment Track Groups",
|
||||
"scheme": "enrollment_track",
|
||||
"groups": [
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@ class AuthoringMixinTestCase(ModuleStoreTestCase):
|
||||
NO_CONTENT_ENROLLMENT_TRACK_ENABLED = "specific groups of learners based either on their enrollment track, or by content groups that you create"
|
||||
NO_CONTENT_ENROLLMENT_TRACK_DISABLED = "specific groups of learners based on content groups that you create"
|
||||
CONTENT_GROUPS_TITLE = "Content Groups"
|
||||
ENROLLMENT_GROUPS_TITLE = "Enrollment Tracks"
|
||||
ENROLLMENT_GROUPS_TITLE = "Enrollment Track Groups"
|
||||
STAFF_LOCKED = 'The unit that contains this component is hidden from learners'
|
||||
|
||||
FEATURES_WITH_ENROLLMENT_TRACK_DISABLED = settings.FEATURES.copy()
|
||||
|
||||
@@ -2,24 +2,33 @@ define([
|
||||
'js/collections/group_configuration', 'js/models/group_configuration', 'js/views/pages/group_configurations'
|
||||
], function(GroupConfigurationCollection, GroupConfigurationModel, GroupConfigurationsPage) {
|
||||
'use strict';
|
||||
return function(experimentsEnabled, experimentGroupConfigurationsJson, contentGroupConfigurationJson,
|
||||
groupConfigurationUrl, courseOutlineUrl) {
|
||||
return function(experimentsEnabled,
|
||||
experimentGroupConfigurationsJson,
|
||||
allGroupConfigurationJson,
|
||||
groupConfigurationUrl,
|
||||
courseOutlineUrl) {
|
||||
var experimentGroupConfigurations = new GroupConfigurationCollection(
|
||||
experimentGroupConfigurationsJson, {parse: true}
|
||||
),
|
||||
contentGroupConfiguration = new GroupConfigurationModel(contentGroupConfigurationJson, {
|
||||
parse: true, canBeEmpty: true
|
||||
});
|
||||
allGroupConfigurations = [],
|
||||
newGroupConfig,
|
||||
i;
|
||||
|
||||
for (i = 0; i < allGroupConfigurationJson.length; i++) {
|
||||
newGroupConfig = new GroupConfigurationModel(allGroupConfigurationJson[i],
|
||||
{parse: true, canBeEmpty: true});
|
||||
newGroupConfig.urlRoot = groupConfigurationUrl;
|
||||
newGroupConfig.outlineUrl = courseOutlineUrl;
|
||||
allGroupConfigurations.push(newGroupConfig);
|
||||
}
|
||||
|
||||
experimentGroupConfigurations.url = groupConfigurationUrl;
|
||||
experimentGroupConfigurations.outlineUrl = courseOutlineUrl;
|
||||
contentGroupConfiguration.urlRoot = groupConfigurationUrl;
|
||||
contentGroupConfiguration.outlineUrl = courseOutlineUrl;
|
||||
new GroupConfigurationsPage({
|
||||
el: $('#content'),
|
||||
experimentsEnabled: experimentsEnabled,
|
||||
experimentGroupConfigurations: experimentGroupConfigurations,
|
||||
contentGroupConfiguration: contentGroupConfiguration
|
||||
allGroupConfigurations: allGroupConfigurations
|
||||
}).render();
|
||||
};
|
||||
});
|
||||
|
||||
@@ -3,13 +3,13 @@ define([
|
||||
'common/js/spec_helpers/view_helpers', 'js/models/course', 'js/models/group_configuration', 'js/models/group',
|
||||
'js/collections/group_configuration', 'js/collections/group', 'js/views/group_configuration_details',
|
||||
'js/views/group_configurations_list', 'js/views/group_configuration_editor', 'js/views/group_configuration_item',
|
||||
'js/views/experiment_group_edit', 'js/views/content_group_list', 'js/views/content_group_details',
|
||||
'js/views/content_group_editor', 'js/views/content_group_item'
|
||||
'js/views/experiment_group_edit', 'js/views/partition_group_list', 'js/views/partition_group_details',
|
||||
'js/views/content_group_editor', 'js/views/partition_group_item'
|
||||
], function(
|
||||
_, AjaxHelpers, TemplateHelpers, ViewHelpers, Course, GroupConfigurationModel, GroupModel,
|
||||
GroupConfigurationCollection, GroupCollection, GroupConfigurationDetailsView, GroupConfigurationsListView,
|
||||
GroupConfigurationEditorView, GroupConfigurationItemView, ExperimentGroupEditView, GroupList,
|
||||
ContentGroupDetailsView, ContentGroupEditorView, ContentGroupItemView
|
||||
PartitionGroupDetailsView, ContentGroupEditorView, PartitionGroupItemView
|
||||
) {
|
||||
'use strict';
|
||||
var SELECTORS = {
|
||||
@@ -675,7 +675,7 @@ define([
|
||||
verifyEditingGroup, respondToSave, expectGroupsVisible, correctValidationError;
|
||||
|
||||
scopedGroupSelector = function(groupIndex, additionalSelectors) {
|
||||
var groupSelector = '.content-groups-list-item-' + groupIndex;
|
||||
var groupSelector = '.partition-groups-list-item-' + groupIndex;
|
||||
if (additionalSelectors) {
|
||||
return groupSelector + ' ' + additionalSelectors;
|
||||
} else {
|
||||
@@ -775,13 +775,13 @@ define([
|
||||
|
||||
expectGroupsVisible = function(view, groupNames) {
|
||||
_.each(groupNames, function(groupName) {
|
||||
expect(view.$('.content-groups-list-item')).toContainText(groupName);
|
||||
expect(view.$('.partition-groups-list-item')).toContainText(groupName);
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
TemplateHelpers.installTemplates(
|
||||
['content-group-editor', 'content-group-details', 'list']
|
||||
['content-group-editor', 'partition-group-details', 'list']
|
||||
);
|
||||
});
|
||||
|
||||
@@ -792,7 +792,7 @@ define([
|
||||
|
||||
it('can render groups', function() {
|
||||
var groupNames = ['Group 1', 'Group 2', 'Group 3'];
|
||||
renderView(groupNames).$('.content-group-details').each(function(index) {
|
||||
renderView(groupNames).$('.partition-group-details').each(function(index) {
|
||||
expect($(this)).toContainText(groupNames[index]);
|
||||
});
|
||||
});
|
||||
@@ -874,7 +874,7 @@ define([
|
||||
|
||||
describe('Content groups details view', function() {
|
||||
beforeEach(function() {
|
||||
TemplateHelpers.installTemplate('content-group-details', true);
|
||||
TemplateHelpers.installTemplate('partition-group-details', true);
|
||||
this.model = new GroupModel({name: 'Content Group', id: 0, courseOutlineUrl: 'CourseOutlineUrl'});
|
||||
|
||||
var saveableModel = new GroupConfigurationModel({
|
||||
@@ -889,7 +889,7 @@ define([
|
||||
this.collection = new GroupConfigurationCollection([saveableModel]);
|
||||
this.collection.outlineUrl = '/outline';
|
||||
|
||||
this.view = new ContentGroupDetailsView({
|
||||
this.view = new PartitionGroupDetailsView({
|
||||
model: this.model
|
||||
});
|
||||
appendSetFixtures(this.view.render().el);
|
||||
@@ -901,7 +901,7 @@ define([
|
||||
|
||||
it('should show empty usage appropriately', function() {
|
||||
this.view.$('.show-groups').click();
|
||||
assertShowEmptyUsages(this.view, 'This content group is not in use. ');
|
||||
assertShowEmptyUsages(this.view, "Use this group to control a component's visibility in the ");
|
||||
});
|
||||
|
||||
it('should hide empty usage appropriately', function() {
|
||||
@@ -915,7 +915,7 @@ define([
|
||||
|
||||
assertShowNonEmptyUsages(
|
||||
this.view,
|
||||
'This content group is used in:',
|
||||
'This group controls visibility of:',
|
||||
'Cannot delete when in use by a unit'
|
||||
);
|
||||
});
|
||||
@@ -1015,7 +1015,7 @@ define([
|
||||
describe('Content group controller view', function() {
|
||||
beforeEach(function() {
|
||||
TemplateHelpers.installTemplates([
|
||||
'content-group-editor', 'content-group-details'
|
||||
'content-group-editor', 'partition-group-details'
|
||||
], true);
|
||||
|
||||
this.model = new GroupModel({name: 'Content Group', id: 0, courseOutlineUrl: 'CourseOutlineUrl'});
|
||||
@@ -1029,14 +1029,14 @@ define([
|
||||
this.saveableModel.urlRoot = '/group_configurations';
|
||||
this.collection = new GroupConfigurationCollection([this.saveableModel]);
|
||||
this.collection.url = '/group_configurations';
|
||||
this.view = new ContentGroupItemView({
|
||||
this.view = new PartitionGroupItemView({
|
||||
model: this.model
|
||||
});
|
||||
appendSetFixtures(this.view.render().el);
|
||||
});
|
||||
|
||||
it('should render properly', function() {
|
||||
assertControllerView(this.view, '.content-group-details', '.content-group-edit');
|
||||
assertControllerView(this.view, '.partition-group-details', '.content-group-edit');
|
||||
});
|
||||
|
||||
it('should destroy itself on confirmation of deleting', function() {
|
||||
@@ -1047,7 +1047,7 @@ define([
|
||||
assertAndDeleteItemWithError(
|
||||
this,
|
||||
'/group_configurations/0/0',
|
||||
'.content-groups-list-item',
|
||||
'.partition-groups-list-item',
|
||||
'Delete this content group'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -19,9 +19,8 @@ define([
|
||||
name: 'Configuration 1',
|
||||
courseOutlineUrl: 'CourseOutlineUrl'
|
||||
}),
|
||||
contentGroupConfiguration: new GroupConfigurationModel({groups: []})
|
||||
allGroupConfigurations: [new GroupConfigurationModel({groups: []})]
|
||||
});
|
||||
|
||||
if (!disableSpy) {
|
||||
spyOn(view, 'addWindowActions');
|
||||
}
|
||||
@@ -36,7 +35,7 @@ define([
|
||||
beforeEach(function() {
|
||||
setFixtures(mockGroupConfigurationsPage);
|
||||
TemplateHelpers.installTemplates([
|
||||
'group-configuration-editor', 'group-configuration-details', 'content-group-details',
|
||||
'group-configuration-editor', 'group-configuration-details', 'partition-group-details',
|
||||
'content-group-editor', 'group-edit', 'list'
|
||||
]);
|
||||
|
||||
@@ -116,7 +115,7 @@ define([
|
||||
});
|
||||
|
||||
it('should show a notification message if a content group is changed', function() {
|
||||
this.view.contentGroupConfiguration.get('groups').add({id: 0, name: 'Content Group'});
|
||||
this.view.allGroupConfigurations[0].get('groups').add({id: 0, name: 'Content Group'});
|
||||
expect(this.view.onBeforeUnload())
|
||||
.toBe('You have unsaved changes. Do you really want to leave this page?');
|
||||
});
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* This class defines a list view for content groups.
|
||||
* It is expected to be backed by a Group collection.
|
||||
*/
|
||||
define([
|
||||
'js/views/list', 'js/views/content_group_item', 'gettext'
|
||||
], function(ListView, ContentGroupItemView, gettext) {
|
||||
'use strict';
|
||||
|
||||
var ContentGroupListView = ListView.extend({
|
||||
tagName: 'div',
|
||||
|
||||
className: 'content-group-list',
|
||||
|
||||
// Translators: This refers to a content group that can be linked to a student cohort.
|
||||
itemCategoryDisplayName: gettext('content group'),
|
||||
|
||||
newItemMessage: gettext('Add your first content group'),
|
||||
|
||||
emptyMessage: gettext('You have not created any content groups yet.'),
|
||||
|
||||
createItemView: function(options) {
|
||||
return new ContentGroupItemView(options);
|
||||
}
|
||||
});
|
||||
|
||||
return ContentGroupListView;
|
||||
});
|
||||
@@ -11,6 +11,7 @@
|
||||
* of items this list contains. For example, 'Group Configuration'.
|
||||
* Note that it must be translated.
|
||||
* - emptyMessage (string): Text to render when the list is empty.
|
||||
* - restrictEditing (bool) : Boolean flag for hiding edit and remove options, defaults to false.
|
||||
*/
|
||||
define([
|
||||
'js/views/baseview'
|
||||
@@ -25,6 +26,7 @@ define([
|
||||
listContainerCss: '.list-items',
|
||||
|
||||
initialize: function() {
|
||||
this.restrictEditing = this.options.restrictEditing || false;
|
||||
this.listenTo(this.collection, 'add', this.addNewItemView);
|
||||
this.listenTo(this.collection, 'remove', this.onRemoveItem);
|
||||
this.template = this.loadTemplate('list');
|
||||
@@ -42,11 +44,14 @@ define([
|
||||
emptyMessage: this.emptyMessage,
|
||||
length: this.collection.length,
|
||||
isEditing: model && model.get('editing'),
|
||||
canCreateNewItem: this.canCreateItem(this.collection)
|
||||
canCreateNewItem: this.canCreateItem(this.collection),
|
||||
restrictEditing: this.restrictEditing
|
||||
}));
|
||||
|
||||
this.collection.each(function(model) {
|
||||
this.$(this.listContainerCss).append(this.createItemView({model: model}).render().el);
|
||||
this.$(this.listContainerCss).append(
|
||||
this.createItemView({model: model, restrictEditing: this.restrictEditing}).render().el
|
||||
);
|
||||
}, this);
|
||||
|
||||
return this;
|
||||
|
||||
@@ -22,6 +22,7 @@ define([
|
||||
canDelete: false,
|
||||
|
||||
initialize: function() {
|
||||
this.restrictEditing = this.options.restrictEditing || false;
|
||||
this.listenTo(this.model, 'change:editing', this.render);
|
||||
this.listenTo(this.model, 'remove', this.remove);
|
||||
},
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
define([
|
||||
'jquery', 'underscore', 'gettext', 'js/views/pages/base_page',
|
||||
'js/views/group_configurations_list', 'js/views/content_group_list'
|
||||
'js/views/group_configurations_list', 'js/views/partition_group_list'
|
||||
],
|
||||
function($, _, gettext, BasePage, GroupConfigurationsListView, ContentGroupListView) {
|
||||
function($, _, gettext, BasePage, GroupConfigurationsListView, PartitionGroupListView) {
|
||||
'use strict';
|
||||
var GroupConfigurationsPage = BasePage.extend({
|
||||
initialize: function(options) {
|
||||
var currentScheme,
|
||||
i,
|
||||
enrollmentScheme = 'enrollment_track';
|
||||
|
||||
BasePage.prototype.initialize.call(this);
|
||||
this.experimentsEnabled = options.experimentsEnabled;
|
||||
if (this.experimentsEnabled) {
|
||||
@@ -14,18 +18,35 @@ function($, _, gettext, BasePage, GroupConfigurationsListView, ContentGroupListV
|
||||
collection: this.experimentGroupConfigurations
|
||||
});
|
||||
}
|
||||
this.contentGroupConfiguration = options.contentGroupConfiguration;
|
||||
this.cohortGroupsListView = new ContentGroupListView({
|
||||
collection: this.contentGroupConfiguration.get('groups')
|
||||
});
|
||||
|
||||
this.allGroupConfigurations = options.allGroupConfigurations || [];
|
||||
this.allGroupViewList = [];
|
||||
for (i = 0; i < this.allGroupConfigurations.length; i++) {
|
||||
currentScheme = this.allGroupConfigurations[i].get('scheme');
|
||||
this.allGroupViewList.push(
|
||||
new PartitionGroupListView({
|
||||
collection: this.allGroupConfigurations[i].get('groups'),
|
||||
restrictEditing: currentScheme === enrollmentScheme,
|
||||
scheme: currentScheme
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
renderPage: function() {
|
||||
var hash = this.getLocationHash();
|
||||
var hash = this.getLocationHash(),
|
||||
i,
|
||||
currentClass;
|
||||
if (this.experimentsEnabled) {
|
||||
this.$('.wrapper-groups.experiment-groups').append(this.experimentGroupsListView.render().el);
|
||||
}
|
||||
this.$('.wrapper-groups.content-groups').append(this.cohortGroupsListView.render().el);
|
||||
|
||||
// Render the remaining Configuration groups
|
||||
for (i = 0; i < this.allGroupViewList.length; i++) {
|
||||
currentClass = '.wrapper-groups.content-groups.' + this.allGroupViewList[i].scheme;
|
||||
this.$(currentClass).append(this.allGroupViewList[i].render().el);
|
||||
}
|
||||
|
||||
this.addWindowActions();
|
||||
if (hash) {
|
||||
// Strip leading '#' to get id string to match
|
||||
@@ -38,8 +59,22 @@ function($, _, gettext, BasePage, GroupConfigurationsListView, ContentGroupListV
|
||||
$(window).on('beforeunload', this.onBeforeUnload.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks the Partition Group Configurations to see if the isDirty bit is set
|
||||
* @returns {boolean} True if any partition group has the dirty bit set.
|
||||
*/
|
||||
areAnyConfigurationsDirty: function() {
|
||||
var i;
|
||||
for (i = 0; i < this.allGroupConfigurations.length; i++) {
|
||||
if (this.allGroupConfigurations[i].isDirty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
onBeforeUnload: function() {
|
||||
var dirty = this.contentGroupConfiguration.isDirty() ||
|
||||
var dirty = this.areAnyConfigurationsDirty() ||
|
||||
(this.experimentsEnabled && this.experimentGroupConfigurations.find(function(configuration) {
|
||||
return configuration.isDirty();
|
||||
}));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* This class defines a simple display view for a content group.
|
||||
* This class defines a simple display view for a partition group.
|
||||
* It is expected to be backed by a Group model.
|
||||
*/
|
||||
define([
|
||||
@@ -8,7 +8,7 @@ define([
|
||||
], function(BaseView, _, gettext, str, StringUtils, HtmlUtils) {
|
||||
'use strict';
|
||||
|
||||
var ContentGroupDetailsView = BaseView.extend({
|
||||
var PartitionGroupDetailsView = BaseView.extend({
|
||||
tagName: 'div',
|
||||
events: {
|
||||
'click .edit': 'editGroup',
|
||||
@@ -21,17 +21,18 @@ define([
|
||||
|
||||
return [
|
||||
'collection',
|
||||
'content-group-details',
|
||||
'content-group-details-' + index
|
||||
'partition-group-details',
|
||||
'partition-group-details-' + index
|
||||
].join(' ');
|
||||
},
|
||||
|
||||
editGroup: function() {
|
||||
this.model.set({'editing': true});
|
||||
this.model.set({editing: true});
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
this.template = this.loadTemplate('content-group-details');
|
||||
this.template = this.loadTemplate('partition-group-details');
|
||||
this.restrictEditing = this.options.restrictEditing || false;
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
},
|
||||
|
||||
@@ -41,9 +42,10 @@ define([
|
||||
courseOutlineUrl: this.model.collection.parents[0].outlineUrl,
|
||||
index: this.model.collection.indexOf(this.model),
|
||||
showContentGroupUsages: showContentGroupUsages || false,
|
||||
HtmlUtils: HtmlUtils
|
||||
HtmlUtils: HtmlUtils,
|
||||
restrictEditing: this.restrictEditing
|
||||
});
|
||||
this.$el.html(this.template(attrs));
|
||||
HtmlUtils.setHtml(this.$el, HtmlUtils.HTML(this.template(attrs)));
|
||||
return this;
|
||||
},
|
||||
|
||||
@@ -78,5 +80,5 @@ define([
|
||||
}
|
||||
});
|
||||
|
||||
return ContentGroupDetailsView;
|
||||
return PartitionGroupDetailsView;
|
||||
});
|
||||
@@ -1,22 +1,23 @@
|
||||
/**
|
||||
* This class defines an controller view for content groups.
|
||||
* This class defines an controller view for partition groups.
|
||||
* It renders an editor view or a details view depending on the state
|
||||
* of the underlying model.
|
||||
* It is expected to be backed by a Group model.
|
||||
*/
|
||||
define([
|
||||
'js/views/list_item', 'js/views/content_group_editor', 'js/views/content_group_details', 'gettext', 'common/js/components/utils/view_utils'
|
||||
], function(ListItemView, ContentGroupEditorView, ContentGroupDetailsView, gettext) {
|
||||
'js/views/list_item', 'js/views/content_group_editor', 'js/views/partition_group_details',
|
||||
'gettext', 'common/js/components/utils/view_utils'
|
||||
], function(ListItemView, ContentGroupEditorView, PartitionGroupDetailsView, gettext) {
|
||||
'use strict';
|
||||
|
||||
var ContentGroupItemView = ListItemView.extend({
|
||||
var PartitionGroupItemView = ListItemView.extend({
|
||||
events: {
|
||||
'click .delete': 'deleteItem'
|
||||
},
|
||||
|
||||
tagName: 'section',
|
||||
|
||||
baseClassName: 'content-group',
|
||||
baseClassName: 'partition-group',
|
||||
|
||||
canDelete: true,
|
||||
|
||||
@@ -24,8 +25,8 @@ define([
|
||||
|
||||
attributes: function() {
|
||||
return {
|
||||
'id': this.model.get('id'),
|
||||
'tabindex': -1
|
||||
id: this.model.get('id'),
|
||||
tabindex: -1
|
||||
};
|
||||
},
|
||||
|
||||
@@ -34,9 +35,11 @@ define([
|
||||
},
|
||||
|
||||
createDetailsView: function() {
|
||||
return new ContentGroupDetailsView({model: this.model});
|
||||
return new PartitionGroupDetailsView({model: this.model,
|
||||
restrictEditing: this.options.restrictEditing
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return ContentGroupItemView;
|
||||
return PartitionGroupItemView;
|
||||
});
|
||||
33
cms/static/js/views/partition_group_list.js
Normal file
33
cms/static/js/views/partition_group_list.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* This class defines a list view for partition groups.
|
||||
* It is expected to be backed by a Group collection.
|
||||
*/
|
||||
define([
|
||||
'underscore', 'js/views/list', 'js/views/partition_group_item', 'gettext'
|
||||
], function(_, ListView, PartitionGroupItemView, gettext) {
|
||||
'use strict';
|
||||
|
||||
var PartitionGroupListView = ListView.extend({
|
||||
initialize: function(options) {
|
||||
ListView.prototype.initialize.apply(this, [options]);
|
||||
this.scheme = options.scheme;
|
||||
},
|
||||
|
||||
tagName: 'div',
|
||||
|
||||
className: 'partition-group-list',
|
||||
|
||||
// Translators: This refers to a content group that can be linked to a student cohort.
|
||||
itemCategoryDisplayName: gettext('content group'),
|
||||
|
||||
newItemMessage: gettext('Add your first content group'),
|
||||
|
||||
emptyMessage: gettext('You have not created any content groups yet.'),
|
||||
|
||||
createItemView: function(options) {
|
||||
return new PartitionGroupItemView(_.extend({}, options, {scheme: this.scheme}));
|
||||
}
|
||||
});
|
||||
|
||||
return PartitionGroupListView;
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
<%page expression_filter="h"/>
|
||||
<%inherit file="base.html" />
|
||||
<%def name="content_groups_help_token()"><% return "content_groups" %></%def>
|
||||
<%def name="enrollment_track_help_token()"><% return "enrollment_tracks" %></%def>
|
||||
<%def name="experiment_group_configurations_help_token()"><% return "group_configurations" %></%def>
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<%!
|
||||
@@ -16,7 +17,7 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
<%block name="bodyclass">is-signedin course view-group-configurations</%block>
|
||||
|
||||
<%block name="header_extras">
|
||||
% for template_name in ["group-configuration-details", "group-configuration-editor", "group-edit", "content-group-editor", "content-group-details", "basic-modal", "modal-button", "list"]:
|
||||
% for template_name in ["group-configuration-details", "group-configuration-editor", "group-edit", "content-group-editor", "partition-group-details", "basic-modal", "modal-button", "list"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="js/${template_name}.underscore" />
|
||||
</script>
|
||||
@@ -28,9 +29,10 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
GroupConfigurationsFactory(
|
||||
${should_show_experiment_groups | n, dump_js_escaped_json},
|
||||
${experiment_group_configurations | n, dump_js_escaped_json},
|
||||
${content_group_configuration | n, dump_js_escaped_json},
|
||||
${all_group_configurations | n, dump_js_escaped_json},
|
||||
"${group_configuration_url | n, js_escaped_string}",
|
||||
"${course_outline_url | n, js_escaped_string}"
|
||||
"${course_outline_url | n, js_escaped_string}",
|
||||
${should_show_enrollment_track | n, dump_js_escaped_json}
|
||||
);
|
||||
});
|
||||
</%block>
|
||||
@@ -47,37 +49,52 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<article class="content-primary" role="main">
|
||||
<div class="wrapper-groups content-groups">
|
||||
<h3 class="title">${_("Content Groups")}</h3>
|
||||
<div class="ui-loading">
|
||||
<p><span class="spin"><span class="icon fa fa-refresh" aria-hidden="true"></span></span> <span class="copy">${_("Loading")}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
% if should_show_experiment_groups:
|
||||
<div class="wrapper-groups experiment-groups">
|
||||
<h3 class="title">${_("Experiment Group Configurations")}</h3>
|
||||
% if experiment_group_configurations is None:
|
||||
<div class="notice notice-incontext notice-moduledisabled">
|
||||
<p class="copy">
|
||||
${_("This module is disabled at the moment.")}
|
||||
</p>
|
||||
</div>
|
||||
% else:
|
||||
<div class="ui-loading">
|
||||
<p><span class="spin"><span class="icon fa fa-refresh" aria-hidden="true"></span></span> <span class="copy">${_("Loading")}</span></p>
|
||||
</div>
|
||||
% endif
|
||||
</div>
|
||||
% endif
|
||||
<article class="content-primary" role="main">
|
||||
|
||||
% for config in all_group_configurations:
|
||||
<div class="wrapper-groups content-groups ${config['scheme']}">
|
||||
<h3 class="title">${config['name']}</h3>
|
||||
<div class="ui-loading">
|
||||
<p><span class="spin"><span class="icon fa fa-refresh" aria-hidden="true"></span></span> <span class="copy">${_("Loading")}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
% endfor
|
||||
|
||||
% if should_show_experiment_groups:
|
||||
<div class="wrapper-groups experiment-groups">
|
||||
<h3 class="title">${_("Experiment Group Configurations")}</h3>
|
||||
% if experiment_group_configurations is None:
|
||||
<div class="notice notice-incontext notice-moduledisabled">
|
||||
<p class="copy">
|
||||
${_("This module is disabled at the moment.")}
|
||||
</p>
|
||||
</div>
|
||||
% else:
|
||||
<div class="ui-loading">
|
||||
<p><span class="spin"><span class="icon fa fa-refresh" aria-hidden="true"></span></span> <span class="copy">${_("Loading")}</span></p>
|
||||
</div>
|
||||
% endif
|
||||
</div>
|
||||
% endif
|
||||
</article>
|
||||
<aside class="content-supplementary" role="complementary">
|
||||
% if should_show_enrollment_track:
|
||||
<div class="bit">
|
||||
<div class="enrollment-track-doc">
|
||||
<h3 class="title-3">${_("Enrollment Track Groups")}</h3>
|
||||
<p>${_("Enrollment track groups allow you to offer different course content to learners in each enrollment track. Learners enrolled in each enrollment track in your course are automatically included in the corresponding enrollment track group.")}</p>
|
||||
<p>${_("On unit pages in the course outline, you can designate components as visible only to learners in a specific enrollment track.")}</p>
|
||||
<p>${_("You cannot edit enrollment track groups, but you can expand each group to view details of the course content that is designated for learners in the group.")}</p>
|
||||
<p><a href="${get_online_help_info(enrollment_track_help_token())['doc_url']}" target="_blank" class="button external-help-button">${_("Learn More")}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
<div class="bit">
|
||||
<div class="content-groups-doc">
|
||||
<h3 class="title-3">${_("Content Groups")}</h3>
|
||||
<p>${_("If you have cohorts enabled in your course, you can use content groups to create cohort-specific courseware. In other words, you can customize the content that particular cohorts see in your course.")}</p>
|
||||
<p>${_("Each content group that you create can be associated with one or more cohorts. In addition to course content that is intended for all students, you can designate some content as visible only to specified content groups. Only learners in the cohorts that are associated with the specified content groups see the additional content.")}</p>
|
||||
<p>${_("Each content group that you create can be associated with one or more cohorts. In addition to course content that is intended for all learners, you can designate some content as visible only to specified content groups. Only learners in the cohorts that are associated with the specified content groups see the additional content.")}</p>
|
||||
<p>${Text(_("Click {em_start}New content group{em_end} to add a new content group. To edit the name of a content group, hover over its box and click {em_start}Edit{em_end}. You can delete a content group only if it is not in use by a unit. To delete a content group, hover over its box and click the delete icon.")).format(em_start=HTML("<strong>"), em_end=HTML("</strong>"))}</p>
|
||||
<p><a href="${get_online_help_info(content_groups_help_token())['doc_url']}" target="_blank" class="button external-help-button">${_("Learn More")}</a></p>
|
||||
</div>
|
||||
@@ -86,12 +103,13 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
<div class="bit">
|
||||
<div class="experiment-groups-doc">
|
||||
<h3 class="title-3">${_("Experiment Group Configurations")}</h3>
|
||||
<p>${_("Use experiment group configurations if you are conducting content experiments, also known as A/B testing, in your course. Experiment group configurations define how many groups of students are in a content experiment. When you create a content experiment for a course, you select the group configuration to use.")}</p>
|
||||
<p>${_("Use experiment group configurations if you are conducting content experiments, also known as A/B testing, in your course. Experiment group configurations define how many groups of learners are in a content experiment. When you create a content experiment for a course, you select the group configuration to use.")}</p>
|
||||
<p>${Text(_("Click {em_start}New Group Configuration{em_end} to add a new configuration. To edit a configuration, hover over its box and click {em_start}Edit{em_end}. You can delete a group configuration only if it is not in use in an experiment. To delete a configuration, hover over its box and click the delete icon.")).format(em_start=HTML("<strong>"), em_end=HTML("</strong>"))}</p>
|
||||
<p><a href="${get_online_help_info(experiment_group_configurations_help_token())['doc_url']}" target="_blank" class="button external-help-button">${_("Learn More")}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
<div class="bit">
|
||||
% if context_course:
|
||||
<%
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<% } else { %>
|
||||
<div class="list-items"></div>
|
||||
<% if (!isEditing) { %>
|
||||
<% if (!isEditing && !restrictEditing) { %>
|
||||
<button class="action action-add <% if(!canCreateNewItem) {%> action-add-hidden <% }%>" >
|
||||
<span class="icon fa fa-plus" aria-hidden="true"></span>
|
||||
<%- interpolate(
|
||||
|
||||
@@ -23,17 +23,19 @@
|
||||
</ol>
|
||||
|
||||
<ul class="actions group-configuration-actions">
|
||||
<li class="action action-edit">
|
||||
<button class="edit"><span class="icon fa fa-pencil" aria-hidden="true"></span> <%- gettext("Edit") %></button>
|
||||
</li>
|
||||
<% if (_.isEmpty(usage)) { %>
|
||||
<li class="action action-delete wrapper-delete-button" data-tooltip="<%- gettext('Delete') %>">
|
||||
<button class="delete action-icon" title="<%- gettext('Delete') %>"><span class="icon fa fa-trash-o" aria-hidden="true"></span></button>
|
||||
</li>
|
||||
<% } else { %>
|
||||
<li class="action action-delete wrapper-delete-button" data-tooltip="<%- gettext('Cannot delete when in use by a unit') %>">
|
||||
<button class="delete action-icon is-disabled" disabled="disabled" title="<%- gettext('Delete') %>"><span class="icon fa fa-trash-o" aria-hidden="true"></span></button>
|
||||
<% if (!restrictEditing) { %>
|
||||
<li class="action action-edit">
|
||||
<button class="edit"><span class="icon fa fa-pencil" aria-hidden="true"></span> <%- gettext("Edit") %></button>
|
||||
</li>
|
||||
<% if (_.isEmpty(usage)) { %>
|
||||
<li class="action action-delete wrapper-delete-button" data-tooltip="<%- gettext('Delete') %>">
|
||||
<button class="delete action-icon" title="<%- gettext('Delete') %>"><span class="icon fa fa-trash-o" aria-hidden="true"></span></button>
|
||||
</li>
|
||||
<% } else { %>
|
||||
<li class="action action-delete wrapper-delete-button" data-tooltip="<%- gettext('Cannot delete when in use by a unit') %>">
|
||||
<button class="delete action-icon is-disabled" disabled="disabled" title="<%- gettext('Delete') %>"><span class="icon fa fa-trash-o" aria-hidden="true"></span></button>
|
||||
</li>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -41,7 +43,9 @@
|
||||
<% if (showContentGroupUsages) { %>
|
||||
<div class="collection-references wrapper-group-configuration-usages">
|
||||
<% if (!_.isEmpty(usage)) { %>
|
||||
<h4 class="intro group-configuration-usage-text"><%- gettext('This content group is used in:') %></h4>
|
||||
<h4 class="intro group-configuration-usage-text">
|
||||
<%- gettext('This group controls visibility of:') %>
|
||||
</h4>
|
||||
<ol class="usage group-configuration-usage">
|
||||
<% _.each(usage, function(unit) { %>
|
||||
<li class="usage-unit group-configuration-usage-unit">
|
||||
@@ -52,15 +56,18 @@
|
||||
<% } else { %>
|
||||
<p class="group-configuration-usage-text">
|
||||
<%= HtmlUtils.interpolateHtml(
|
||||
gettext('This content group is not in use. Add a content group to any unit from the {linkStart}Course Outline{linkEnd}.'),
|
||||
{
|
||||
linkStart: HtmlUtils.interpolateHtml(
|
||||
HtmlUtils.HTML('<a href="{courseOutlineUrl}" title="{courseOutlineTitle}">'),
|
||||
{courseOutlineUrl: courseOutlineUrl, courseOutlineTitle: gettext('Course Outline')}),
|
||||
linkEnd: HtmlUtils.HTML('</a>')
|
||||
})
|
||||
gettext("Use this group to control a component's visibility in the {linkStart}Course Outline{linkEnd}."),
|
||||
{
|
||||
linkStart: HtmlUtils.interpolateHtml(
|
||||
HtmlUtils.HTML('<a href="{courseOutlineUrl}" title="{courseOutlineTitle}">'),
|
||||
{courseOutlineUrl: courseOutlineUrl, courseOutlineTitle: gettext('Course Outline')}
|
||||
),
|
||||
linkEnd: HtmlUtils.HTML('</a>')
|
||||
}
|
||||
)
|
||||
%>
|
||||
|
||||
</p>
|
||||
<% } %>
|
||||
</div>
|
||||
<% } %>
|
||||
<% } %>
|
||||
Reference in New Issue
Block a user