diff --git a/src/discussions/common/ActionsDropdown.jsx b/src/discussions/common/ActionsDropdown.jsx index 0df904c8..f699473c 100644 --- a/src/discussions/common/ActionsDropdown.jsx +++ b/src/discussions/common/ActionsDropdown.jsx @@ -13,9 +13,9 @@ import { import { MoreHoriz } from '@edx/paragon/icons'; import { ContentActions } from '../../data/constants'; -import { selectBlackoutDate } from '../data/selectors'; +import { selectIsPostingEnabled } from '../data/selectors'; import messages from '../messages'; -import { inBlackoutDateRange, useActions } from '../utils'; +import { useActions } from '../utils'; const ActionsDropdown = ({ actionHandlers, @@ -29,7 +29,7 @@ const ActionsDropdown = ({ const intl = useIntl(); const [isOpen, open, close] = useToggle(false); const [target, setTarget] = useState(null); - const blackoutDateRange = useSelector(selectBlackoutDate); + const isPostingEnabled = useSelector(selectIsPostingEnabled); const actions = useActions(contentType, id); const handleActions = useCallback((action) => { @@ -41,12 +41,12 @@ const ActionsDropdown = ({ } }, [actionHandlers]); - // Find and remove edit action if in blackout date range. + // Find and remove edit action if in Posting is disabled. useMemo(() => { - if (inBlackoutDateRange(blackoutDateRange)) { + if (!isPostingEnabled) { actions.splice(actions.findIndex(action => action.id === 'edit'), 1); } - }, [actions, blackoutDateRange]); + }, [actions, isPostingEnabled]); const onClickButton = useCallback(() => { setTarget(buttonRef.current); diff --git a/src/discussions/common/ActionsDropdown.test.jsx b/src/discussions/common/ActionsDropdown.test.jsx index dd4711d7..570705b2 100644 --- a/src/discussions/common/ActionsDropdown.test.jsx +++ b/src/discussions/common/ActionsDropdown.test.jsx @@ -13,6 +13,8 @@ import { AppProvider } from '@edx/frontend-platform/react'; import { ContentActions } from '../../data/constants'; import { initializeStore } from '../../store'; import { executeThunk } from '../../test-utils'; +import { getCourseConfigApiUrl } from '../data/api'; +import { fetchCourseConfig } from '../data/thunks'; import messages from '../messages'; import { getCommentsApiUrl } from '../post-comments/data/api'; import { addComment, fetchThreadComments } from '../post-comments/data/thunks'; @@ -29,6 +31,7 @@ let store; let axiosMock; const commentsApiUrl = getCommentsApiUrl(); const threadsApiUrl = getThreadsApiUrl(); +const courseId = 'course-v1:edX+TestX+Test_Course'; const discussionThreadId = 'thread-1'; const questionThreadId = 'thread-2'; const commentContent = 'This is a comment for thread-1'; @@ -170,7 +173,7 @@ const findOpenActionsDropdownButton = async () => ( ); describe('ActionsDropdown', () => { - beforeEach(() => { + beforeEach(async () => { initializeMockApp({ authenticatedUser: { userId: 3, @@ -182,6 +185,11 @@ describe('ActionsDropdown', () => { store = initializeStore(); Factory.resetAll(); axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + + axiosMock.onGet(`${getCourseConfigApiUrl()}${courseId}/`) + .reply(200, { isPostingEnabled: true }); + + await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState); }); it.each(Object.values(buildTestContent()))('can open drop down if enabled', async (commentOrPost) => { diff --git a/src/discussions/common/HoverCard.jsx b/src/discussions/common/HoverCard.jsx index 0cd24746..05c51cbe 100644 --- a/src/discussions/common/HoverCard.jsx +++ b/src/discussions/common/HoverCard.jsx @@ -11,7 +11,7 @@ import { import { StarFilled, StarOutline, ThumbUpFilled, ThumbUpOutline, } from '../../components/icons'; -import { useUserCanAddThreadInBlackoutDate } from '../data/hooks'; +import { useUserPostingEnabled } from '../data/hooks'; import { PostCommentsContext } from '../post-comments/postCommentsContext'; import ActionsDropdown from './ActionsDropdown'; import { DiscussionContext } from './context'; @@ -31,7 +31,7 @@ const HoverCard = ({ const intl = useIntl(); const { enableInContextSidebar } = useContext(DiscussionContext); const { isClosed } = useContext(PostCommentsContext); - const userCanAddThreadInBlackoutDate = useUserCanAddThreadInBlackoutDate(); + const isUserPrivilagedInPostingRestriction = useUserPostingEnabled(); return (
- {userCanAddThreadInBlackoutDate && ( + {isUserPrivilagedInPostingRestriction && (
{isFeedbackBannerVisible && } - +
{provider === DiscussionProvider.LEGACY && ( )}> diff --git a/src/discussions/discussions-home/BlackoutInformationBanner.jsx b/src/discussions/discussions-home/DiscussionsRestrictionBanner.jsx similarity index 53% rename from src/discussions/discussions-home/BlackoutInformationBanner.jsx rename to src/discussions/discussions-home/DiscussionsRestrictionBanner.jsx index 301a460b..e948036b 100644 --- a/src/discussions/discussions-home/BlackoutInformationBanner.jsx +++ b/src/discussions/discussions-home/DiscussionsRestrictionBanner.jsx @@ -1,23 +1,20 @@ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { useSelector } from 'react-redux'; import { useIntl } from '@edx/frontend-platform/i18n'; import { PageBanner } from '@edx/paragon'; -import { selectBlackoutDate } from '../data/selectors'; +import { RequestStatus } from '../../data/constants'; +import { selectconfigLoadingStatus, selectIsPostingEnabled } from '../data/selectors'; import messages from '../messages'; -import { inBlackoutDateRange } from '../utils'; -const BlackoutInformationBanner = () => { +const DiscussionsRestrictionBanner = () => { const intl = useIntl(); - const blackoutDate = useSelector(selectBlackoutDate); + const isPostingEnabled = useSelector(selectIsPostingEnabled); + const configLoadingStatus = useSelector(selectconfigLoadingStatus); const [showBanner, setShowBanner] = useState(true); - const isDiscussionsBlackout = useMemo(() => ( - inBlackoutDateRange(blackoutDate) - ), [blackoutDate]); - const handleDismiss = useCallback(() => { setShowBanner(false); }, []); @@ -25,7 +22,7 @@ const BlackoutInformationBanner = () => { return ( @@ -36,4 +33,4 @@ const BlackoutInformationBanner = () => { ); }; -export default BlackoutInformationBanner; +export default DiscussionsRestrictionBanner; diff --git a/src/discussions/discussions-home/BlackoutInformationBanner.test.jsx b/src/discussions/discussions-home/DiscussionsRestrictionBanner.test.jsx similarity index 72% rename from src/discussions/discussions-home/BlackoutInformationBanner.test.jsx rename to src/discussions/discussions-home/DiscussionsRestrictionBanner.test.jsx index 58a220be..f308e521 100644 --- a/src/discussions/discussions-home/BlackoutInformationBanner.test.jsx +++ b/src/discussions/discussions-home/DiscussionsRestrictionBanner.test.jsx @@ -8,7 +8,7 @@ import { initializeStore } from '../../store'; import { DiscussionContext } from '../common/context'; import { fetchConfigSuccess } from '../data/slices'; import messages from '../messages'; -import BlackoutInformationBanner from './BlackoutInformationBanner'; +import DiscussionsRestrictionBanner from './DiscussionsRestrictionBanner'; let store; let container; @@ -20,13 +20,13 @@ activeEndDate.setDate(activeEndDate.getDate() + 2); activeStartDate = activeStartDate.toISOString(); activeEndDate = activeEndDate.toISOString(); -const getConfigData = (blackouts = []) => ({ +const getConfigData = (isPostingEnabled) => ({ id: 'course-v1:edX+DemoX+Demo_Course', userRoles: ['Admin', 'Student'], hasModerationPrivileges: false, isGroupTa: false, isUserAdmin: false, - blackouts, + isPostingEnabled, }); function renderComponent() { @@ -34,7 +34,7 @@ function renderComponent() { - + , @@ -43,7 +43,7 @@ function renderComponent() { return container; } -describe('Blackout Information Banner', () => { +describe('Discussions Restriction Banner', () => { beforeEach(async () => { initializeMockApp({ authenticatedUser: { @@ -56,13 +56,11 @@ describe('Blackout Information Banner', () => { }); test.each([ - { blackouts: [], visibility: false }, - { blackouts: ['2021-12-31T10:15', '2021-12-31T10:20'], visibility: false }, - { blackouts: [{ start: activeStartDate, end: activeEndDate }], visibility: true }, - { blackouts: [{ start: activeEndDate, end: activeEndDate }], visibility: false }, - ])('Test Blackout Banner is visible on app load if blackout date is active', async ({ blackouts, visibility }) => { + { isPostingEnabled: false, visibility: true }, + { isPostingEnabled: true, visibility: false }, + ])('Test Discussions Restriction is visible on app load if posting is disabled', async ({ isPostingEnabled, visibility }) => { store = initializeStore(); - await store.dispatch(fetchConfigSuccess(getConfigData(blackouts))); + await store.dispatch(fetchConfigSuccess(getConfigData(isPostingEnabled))); renderComponent(); if (visibility) { const element = await screen.findByRole('alert'); diff --git a/src/discussions/in-context-topics/TopicPostsView.test.jsx b/src/discussions/in-context-topics/TopicPostsView.test.jsx index bb76a2f7..f8abeb2d 100644 --- a/src/discussions/in-context-topics/TopicPostsView.test.jsx +++ b/src/discussions/in-context-topics/TopicPostsView.test.jsx @@ -92,7 +92,6 @@ describe('InContext Topic Posts View', () => { enableInContext: true, provider: 'openedx', hasModerationPrivileges: true, - blackouts: [], }, }); Factory.resetAll(); diff --git a/src/discussions/messages.js b/src/discussions/messages.js index c1cc14ed..a23276f2 100644 --- a/src/discussions/messages.js +++ b/src/discussions/messages.js @@ -185,7 +185,7 @@ const messages = defineMessages({ }, blackoutDiscussionInformation: { id: 'discussion.blackoutBanner.information', - defaultMessage: 'Posting in discussions is temporarily disabled by the course team', + defaultMessage: 'Posting in discussions is disabled by the course team', description: 'Informative text when discussion posting is disabled', }, imageWarningMessage: { diff --git a/src/discussions/post-comments/PostCommentsView.test.jsx b/src/discussions/post-comments/PostCommentsView.test.jsx index ea97dc02..50b6e86c 100644 --- a/src/discussions/post-comments/PostCommentsView.test.jsx +++ b/src/discussions/post-comments/PostCommentsView.test.jsx @@ -109,6 +109,24 @@ async function getThreadAPIResponse(attr = null) { await executeThunk(fetchThread(discussionPostId), store.dispatch, store.getState); } +async function setupCourseConfig(reasonCodesEnabled = true) { + axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`).reply(200, { + has_moderation_privileges: true, + isPostingEnabled: true, + reason_codes_enabled: reasonCodesEnabled, + editReasons: [ + { code: 'reason-1', label: 'reason 1' }, + { code: 'reason-2', label: 'reason 2' }, + ], + postCloseReasons: [ + { code: 'reason-1', label: 'reason 1' }, + { code: 'reason-2', label: 'reason 2' }, + ], + }); + axiosMock.onGet(`${courseConfigApiUrl}${courseId}/settings`).reply(200, {}); + await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState); +} + function renderComponent(postId) { const wrapper = render( @@ -218,6 +236,7 @@ describe('ThreadView', () => { }); it('should show and hide the editor', async () => { + await setupCourseConfig(); await waitFor(() => renderComponent(discussionPostId)); const post = screen.getByTestId('post-thread-1'); @@ -232,6 +251,7 @@ describe('ThreadView', () => { }); it('should allow posting a response', async () => { + await setupCourseConfig(); await waitFor(() => renderComponent(discussionPostId)); const post = await screen.findByTestId('post-thread-1'); @@ -246,6 +266,7 @@ describe('ThreadView', () => { }); it('should not allow posting a response on a closed post', async () => { + await setupCourseConfig(); renderComponent(closedPostId); const post = screen.getByTestId('post-thread-2'); const hoverCard = within(post).getByTestId('hover-card-thread-2'); @@ -254,6 +275,7 @@ describe('ThreadView', () => { }); it('should allow posting a comment', async () => { + await setupCourseConfig(); await waitFor(() => renderComponent(discussionPostId)); const comment = await waitFor(() => screen.findByTestId('comment-comment-1')); @@ -267,6 +289,7 @@ describe('ThreadView', () => { }); it('should allow editing an existing comment', async () => { + await setupCourseConfig(); await waitFor(() => renderComponent(discussionPostId)); const comment = await waitFor(() => screen.findByTestId('comment-comment-1')); @@ -281,23 +304,6 @@ describe('ThreadView', () => { }); }); - async function setupCourseConfig(reasonCodesEnabled = true) { - axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`).reply(200, { - has_moderation_privileges: true, - reason_codes_enabled: reasonCodesEnabled, - editReasons: [ - { code: 'reason-1', label: 'reason 1' }, - { code: 'reason-2', label: 'reason 2' }, - ], - postCloseReasons: [ - { code: 'reason-1', label: 'reason 1' }, - { code: 'reason-2', label: 'reason 2' }, - ], - }); - axiosMock.onGet(`${courseConfigApiUrl}${courseId}/settings`).reply(200, {}); - await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState); - } - it('should show reason codes when closing a post', async () => { await setupCourseConfig(); await waitFor(() => renderComponent(discussionPostId)); diff --git a/src/discussions/post-comments/comments/CommentsView.jsx b/src/discussions/post-comments/comments/CommentsView.jsx index 6011826d..f3e6e27e 100644 --- a/src/discussions/post-comments/comments/CommentsView.jsx +++ b/src/discussions/post-comments/comments/CommentsView.jsx @@ -5,7 +5,7 @@ import { useIntl } from '@edx/frontend-platform/i18n'; import { Button, Spinner } from '@edx/paragon'; import { EndorsementStatus } from '../../../data/constants'; -import { useUserCanAddThreadInBlackoutDate } from '../../data/hooks'; +import { useUserPostingEnabled } from '../../data/hooks'; import { isLastElementOfList } from '../../utils'; import { usePostComments } from '../data/hooks'; import messages from '../messages'; @@ -16,7 +16,8 @@ const CommentsView = ({ endorsed }) => { const intl = useIntl(); const [addingResponse, setAddingResponse] = useState(false); const { isClosed } = useContext(PostCommentsContext); - const userCanAddThreadInBlackoutDate = useUserCanAddThreadInBlackoutDate(); + const isUserPrivilagedInPostingRestriction = useUserPostingEnabled(); + const { endorsedCommentsIds, unEndorsedCommentsIds, @@ -89,7 +90,7 @@ const CommentsView = ({ endorsed }) => { {handleDefinition(messages.responseCount, unEndorsedCommentsIds.length)} {unEndorsedCommentsIds.length === 0 &&
} {handleComments(unEndorsedCommentsIds, false)} - {(userCanAddThreadInBlackoutDate && !!unEndorsedCommentsIds.length && !isClosed) && ( + {(isUserPrivilagedInPostingRestriction && !!unEndorsedCommentsIds.length && !isClosed) && (
{!addingResponse && (
) : ( - !isClosed && userCanAddThreadInBlackoutDate && (inlineReplies.length >= 5) && ( + !isClosed && isUserPrivilagedInPostingRestriction && (inlineReplies.length >= 5) && (