feat: discussion setting and release & due date setting (#976)
* fix: hide release and due dates config in self paced courses * feat: discussion enable setting for unit in outline * refactor: message text Co-authored-by: Kristin Aoki <42981026+KristinAoki@users.noreply.github.com> * fix: modal dialog overflow --------- Co-authored-by: Kristin Aoki <42981026+KristinAoki@users.noreply.github.com>
This commit is contained in:
@@ -438,6 +438,7 @@ const CourseOutline = ({ courseId }) => {
|
||||
onConfigureSubmit={handleConfigureItemSubmit}
|
||||
currentItemData={currentItemData}
|
||||
enableProctoredExams={enableProctoredExams}
|
||||
isSelfPaced={statusBarData.isSelfPaced}
|
||||
/>
|
||||
<DeleteModal
|
||||
category={deleteCategory}
|
||||
|
||||
@@ -1405,6 +1405,7 @@ describe('<CourseOutline />', () => {
|
||||
publish: 'republish',
|
||||
metadata: {
|
||||
visible_to_staff_only: isVisibleToStaffOnly,
|
||||
discussion_enabled: false,
|
||||
group_access: newGroupAccess,
|
||||
},
|
||||
})
|
||||
@@ -1423,6 +1424,7 @@ describe('<CourseOutline />', () => {
|
||||
|
||||
// after configuraiton response
|
||||
unit.visibilityState = 'staff_only';
|
||||
unit.discussion_enabled = false;
|
||||
unit.userPartitionInfo = {
|
||||
selectablePartitions: [
|
||||
{
|
||||
@@ -1465,6 +1467,11 @@ describe('<CourseOutline />', () => {
|
||||
)).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' } });
|
||||
@@ -1481,6 +1488,10 @@ describe('<CourseOutline />', () => {
|
||||
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');
|
||||
|
||||
@@ -310,7 +310,7 @@ export async function configureCourseSubsection(
|
||||
* @param {object} groupAccess
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function configureCourseUnit(unitId, isVisibleToStaffOnly, groupAccess) {
|
||||
export async function configureCourseUnit(unitId, isVisibleToStaffOnly, groupAccess, discussionEnabled) {
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.post(getCourseItemApiUrl(unitId), {
|
||||
publish: 'republish',
|
||||
@@ -318,6 +318,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,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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),
|
||||
));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ const HighlightsModal = ({
|
||||
onClose={onClose}
|
||||
hasCloseButton
|
||||
isFullscreenOnMobile
|
||||
isOverflowVisible={false}
|
||||
>
|
||||
<ModalDialog.Header className="highlights-modal__header">
|
||||
<ModalDialog.Title>
|
||||
|
||||
@@ -30,6 +30,7 @@ const PublishModal = ({
|
||||
onClose={onClose}
|
||||
hasCloseButton
|
||||
isFullscreenOnMobile
|
||||
isOverflowVisible={false}
|
||||
>
|
||||
<ModalDialog.Header className="publish-modal__header">
|
||||
<ModalDialog.Title>
|
||||
|
||||
@@ -10,6 +10,7 @@ const BasicTab = ({
|
||||
setFieldValue,
|
||||
courseGraders,
|
||||
isSubsection,
|
||||
isSelfPaced,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
@@ -27,26 +28,30 @@ const BasicTab = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<h5 className="mt-4 text-gray-700"><FormattedMessage {...messages.releaseDateAndTime} /></h5>
|
||||
<hr />
|
||||
<div data-testid="release-date-stack">
|
||||
<Stack className="mt-3" direction="horizontal" gap={5}>
|
||||
<DatepickerControl
|
||||
type={DATEPICKER_TYPES.date}
|
||||
value={releaseDate}
|
||||
label={intl.formatMessage(messages.releaseDate)}
|
||||
controlName="state-date"
|
||||
onChange={(val) => setFieldValue('releaseDate', val)}
|
||||
/>
|
||||
<DatepickerControl
|
||||
type={DATEPICKER_TYPES.time}
|
||||
value={releaseDate}
|
||||
label={intl.formatMessage(messages.releaseTimeUTC)}
|
||||
controlName="start-time"
|
||||
onChange={(val) => setFieldValue('releaseDate', val)}
|
||||
/>
|
||||
</Stack>
|
||||
</div>
|
||||
{!isSelfPaced && (
|
||||
<>
|
||||
<h5 className="mt-4 text-gray-700"><FormattedMessage {...messages.releaseDateAndTime} /></h5>
|
||||
<hr />
|
||||
<div data-testid="release-date-stack">
|
||||
<Stack className="mt-3" direction="horizontal" gap={5}>
|
||||
<DatepickerControl
|
||||
type={DATEPICKER_TYPES.date}
|
||||
value={releaseDate}
|
||||
label={intl.formatMessage(messages.releaseDate)}
|
||||
controlName="state-date"
|
||||
onChange={(val) => setFieldValue('releaseDate', val)}
|
||||
/>
|
||||
<DatepickerControl
|
||||
type={DATEPICKER_TYPES.time}
|
||||
value={releaseDate}
|
||||
label={intl.formatMessage(messages.releaseTimeUTC)}
|
||||
controlName="start-time"
|
||||
onChange={(val) => setFieldValue('releaseDate', val)}
|
||||
/>
|
||||
</Stack>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{
|
||||
isSubsection && (
|
||||
<div>
|
||||
@@ -66,25 +71,27 @@ const BasicTab = ({
|
||||
{createOptions()}
|
||||
</Form.Control>
|
||||
</Form.Group>
|
||||
<div data-testid="due-date-stack">
|
||||
<Stack className="mt-3" direction="horizontal" gap={5}>
|
||||
<DatepickerControl
|
||||
type={DATEPICKER_TYPES.date}
|
||||
value={dueDate}
|
||||
label={intl.formatMessage(messages.dueDate)}
|
||||
controlName="state-date"
|
||||
onChange={(val) => setFieldValue('dueDate', val)}
|
||||
data-testid="due-date-picker"
|
||||
/>
|
||||
<DatepickerControl
|
||||
type={DATEPICKER_TYPES.time}
|
||||
value={dueDate}
|
||||
label={intl.formatMessage(messages.dueTimeUTC)}
|
||||
controlName="start-time"
|
||||
onChange={(val) => setFieldValue('dueDate', val)}
|
||||
/>
|
||||
</Stack>
|
||||
</div>
|
||||
{!isSelfPaced && (
|
||||
<div data-testid="due-date-stack">
|
||||
<Stack className="mt-3" direction="horizontal" gap={5}>
|
||||
<DatepickerControl
|
||||
type={DATEPICKER_TYPES.date}
|
||||
value={dueDate}
|
||||
label={intl.formatMessage(messages.dueDate)}
|
||||
controlName="state-date"
|
||||
onChange={(val) => setFieldValue('dueDate', val)}
|
||||
data-testid="due-date-picker"
|
||||
/>
|
||||
<DatepickerControl
|
||||
type={DATEPICKER_TYPES.time}
|
||||
value={dueDate}
|
||||
label={intl.formatMessage(messages.dueTimeUTC)}
|
||||
controlName="start-time"
|
||||
onChange={(val) => setFieldValue('dueDate', val)}
|
||||
/>
|
||||
</Stack>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab eventKey="visibility" title={intl.formatMessage(messages.visibilityTabTitle)}>
|
||||
@@ -208,6 +213,7 @@ const ConfigureModal = ({
|
||||
setFieldValue={setFieldValue}
|
||||
isSubsection={isSubsection}
|
||||
courseGraders={courseGraders === 'undefined' ? [] : courseGraders}
|
||||
isSelfPaced={isSelfPaced}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab eventKey="visibility" title={intl.formatMessage(messages.visibilityTabTitle)}>
|
||||
@@ -259,6 +265,7 @@ const ConfigureModal = ({
|
||||
onClose={onClose}
|
||||
hasCloseButton
|
||||
isFullscreenOnMobile
|
||||
isOverflowVisible={false}
|
||||
>
|
||||
<div data-testid="configure-modal">
|
||||
<ModalDialog.Header className="configure-modal__header">
|
||||
@@ -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;
|
||||
|
||||
@@ -44,6 +44,7 @@ const renderComponent = () => render(
|
||||
onClose={onCloseMock}
|
||||
onConfigureSubmit={onConfigureSubmitMock}
|
||||
currentItemData={currentSectionMock}
|
||||
isSelfPaced={false}
|
||||
/>
|
||||
</IntlProvider>,
|
||||
</AppProvider>,
|
||||
@@ -85,7 +86,7 @@ describe('<ConfigureModal /> for Section', () => {
|
||||
});
|
||||
});
|
||||
|
||||
const renderSubsectionComponent = () => render(
|
||||
const renderSubsectionComponent = (props) => render(
|
||||
<AppProvider store={store}>
|
||||
<IntlProvider locale="en">
|
||||
<ConfigureModal
|
||||
@@ -93,6 +94,8 @@ const renderSubsectionComponent = () => render(
|
||||
onClose={onCloseMock}
|
||||
onConfigureSubmit={onConfigureSubmitMock}
|
||||
currentItemData={currentSubsectionMock}
|
||||
isSelfPaced={false}
|
||||
{...props}
|
||||
/>
|
||||
</IntlProvider>,
|
||||
</AppProvider>,
|
||||
@@ -129,6 +132,14 @@ describe('<ConfigureModal /> 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('<ConfigureModal /> 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();
|
||||
|
||||
|
||||
@@ -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 && (
|
||||
<>
|
||||
<h3 className="mt-3"><FormattedMessage {...messages.unitVisibility} /></h3>
|
||||
<h4 className="mt-3"><FormattedMessage {...messages.unitVisibility} /></h4>
|
||||
<hr />
|
||||
<Form.Checkbox checked={isVisibleToStaffOnly} onChange={handleChange} data-testid="unit-visibility-checkbox">
|
||||
<Form.Checkbox checked={isVisibleToStaffOnly} onChange={handleVisibilityChange} data-testid="unit-visibility-checkbox">
|
||||
<FormattedMessage {...messages.hideFromLearners} />
|
||||
</Form.Checkbox>
|
||||
{showWarning && (
|
||||
@@ -52,77 +57,85 @@ const UnitTab = ({
|
||||
<FormattedMessage {...messages.unitVisibilityWarning} />
|
||||
</Alert>
|
||||
)}
|
||||
<hr />
|
||||
</>
|
||||
)}
|
||||
<Form.Group controlId="groupSelect">
|
||||
<Form.Label as="legend" className="font-weight-bold">
|
||||
<FormattedMessage {...messages.restrictAccessTo} />
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
as="select"
|
||||
name="groupSelect"
|
||||
value={selectedPartitionIndex}
|
||||
onChange={handleSelect}
|
||||
data-testid="group-type-select"
|
||||
>
|
||||
<option value="-1" key="-1">
|
||||
{userPartitionInfo.selectedPartitionIndex === -1
|
||||
? intl.formatMessage(messages.unitSelectGroupType)
|
||||
: intl.formatMessage(messages.unitAllLearnersAndStaff)}
|
||||
</option>
|
||||
{userPartitionInfo.selectablePartitions.map((partition, index) => (
|
||||
<option
|
||||
key={partition.id}
|
||||
value={index}
|
||||
>
|
||||
{partition.name}
|
||||
{userPartitionInfo.selectablePartitions.length > 0 && (
|
||||
<Form.Group controlId="groupSelect">
|
||||
<h4 className="mt-3"><FormattedMessage {...messages.unitAccess} /></h4>
|
||||
<hr />
|
||||
<Form.Label as="legend" className="font-weight-bold">
|
||||
<FormattedMessage {...messages.restrictAccessTo} />
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
as="select"
|
||||
name="groupSelect"
|
||||
value={selectedPartitionIndex}
|
||||
onChange={handleSelect}
|
||||
data-testid="group-type-select"
|
||||
>
|
||||
<option value="-1" key="-1">
|
||||
{userPartitionInfo.selectedPartitionIndex === -1
|
||||
? intl.formatMessage(messages.unitSelectGroupType)
|
||||
: intl.formatMessage(messages.unitAllLearnersAndStaff)}
|
||||
</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
|
||||
{selectedPartitionIndex >= 0 && userPartitionInfo.selectablePartitions.length && (
|
||||
<Form.Group controlId="select-groups-checkboxes">
|
||||
<Form.Label><FormattedMessage {...messages.unitSelectGroup} /></Form.Label>
|
||||
<div
|
||||
role="group"
|
||||
className="d-flex flex-column"
|
||||
data-testid="group-checkboxes"
|
||||
aria-labelledby="select-groups-checkboxes"
|
||||
>
|
||||
{userPartitionInfo.selectablePartitions[selectedPartitionIndex].groups.map((group) => (
|
||||
<Form.Group
|
||||
key={group.id}
|
||||
className="pgn__form-checkbox"
|
||||
>
|
||||
<Field
|
||||
as={Form.Control}
|
||||
className="flex-grow-0 mr-1"
|
||||
controlClassName="pgn__form-checkbox-input mr-1"
|
||||
type="checkbox"
|
||||
value={`${group.id}`}
|
||||
name="selectedGroups"
|
||||
/>
|
||||
<div>
|
||||
<Form.Label
|
||||
className={classNames({ 'text-danger': checkIsDeletedGroup(group) })}
|
||||
isInline
|
||||
>
|
||||
{group.name}
|
||||
</Form.Label>
|
||||
{group.deleted && (
|
||||
<Form.Control.Feedback type="invalid" hasIcon={false}>
|
||||
{intl.formatMessage(messages.unitSelectDeletedGroupErrorMessage)}
|
||||
</Form.Control.Feedback>
|
||||
)}
|
||||
</div>
|
||||
</Form.Group>
|
||||
))}
|
||||
</div>
|
||||
</Form.Group>
|
||||
)}
|
||||
</Form.Group>
|
||||
{userPartitionInfo.selectablePartitions.map((partition, index) => (
|
||||
<option
|
||||
key={partition.id}
|
||||
value={index}
|
||||
>
|
||||
{partition.name}
|
||||
</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
|
||||
{selectedPartitionIndex >= 0 && userPartitionInfo.selectablePartitions.length && (
|
||||
<Form.Group controlId="select-groups-checkboxes">
|
||||
<Form.Label><FormattedMessage {...messages.unitSelectGroup} /></Form.Label>
|
||||
<div
|
||||
role="group"
|
||||
className="d-flex flex-column"
|
||||
data-testid="group-checkboxes"
|
||||
aria-labelledby="select-groups-checkboxes"
|
||||
>
|
||||
{userPartitionInfo.selectablePartitions[selectedPartitionIndex].groups.map((group) => (
|
||||
<Form.Group
|
||||
key={group.id}
|
||||
className="pgn__form-checkbox"
|
||||
>
|
||||
<Field
|
||||
as={Form.Control}
|
||||
className="flex-grow-0 mr-1"
|
||||
controlClassName="pgn__form-checkbox-input mr-1"
|
||||
type="checkbox"
|
||||
value={`${group.id}`}
|
||||
name="selectedGroups"
|
||||
/>
|
||||
<div>
|
||||
<Form.Label
|
||||
className={classNames({ 'text-danger': checkIsDeletedGroup(group) })}
|
||||
isInline
|
||||
>
|
||||
{group.name}
|
||||
</Form.Label>
|
||||
{group.deleted && (
|
||||
<Form.Control.Feedback type="invalid" hasIcon={false}>
|
||||
{intl.formatMessage(messages.unitSelectDeletedGroupErrorMessage)}
|
||||
</Form.Control.Feedback>
|
||||
)}
|
||||
</div>
|
||||
</Form.Group>
|
||||
))}
|
||||
</div>
|
||||
</Form.Group>
|
||||
)}
|
||||
</Form.Group>
|
||||
)}
|
||||
<h4 className="mt-4"><FormattedMessage {...messages.discussionEnabledSectionTitle} /></h4>
|
||||
<hr />
|
||||
<Form.Checkbox checked={discussionEnabled} onChange={handleDiscussionChange}>
|
||||
<FormattedMessage {...messages.discussionEnabledCheckbox} />
|
||||
</Form.Checkbox>
|
||||
<p className="x-small font-weight-bold"><FormattedMessage {...messages.discussionEnabledDescription} /></p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -135,6 +148,7 @@ UnitTab.propTypes = {
|
||||
isXBlockComponent: PropTypes.bool,
|
||||
values: PropTypes.shape({
|
||||
isVisibleToStaffOnly: PropTypes.bool.isRequired,
|
||||
discussionEnabled: PropTypes.bool.isRequired,
|
||||
selectedPartitionIndex: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user