feat: integrated backend discussions restriction with MFE (#529)

* feat: integrated backend discussions restriction with MFE

* test: fixes failed test cases

* refactor: fixed lint issues

---------

Co-authored-by: ayesha waris <73840786+ayeshoali@users.noreply.github.com>
Co-authored-by: SundasNoreen <sundas.noreen@arbisoft.com>
This commit is contained in:
ayesha waris
2023-06-06 14:18:53 +05:00
committed by GitHub
parent c8500a0c1e
commit 8c53a7a19e
18 changed files with 103 additions and 114 deletions

View File

@@ -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);

View File

@@ -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) => {

View File

@@ -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 (
<div
@@ -39,7 +39,7 @@ const HoverCard = ({
data-testid={`hover-card-${id}`}
id={`hover-card-${id}`}
>
{userCanAddThreadInBlackoutDate && (
{isUserPrivilagedInPostingRestriction && (
<div className="d-flex">
<Button
variant="tertiary"

View File

@@ -12,6 +12,8 @@ import { AppProvider } from '@edx/frontend-platform/react';
import { initializeStore } from '../../store';
import { executeThunk } from '../../test-utils';
import { getCourseConfigApiUrl } from '../data/api';
import { fetchCourseConfig } from '../data/thunks';
import DiscussionContent from '../discussions-home/DiscussionContent';
import { getCommentsApiUrl } from '../post-comments/data/api';
import { fetchCommentResponses, fetchThreadComments } from '../post-comments/data/thunks';
@@ -118,6 +120,7 @@ describe('HoverCard', () => {
username: 'abc123',
administrator: true,
roles: [],
isPostingEnabled: true,
},
});
@@ -142,7 +145,10 @@ describe('HoverCard', () => {
thread_id: threadId,
})];
});
axiosMock.onGet(`${getCourseConfigApiUrl()}${courseId}/`)
.reply(200, { isPostingEnabled: true });
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
await executeThunk(fetchThreads(courseId), store.dispatch, store.getState);
await mockAxiosReturnPagedComments();
await mockAxiosReturnPagedCommentsResponses();

View File

@@ -22,13 +22,13 @@ import tourCheckpoints from '../tours/constants';
import { selectTours } from '../tours/data/selectors';
import { updateTourShowStatus } from '../tours/data/thunks';
import messages from '../tours/messages';
import { discussionsPath, inBlackoutDateRange } from '../utils';
import { discussionsPath } from '../utils';
import {
selectAreThreadsFiltered,
selectBlackoutDate,
selectEnableInContext,
selectIsCourseAdmin,
selectIsCourseStaff,
selectIsPostingEnabled,
selectLearnersTabEnabled,
selectModerationSettings,
selectPostThreadCount,
@@ -192,17 +192,16 @@ export const useCurrentDiscussionTopic = () => {
return null;
};
export const useUserCanAddThreadInBlackoutDate = () => {
const blackoutDateRange = useSelector(selectBlackoutDate);
export const useUserPostingEnabled = () => {
const isPostingEnabled = useSelector(selectIsPostingEnabled);
const isUserAdmin = useSelector(selectUserIsStaff);
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
const isUserGroupTA = useSelector(selectUserIsGroupTa);
const isCourseAdmin = useSelector(selectIsCourseAdmin);
const isCourseStaff = useSelector(selectIsCourseStaff);
const isPrivileged = isUserAdmin || userHasModerationPrivileges || isUserGroupTA || isCourseAdmin || isCourseStaff;
const isInBlackoutDateRange = useMemo(() => inBlackoutDateRange(blackoutDateRange), [blackoutDateRange]);
return (!(isInBlackoutDateRange) || (isPrivileged));
return (isPostingEnabled || isPrivileged);
};
function camelToConstant(string) {

View File

@@ -11,7 +11,7 @@ import { initializeStore } from '../../store';
import { executeThunk } from '../../test-utils';
import { DiscussionContext } from '../common/context';
import { getCourseConfigApiUrl } from './api';
import { useCurrentDiscussionTopic, useUserCanAddThreadInBlackoutDate } from './hooks';
import { useCurrentDiscussionTopic, useUserPostingEnabled } from './hooks';
import { fetchCourseConfig } from './thunks';
const courseId = 'course-v1:edX+TestX+Test_Course';
@@ -19,8 +19,8 @@ const courseConfigApiUrl = getCourseConfigApiUrl();
let store;
let axiosMock;
const generateApiResponse = (blackouts = [], isCourseAdmin = false) => ({
blackouts,
const generateApiResponse = (isPostingEnabled, isCourseAdmin = false) => ({
isPostingEnabled,
hasModerationPrivileges: false,
isGroupTa: false,
isCourseAdmin,
@@ -102,12 +102,12 @@ describe('Hooks', () => {
});
});
describe('useUserCanAddThreadInBlackoutDate', () => {
describe('useUserPostingEnabled', () => {
const ComponentWithHook = () => {
const userCanAddThreadInBlackoutDate = useUserCanAddThreadInBlackoutDate();
const isUserPrivilagedInPostingRestriction = useUserPostingEnabled();
return (
<div>
{String(userCanAddThreadInBlackoutDate)}
{String(isUserPrivilagedInPostingRestriction)}
</div>
);
};
@@ -121,7 +121,7 @@ describe('Hooks', () => {
</IntlProvider>,
);
}
describe('User can add Thread in blackoutdates ', () => {
describe('User can add Thread in Posting Restrictions ', () => {
beforeEach(() => {
initializeMockApp({
authenticatedUser: {
@@ -136,37 +136,34 @@ describe('Hooks', () => {
store = initializeStore();
});
test('when blackoutdates are not active and Role is Learner return true', async () => {
test('when posting is not disabled and Role is Learner return true', async () => {
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`)
.reply(200, generateApiResponse([], false));
.reply(200, generateApiResponse(true, false));
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
const { queryByText } = renderComponent();
expect(queryByText('true')).toBeInTheDocument();
});
test('when blackoutdates are not active and Role is not Learner return true', async () => {
test('when posting is not disabled and Role is not Learner return true', async () => {
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`)
.reply(200, generateApiResponse([], true));
.reply(200, generateApiResponse(true, true));
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
const { queryByText } = renderComponent();
expect(queryByText('true')).toBeInTheDocument();
});
test('when blackoutdates are active and Role is Learner return false', async () => {
test('when posting is disabled and Role is Learner return false', async () => {
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`)
.reply(200, generateApiResponse([{
start: '2022-11-25T00:00:00Z',
end: '2050-11-25T23:59:00Z',
}], false));
.reply(200, generateApiResponse(false, false));
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
const { queryByText } = renderComponent();
expect(queryByText('false')).toBeInTheDocument();
});
test('when blackoutdates are active and Role is not Learner return true', async () => {
test('when posting is not disabled and Role is not Learner return true', async () => {
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`)
.reply(200, generateApiResponse([
{ start: '2022-11-25T00:00:00Z', end: '2050-11-25T23:59:00Z' }], true));
.reply(200, generateApiResponse(false, true));
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
const { queryByText } = renderComponent();
expect(queryByText('true')).toBeInTheDocument();
});

View File

@@ -20,8 +20,6 @@ export const selectUserRoles = state => state.config.userRoles;
export const selectDivisionSettings = state => state.config.settings;
export const selectBlackoutDate = state => state.config.blackouts;
export const selectGroupAtSubsection = state => state.config.groupAtSubsection;
export const selectIsCourseAdmin = state => state.config.isCourseAdmin;
@@ -30,6 +28,8 @@ export const selectIsCourseStaff = state => state.config.isCourseStaff;
export const selectEnableInContext = state => state.config.enableInContext;
export const selectIsPostingEnabled = state => state.config.isPostingEnabled;
export const selectModerationSettings = state => ({
postCloseReasons: state.config.postCloseReasons,
editReasons: state.config.editReasons,

View File

@@ -7,7 +7,6 @@ const configSlice = createSlice({
name: 'config',
initialState: {
status: RequestStatus.IN_PROGRESS,
blackouts: [],
allowAnonymous: false,
allowAnonymousToPeers: false,
userRoles: [],
@@ -18,6 +17,7 @@ const configSlice = createSlice({
isCourseStaff: false,
isUserAdmin: false,
learnersTabEnabled: false,
isPostingEnabled: false,
settings: {
divisionScheme: 'none',
alwaysDivideInlineDiscussions: false,

View File

@@ -30,7 +30,7 @@ const CourseTabsNavigation = lazy(() => import('../../components/NavigationBar/C
const LegacyBreadcrumbMenu = lazy(() => import('../navigation/breadcrumb-menu/LegacyBreadcrumbMenu'));
const NavigationBar = lazy(() => import('../navigation/navigation-bar/NavigationBar'));
const DiscussionsProductTour = lazy(() => import('../tours/DiscussionsProductTour'));
const BlackoutInformationBanner = lazy(() => import('./BlackoutInformationBanner'));
const DiscussionsRestrictionBanner = lazy(() => import('./DiscussionsRestrictionBanner'));
const DiscussionContent = lazy(() => import('./DiscussionContent'));
const DiscussionSidebar = lazy(() => import('./DiscussionSidebar'));
const InformationBanner = lazy(() => import('./InformationBanner'));
@@ -96,7 +96,7 @@ const DiscussionsHome = () => {
<PostActionsBar />
</div>
{isFeedbackBannerVisible && <InformationBanner />}
<BlackoutInformationBanner />
<DiscussionsRestrictionBanner />
</div>
{provider === DiscussionProvider.LEGACY && (
<Suspense fallback={(<Spinner />)}>

View File

@@ -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 (
<PageBanner
variant="accentB"
show={isDiscussionsBlackout && showBanner}
show={!isPostingEnabled && showBanner && configLoadingStatus === RequestStatus.SUCCESSFUL}
dismissible
onDismiss={handleDismiss}
>
@@ -36,4 +33,4 @@ const BlackoutInformationBanner = () => {
);
};
export default BlackoutInformationBanner;
export default DiscussionsRestrictionBanner;

View File

@@ -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() {
<IntlProvider locale="en">
<AppProvider store={store}>
<DiscussionContext.Provider value={{ courseId }}>
<BlackoutInformationBanner />
<DiscussionsRestrictionBanner />
</DiscussionContext.Provider>
</AppProvider>
</IntlProvider>,
@@ -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');

View File

@@ -92,7 +92,6 @@ describe('InContext Topic Posts View', () => {
enableInContext: true,
provider: 'openedx',
hasModerationPrivileges: true,
blackouts: [],
},
});
Factory.resetAll();

View File

@@ -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: {

View File

@@ -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(
<IntlProvider locale="en">
@@ -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));

View File

@@ -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 && <br />}
{handleComments(unEndorsedCommentsIds, false)}
{(userCanAddThreadInBlackoutDate && !!unEndorsedCommentsIds.length && !isClosed) && (
{(isUserPrivilagedInPostingRestriction && !!unEndorsedCommentsIds.length && !isClosed) && (
<div className="mx-4">
{!addingResponse && (
<Button

View File

@@ -15,7 +15,7 @@ import { AlertBanner, Confirmation, EndorsedAlertBanner } from '../../../common'
import { DiscussionContext } from '../../../common/context';
import HoverCard from '../../../common/HoverCard';
import { ContentTypes } from '../../../data/constants';
import { useUserCanAddThreadInBlackoutDate } from '../../../data/hooks';
import { useUserPostingEnabled } from '../../../data/hooks';
import { fetchThread } from '../../../posts/data/thunks';
import LikeButton from '../../../posts/post/LikeButton';
import { useActions } from '../../../utils';
@@ -61,7 +61,7 @@ const Comment = ({
const currentPage = useSelector(selectCommentCurrentPage(id));
const sortedOrder = useSelector(selectCommentSortOrder);
const actions = useActions(ContentTypes.COMMENT, id);
const userCanAddThreadInBlackoutDate = useUserCanAddThreadInBlackoutDate();
const isUserPrivilagedInPostingRestriction = useUserPostingEnabled();
useEffect(() => {
// If the comment has a parent comment, it won't have any children, so don't fetch them.
@@ -119,10 +119,10 @@ const Comment = ({
), [id, currentPage, sortedOrder]);
const handleAddCommentButton = useCallback(() => {
if (userCanAddThreadInBlackoutDate) {
if (isUserPrivilagedInPostingRestriction) {
setReplying(true);
}
}, [userCanAddThreadInBlackoutDate]);
}, [isUserPrivilagedInPostingRestriction]);
const handleCommentLike = useCallback(async () => {
await dispatch(editComment(id, { voted: !voted }));
@@ -265,7 +265,7 @@ const Comment = ({
/>
</div>
) : (
!isClosed && userCanAddThreadInBlackoutDate && (inlineReplies.length >= 5) && (
!isClosed && isUserPrivilagedInPostingRestriction && (inlineReplies.length >= 5) && (
<Button
className="d-flex flex-grow mt-2 font-size-14 font-style font-weight-500 text-primary-500"
variant="plain"

View File

@@ -12,7 +12,7 @@ import { Close } from '@edx/paragon/icons';
import Search from '../../../components/Search';
import { RequestStatus } from '../../../data/constants';
import { DiscussionContext } from '../../common/context';
import { useUserCanAddThreadInBlackoutDate } from '../../data/hooks';
import { useUserPostingEnabled } from '../../data/hooks';
import { selectconfigLoadingStatus, selectEnableInContext } from '../../data/selectors';
import { TopicSearchBar as IncontextSearch } from '../../in-context-topics/topic-search';
import { postMessageToParent } from '../../utils';
@@ -26,7 +26,7 @@ const PostActionsBar = () => {
const dispatch = useDispatch();
const loadingStatus = useSelector(selectconfigLoadingStatus);
const enableInContext = useSelector(selectEnableInContext);
const userCanAddThreadInBlackoutDate = useUserCanAddThreadInBlackoutDate();
const isUserPrivilagedInPostingRestriction = useUserPostingEnabled();
const { enableInContextSidebar, page } = useContext(DiscussionContext);
const handleCloseInContext = useCallback(() => {
@@ -49,7 +49,7 @@ const PostActionsBar = () => {
{intl.formatMessage(messages.title)}
</h4>
)}
{loadingStatus === RequestStatus.SUCCESSFUL && userCanAddThreadInBlackoutDate && (
{loadingStatus === RequestStatus.SUCCESSFUL && isUserPrivilagedInPostingRestriction && (
<>
{!enableInContextSidebar && <div className="border-right border-light-400 mx-3" />}
<Button

View File

@@ -267,28 +267,6 @@ export const filterPosts = (posts, filterBy) => uniqBy(posts, 'id').filter(
post => (filterBy.startsWith('un') ? !post[filterBy.slice(2)] : post[filterBy]),
);
/**
* Helper function to make a check if date is in given range
* @param {Date} date this date to be checked in range
* @param {Date} start start date
* @param {Date} end end date
*/
export function dateInDateRange(date, start, end) {
return date >= start && date <= end;
}
/**
* Helper function to make a check if date is in given range
* @param {array} blackoutDateRanges start date
* @return Boolean
*/
export function inBlackoutDateRange(blackoutDateRanges) {
const now = new Date();
return blackoutDateRanges.some(
(blackoutDateRange) => dateInDateRange(now, new Date(blackoutDateRange.start), new Date(blackoutDateRange.end)),
);
}
export function handleKeyDown(event) {
const { key } = event;
if (key !== 'ArrowDown' && key !== 'ArrowUp') { return; }