feat: learners area post summary will show reported if any comment is reported (#325)
Co-authored-by: adeel.tajamul <adeel.tajamul@arbisoft.com>
This commit is contained in:
committed by
GitHub
parent
d7d159dac2
commit
0a4f2a41c5
@@ -14,6 +14,7 @@ import { ArrowBack } from '@edx/paragon/icons';
|
|||||||
|
|
||||||
import { RequestStatus, Routes } from '../../data/constants';
|
import { RequestStatus, Routes } from '../../data/constants';
|
||||||
import { DiscussionContext } from '../common/context';
|
import { DiscussionContext } from '../common/context';
|
||||||
|
import { selectUserHasModerationPrivileges, selectUserIsStaff } from '../data/selectors';
|
||||||
import {
|
import {
|
||||||
selectAllThreads,
|
selectAllThreads,
|
||||||
selectThreadNextPage,
|
selectThreadNextPage,
|
||||||
@@ -34,14 +35,19 @@ function LearnerPostsView({ intl }) {
|
|||||||
const loadingStatus = useSelector(threadsLoadingStatus());
|
const loadingStatus = useSelector(threadsLoadingStatus());
|
||||||
const { courseId, learnerUsername: username } = useContext(DiscussionContext);
|
const { courseId, learnerUsername: username } = useContext(DiscussionContext);
|
||||||
const nextPage = useSelector(selectThreadNextPage());
|
const nextPage = useSelector(selectThreadNextPage());
|
||||||
|
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
|
||||||
|
const userIsStaff = useSelector(selectUserIsStaff);
|
||||||
|
const countFlagged = userHasModerationPrivileges || userIsStaff;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(fetchUserPosts(courseId, username));
|
dispatch(fetchUserPosts(courseId, { username, countFlagged }));
|
||||||
}, [courseId, username]);
|
}, [courseId, username]);
|
||||||
|
|
||||||
const loadMorePosts = () => (
|
const loadMorePosts = () => (
|
||||||
dispatch(fetchUserPosts(courseId, username, {
|
dispatch(fetchUserPosts(courseId, {
|
||||||
|
username,
|
||||||
page: nextPage,
|
page: nextPage,
|
||||||
|
countFlagged,
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
90
src/discussions/learners/LearnerPostsView.test.jsx
Normal file
90
src/discussions/learners/LearnerPostsView.test.jsx
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import MockAdapter from 'axios-mock-adapter';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
|
import { MemoryRouter, Route } from 'react-router';
|
||||||
|
import { Factory } from 'rosie';
|
||||||
|
|
||||||
|
import { initializeMockApp } from '@edx/frontend-platform';
|
||||||
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
|
import { AppProvider } from '@edx/frontend-platform/react';
|
||||||
|
|
||||||
|
import { initializeStore } from '../../store';
|
||||||
|
import { executeThunk } from '../../test-utils';
|
||||||
|
import { DiscussionContext } from '../common/context';
|
||||||
|
import { courseConfigApiUrl } from '../data/api';
|
||||||
|
import { fetchCourseConfig } from '../data/thunks';
|
||||||
|
import { coursesApiUrl } from './data/api';
|
||||||
|
import LearnerPostsView from './LearnerPostsView';
|
||||||
|
|
||||||
|
import './data/__factories__';
|
||||||
|
|
||||||
|
let store;
|
||||||
|
let axiosMock;
|
||||||
|
const courseId = 'course-v1:edX+TestX+Test_Course';
|
||||||
|
const username = 'abc123';
|
||||||
|
|
||||||
|
function renderComponent(path = `/${courseId}/learners/${username}/posts`) {
|
||||||
|
return render(
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<AppProvider store={store}>
|
||||||
|
<DiscussionContext.Provider
|
||||||
|
value={{
|
||||||
|
learnerUsername: username,
|
||||||
|
courseId,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MemoryRouter initialEntries={[path]}>
|
||||||
|
<Route path={path}>
|
||||||
|
<LearnerPostsView />
|
||||||
|
</Route>
|
||||||
|
</MemoryRouter>
|
||||||
|
</DiscussionContext.Provider>
|
||||||
|
</AppProvider>
|
||||||
|
</IntlProvider>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('LearnerPostsView', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
initializeMockApp({
|
||||||
|
authenticatedUser: {
|
||||||
|
userId: 3,
|
||||||
|
username,
|
||||||
|
administrator: true,
|
||||||
|
roles: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
store = initializeStore();
|
||||||
|
Factory.resetAll();
|
||||||
|
const learnerPosts = Factory.build('learnerPosts', {}, {
|
||||||
|
abuseFlaggedCount: 1,
|
||||||
|
});
|
||||||
|
const apiUrl = `${coursesApiUrl}${courseId}/learner/`;
|
||||||
|
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||||
|
axiosMock.onGet(apiUrl, { username, count_flagged: true })
|
||||||
|
.reply(() => [200, learnerPosts]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Basic', () => {
|
||||||
|
test('Reported icon is visible to moderator for post with reported comment', async () => {
|
||||||
|
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);
|
||||||
|
await act(async () => {
|
||||||
|
renderComponent();
|
||||||
|
});
|
||||||
|
expect(screen.queryAllByTestId('reported-post')[0]).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Reported icon is not visible to learner for post with reported comment', async () => {
|
||||||
|
await renderComponent();
|
||||||
|
expect(screen.queryByTestId('reported-post')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -86,3 +86,63 @@ Factory.define('learnersProfile')
|
|||||||
}));
|
}));
|
||||||
return profiles;
|
return profiles;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Factory.define('learnerPosts')
|
||||||
|
.option('abuseFlaggedCount', null, null)
|
||||||
|
.option('courseId', null, 'course-v1:edX+TestX+Test_Course')
|
||||||
|
.attr(
|
||||||
|
'results',
|
||||||
|
['abuseFlaggedCount', 'courseId'],
|
||||||
|
(abuseFlaggedCount, courseId) => {
|
||||||
|
const threads = [];
|
||||||
|
for (let i = 0; i < 2; i++) {
|
||||||
|
threads.push({
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
editable_fields: [
|
||||||
|
'abuse_flagged',
|
||||||
|
'following',
|
||||||
|
'group_id',
|
||||||
|
'raw_body',
|
||||||
|
'closed',
|
||||||
|
'read',
|
||||||
|
'title',
|
||||||
|
'topic_id',
|
||||||
|
'type',
|
||||||
|
'voted',
|
||||||
|
'pinned',
|
||||||
|
],
|
||||||
|
id: `post_id${i}`,
|
||||||
|
author: 'test_user',
|
||||||
|
author_label: 'Staff',
|
||||||
|
abuse_flagged: false,
|
||||||
|
can_delete: true,
|
||||||
|
voted: false,
|
||||||
|
vote_count: 1,
|
||||||
|
title: `Title ${i}`,
|
||||||
|
raw_body: `<p>body ${i}</p>`,
|
||||||
|
preview_body: `<p>body ${i}</p>`,
|
||||||
|
course_id: courseId,
|
||||||
|
group_id: null,
|
||||||
|
group_name: null,
|
||||||
|
abuse_flagged_count: abuseFlaggedCount,
|
||||||
|
following: false,
|
||||||
|
comment_count: 8,
|
||||||
|
unread_comment_count: 0,
|
||||||
|
endorsed_comment_list_url: null,
|
||||||
|
non_endorsed_comment_list_url: null,
|
||||||
|
read: false,
|
||||||
|
has_endorsed: false,
|
||||||
|
pinned: false,
|
||||||
|
topic_id: 'topic',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return threads;
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.attr('pagination', [], () => ({
|
||||||
|
next: null,
|
||||||
|
prev: null,
|
||||||
|
count: 2,
|
||||||
|
num_pages: 1,
|
||||||
|
}));
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable import/prefer-default-export */
|
/* eslint-disable import/prefer-default-export */
|
||||||
import { ensureConfig, getConfig } from '@edx/frontend-platform';
|
import { ensureConfig, getConfig, snakeCaseObject } from '@edx/frontend-platform';
|
||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
|
|
||||||
ensureConfig([
|
ensureConfig([
|
||||||
@@ -45,10 +45,11 @@ export async function getUserProfiles(usernames) {
|
|||||||
* pagination: {count, num_pages, next, previous}
|
* pagination: {count, num_pages, next, previous}
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export async function getUserPosts(courseId, username, { page }) {
|
export async function getUserPosts(courseId, { username, page, countFlagged }) {
|
||||||
const learnerPostsApiUrl = `${coursesApiUrl}${courseId}/learner/`;
|
const learnerPostsApiUrl = `${coursesApiUrl}${courseId}/learner/`;
|
||||||
|
|
||||||
|
const params = snakeCaseObject({ username, page, countFlagged });
|
||||||
const { data } = await getAuthenticatedHttpClient()
|
const { data } = await getAuthenticatedHttpClient()
|
||||||
.get(learnerPostsApiUrl, { params: { username, page } });
|
.get(learnerPostsApiUrl, { params });
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,12 +67,12 @@ export function fetchLearners(courseId, {
|
|||||||
* @param page
|
* @param page
|
||||||
* @returns a promise that will update the state with the learner's posts
|
* @returns a promise that will update the state with the learner's posts
|
||||||
*/
|
*/
|
||||||
export function fetchUserPosts(courseId, username, { page = 1 } = {}) {
|
export function fetchUserPosts(courseId, { username, page = 1, countFlagged = false } = {}) {
|
||||||
return async (dispatch) => {
|
return async (dispatch) => {
|
||||||
try {
|
try {
|
||||||
dispatch(fetchLearnerThreadsRequest({ courseId, author: username }));
|
dispatch(fetchLearnerThreadsRequest({ courseId, author: username }));
|
||||||
|
|
||||||
const data = await getUserPosts(courseId, username, { page });
|
const data = await getUserPosts(courseId, { username, page, countFlagged });
|
||||||
const normalisedData = normaliseThreads(camelCaseObject(data));
|
const normalisedData = normaliseThreads(camelCaseObject(data));
|
||||||
|
|
||||||
dispatch(fetchThreadsSuccess({ ...normalisedData, page, author: username }));
|
dispatch(fetchThreadsSuccess({ ...normalisedData, page, author: username }));
|
||||||
|
|||||||
Reference in New Issue
Block a user