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

feat: add content groups support to teams #33105

Closed
wants to merge 1 commit into from
Closed
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
16 changes: 10 additions & 6 deletions cms/djangoapps/contentstore/course_group_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from cms.djangoapps.contentstore.utils import reverse_usage_url
from common.djangoapps.util.db import MYSQL_MAX_INT, generate_int_id
from lms.lib.utils import get_parent_unit
from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_user_partition
from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_user_partition, get_teamed_user_partition # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.partitions.partitions import MINIMUM_STATIC_PARTITION_ID, ReadOnlyUserPartitionError, UserPartition # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.partitions.partitions_service import get_all_partitions_for_course # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.split_test_block import get_split_user_partitions # lint-amnesty, pylint: disable=wrong-import-order
Expand All @@ -22,6 +22,7 @@
RANDOM_SCHEME = "random"
COHORT_SCHEME = "cohort"
ENROLLMENT_SCHEME = "enrollment_track"
TEAM_SCHEME = "team"

CONTENT_GROUP_CONFIGURATION_DESCRIPTION = _(
'The groups in this configuration can be mapped to cohorts in the Instructor Dashboard.'
Expand Down Expand Up @@ -114,7 +115,7 @@ def get_user_partition(self):
raise GroupConfigurationsValidationError(_("unable to load this type of group configuration")) # lint-amnesty, pylint: disable=raise-missing-from

@staticmethod
def _get_usage_dict(course, unit, block, scheme_name=None):
def _get_usage_dict(course, unit, block, scheme_name=COHORT_SCHEME):
"""
Get usage info for unit/block.
"""
Expand Down Expand Up @@ -347,22 +348,25 @@ def update_partition_usage_info(store, course, configuration):
return partition_configuration

@staticmethod
def get_or_create_content_group(store, course):
def get_or_create_content_group(store, course, scheme_name=COHORT_SCHEME):
"""
Returns the first user partition from the course which uses the
CohortPartitionScheme, or generates one if no such partition is
found. The created partition is not saved to the course until
the client explicitly creates a group within the partition and
POSTs back.
"""
content_group_configuration = get_cohorted_user_partition(course)
if scheme_name == COHORT_SCHEME:
content_group_configuration = get_cohorted_user_partition(course)
elif scheme_name == TEAM_SCHEME:
content_group_configuration = get_teamed_user_partition(course)
Comment on lines +359 to +362
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be more generic? In the meantime, let's not refactor

if content_group_configuration is None:
content_group_configuration = UserPartition(
id=generate_int_id(MINIMUM_GROUP_ID, MYSQL_MAX_INT, GroupConfiguration.get_used_ids(course)),
name=CONTENT_GROUP_CONFIGURATION_NAME,
name=f"Content Groups for {scheme_name}",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use the same name but parametrized

description=CONTENT_GROUP_CONFIGURATION_DESCRIPTION,
groups=[],
scheme_id=COHORT_SCHEME
scheme_id=scheme_name,
)
return content_group_configuration.to_json()

Expand Down
3 changes: 2 additions & 1 deletion cms/djangoapps/contentstore/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,7 @@ def get_visibility_partition_info(xblock, course=None):
selectable_partitions = []
# We wish to display enrollment partitions before cohort partitions.
enrollment_user_partitions = get_user_partition_info(xblock, schemes=["enrollment_track"], course=course)
team_partitions = get_user_partition_info(xblock, schemes=["team"], course=course)

# For enrollment partitions, we only show them if there is a selected group or
# or if the number of groups > 1.
Expand All @@ -776,7 +777,7 @@ def get_visibility_partition_info(xblock, course=None):
selectable_partitions += get_user_partition_info(xblock, schemes=[CONTENT_TYPE_GATING_SCHEME], course=course)

# Now add the cohort user partitions.
selectable_partitions = selectable_partitions + get_user_partition_info(xblock, schemes=["cohort"], course=course)
selectable_partitions = selectable_partitions + get_user_partition_info(xblock, schemes=["cohort"], course=course) + team_partitions
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to author: this can be generalized

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like in enrollment tracks


# Find the first partition with a selected group. That will be the one initially enabled in the dialog
# (if the course has only been added in Studio, only one partition should have a selected group).
Expand Down
6 changes: 5 additions & 1 deletion cms/djangoapps/contentstore/views/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
COHORT_SCHEME,
ENROLLMENT_SCHEME,
RANDOM_SCHEME,
TEAM_SCHEME,
GroupConfiguration,
GroupConfigurationsValidationError
)
Expand Down Expand Up @@ -1608,11 +1609,13 @@ def group_configurations_list_handler(request, course_key_string):
# Add it to the front of the list if it should be shown.
if should_show_enrollment_track:
displayable_partitions.insert(0, partition)
elif partition['scheme'] == TEAM_SCHEME:
has_content_groups = True
displayable_partitions.append(partition)
elif partition['scheme'] != RANDOM_SCHEME:
# Experiment group configurations are handled explicitly above. We don't
# want to display their groups twice.
displayable_partitions.append(partition)

