feat: add team groups section in group configurations (#1728)

This commit is contained in:
Bryann Valderrama
2026-02-26 17:02:31 -05:00
committed by GitHub
parent c4a09a2b43
commit e8cd7c2dcc
8 changed files with 219 additions and 4 deletions

View File

@@ -20,6 +20,7 @@ let store;
const courseId = 'course-v1:org+101+101';
const enrollmentTrackGroups = groupConfigurationResponseMock.allGroupConfigurations[0];
const contentGroups = groupConfigurationResponseMock.allGroupConfigurations[1];
const teamGroups = groupConfigurationResponseMock.allGroupConfigurations[2];
const renderComponent = () => render(
<CourseAuthoringProvider courseId={courseId}>
@@ -61,6 +62,7 @@ describe('<GroupConfigurations />', () => {
).toBeInTheDocument();
expect(getByText(contentGroups.name)).toBeInTheDocument();
expect(getByText(enrollmentTrackGroups.name)).toBeInTheDocument();
expect(getByText(teamGroups.name)).toBeInTheDocument();
});
});

View File

@@ -26,9 +26,9 @@ module.exports = {
usage: null,
name: 'Enrollment Track Groups',
parameters: {
course_id: 'course-v1:org+101+101',
courseId: 'course-v1:org+101+101',
},
read_only: true,
readOnly: true,
scheme: 'enrollment_track',
version: 3,
},
@@ -68,6 +68,39 @@ module.exports = {
scheme: 'cohort',
version: 3,
},
{
active: true,
description: 'Partition for segmenting users by team-set',
groups: [
{
id: 6,
name: 'My Team 1',
usage: [],
version: 1,
},
{
id: 7,
name: 'My Team 2',
usage: [
{
label: 'Subsection / Unit',
url: '/container/block-v1:org+101+101+type@vertical+block@e960cb847be24b8c835ae1a0184d7831',
},
],
version: 1,
},
],
id: 92768376,
usage: null,
name: 'Team Group: My Group',
parameters: {
courseId: 'course-v1:org+101+101',
teamSetId: '0ec11208-335f-4b48-9475-136f02cc30f3"',
},
readOnly: true,
scheme: 'team',
version: 3,
},
],
experimentGroupConfigurations: [
{

View File

@@ -2,3 +2,4 @@ export { default as contentGroupsMock } from './contentGroupsMock';
export { default as enrollmentTrackGroupsMock } from './enrollmentTrackGroupsMock';
export { default as experimentGroupConfigurationsMock } from './experimentGroupConfigurationsMock';
export { default as groupConfigurationResponseMock } from './groupConfigurationResponseMock';
export { default as teamGroupsMock } from './teamGroupsMock';

View File

@@ -0,0 +1,35 @@
import { AvailableGroup } from '@src/group-configurations/types';
const teamGroupsMock: AvailableGroup = {
active: true,
description: 'Partition for segmenting users by team-set',
groups: [
{
id: 6,
name: 'My Team 1',
usage: [],
version: 1,
},
{
id: 7,
name: 'My Team 2',
usage: [
{
label: 'Subsection / Unit',
url: '/container/block-v1:org+101+101+type@vertical+block@e960cb847be24b8c835ae1a0184d7831',
},
],
version: 1,
},
],
id: 92768376,
name: 'Team Group: My Group',
parameters: {
courseId: 'course-v1:org+101+101',
},
readOnly: true,
scheme: 'team',
version: 3,
};
export default teamGroupsMock;

View File

@@ -12,6 +12,7 @@ import { SavingErrorAlert } from '../generic/saving-error-alert';
import messages from './messages';
import ContentGroupsSection from './content-groups-section';
import ExperimentConfigurationsSection from './experiment-configurations-section';
import TeamGroupsSection from './team-groups-section';
import EnrollmentTrackGroupsSection from './enrollment-track-groups-section';
import GroupConfigurationSidebar from './group-configuration-sidebar';
import { useGroupConfigurations } from './hooks';
@@ -59,9 +60,12 @@ const GroupConfigurations = () => {
}
const enrollmentTrackGroup = shouldShowEnrollmentTrack
? allGroupConfigurations[0]
? allGroupConfigurations.find((group) => group.scheme === 'enrollment_track')
: null;
const contentGroup = allGroupConfigurations?.[shouldShowEnrollmentTrack ? 1 : 0];
const contentGroup = allGroupConfigurations.find((group) => group.scheme === 'cohort');
const teamGroups = allGroupConfigurations.filter((group) => group.scheme === 'team');
return (
<>
@@ -83,6 +87,13 @@ const GroupConfigurations = () => {
gap={3}
data-testid="group-configurations-main-content-wrapper"
>
{!!teamGroups && teamGroups.length > 0 && (
teamGroups.map((teamGroup) => (
<TeamGroupsSection
availableGroup={teamGroup}
/>
))
)}
{!!enrollmentTrackGroup && (
<EnrollmentTrackGroupsSection
availableGroup={enrollmentTrackGroup}

View File

@@ -0,0 +1,81 @@
import React from 'react';
import { initializeMocks, render } from '@src/testUtils';
import { teamGroupsMock } from '@src/group-configurations/__mocks__';
import TeamGroupsSection from '.';
const renderComponent = (
props: Partial<React.ComponentProps<typeof TeamGroupsSection>> = {},
) => {
initializeMocks();
return render(
<TeamGroupsSection
availableGroup={teamGroupsMock}
{...props}
/>,
);
};
describe('<TeamGroupsSection />', () => {
it('renders component correctly', () => {
const { getByText, getAllByTestId } = renderComponent();
expect(getByText(teamGroupsMock.name)).toBeInTheDocument();
expect(getAllByTestId('content-group-card')).toHaveLength(
teamGroupsMock.groups.length,
);
});
it('renders the team group name as a heading', () => {
const { getByRole } = renderComponent();
const heading = getByRole('heading', { name: teamGroupsMock.name });
expect(heading).toBeInTheDocument();
expect(heading).toHaveClass('configuration-section-name');
});
it('renders all team groups from the availableGroup prop', () => {
const { getByText } = renderComponent();
teamGroupsMock.groups.forEach((group) => {
expect(getByText(group.name)).toBeInTheDocument();
});
});
it('renders content group cards as read-only', () => {
const { getAllByTestId } = renderComponent();
const cards = getAllByTestId('content-group-card');
expect(cards).toHaveLength(teamGroupsMock.groups.length);
// Verify that no edit or delete buttons are present (read-only mode)
const editButtons = document.querySelectorAll('[data-testid="content-group-card-header-edit"]');
const deleteButtons = document.querySelectorAll('[data-testid="content-group-card-header-delete"]');
expect(editButtons).toHaveLength(0);
expect(deleteButtons).toHaveLength(0);
});
it('renders with custom availableGroup prop', () => {
const customGroup = {
...teamGroupsMock,
name: 'Custom Team Group',
groups: [
{
id: 100,
name: 'Custom Team 1',
usage: [],
version: 1,
},
],
};
const { getByText, getAllByTestId } = renderComponent({
availableGroup: customGroup,
});
expect(getByText('Custom Team Group')).toBeInTheDocument();
expect(getByText('Custom Team 1')).toBeInTheDocument();
expect(getAllByTestId('content-group-card')).toHaveLength(1);
});
});

View File

@@ -0,0 +1,25 @@
import React from 'react';
import { AvailableGroup } from '@src/group-configurations/types';
import ContentGroupCard from '@src/group-configurations/content-groups-section/ContentGroupCard';
interface TeamGroupsSectionProps {
availableGroup: AvailableGroup;
}
const TeamGroupsSection: React.FC<TeamGroupsSectionProps> = ({
availableGroup: { groups, name },
}) => (
<div className="mt-2.5">
<h2 className="lead text-black mb-3 configuration-section-name">{name}</h2>
{groups.map((group) => (
<ContentGroupCard
group={group}
key={group.id}
readOnly
/>
))}
</div>
);
export default TeamGroupsSection;

View File

@@ -0,0 +1,27 @@
export interface Usage {
label: string;
url: string;
}
export interface Group {
id: number;
name: string;
usage: Usage[];
version: number;
}
export interface AvailableGroupParameters {
courseId: string;
}
export interface AvailableGroup {
active?: boolean;
description?: string;
groups: Group[];
id: number;
name: string;
parameters?: AvailableGroupParameters;
readOnly?: boolean;
scheme: string;
version: number;
}