diff --git a/src/course-outline/CourseOutline.jsx b/src/course-outline/CourseOutline.jsx index 21a9519a7c..e7468e68fb 100644 --- a/src/course-outline/CourseOutline.jsx +++ b/src/course-outline/CourseOutline.jsx @@ -459,6 +459,7 @@ const CourseOutline = ({ courseId }) => { onConfigureSubmit={handleConfigureItemSubmit} currentItemData={currentItemData} enableProctoredExams={enableProctoredExams} + isSelfPaced={statusBarData.isSelfPaced} /> ', () => { publish: 'republish', metadata: { visible_to_staff_only: isVisibleToStaffOnly, + discussion_enabled: false, group_access: newGroupAccess, }, }) @@ -1427,6 +1428,7 @@ describe('', () => { // after configuraiton response unit.visibilityState = 'staff_only'; + unit.discussion_enabled = false; unit.userPartitionInfo = { selectablePartitions: [ { @@ -1469,6 +1471,11 @@ describe('', () => { )).toBeInTheDocument(); let visibilityCheckbox = await within(configureModal).findByTestId('unit-visibility-checkbox'); await act(async () => fireEvent.click(visibilityCheckbox)); + let discussionCheckbox = await within(configureModal).findByLabelText( + configureModalMessages.discussionEnabledCheckbox.defaultMessage, + ); + expect(discussionCheckbox).toBeChecked(); + await act(async () => fireEvent.click(discussionCheckbox)); let groupeType = await within(configureModal).findByTestId('group-type-select'); fireEvent.change(groupeType, { target: { value: '0' } }); @@ -1485,6 +1492,10 @@ describe('', () => { configureModal = await findByTestId('configure-modal'); visibilityCheckbox = await within(configureModal).findByTestId('unit-visibility-checkbox'); expect(visibilityCheckbox).toBeChecked(); + discussionCheckbox = await within(configureModal).findByLabelText( + configureModalMessages.discussionEnabledCheckbox.defaultMessage, + ); + expect(discussionCheckbox).not.toBeChecked(); groupeType = await within(configureModal).findByTestId('group-type-select'); expect(groupeType).toHaveValue('0'); diff --git a/src/course-outline/data/api.js b/src/course-outline/data/api.js index b6793d1a85..fc61f3c117 100644 --- a/src/course-outline/data/api.js +++ b/src/course-outline/data/api.js @@ -311,7 +311,7 @@ export async function configureCourseSubsection( * @param {object} groupAccess * @returns {Promise} */ -export async function configureCourseUnit(unitId, isVisibleToStaffOnly, groupAccess) { +export async function configureCourseUnit(unitId, isVisibleToStaffOnly, groupAccess, discussionEnabled) { const { data } = await getAuthenticatedHttpClient() .post(getCourseItemApiUrl(unitId), { publish: 'republish', @@ -319,6 +319,7 @@ export async function configureCourseUnit(unitId, isVisibleToStaffOnly, groupAcc // The backend expects metadata.visible_to_staff_only to either true or null visible_to_staff_only: isVisibleToStaffOnly ? true : null, group_access: groupAccess, + discussion_enabled: discussionEnabled, }, }); diff --git a/src/course-outline/data/thunk.js b/src/course-outline/data/thunk.js index f877bc2227..3508fde0bf 100644 --- a/src/course-outline/data/thunk.js +++ b/src/course-outline/data/thunk.js @@ -334,11 +334,11 @@ export function configureCourseSubsectionQuery( }; } -export function configureCourseUnitQuery(itemId, sectionId, isVisibleToStaffOnly, groupAccess) { +export function configureCourseUnitQuery(itemId, sectionId, isVisibleToStaffOnly, groupAccess, discussionEnabled) { return async (dispatch) => { dispatch(configureCourseItemQuery( sectionId, - async () => configureCourseUnit(itemId, isVisibleToStaffOnly, groupAccess), + async () => configureCourseUnit(itemId, isVisibleToStaffOnly, groupAccess, discussionEnabled), )); }; } diff --git a/src/course-outline/highlights-modal/HighlightsModal.jsx b/src/course-outline/highlights-modal/HighlightsModal.jsx index 0e26ebf39a..91d806f0b2 100644 --- a/src/course-outline/highlights-modal/HighlightsModal.jsx +++ b/src/course-outline/highlights-modal/HighlightsModal.jsx @@ -38,6 +38,7 @@ const HighlightsModal = ({ onClose={onClose} hasCloseButton isFullscreenOnMobile + isOverflowVisible={false} > diff --git a/src/course-outline/publish-modal/PublishModal.jsx b/src/course-outline/publish-modal/PublishModal.jsx index 03c2d2f0f6..b56488c050 100644 --- a/src/course-outline/publish-modal/PublishModal.jsx +++ b/src/course-outline/publish-modal/PublishModal.jsx @@ -30,6 +30,7 @@ const PublishModal = ({ onClose={onClose} hasCloseButton isFullscreenOnMobile + isOverflowVisible={false} > diff --git a/src/generic/configure-modal/BasicTab.jsx b/src/generic/configure-modal/BasicTab.jsx index 182de34df1..fc924a9821 100644 --- a/src/generic/configure-modal/BasicTab.jsx +++ b/src/generic/configure-modal/BasicTab.jsx @@ -10,6 +10,7 @@ const BasicTab = ({ setFieldValue, courseGraders, isSubsection, + isSelfPaced, }) => { const intl = useIntl(); @@ -27,26 +28,30 @@ const BasicTab = ({ return ( <> -
-
-
- - setFieldValue('releaseDate', val)} - /> - setFieldValue('releaseDate', val)} - /> - -
+ {!isSelfPaced && ( + <> +
+
+
+ + setFieldValue('releaseDate', val)} + /> + setFieldValue('releaseDate', val)} + /> + +
+ + )} { isSubsection && (
@@ -66,25 +71,27 @@ const BasicTab = ({ {createOptions()} -
- - setFieldValue('dueDate', val)} - data-testid="due-date-picker" - /> - setFieldValue('dueDate', val)} - /> - -
+ {!isSelfPaced && ( +
+ + setFieldValue('dueDate', val)} + data-testid="due-date-picker" + /> + setFieldValue('dueDate', val)} + /> + +
+ )}
) } @@ -101,6 +108,7 @@ BasicTab.propTypes = { }).isRequired, courseGraders: PropTypes.arrayOf(PropTypes.string).isRequired, setFieldValue: PropTypes.func.isRequired, + isSelfPaced: PropTypes.bool.isRequired, }; export default injectIntl(BasicTab); diff --git a/src/generic/configure-modal/ConfigureModal.jsx b/src/generic/configure-modal/ConfigureModal.jsx index a78bd386a7..ef9da846bb 100644 --- a/src/generic/configure-modal/ConfigureModal.jsx +++ b/src/generic/configure-modal/ConfigureModal.jsx @@ -28,6 +28,7 @@ const ConfigureModal = ({ currentItemData, enableProctoredExams, isXBlockComponent, + isSelfPaced, }) => { const intl = useIntl(); const { @@ -58,6 +59,7 @@ const ConfigureModal = ({ supportsOnboarding, showReviewRules, onlineProctoringRules, + discussionEnabled, } = currentItemData; const getSelectedGroups = () => { @@ -98,6 +100,7 @@ const ConfigureModal = ({ // by default it is -1 i.e. accessible to all learners & staff selectedPartitionIndex: userPartitionInfo?.selectedPartitionIndex, selectedGroups: getSelectedGroups(), + discussionEnabled, }; const validationSchema = Yup.object().shape({ @@ -127,6 +130,7 @@ const ConfigureModal = ({ ).nullable(true), selectedPartitionIndex: Yup.number().integer(), selectedGroups: Yup.array().of(Yup.string()), + discussionEnabled: Yup.boolean(), }); const isSubsection = category === COURSE_BLOCK_NAMES.sequential.id; @@ -168,7 +172,7 @@ const ConfigureModal = ({ const partitionId = userPartitionInfo.selectablePartitions[data.selectedPartitionIndex].id; groupAccess[partitionId] = data.selectedGroups.map(g => parseInt(g, 10)); } - onConfigureSubmit(data.isVisibleToStaffOnly, groupAccess); + onConfigureSubmit(data.isVisibleToStaffOnly, groupAccess, data.discussionEnabled); break; default: break; @@ -186,6 +190,7 @@ const ConfigureModal = ({ setFieldValue={setFieldValue} isSubsection={isSubsection} courseGraders={courseGraders === 'undefined' ? [] : courseGraders} + isSelfPaced={isSelfPaced} /> @@ -208,6 +213,7 @@ const ConfigureModal = ({ setFieldValue={setFieldValue} isSubsection={isSubsection} courseGraders={courseGraders === 'undefined' ? [] : courseGraders} + isSelfPaced={isSelfPaced} /> @@ -259,6 +265,7 @@ const ConfigureModal = ({ onClose={onClose} hasCloseButton isFullscreenOnMobile + isOverflowVisible={false} >
@@ -358,8 +365,10 @@ ConfigureModal.propTypes = { supportsOnboarding: PropTypes.bool, showReviewRules: PropTypes.bool, onlineProctoringRules: PropTypes.string, + discussionEnabled: PropTypes.bool.isRequired, }).isRequired, isXBlockComponent: PropTypes.bool, + isSelfPaced: PropTypes.bool.isRequired, }; export default ConfigureModal; diff --git a/src/generic/configure-modal/ConfigureModal.test.jsx b/src/generic/configure-modal/ConfigureModal.test.jsx index 3c4d699446..d127b27dc9 100644 --- a/src/generic/configure-modal/ConfigureModal.test.jsx +++ b/src/generic/configure-modal/ConfigureModal.test.jsx @@ -44,6 +44,7 @@ const renderComponent = () => render( onClose={onCloseMock} onConfigureSubmit={onConfigureSubmitMock} currentItemData={currentSectionMock} + isSelfPaced={false} /> , , @@ -85,7 +86,7 @@ describe(' for Section', () => { }); }); -const renderSubsectionComponent = () => render( +const renderSubsectionComponent = (props) => render( render( onClose={onCloseMock} onConfigureSubmit={onConfigureSubmitMock} currentItemData={currentSubsectionMock} + isSelfPaced={false} + {...props} /> , , @@ -129,6 +132,14 @@ describe(' for Subsection', () => { expect(getByRole('button', { name: messages.saveButton.defaultMessage })).toBeInTheDocument(); }); + it('hides release and due dates for self paced courses', () => { + const { queryByText } = renderSubsectionComponent({ isSelfPaced: true }); + expect(queryByText(messages.releaseDate.defaultMessage)).not.toBeInTheDocument(); + expect(queryByText(messages.releaseTimeUTC.defaultMessage)).not.toBeInTheDocument(); + expect(queryByText(messages.dueDate.defaultMessage)).not.toBeInTheDocument(); + expect(queryByText(messages.dueTimeUTC.defaultMessage)).not.toBeInTheDocument(); + }); + it('switches to the subsection Visibility tab and renders correctly', () => { const { getByRole, getByText } = renderSubsectionComponent(); @@ -198,6 +209,7 @@ describe(' for Unit', () => { expect(getByText(`${currentUnitMock.displayName} settings`)).toBeInTheDocument(); expect(getByText(messages.unitVisibility.defaultMessage)).toBeInTheDocument(); expect(getByText(messages.hideFromLearners.defaultMessage)).toBeInTheDocument(); + expect(getByText(messages.discussionEnabledCheckbox.defaultMessage)).toBeInTheDocument(); expect(getByText(messages.restrictAccessTo.defaultMessage)).toBeInTheDocument(); expect(getByText(messages.unitSelectGroupType.defaultMessage)).toBeInTheDocument(); diff --git a/src/generic/configure-modal/UnitTab.jsx b/src/generic/configure-modal/UnitTab.jsx index 2c38ab17d0..34499a04ab 100644 --- a/src/generic/configure-modal/UnitTab.jsx +++ b/src/generic/configure-modal/UnitTab.jsx @@ -21,12 +21,17 @@ const UnitTab = ({ isVisibleToStaffOnly, selectedPartitionIndex, selectedGroups, + discussionEnabled, } = values; - const handleChange = (e) => { + const handleVisibilityChange = (e) => { setFieldValue('isVisibleToStaffOnly', e.target.checked); }; + const handleDiscussionChange = (e) => { + setFieldValue('discussionEnabled', e.target.checked); + }; + const handleSelect = (e) => { setFieldValue('selectedPartitionIndex', parseInt(e.target.value, 10)); setFieldValue('selectedGroups', []); @@ -42,9 +47,9 @@ const UnitTab = ({ <> {!isXBlockComponent && ( <> -

+


- + {showWarning && ( @@ -52,77 +57,84 @@ const UnitTab = ({ )} -
)} - - - - - - - {userPartitionInfo.selectablePartitions.map((partition, index) => ( - + ))} + + {selectedPartitionIndex >= 0 && userPartitionInfo.selectablePartitions.length && ( + + +
+ {userPartitionInfo.selectablePartitions[selectedPartitionIndex].groups.map((group) => ( + + +
+ + {group.name} + + {group.deleted && ( + + {intl.formatMessage(messages.unitSelectDeletedGroupErrorMessage)} + + )} +
+
+ ))} +
+
+ )} +
+ )} +

+
+ + + +

); }; @@ -135,6 +147,7 @@ UnitTab.propTypes = { isXBlockComponent: PropTypes.bool, values: PropTypes.shape({ isVisibleToStaffOnly: PropTypes.bool.isRequired, + discussionEnabled: PropTypes.bool.isRequired, selectedPartitionIndex: PropTypes.oneOfType([ PropTypes.string, PropTypes.number, diff --git a/src/generic/configure-modal/messages.js b/src/generic/configure-modal/messages.js index d27f943dc6..5d785da574 100644 --- a/src/generic/configure-modal/messages.js +++ b/src/generic/configure-modal/messages.js @@ -42,6 +42,22 @@ const messages = defineMessages({ id: 'course-authoring.course-outline.configure-modal.visibility-tab.unit-visibility', defaultMessage: 'Unit visibility', }, + unitAccess: { + id: 'course-authoring.course-outline.configure-modal.visibility-tab.unit-access', + defaultMessage: 'Unit access', + }, + discussionEnabledSectionTitle: { + id: 'course-authoring.course-outline.configure-modal.discussion-enabled.section-title', + defaultMessage: 'Discussion', + }, + discussionEnabledCheckbox: { + id: 'course-authoring.course-outline.configure-modal.discussion-enabled.checkbox', + defaultMessage: 'Enable discussion', + }, + discussionEnabledDescription: { + id: 'course-authoring.course-outline.configure-modal.discussion-enabled.description', + defaultMessage: 'Topics for unpublished units will not be created', + }, hideFromLearners: { id: 'course-authoring.course-outline.configure-modal.visibility.hide-from-learners', defaultMessage: 'Hide from learners',