Skip to content

feat: Add notify all learners discussion post checkbox #779

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/discussions/data/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ export const selectEnableInContext = state => state.config.enableInContext;

export const selectIsPostingEnabled = state => state.config.isPostingEnabled;

export const selectIsNotifyAllLearnersEnabled = state => state.config.isNotifyAllLearnersEnabled;

export const selectModerationSettings = state => ({
postCloseReasons: state.config.postCloseReasons,
editReasons: state.config.editReasons,
Expand Down
2 changes: 2 additions & 0 deletions src/discussions/posts/data/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export const postThread = async (
cohort,
anonymous,
anonymousToPeers,
notifyAllLearners,
} = {},
enableInContextSidebar = false,
) => {
Expand All @@ -112,6 +113,7 @@ export const postThread = async (
anonymousToPeers,
groupId: cohort,
enableInContextSidebar,
notifyAllLearners,
});
const { data } = await getAuthenticatedHttpClient()
.post(getThreadsApiUrl(), postData);
Expand Down
3 changes: 3 additions & 0 deletions src/discussions/posts/data/thunks.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ export function createNewThread({
anonymousToPeers,
cohort,
enableInContextSidebar,
notifyAllLearners,
}) {
return async (dispatch) => {
try {
Expand All @@ -217,12 +218,14 @@ export function createNewThread({
anonymous,
anonymousToPeers,
cohort,
notifyAllLearners,
}));
const data = await postThread(courseId, topicId, type, title, content, {
cohort,
following,
anonymous,
anonymousToPeers,
notifyAllLearners,
}, enableInContextSidebar);
dispatch(postThreadSuccess(camelCaseObject(data)));
} catch (error) {
Expand Down
21 changes: 21 additions & 0 deletions src/discussions/posts/post-editor/PostEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
selectAnonymousPostingConfig,
selectDivisionSettings,
selectEnableInContext,
selectIsNotifyAllLearnersEnabled,
selectModerationSettings,
selectUserHasModerationPrivileges,
selectUserIsGroupTa,
Expand Down Expand Up @@ -79,6 +80,7 @@ const PostEditor = ({
const userIsStaff = useSelector(selectUserIsStaff);
const archivedTopics = useSelector(selectArchivedTopics);
const postEditorId = `post-editor-${editExisting ? postId : 'new'}`;
const isNotifyAllLearnersEnabled = useSelector(selectIsNotifyAllLearnersEnabled);

const canDisplayEditReason = (editExisting
&& (userHasModerationPrivileges || userIsGroupTa || userIsStaff)
Expand Down Expand Up @@ -108,6 +110,7 @@ const PostEditor = ({
title: post?.title || '',
comment: post?.rawBody || '',
follow: isEmpty(post?.following) ? true : post?.following,
notifyAllLearners: false,
anonymous: allowAnonymous ? false : undefined,
anonymousToPeers: allowAnonymousToPeers ? false : undefined,
cohort: post?.cohort || 'default',
Expand Down Expand Up @@ -161,6 +164,7 @@ const PostEditor = ({
anonymousToPeers: allowAnonymousToPeers ? values.anonymousToPeers : undefined,
cohort,
enableInContextSidebar,
notifyAllLearners: values.notifyAllLearners,
}));
}
/* istanbul ignore if: TinyMCE is mocked so this cannot be easily tested */
Expand Down Expand Up @@ -216,6 +220,8 @@ const PostEditor = ({
anonymousToPeers: Yup.bool()
.default(false)
.nullable(),
notifyAllLearners: Yup.bool()
.default(false),
cohort: Yup.string()
.nullable()
.default(null),
Expand Down Expand Up @@ -417,6 +423,21 @@ const PostEditor = ({
<div className="d-flex flex-row mt-n4 w-75 text-primary font-style">
{!editExisting && (
<>
{isNotifyAllLearnersEnabled && (
<Form.Group>
<Form.Checkbox
name="notifyAllLearners"
checked={values.notifyAllLearners}
onChange={handleChange}
onBlur={handleBlur}
className="mr-4.5"
>
<span>
{intl.formatMessage(messages.notifyAllLearners)}
</span>
</Form.Checkbox>
</Form.Group>
)}
<Form.Group>
<Form.Checkbox
name="follow"
Expand Down
39 changes: 39 additions & 0 deletions src/discussions/posts/post-editor/PostEditor.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { initializeStore } from '../../../store';
import executeThunk from '../../../test-utils';
import { getCohortsApiUrl } from '../../cohorts/data/api';
import DiscussionContext from '../../common/context';
import { getCourseConfigApiUrl } from '../../data/api';
import fetchCourseConfig from '../../data/thunks';
import fetchCourseTopics from '../../topics/data/thunks';
import { getThreadsApiUrl } from '../data/api';
import { fetchThread } from '../data/thunks';
Expand All @@ -31,6 +33,7 @@ import '../data/__factories__';

const courseId = 'course-v1:edX+DemoX+Demo_Course';
const topicsApiUrl = `${getApiBaseUrl()}/api/discussion/v1/course_topics/${courseId}`;
const courseConfigApiUrl = getCourseConfigApiUrl();
const threadsApiUrl = getThreadsApiUrl();
let store;
let axiosMock;
Expand Down Expand Up @@ -143,6 +146,42 @@ describe('PostEditor', () => {
});
});

describe.each([
{
isNotifyAllLearnersEnabled: true,
description: 'when "Notify All Learners" is enabled',
},
{
isNotifyAllLearnersEnabled: false,
description: 'when "Notify All Learners" is disabled',
},
])('$description', ({ isNotifyAllLearnersEnabled }) => {
beforeEach(async () => {
store = initializeStore({
config: {
provider: 'legacy',
is_notify_all_learners_enabled: isNotifyAllLearnersEnabled,
moderationSettings: {},
},
});

axiosMock
.onGet(`${courseConfigApiUrl}${courseId}/`)
.reply(200, { is_notify_all_learners_enabled: isNotifyAllLearnersEnabled });

await store.dispatch(fetchCourseConfig(courseId));
renderComponent();
});

test(`should ${isNotifyAllLearnersEnabled ? 'show' : 'not show'} the "Notify All Learners" option`, async () => {
if (isNotifyAllLearnersEnabled) {
await waitFor(() => expect(screen.queryByText('Notify All Learners')).toBeInTheDocument());
} else {
await waitFor(() => expect(screen.queryByText('Notify All Learners')).not.toBeInTheDocument());
}
});
});

describe('cohorting', () => {
const dividedncw = ['ncw-topic-2'];
const dividedcw = ['category-1-topic-2', 'category-2-topic-1', 'category-2-topic-2'];
Expand Down
4 changes: 4 additions & 0 deletions src/discussions/posts/post-editor/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ const messages = defineMessages({
id: 'discussions.post.editor.anonymousToPeersPost',
defaultMessage: 'Post anonymously to peers',
},
notifyAllLearners: {
id: 'discussions.post.editor.notifyAllLearners',
defaultMessage: 'Notify All Learners',
},
submit: {
id: 'discussions.editor.submit',
defaultMessage: 'Submit',
Expand Down