# Set the sort-order. Higher numbers sort earlier
scheme_priority = defaultdict(lambda: -1, {
ENROLLMENT_SCHEME: 1,
Expand All @@ -1623,6 +1626,7 @@ def group_configurations_list_handler(request, course_key_string):
# This will add ability to add new groups in the view.
if not has_content_groups:
displayable_partitions.append(GroupConfiguration.get_or_create_content_group(store, course))
displayable_partitions.append(GroupConfiguration.get_or_create_content_group(store, course, scheme_name="team"))
return render_to_response('group_configurations.html', {
'context_course': course,
'group_configuration_url': group_configuration_url,
Expand Down
3 changes: 2 additions & 1 deletion lms/djangoapps/teams/static/teams/js/models/team.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
country: '',
language: '',
membership: [],
last_activity_at: ''
last_activity_at: '',
content_group: '',
},

initialize: function(options) {
Expand Down
21 changes: 19 additions & 2 deletions lms/djangoapps/teams/static/teams/js/views/edit_team.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
this.topic = options.topic;
this.collection = options.collection;
this.action = options.action;
this.contentGroupsNameMap = _.map(this.context.contentGroups, function(group) {
return [group.id, group.name]
});

if (this.action === 'create') {
this.teamModel = new TeamModel({});
Expand Down Expand Up @@ -57,6 +60,17 @@
+ 'goals or direction of the team (maximum 300 characters).')
});

this.teamContentGroupsField = new FieldViews.DropdownFieldView({
model: this.teamModel,
title: gettext('Content Groups'),
valueAttribute: 'content_group',
required: false,
showMessages: false,
options: this.contentGroupsNameMap,
helpMessage: gettext(
'The content groups that the team is associated with.')
});

this.teamLanguageField = new FieldViews.DropdownFieldView({
model: this.teamModel,
title: gettext('Language'),
Expand Down Expand Up @@ -92,6 +106,7 @@
);
this.set(this.teamNameField, '.team-required-fields');
this.set(this.teamDescriptionField, '.team-required-fields');
this.set(this.teamContentGroupsField, '.team-optional-fields');
this.set(this.teamLanguageField, '.team-optional-fields');
this.set(this.teamCountryField, '.team-optional-fields');
return this;
Expand All @@ -105,17 +120,19 @@
this.$(selector).append(view.render().$el);
}
},

createOrUpdateTeam: function(event) {
event.preventDefault();
var view = this, // eslint-disable-line vars-on-top
teamLanguage = this.teamLanguageField.fieldValue(),
teamCountry = this.teamCountryField.fieldValue(),
teamContentGroup = this.teamContentGroupsField.fieldValue(),
data = {
name: this.teamNameField.fieldValue(),
description: this.teamDescriptionField.fieldValue(),
language: _.isNull(teamLanguage) ? '' : teamLanguage,
country: _.isNull(teamCountry) ? '' : teamCountry
country: _.isNull(teamCountry) ? '' : teamCountry,
content_group: _.isNull(teamContentGroup) ? '' : teamContentGroup,
user_partition_id: this.context.partitionID
},
saveOptions = {
wait: true
Expand Down
24 changes: 22 additions & 2 deletions lms/djangoapps/teams/templates/teams/teams.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@

<%include file="/courseware/course_navigation.html" args="active_page='teams'" />

<%!
from openedx.core.djangoapps.course_groups.partition_scheme import get_teamed_user_partition
%>

<div class="container">
<div class="teams-wrapper">
<main id="main" aria-label="Content" tabindex="-1">
Expand All @@ -38,7 +42,21 @@
<%include file="../discussion/_js_body_dependencies.html" />

<%static:require_module module_name="teams/js/teams_tab_factory" class_name="TeamsTabFactory">
TeamsTabFactory({
<%
teamed_user_partition = get_teamed_user_partition(course)
content_groups = teamed_user_partition.groups if teamed_user_partition else []
%>
var groupUserPartitionId = ${teamed_user_partition.id if teamed_user_partition else None | n, dump_js_escaped_json},
contentGroups = [
% for content_group in content_groups:
{
id: ${content_group.id | n, dump_js_escaped_json},
name: "${content_group.name | n, js_escaped_string}",
user_partition_id: groupUserPartitionId
},
% endfor
];
TeamsTabFactory({
courseID: '${str(course.id) | n, js_escaped_string}',
topics: ${topics | n, dump_js_escaped_json},
hasManagedTopic: ${has_managed_teamset | n, dump_js_escaped_json},
Expand All @@ -59,7 +77,9 @@
courseMaxTeamSize: ${course.teams_configuration.default_max_team_size | n, dump_js_escaped_json},
languages: ${languages | n, dump_js_escaped_json},
countries: ${countries | n, dump_js_escaped_json},
teamsBaseUrl: '${teams_base_url | n, js_escaped_string}'
teamsBaseUrl: '${teams_base_url | n, js_escaped_string}',
contentGroups: contentGroups,
partitionID: groupUserPartitionId,
});
</%static:require_module>
</%block>
Expand Down
32 changes: 32 additions & 0 deletions lms/djangoapps/teams/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@
from .toggles import are_team_submissions_enabled
from .utils import emit_team_event

from openedx.core.djangoapps.course_groups.api import *

TEAM_MEMBERSHIPS_PER_PAGE = 5
TOPICS_PER_PAGE = 12
MAXIMUM_SEARCH_SIZE = 10000
Expand Down Expand Up @@ -637,6 +639,13 @@ def post(self, request):
)

data = CourseTeamSerializer(team, context={"request": request}).data
group = add_team_to_course(
team.name,
course_key,
)
if request.data.get("user_partition_id") and request.data.get("content_group"):
link_team_to_partition_group(group, request.data["user_partition_id"], request.data["content_group"])

return Response(data)

def get_page(self):
Expand Down Expand Up @@ -838,6 +847,27 @@ def delete(self, request, team_id):
})
return Response(status=status.HTTP_204_NO_CONTENT)

def patch(self, request, team_id):
course_team = get_object_or_404(CourseTeam, team_id=team_id)
data = request.data

content_group_id = data["content_group"]
if not content_group_id:
return super().patch(request, team_id)

group = get_course_team_qs(course_team.course_id).filter(name=course_team.name).first()
partitioned_group_info = get_group_info_for_team(group)
if not partitioned_group_info:
link_team_to_partition_group(group, data["user_partition_id"], data["content_group"])
return super().patch(request, team_id)

existing_content_group_id, existing_partition_id = get_group_info_for_team(group)
if content_group_id != existing_content_group_id or data["user_partition_id"] != existing_partition_id:
unlink_team_partition_group(group)
link_team_to_partition_group(group, data["user_partition_id"], data["content_group"])

return super().patch(request, team_id)
Comment on lines +850 to +869
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add docstring and explain why are we doing this



class TeamsAssignmentsView(GenericAPIView):
"""
Expand Down Expand Up @@ -1479,6 +1509,8 @@ def post(self, request):

try:
membership = team.add_user(user)
group = get_course_team_qs(team.course_id).get(name=team.name)
add_user_to_team(group, user.username)
Comment on lines +1512 to +1513
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we make this toggable?

emit_team_event(
'edx.team.learner_added',
team.course_id,
Expand Down
Loading
Loading