From 45c596b7706d4d9f8519c42bd9a76fdb0fac8438 Mon Sep 17 00:00:00 2001
From: Awais Ansari <79941147+awais-ansari@users.noreply.github.com>
Date: Wed, 24 Aug 2022 12:31:31 +0500
Subject: [PATCH] fix: allow actions according to user role and privileges
(#260)
* fix: allow actions according to user role and privileges
* test: fix failed test case for has_moderation_privileges change
---
.../comments/CommentsView.test.jsx | 2 +-
.../comments/comment/CommentEditor.jsx | 11 ++++++---
src/discussions/common/AlertBanner.jsx | 14 +++++++----
src/discussions/common/AlertBanner.test.jsx | 2 +-
.../common/EndorsedAlertBanner.test.jsx | 2 +-
.../data/__factories__/config.factory.js | 4 ++--
src/discussions/data/hooks.js | 18 ++++++++++-----
src/discussions/data/selectors.js | 4 +++-
src/discussions/data/slices.js | 3 ++-
src/discussions/data/thunks.js | 14 +++++++++--
.../discussions-home/DiscussionSidebar.jsx | 23 ++++++++++++-------
.../learners/learner/LearnerFilterBar.jsx | 7 +++---
.../learners/learner/LearnerFooter.jsx | 9 +++++++-
src/discussions/posts/PostsList.jsx | 6 ++---
src/discussions/posts/PostsView.test.jsx | 2 +-
.../posts/post-editor/PostEditor.jsx | 15 +++++++-----
.../posts/post-editor/PostEditor.test.jsx | 18 ++++++++++-----
.../posts/post-filter-bar/PostFilterBar.jsx | 14 +++++------
src/discussions/posts/post/PostFooter.jsx | 6 +++--
.../posts/post/PostFooter.test.jsx | 6 ++++-
src/discussions/posts/post/PostLink.jsx | 7 +++++-
src/discussions/posts/post/PostLink.test.jsx | 10 +++++++-
22 files changed, 134 insertions(+), 63 deletions(-)
diff --git a/src/discussions/comments/CommentsView.test.jsx b/src/discussions/comments/CommentsView.test.jsx
index e5a9c423..52c01a81 100644
--- a/src/discussions/comments/CommentsView.test.jsx
+++ b/src/discussions/comments/CommentsView.test.jsx
@@ -257,7 +257,7 @@ describe('CommentsView', () => {
async function setupCourseConfig(reasonCodesEnabled = true) {
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`).reply(200, {
- user_is_privileged: true,
+ has_moderation_privileges: true,
reason_codes_enabled: reasonCodesEnabled,
editReasons: [
{ code: 'reason-1', label: 'reason 1' },
diff --git a/src/discussions/comments/comment/CommentEditor.jsx b/src/discussions/comments/comment/CommentEditor.jsx
index 3708ef0a..0fec7c8a 100644
--- a/src/discussions/comments/comment/CommentEditor.jsx
+++ b/src/discussions/comments/comment/CommentEditor.jsx
@@ -13,7 +13,11 @@ import { TinyMCEEditor } from '../../../components';
import FormikErrorFeedback from '../../../components/FormikErrorFeedback';
import PostPreviewPane from '../../../components/PostPreviewPane';
import { useDispatchWithState } from '../../../data/hooks';
-import { selectModerationSettings, selectUserIsPrivileged } from '../../data/selectors';
+import {
+ selectModerationSettings,
+ selectUserHasModerationPrivileges,
+ selectUserIsGroupTa,
+} from '../../data/selectors';
import { formikCompatibleHandler, isFormikFieldInvalid } from '../../utils';
import { addComment, editComment } from '../data/thunks';
import messages from '../messages';
@@ -26,11 +30,12 @@ function CommentEditor({
}) {
const editorRef = useRef(null);
const { authenticatedUser } = useContext(AppContext);
- const userIsPrivileged = useSelector(selectUserIsPrivileged);
+ const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
+ const userIsGroupTa = useSelector(selectUserIsGroupTa);
const { reasonCodesEnabled, editReasons } = useSelector(selectModerationSettings);
const [submitting, dispatch] = useDispatchWithState();
- const canDisplayEditReason = (reasonCodesEnabled && userIsPrivileged
+ const canDisplayEditReason = (reasonCodesEnabled && (userHasModerationPrivileges || userIsGroupTa)
&& edit && comment.author !== authenticatedUser.username
);
diff --git a/src/discussions/common/AlertBanner.jsx b/src/discussions/common/AlertBanner.jsx
index 27fb084b..90987694 100644
--- a/src/discussions/common/AlertBanner.jsx
+++ b/src/discussions/common/AlertBanner.jsx
@@ -10,7 +10,7 @@ import { Error } from '@edx/paragon/icons';
import { commentShape } from '../comments/comment/proptypes';
import messages from '../comments/messages';
-import { selectModerationSettings, selectUserIsPrivileged } from '../data/selectors';
+import { selectModerationSettings, selectUserHasModerationPrivileges, selectUserIsGroupTa } from '../data/selectors';
import { postShape } from '../posts/post/proptypes';
import AuthorLabel from './AuthorLabel';
@@ -18,18 +18,22 @@ function AlertBanner({
intl,
content,
}) {
- const userIsPrivileged = useSelector(selectUserIsPrivileged);
+ const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
+ const userIsGroupTa = useSelector(selectUserIsGroupTa);
const { reasonCodesEnabled } = useSelector(selectModerationSettings);
const userIsContentAuthor = getAuthenticatedUser().username === content.author;
+ const canSeeLastEditOrClosedAlert = (userHasModerationPrivileges || userIsContentAuthor || userIsGroupTa);
+ const isReportedByCurrentUser = getAuthenticatedUser().username === content?.abuseFlaggedBy;
+ const canSeeReportedBanner = (userHasModerationPrivileges || userIsGroupTa || isReportedByCurrentUser);
return (
<>
- {content.abuseFlagged && (
+ {content.abuseFlagged && canSeeReportedBanner && (
{intl.formatMessage(messages.abuseFlaggedMessage)}
)}
- {reasonCodesEnabled && (userIsPrivileged || userIsContentAuthor) && (
+ {reasonCodesEnabled && canSeeLastEditOrClosedAlert && (
<>
{content.lastEdit?.reason && (
@@ -50,7 +54,7 @@ function AlertBanner({
- {intl.formatMessage(messages.reason)}: {content.closeReason}
+ {content.closeReason && (`${intl.formatMessage(messages.reason)}: ${content.closeReason}`)}
)}
diff --git a/src/discussions/common/AlertBanner.test.jsx b/src/discussions/common/AlertBanner.test.jsx
index e6f888a8..388f75e9 100644
--- a/src/discussions/common/AlertBanner.test.jsx
+++ b/src/discussions/common/AlertBanner.test.jsx
@@ -82,7 +82,7 @@ describe.each([
});
store = initializeStore({
config: {
- userIsPrivileged: true,
+ hasModerationPrivileges: true,
reasonCodesEnabled: true,
},
});
diff --git a/src/discussions/common/EndorsedAlertBanner.test.jsx b/src/discussions/common/EndorsedAlertBanner.test.jsx
index bfc9de8a..b9ab9b5b 100644
--- a/src/discussions/common/EndorsedAlertBanner.test.jsx
+++ b/src/discussions/common/EndorsedAlertBanner.test.jsx
@@ -76,7 +76,7 @@ describe.each([
});
store = initializeStore({
config: {
- userIsPrivileged: true,
+ hasModerationPrivileges: true,
reasonCodesEnabled: true,
},
});
diff --git a/src/discussions/data/__factories__/config.factory.js b/src/discussions/data/__factories__/config.factory.js
index 7d3118f9..768a1ac8 100644
--- a/src/discussions/data/__factories__/config.factory.js
+++ b/src/discussions/data/__factories__/config.factory.js
@@ -4,6 +4,6 @@ Factory.define('config')
.attrs({
allow_anonymous: false,
allow_anonymous_to_peers: false,
- user_is_privileged: false,
+ has_moderation_privileges: false,
})
- .attr('user_roles', ['user_is_privileged'], (userIsPrivileged) => (userIsPrivileged ? ['Student', 'Moderator'] : ['Student']));
+ .attr('user_roles', ['has_moderation_privileges'], (hasModerationPrivileges) => (hasModerationPrivileges ? ['Student', 'Moderator'] : ['Student']));
diff --git a/src/discussions/data/hooks.js b/src/discussions/data/hooks.js
index c2b6c8cc..04f1a63c 100644
--- a/src/discussions/data/hooks.js
+++ b/src/discussions/data/hooks.js
@@ -15,7 +15,11 @@ import { selectTopics } from '../topics/data/selectors';
import { fetchCourseTopics } from '../topics/data/thunks';
import { discussionsPath, postMessageToParent } from '../utils';
import {
- selectAreThreadsFiltered, selectModerationSettings, selectPostThreadCount, selectUserIsPrivileged,
+ selectAreThreadsFiltered,
+ selectModerationSettings,
+ selectPostThreadCount,
+ selectUserHasModerationPrivileges,
+ selectUserIsGroupTa,
} from './selectors';
import { fetchCourseConfig } from './thunks';
@@ -150,14 +154,16 @@ export function useContainerSizeForParent(refContainer) {
}
export const useAlertBannerVisible = (content) => {
- const userIsPrivileged = useSelector(selectUserIsPrivileged);
+ const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
+ const userIsGroupTa = useSelector(selectUserIsGroupTa);
const { reasonCodesEnabled } = useSelector(selectModerationSettings);
const userIsContentAuthor = getAuthenticatedUser().username === content.author;
+ const canSeeLastEditOrClosedAlert = (userHasModerationPrivileges || userIsContentAuthor || userIsGroupTa);
+ const isReportedByCurrentUser = getAuthenticatedUser().username === content?.abuseFlaggedBy;
+ const canSeeReportedBanner = (userHasModerationPrivileges || userIsGroupTa || isReportedByCurrentUser);
return (
- (reasonCodesEnabled
- && (userIsPrivileged || userIsContentAuthor)
- && (content.lastEdit?.reason || content.closed)
- ) || content.abuseFlagged
+ (reasonCodesEnabled && canSeeLastEditOrClosedAlert && (content.lastEdit?.reason || content.closed))
+ || (content.abuseFlagged && canSeeReportedBanner)
);
};
diff --git a/src/discussions/data/selectors.js b/src/discussions/data/selectors.js
index a767a7b3..72378de6 100644
--- a/src/discussions/data/selectors.js
+++ b/src/discussions/data/selectors.js
@@ -6,10 +6,12 @@ export const selectAnonymousPostingConfig = state => ({
allowAnonymousToPeers: state.config.allowAnonymousToPeers,
});
-export const selectUserIsPrivileged = state => state.config.userIsPrivileged;
+export const selectUserHasModerationPrivileges = state => state.config.hasModerationPrivileges;
export const selectUserIsStaff = state => state.config.isUserAdmin;
+export const selectUserIsGroupTa = state => state.config.isGroupTa;
+
export const selectconfigLoadingStatus = state => state.config.status;
export const selectLearnersTabEnabled = state => state.config.learnersTabEnabled;
diff --git a/src/discussions/data/slices.js b/src/discussions/data/slices.js
index 210c8911..37ca4bad 100644
--- a/src/discussions/data/slices.js
+++ b/src/discussions/data/slices.js
@@ -11,7 +11,8 @@ const configSlice = createSlice({
allowAnonymous: false,
allowAnonymousToPeers: false,
userRoles: [],
- userIsPrivileged: false,
+ hasModerationPrivileges: false,
+ isGroupTa: false,
isUserAdmin: false,
learnersTabEnabled: false,
settings: {
diff --git a/src/discussions/data/thunks.js b/src/discussions/data/thunks.js
index e0ff9e69..06164089 100644
--- a/src/discussions/data/thunks.js
+++ b/src/discussions/data/thunks.js
@@ -2,8 +2,12 @@
import { camelCaseObject } from '@edx/frontend-platform';
import { logError } from '@edx/frontend-platform/logging';
-import { LearnersOrdering } from '../../data/constants';
+import {
+ LearnersOrdering,
+ PostsStatusFilter,
+} from '../../data/constants';
import { setSortedBy } from '../learners/data';
+import { setStatusFilter } from '../posts/data';
import { getHttpErrorStatus } from '../utils';
import { getDiscussionsConfig, getDiscussionsSettings } from './api';
import {
@@ -19,17 +23,23 @@ export function fetchCourseConfig(courseId) {
return async (dispatch) => {
try {
let learnerSort = LearnersOrdering.BY_LAST_ACTIVITY;
+ let postsFilterStatus = PostsStatusFilter.ALL;
dispatch(fetchConfigRequest());
const config = await getDiscussionsConfig(courseId);
- if (config.is_user_admin || config.user_is_privileged) {
+ if (config.has_moderation_privileges) {
const settings = await getDiscussionsSettings(courseId);
Object.assign(config, { settings });
+ }
+
+ if ((config.has_moderation_privileges || config.is_group_ta)) {
learnerSort = LearnersOrdering.BY_FLAG;
+ postsFilterStatus = PostsStatusFilter.REPORTED;
}
dispatch(fetchConfigSuccess(camelCaseObject(config)));
dispatch(setSortedBy(learnerSort));
+ dispatch(setStatusFilter(postsFilterStatus));
} catch (error) {
if (getHttpErrorStatus(error) === 403) {
dispatch(fetchConfigDenied());
diff --git a/src/discussions/discussions-home/DiscussionSidebar.jsx b/src/discussions/discussions-home/DiscussionSidebar.jsx
index 29a3df6c..3689c47c 100644
--- a/src/discussions/discussions-home/DiscussionSidebar.jsx
+++ b/src/discussions/discussions-home/DiscussionSidebar.jsx
@@ -2,12 +2,14 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
+import { useSelector } from 'react-redux';
import {
Redirect, Route, Switch, useLocation,
} from 'react-router';
-import { Routes } from '../../data/constants';
+import { RequestStatus, Routes } from '../../data/constants';
import { useIsOnDesktop, useIsOnXLDesktop } from '../data/hooks';
+import { selectconfigLoadingStatus, selectUserHasModerationPrivileges, selectUserIsGroupTa } from '../data/selectors';
import { LearnerPostsView, LearnersView } from '../learners';
import { PostsView } from '../posts';
import { TopicsView } from '../topics';
@@ -16,6 +18,9 @@ export default function DiscussionSidebar({ displaySidebar }) {
const location = useLocation();
const isOnDesktop = useIsOnDesktop();
const isOnXLDesktop = useIsOnXLDesktop();
+ const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
+ const userIsGroupTa = useSelector(selectUserIsGroupTa);
+ const configStatus = useSelector(selectconfigLoadingStatus);
return (
-
+ {configStatus === RequestStatus.SUCCESSFUL && (
+
+ )}
);
diff --git a/src/discussions/learners/learner/LearnerFilterBar.jsx b/src/discussions/learners/learner/LearnerFilterBar.jsx
index 49d4522b..856bac9a 100644
--- a/src/discussions/learners/learner/LearnerFilterBar.jsx
+++ b/src/discussions/learners/learner/LearnerFilterBar.jsx
@@ -9,7 +9,7 @@ import { Collapsible, Form, Icon } from '@edx/paragon';
import { Check, Tune } from '@edx/paragon/icons';
import { LearnersOrdering } from '../../../data/constants';
-import { selectUserIsPrivileged } from '../../data/selectors';
+import { selectUserHasModerationPrivileges, selectUserIsGroupTa } from '../../data/selectors';
import { setSortedBy } from '../data';
import { selectLearnerSorting } from '../data/selectors';
import messages from '../messages';
@@ -48,7 +48,8 @@ function LearnerFilterBar({
intl,
}) {
const dispatch = useDispatch();
- const userIsPrivileged = useSelector(selectUserIsPrivileged);
+ const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
+ const userIsGroupTa = useSelector(selectUserIsGroupTa);
const currentSorting = useSelector(selectLearnerSorting());
const [isOpen, setOpen] = useState(false);
@@ -94,7 +95,7 @@ function LearnerFilterBar({
value={LearnersOrdering.BY_LAST_ACTIVITY}
selected={currentSorting}
/>
- {userIsPrivileged && (
+ {(userHasModerationPrivileges || userIsGroupTa) && (
@@ -24,7 +31,7 @@ function LearnerFooter({
{learner.replies + learner.responses}
- {Boolean(activeFlags || inactiveFlags) && (
+ {canSeeLearnerReportedStats && (
diff --git a/src/discussions/posts/PostsList.jsx b/src/discussions/posts/PostsList.jsx
index 056db78f..134557ae 100644
--- a/src/discussions/posts/PostsList.jsx
+++ b/src/discussions/posts/PostsList.jsx
@@ -10,7 +10,7 @@ import { Button, Spinner } from '@edx/paragon';
import { RequestStatus } from '../../data/constants';
import { DiscussionContext } from '../common/context';
-import { selectconfigLoadingStatus, selectUserIsPrivileged, selectUserIsStaff } from '../data/selectors';
+import { selectconfigLoadingStatus, selectUserHasModerationPrivileges, selectUserIsStaff } from '../data/selectors';
import messages from '../messages';
import {
selectThreadFilters, selectThreadNextPage, selectThreadSorting, threadsLoadingStatus,
@@ -31,7 +31,7 @@ function PostsList({ posts, topics, intl }) {
const filters = useSelector(selectThreadFilters());
const nextPage = useSelector(selectThreadNextPage());
const showOwnPosts = page === 'my-posts';
- const userIsPrivileged = useSelector(selectUserIsPrivileged);
+ const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
const userIsStaff = useSelector(selectUserIsStaff);
const configStatus = useSelector(selectconfigLoadingStatus);
@@ -42,7 +42,7 @@ function PostsList({ posts, topics, intl }) {
filters,
page: pageNum,
author: showOwnPosts ? authenticatedUser.username : null,
- countFlagged: userIsPrivileged || userIsStaff,
+ countFlagged: userHasModerationPrivileges || userIsStaff,
}))
);
diff --git a/src/discussions/posts/PostsView.test.jsx b/src/discussions/posts/PostsView.test.jsx
index 9962e824..0e46726f 100644
--- a/src/discussions/posts/PostsView.test.jsx
+++ b/src/discussions/posts/PostsView.test.jsx
@@ -88,7 +88,7 @@ describe('PostsView', () => {
store = initializeStore({
blocks: { blocks: { 'test-usage-key': { topics: ['some-topic-2', 'some-topic-0'] } } },
- config: { userIsPrivileged: true },
+ config: { hasModerationPrivileges: true },
});
store.dispatch(fetchConfigSuccess({}));
Factory.resetAll();
diff --git a/src/discussions/posts/post-editor/PostEditor.jsx b/src/discussions/posts/post-editor/PostEditor.jsx
index 5910a555..fd78dd82 100644
--- a/src/discussions/posts/post-editor/PostEditor.jsx
+++ b/src/discussions/posts/post-editor/PostEditor.jsx
@@ -27,7 +27,8 @@ import {
selectAnonymousPostingConfig,
selectDivisionSettings,
selectModerationSettings,
- selectUserIsPrivileged,
+ selectUserHasModerationPrivileges,
+ selectUserIsGroupTa,
} from '../../data/selectors';
import { selectCoursewareTopics, selectNonCoursewareIds, selectNonCoursewareTopics } from '../../topics/data/selectors';
import {
@@ -96,13 +97,14 @@ function PostEditor({
const coursewareTopics = useSelector(selectCoursewareTopics);
const cohorts = useSelector(selectCourseCohorts);
const post = useSelector(selectThread(postId));
- const userIsPrivileged = useSelector(selectUserIsPrivileged);
+ const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
+ const userIsGroupTa = useSelector(selectUserIsGroupTa);
const settings = useSelector(selectDivisionSettings);
const { allowAnonymous, allowAnonymousToPeers } = useSelector(selectAnonymousPostingConfig);
const { reasonCodesEnabled, editReasons } = useSelector(selectModerationSettings);
const canDisplayEditReason = (reasonCodesEnabled && editExisting
- && userIsPrivileged && post.author !== authenticatedUser.username
+ && (userHasModerationPrivileges || userIsGroupTa) && post?.author !== authenticatedUser.username
);
const editReasonCodeValidation = canDisplayEditReason && {
@@ -112,13 +114,14 @@ function PostEditor({
const canSelectCohort = (tId) => {
// If the user isn't privileged, they can't edit the cohort.
// If the topic is being edited the cohort can't be changed.
- if (!userIsPrivileged || editExisting) {
+ if (!userHasModerationPrivileges) {
return false;
}
if (nonCoursewareIds.includes(tId)) {
return settings.dividedCourseWideDiscussions.includes(tId);
}
- return settings.alwaysDivideInlineDiscussions || settings.dividedInlineDiscussions.includes(tId);
+ const isCohorting = settings.alwaysDivideInlineDiscussions || settings.dividedInlineDiscussions.includes(tId);
+ return isCohorting;
};
const hideEditor = () => {
if (editExisting) {
@@ -166,7 +169,7 @@ function PostEditor({
};
useEffect(() => {
- if (userIsPrivileged && isEmpty(cohorts)) {
+ if (userHasModerationPrivileges && isEmpty(cohorts)) {
dispatch(fetchCourseCohorts(courseId));
}
if (editExisting) {
diff --git a/src/discussions/posts/post-editor/PostEditor.test.jsx b/src/discussions/posts/post-editor/PostEditor.test.jsx
index bc997171..43e04363 100644
--- a/src/discussions/posts/post-editor/PostEditor.test.jsx
+++ b/src/discussions/posts/post-editor/PostEditor.test.jsx
@@ -151,8 +151,7 @@ describe('PostEditor', () => {
config: {
provider: 'legacy',
userRoles: ['Student', 'Moderator'],
- userIsPrivileged: true,
- moderationSettings: {},
+ hasModerationPrivileges: true,
settings: {
dividedInlineDiscussions: dividedcw,
dividedCourseWideDiscussions: dividedncw,
@@ -252,7 +251,7 @@ describe('PostEditor', () => {
});
});
test('test unprivileged user', async () => {
- await setupData({ userIsPrivileged: false });
+ await setupData({ hasModerationPrivileges: false });
await renderComponent();
['ncw-topic 1', 'ncw-topic 2', 'category-1-topic 1', 'category-2-topic 1'].forEach((topicName) => {
act(() => {
@@ -271,9 +270,9 @@ describe('PostEditor', () => {
.toBeInTheDocument();
});
});
- test('edit existing post should not show cohort selector', async () => {
+ test('edit existing post should not show cohort selector to unprivileged users', async () => {
const threadId = 'thread-1';
- await setupData();
+ await setupData({ hasModerationPrivileges: false });
axiosMock.onGet(`${threadsApiUrl}${threadId}/`)
.reply(200, Factory.build('thread'));
await executeThunk(fetchThread(threadId), store.dispatch, store.getState);
@@ -315,10 +314,13 @@ describe('PostEditor', () => {
describe('Edit codes', () => {
const threadId = 'thread-1';
beforeEach(async () => {
+ const dividedncw = ['ncw-topic-2'];
+ const dividedcw = ['category-1-topic-2', 'category-2-topic-1', 'category-2-topic-2'];
+
store = initializeStore({
config: {
provider: 'legacy',
- userIsPrivileged: true,
+ hasModerationPrivileges: true,
reasonCodesEnabled: true,
editReasons: [
{
@@ -330,6 +332,10 @@ describe('PostEditor', () => {
label: 'Reason 2',
},
],
+ settings: {
+ dividedInlineDiscussions: dividedcw,
+ dividedCourseWideDiscussions: dividedncw,
+ },
},
});
await executeThunk(fetchCourseTopics(courseId), store.dispatch, store.getState);
diff --git a/src/discussions/posts/post-filter-bar/PostFilterBar.jsx b/src/discussions/posts/post-filter-bar/PostFilterBar.jsx
index 3ce4e52a..797d9169 100644
--- a/src/discussions/posts/post-filter-bar/PostFilterBar.jsx
+++ b/src/discussions/posts/post-filter-bar/PostFilterBar.jsx
@@ -18,7 +18,7 @@ import {
} from '../../../data/constants';
import { selectCourseCohorts } from '../../cohorts/data/selectors';
import { fetchCourseCohorts } from '../../cohorts/data/thunks';
-import { selectUserIsPrivileged, selectUserIsStaff } from '../../data/selectors';
+import { selectUserHasModerationPrivileges, selectUserIsGroupTa } from '../../data/selectors';
import {
setCohortFilter, setPostsTypeFilter, setSortedBy, setStatusFilter,
} from '../data';
@@ -61,12 +61,12 @@ function PostFilterBar({
}) {
const dispatch = useDispatch();
const { courseId } = useParams();
- const userIsPrivileged = useSelector(selectUserIsPrivileged);
+ const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
+ const userIsGroupTa = useSelector(selectUserIsGroupTa);
const currentSorting = useSelector(selectThreadSorting());
const currentFilters = useSelector(selectThreadFilters());
const { status } = useSelector(state => state.cohorts);
const cohorts = useSelector(selectCourseCohorts);
- const userIsStaff = useSelector(selectUserIsStaff);
const [isOpen, setOpen] = useState(false);
@@ -106,10 +106,10 @@ function PostFilterBar({
};
useEffect(() => {
- if (userIsPrivileged && isEmpty(cohorts)) {
+ if (userHasModerationPrivileges && isEmpty(cohorts)) {
dispatch(fetchCourseCohorts(courseId));
}
- }, [courseId, userIsPrivileged]);
+ }, [courseId, userHasModerationPrivileges]);
return (
- {(userIsPrivileged || userIsStaff) && (
+ {(userHasModerationPrivileges || userIsGroupTa) && (
- {userIsPrivileged && (
+ {userHasModerationPrivileges && (
<>
{status === RequestStatus.IN_PROGRESS ? (
diff --git a/src/discussions/posts/post/PostFooter.jsx b/src/discussions/posts/post/PostFooter.jsx
index 0b9b2095..a855222b 100644
--- a/src/discussions/posts/post/PostFooter.jsx
+++ b/src/discussions/posts/post/PostFooter.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { useDispatch } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
import * as timeago from 'timeago.js';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
@@ -20,6 +20,7 @@ import {
StarOutline,
} from '../../../components/icons';
import timeLocale from '../../common/time-locale';
+import { selectUserHasModerationPrivileges } from '../../data/selectors';
import { updateExistingThread } from '../data/thunks';
import LikeButton from './LikeButton';
import messages from './messages';
@@ -31,6 +32,7 @@ function PostFooter({
preview,
}) {
const dispatch = useDispatch();
+ const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
timeago.register('time-locale', timeLocale);
return (
@@ -77,7 +79,7 @@ function PostFooter({
)}
- {post.groupId && (
+ {post.groupId && userHasModerationPrivileges && (
<>
{
roles: [],
},
});
- store = initializeStore();
+ store = initializeStore({
+ config: {
+ hasModerationPrivileges: true,
+ },
+ });
});
it("shows 'x new' badge for new comments", () => {
diff --git a/src/discussions/posts/post/PostLink.jsx b/src/discussions/posts/post/PostLink.jsx
index 3474c179..882907b5 100644
--- a/src/discussions/posts/post/PostLink.jsx
+++ b/src/discussions/posts/post/PostLink.jsx
@@ -2,6 +2,7 @@ import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
+import { useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
@@ -11,6 +12,7 @@ import { PushPin } from '../../../components/icons';
import { AvatarOutlineAndLabelColors, Routes, ThreadType } from '../../../data/constants';
import AuthorLabel from '../../common/AuthorLabel';
import { DiscussionContext } from '../../common/context';
+import { selectUserHasModerationPrivileges, selectUserIsGroupTa } from '../../data/selectors';
import { discussionsPath, isPostPreviewAvailable } from '../../utils';
import messages from './messages';
import PostFooter from './PostFooter';
@@ -40,9 +42,12 @@ function PostLink({
category,
learnerUsername,
});
+ const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
+ const userIsGroupTa = useSelector(selectUserIsGroupTa);
const showAnsweredBadge = post.hasEndorsed && post.type === ThreadType.QUESTION;
const authorLabelColor = AvatarOutlineAndLabelColors[post.authorLabel];
const postReported = post.abuseFlagged || post.abuseFlaggedCount;
+ const canSeeReportedBadge = (userHasModerationPrivileges || userIsGroupTa);
return (
<>
@@ -86,7 +91,7 @@ function PostLink({
)}
- {postReported && (
+ {postReported && canSeeReportedBadge && (
{
},
});
store = initializeStore();
+ axiosMock = new MockAdapter(getAuthenticatedHttpClient());
+ axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`).reply(200, {
+ has_moderation_privileges: true,
+ });
+ axiosMock.onGet(`${courseConfigApiUrl}${courseId}/settings`).reply(200, {});
+ await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
});
- it('has reported text only when abuseFlagged is true', () => {
+ it('has reported text only when abuseFlagged is true', async () => {
renderComponent(mockPost);
expect(screen.queryByTestId('reported-post')).toBeFalsy();
@@ -90,7 +96,9 @@ describe('Post username', () => {
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`).reply(200, {
learners_tab_enabled: true,
+ has_moderation_privileges: true,
});
+ axiosMock.onGet(`${courseConfigApiUrl}${courseId}/settings`).reply(200, {});
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
});