From 023c841fbb147d6fa95d0bbc0ff05a3c9f9a816e Mon Sep 17 00:00:00 2001 From: Bryann Valderrama Date: Thu, 19 Jun 2025 17:12:25 -0500 Subject: [PATCH] feat(poc): add support for divided discussions with user groups --- src/course-outline/unit-card/UnitCard.jsx | 9 ++-- src/data/constants.ts | 1 + .../apps/openedx/OpenedXConfigForm.jsx | 6 +++ .../apps/shared/DivisionByUserGroupFields.jsx | 54 +++++++++++++++++++ .../discussions/app-config-form/messages.js | 19 +++++++ .../discussions/data/api.js | 21 ++++++++ 6 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 src/pages-and-resources/discussions/app-config-form/apps/shared/DivisionByUserGroupFields.jsx diff --git a/src/course-outline/unit-card/UnitCard.jsx b/src/course-outline/unit-card/UnitCard.jsx index 25c9bfd5b7..caab38c45b 100644 --- a/src/course-outline/unit-card/UnitCard.jsx +++ b/src/course-outline/unit-card/UnitCard.jsx @@ -68,7 +68,8 @@ const UnitCard = ({ } = unit; const blockSyncData = useMemo(() => { - if (!upstreamInfo.readyToSync) { + // Unknow error when upstreamInfo is undefined + if (!upstreamInfo?.readyToSync) { return undefined; } return { @@ -78,7 +79,7 @@ const UnitCard = ({ upstreamBlockVersionSynced: upstreamInfo.versionSynced, isVertical: true, }; - }, [upstreamInfo]); + }, [upstreamInfo, displayName, id]); const readOnly = isUnitReadOnly(unit); @@ -218,7 +219,7 @@ const UnitCard = ({ discussionsSettings={discussionsSettings} parentInfo={parentInfo} extraActionsComponent={extraActionsComponent} - readyToSync={upstreamInfo.readyToSync} + readyToSync={upstreamInfo?.readyToSync} />
+ + diff --git a/src/pages-and-resources/discussions/app-config-form/apps/shared/DivisionByUserGroupFields.jsx b/src/pages-and-resources/discussions/app-config-form/apps/shared/DivisionByUserGroupFields.jsx new file mode 100644 index 0000000000..b0113afee8 --- /dev/null +++ b/src/pages-and-resources/discussions/app-config-form/apps/shared/DivisionByUserGroupFields.jsx @@ -0,0 +1,54 @@ +import { useEffect } from 'react'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { useFormikContext } from 'formik'; +import { DivisionSchemes } from '../../../../../data/constants'; +import FormSwitchGroup from '../../../../../generic/FormSwitchGroup'; +import messages from '../../messages'; + +const DivisionByUserGroupFields = ({ intl }) => { + const { + handleChange, + handleBlur, + values: appConfig, + setFieldValue, + } = useFormikContext(); + + const { divideByUserGroups, userGroupsEnabled } = appConfig; + + useEffect(() => { + if (divideByUserGroups) { + setFieldValue('division_scheme', DivisionSchemes.USER_GROUP); + } else { + setFieldValue('division_scheme', DivisionSchemes.NONE); + } + }, [divideByUserGroups, setFieldValue]); + + return ( + <> +
+ {intl.formatMessage(messages.divisionByUserGroup)} +
+ {!userGroupsEnabled && ( +
+ {intl.formatMessage(messages.userGroupsEnabled)} +
+ )} + + + ); +}; + +DivisionByUserGroupFields.propTypes = { + intl: intlShape.isRequired, +}; + +export default injectIntl(DivisionByUserGroupFields); diff --git a/src/pages-and-resources/discussions/app-config-form/messages.js b/src/pages-and-resources/discussions/app-config-form/messages.js index 4d76c5bec2..3663067124 100644 --- a/src/pages-and-resources/discussions/app-config-form/messages.js +++ b/src/pages-and-resources/discussions/app-config-form/messages.js @@ -154,11 +154,30 @@ const messages = defineMessages({ defaultMessage: 'To adjust these settings, enable cohorts on the ', description: 'Label text informing the user to enable cohort', }, + userGroupsEnabled: { + id: 'authoring.discussions.builtIn.userGroupsEnabled.label', + defaultMessage: 'To adjust this setting, enable user groups in the course settings.', + description: 'Label text informing the user to enable user groups', + }, instructorDashboard: { id: 'authoring.discussions.builtIn.instructorDashboard.label', defaultMessage: 'instructor dashboard', description: 'Label text for instructor dashboard', }, + divisionByUserGroup: { + id: 'authoring.discussions.builtIn.divisionByUserGroup', + defaultMessage: 'User Groups', + }, + divideByUserGroupsLabel: { + id: 'authoring.discussions.builtIn.divideByUserGroups.label', + defaultMessage: 'Divide discussions by user groups', + description: 'Label for a switch that enables dividing discussions by user groups.', + }, + divideByUserGroupsHelp: { + id: 'authoring.discussions.builtIn.divideByUserGroups.help', + defaultMessage: 'Learners will only be able to view and respond to discussions posted by members of their user group.', + description: 'Help text for a switch that enables dividing discussions by user groups.', + }, // In-context discussion fields visibilityInContext: { id: 'authoring.discussions.builtIn.visibilityInContext', diff --git a/src/pages-and-resources/discussions/data/api.js b/src/pages-and-resources/discussions/data/api.js index 706ae44094..e1b8e99324 100644 --- a/src/pages-and-resources/discussions/data/api.js +++ b/src/pages-and-resources/discussions/data/api.js @@ -58,6 +58,7 @@ function normalizePluginConfig(data) { } const enableDivideByCohorts = data.always_divide_inline_discussions && data.division_scheme === 'cohort'; + const enableDivideByUserGroups = data.always_divide_inline_discussions && data.division_scheme === 'user_group'; const enableDivideCourseTopicsByCohorts = enableDivideByCohorts && data.divided_course_wide_discussions.length > 0; return { allowAnonymousPosts: data.allow_anonymous, @@ -68,8 +69,10 @@ function normalizePluginConfig(data) { restrictedDates: normalizeRestrictedDates(data.discussion_blackouts), allowDivisionByUnit: false, divideByCohorts: enableDivideByCohorts, + divideByUserGroups: enableDivideByUserGroups, divideCourseTopicsByCohorts: enableDivideCourseTopicsByCohorts, cohortsEnabled: data.available_division_schemes?.includes('cohort') || false, + userGroupsEnabled: data.available_division_schemes?.includes('user_group') || false, groupAtSubsection: data.group_at_subsection, }; } @@ -184,6 +187,24 @@ function denormalizeData(courseId, appId, data) { pluginConfiguration.division_scheme = data.divideByCohorts ? DivisionSchemes.COHORT : DivisionSchemes.NONE; pluginConfiguration.always_divide_inline_discussions = data.divideByCohorts; } + if ('divideByUserGroups' in data) { + pluginConfiguration.division_scheme = data.divideByUserGroups ? DivisionSchemes.USER_GROUP : DivisionSchemes.NONE; + pluginConfiguration.always_divide_inline_discussions = data.divideByUserGroups; + } + // Handle division scheme priority - user groups take precedence over cohorts + // This is only used for the POC + if ('divideByUserGroups' in data && 'divideByCohorts' in data) { + if (data.divideByUserGroups) { + pluginConfiguration.division_scheme = DivisionSchemes.USER_GROUP; + pluginConfiguration.always_divide_inline_discussions = true; + } else if (data.divideByCohorts) { + pluginConfiguration.division_scheme = DivisionSchemes.COHORT; + pluginConfiguration.always_divide_inline_discussions = true; + } else { + pluginConfiguration.division_scheme = DivisionSchemes.NONE; + pluginConfiguration.always_divide_inline_discussions = false; + } + } if ('groupAtSubsection' in data) { pluginConfiguration.group_at_subsection = data.groupAtSubsection; }