Skip to content

Commit 8b3c3fd

Browse files
feat: Simplify content groups v2 response JSON (#37976)
1 parent 7601818 commit 8b3c3fd

4 files changed

Lines changed: 92 additions & 72 deletions

File tree

openedx/core/djangoapps/course_groups/rest_api/docs/content-groups-api-v2-spec.yaml

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ paths:
4646
get:
4747
tags:
4848
- Content Groups
49-
summary: List content group configurations
49+
summary: List content groups
5050
description: |
51-
Returns all content group configurations (scheme='cohort') for a course.
52-
If no content group exists, an empty one is automatically created.
51+
Returns the content groups for a course along with the configuration ID
52+
and a link to Studio for managing content groups.
5353
operationId: listGroupConfigurations
5454
produces:
5555
- application/json
@@ -153,20 +153,15 @@ definitions:
153153
ContentGroupsListResponse:
154154
type: object
155155
properties:
156-
all_group_configurations:
156+
id:
157+
type: integer
158+
nullable: true
159+
description: ID of the content group configuration (null if none exists)
160+
groups:
157161
type: array
158162
items:
159-
$ref: '#/definitions/ContentGroupConfiguration'
160-
description: List of content group configurations
161-
should_show_enrollment_track:
162-
type: boolean
163-
description: Whether enrollment track groups should be displayed
164-
should_show_experiment_groups:
165-
type: boolean
166-
description: Whether experiment groups should be displayed
167-
group_configuration_url:
168-
type: string
169-
description: Base URL for accessing individual configurations
170-
course_outline_url:
163+
$ref: '#/definitions/Group'
164+
description: Flat list of content groups for the course
165+
studio_content_groups_link:
171166
type: string
172-
description: URL to the course outline
167+
description: Full URL to Studio's content group configuration page

openedx/core/djangoapps/course_groups/rest_api/serializers.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,15 @@ class ContentGroupConfigurationSerializer(serializers.Serializer):
3636
class ContentGroupsListResponseSerializer(serializers.Serializer):
3737
"""
3838
Response serializer for listing all content groups.
39+
40+
Returns the content group configuration ID, a flat list of content groups,
41+
and a link to Studio where instructors can manage content groups.
3942
"""
40-
all_group_configurations = ContentGroupConfigurationSerializer(many=True)
41-
should_show_enrollment_track = serializers.BooleanField()
42-
should_show_experiment_groups = serializers.BooleanField()
43-
context_course = serializers.JSONField(required=False, allow_null=True)
44-
group_configuration_url = serializers.CharField()
45-
course_outline_url = serializers.CharField()
43+
id = serializers.IntegerField(
44+
allow_null=True,
45+
help_text="ID of the content group configuration (null if none exists)"
46+
)
47+
groups = GroupSerializer(many=True)
48+
studio_content_groups_link = serializers.CharField(
49+
help_text="Full URL to Studio's content group configuration page"
50+
)

openedx/core/djangoapps/course_groups/rest_api/tests/test_views.py

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@
33
"""
44
from unittest.mock import patch
55

6+
from django.test import override_settings
67
from rest_framework import status
78
from rest_framework.test import APIClient
89

9-
from xmodule.partitions.partitions import Group, UserPartition
1010
from common.djangoapps.student.tests.factories import UserFactory
11-
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
12-
from xmodule.modulestore.tests.factories import CourseFactory
1311
from openedx.core.djangoapps.course_groups.constants import COHORT_SCHEME
1412
from openedx.core.djangolib.testing.utils import skip_unless_lms
13+
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
14+
from xmodule.modulestore.tests.factories import CourseFactory
15+
from xmodule.partitions.partitions import Group, UserPartition
16+
17+
TEST_STUDIO_BASE_URL = "https://studio.example.com"
1518

1619

1720
@skip_unless_lms
@@ -28,10 +31,16 @@ def setUp(self):
2831
self.api_client.force_authenticate(user=self.user)
2932

3033
def _get_url(self, course_id=None):
31-
"""Helper to get the list URL"""
34+
"""Helper to get the API URL"""
3235
course_id = course_id or str(self.course.id)
3336
return f'/api/cohorts/v2/courses/{course_id}/group_configurations'
3437

38+
def _get_expected_studio_url(self, course_id=None):
39+
"""Helper to get the expected Studio URL"""
40+
course_id = course_id or str(self.course.id)
41+
return f'{TEST_STUDIO_BASE_URL}/course/{course_id}/group_configurations'
42+
43+
@override_settings(MFE_CONFIG={"STUDIO_BASE_URL": TEST_STUDIO_BASE_URL})
3544
@patch('lms.djangoapps.instructor.permissions.InstructorPermission.has_permission')
3645
def test_list_content_groups_returns_json(self, mock_perm):
3746
"""Verify endpoint returns JSON with correct structure"""
@@ -57,18 +66,26 @@ def test_list_content_groups_returns_json(self, mock_perm):
5766
self.assertEqual(response['Content-Type'], 'application/json')
5867

5968
data = response.json()
60-
self.assertIn('all_group_configurations', data)
61-
self.assertIn('should_show_enrollment_track', data)
62-
self.assertIn('should_show_experiment_groups', data)
69+
self.assertIn('id', data)
70+
self.assertIn('groups', data)
71+
self.assertIn('studio_content_groups_link', data)
72+
73+
# Verify partition ID is returned
74+
self.assertEqual(data['id'], 50)
75+
76+
# Verify groups
77+
groups = data['groups']
78+
self.assertEqual(len(groups), 2)
79+
self.assertEqual(groups[0]['name'], 'Content Group A')
80+
self.assertEqual(groups[1]['name'], 'Content Group B')
6381

64-
configs = data['all_group_configurations']
65-
self.assertEqual(len(configs), 1)
66-
self.assertEqual(configs[0]['scheme'], COHORT_SCHEME)
67-
self.assertEqual(len(configs[0]['groups']), 2)
82+
# Verify full Studio URL
83+
expected_studio_url = self._get_expected_studio_url()
84+
self.assertEqual(data['studio_content_groups_link'], expected_studio_url)
6885

6986
@patch('lms.djangoapps.instructor.permissions.InstructorPermission.has_permission')
7087
def test_list_content_groups_filters_non_cohort_partitions(self, mock_perm):
71-
"""Verify only cohort-scheme partitions are returned"""
88+
"""Verify only groups from cohort-scheme partitions are returned"""
7289
mock_perm.return_value = True
7390

7491
self.course.user_partitions = [
@@ -92,25 +109,32 @@ def test_list_content_groups_filters_non_cohort_partitions(self, mock_perm):
92109
response = self.api_client.get(self._get_url())
93110

94111
data = response.json()
95-
configs = data['all_group_configurations']
96112

97-
self.assertEqual(len(configs), 1)
98-
self.assertEqual(configs[0]['id'], 50)
99-
self.assertEqual(configs[0]['scheme'], COHORT_SCHEME)
113+
# Verify cohort partition ID is returned
114+
self.assertEqual(data['id'], 50)
115+
116+
# Only groups from cohort partition should be returned
117+
groups = data['groups']
118+
self.assertEqual(len(groups), 1)
119+
self.assertEqual(groups[0]['name'], 'Group A')
100120

121+
@override_settings(MFE_CONFIG={"STUDIO_BASE_URL": TEST_STUDIO_BASE_URL})
101122
@patch('lms.djangoapps.instructor.permissions.InstructorPermission.has_permission')
102-
def test_list_auto_creates_empty_content_group_if_none_exists(self, mock_perm):
103-
"""Verify empty content group is auto-created when none exists"""
123+
def test_list_returns_empty_groups_when_none_exist(self, mock_perm):
124+
"""Verify empty groups array and null id when no content groups exist"""
104125
mock_perm.return_value = True
105126

106127
response = self.api_client.get(self._get_url())
107128

108129
data = response.json()
109-
configs = data['all_group_configurations']
110130

111-
self.assertEqual(len(configs), 1)
112-
self.assertEqual(configs[0]['scheme'], COHORT_SCHEME)
113-
self.assertEqual(len(configs[0]['groups']), 0)
131+
# ID should be null when no partition exists
132+
self.assertIsNone(data['id'])
133+
self.assertEqual(len(data['groups']), 0)
134+
135+
# Verify full Studio URL
136+
expected_studio_url = self._get_expected_studio_url()
137+
self.assertEqual(data['studio_content_groups_link'], expected_studio_url)
114138

115139
def test_list_requires_authentication(self):
116140
"""Verify endpoint requires authentication"""

openedx/core/djangoapps/course_groups/rest_api/views.py

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,25 @@
22
REST API views for content group configurations.
33
"""
44
import edx_api_doc_tools as apidocs
5-
from common.djangoapps.util.db import MYSQL_MAX_INT, generate_int_id
5+
from django.conf import settings
66
from opaque_keys import InvalidKeyError
77
from opaque_keys.edx.keys import CourseKey
88
from rest_framework import status
99
from rest_framework.permissions import IsAuthenticated
1010
from rest_framework.response import Response
1111
from rest_framework.views import APIView
12-
from xmodule.modulestore.exceptions import ItemNotFoundError
13-
from xmodule.partitions.partitions import MINIMUM_UNUSED_PARTITION_ID, UserPartition
1412

1513
from lms.djangoapps.instructor import permissions
16-
from openedx.core.djangoapps.course_groups.constants import (
17-
COHORT_SCHEME,
18-
CONTENT_GROUP_CONFIGURATION_DESCRIPTION,
19-
CONTENT_GROUP_CONFIGURATION_NAME,
20-
)
14+
from openedx.core.djangoapps.course_groups.constants import COHORT_SCHEME
2115
from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_user_partition
2216
from openedx.core.djangoapps.course_groups.rest_api.serializers import (
2317
ContentGroupConfigurationSerializer,
24-
ContentGroupsListResponseSerializer,
18+
ContentGroupsListResponseSerializer
2519
)
20+
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
2621
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin
2722
from openedx.core.lib.courses import get_course_by_id
23+
from xmodule.modulestore.exceptions import ItemNotFoundError
2824

2925

3026
class GroupConfigurationsListView(DeveloperErrorViewMixin, APIView):
@@ -72,26 +68,26 @@ def get(self, request, course_id):
7268

7369
content_group_partition = get_cohorted_user_partition(course)
7470

75-
if content_group_partition is None:
76-
used_ids = {p.id for p in course.user_partitions}
77-
content_group_partition = UserPartition(
78-
id=generate_int_id(MINIMUM_UNUSED_PARTITION_ID, MYSQL_MAX_INT, used_ids),
79-
name=str(CONTENT_GROUP_CONFIGURATION_NAME),
80-
description=str(CONTENT_GROUP_CONFIGURATION_DESCRIPTION),
81-
groups=[],
82-
scheme_id=COHORT_SCHEME
83-
)
84-
85-
context = {
86-
"all_group_configurations": [content_group_partition.to_json()],
87-
"should_show_enrollment_track": False,
88-
"should_show_experiment_groups": True,
89-
"context_course": None,
90-
"group_configuration_url": f"/api/cohorts/v2/courses/{course_id}/group_configurations",
91-
"course_outline_url": f"/api/contentstore/v1/courses/{course_id}",
71+
# Extract partition ID and groups, or None/empty list if no partition exists
72+
if content_group_partition is not None:
73+
partition_id = content_group_partition.id
74+
groups = [group.to_json() for group in content_group_partition.groups]
75+
else:
76+
partition_id = None
77+
groups = []
78+
79+
# Build full Studio URL for content group configuration
80+
mfe_config = configuration_helpers.get_value("MFE_CONFIG", settings.MFE_CONFIG)
81+
studio_base_url = mfe_config.get("STUDIO_BASE_URL", "")
82+
studio_content_groups_link = f"{studio_base_url}/course/{course_id}/group_configurations"
83+
84+
response_data = {
85+
"id": partition_id,
86+
"groups": groups,
87+
"studio_content_groups_link": studio_content_groups_link,
9288
}
9389

94-
serializer = ContentGroupsListResponseSerializer(context)
90+
serializer = ContentGroupsListResponseSerializer(response_data)
9591
return Response(serializer.data, status=status.HTTP_200_OK)
9692

9793

0 commit comments

Comments
 (0)