diff --git a/src/discussions/posts/PostsView.test.jsx b/src/discussions/posts/PostsView.test.jsx index f7520ebb..accddcc4 100644 --- a/src/discussions/posts/PostsView.test.jsx +++ b/src/discussions/posts/PostsView.test.jsx @@ -15,11 +15,13 @@ import { AppProvider } from '@edx/frontend-platform/react'; import { Routes, ThreadType } from '../../data/constants'; import { initializeStore } from '../../store'; +import { getCohortsApiUrl } from '../cohorts/data/api'; import { DiscussionContext } from '../common/context'; import { threadsApiUrl } from './data/api'; import { PostsView } from './index'; import './data/__factories__'; +import '../cohorts/data/__factories__'; const courseId = 'course-v1:edX+TestX+Test_Course'; let store; @@ -89,6 +91,7 @@ describe('PostsView', () => { }); Factory.resetAll(); axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + axiosMock.onGet(getCohortsApiUrl(courseId)).reply(200, Factory.buildList('cohort', 1)); axiosMock.onGet(threadsApiUrl) .reply((args) => { const threadAttrs = {}; @@ -152,11 +155,25 @@ describe('PostsView', () => { fireEvent.click(dropDownButton); }); }); + test('test that the filter bar works', async () => { // 3 type filters: all, discussion, question // 5 status filters: any, unread, following, reported, unanswered // 3 sort: activity, comments, likes - expect(screen.queryAllByRole('radio')).toHaveLength(11); + // 2 cohort: all groups, 1 api mock response cohort + expect(screen.queryAllByRole('radio')).toHaveLength(13); + }); + + test('test that the cohorts filter works', async () => { + await act(async () => { + fireEvent.click(screen.getByLabelText('Cohort 1')); + }); + + dropDownButton = screen.getByRole('button', { + name: /All posts in Cohort 1 by recent activity/i, + }); + + expect(dropDownButton).toBeInTheDocument(); }); describe.each([ @@ -192,6 +209,14 @@ describe('PostsView', () => { label: 'Most likes', queryParam: { order_by: 'vote_count' }, }, + { + label: 'All groups', + queryParam: { group_id: undefined }, + }, + { + label: 'Cohort 1', + queryParam: { group_id: 'cohort-1' }, + }, ])( 'one at a time', ({ diff --git a/src/discussions/posts/data/api.js b/src/discussions/posts/data/api.js index 196b3821..be4a5d5b 100644 --- a/src/discussions/posts/data/api.js +++ b/src/discussions/posts/data/api.js @@ -42,6 +42,7 @@ export async function getThreads( flagged, threadType, countFlagged, + cohort, } = {}, ) { const params = snakeCaseObject({ @@ -58,6 +59,7 @@ export async function getThreads( author, flagged, countFlagged, + groupId: cohort, }); const { data } = await getAuthenticatedHttpClient().get(threadsApiUrl, { params }); return data; diff --git a/src/discussions/posts/data/slices.js b/src/discussions/posts/data/slices.js index 1cdf389a..a3248fb9 100644 --- a/src/discussions/posts/data/slices.js +++ b/src/discussions/posts/data/slices.js @@ -31,6 +31,7 @@ const threadsSlice = createSlice({ filters: { status: PostsStatusFilter.ALL, postType: ThreadType.ALL, + cohort: '', search: '', }, postEditorVisible: false, @@ -146,6 +147,10 @@ const threadsSlice = createSlice({ state.filters.postType = payload; state.pages = []; }, + setCohortFilter: (state, { payload }) => { + state.filters.cohort = payload; + state.pages = []; + }, setSearchQuery: (state, { payload }) => { state.filters.search = payload; // Search doesn't work with following @@ -190,6 +195,7 @@ export const { updateThreadAsRead, updateThreadSuccess, setPostsTypeFilter, + setCohortFilter, setSortedBy, setStatusFilter, setSearchQuery, diff --git a/src/discussions/posts/data/thunks.js b/src/discussions/posts/data/thunks.js index e198e009..ba8412eb 100644 --- a/src/discussions/posts/data/thunks.js +++ b/src/discussions/posts/data/thunks.js @@ -122,6 +122,9 @@ export function fetchThreads(courseId, { if (filters.search) { options.textSearch = filters.search; } + if (filters.cohort) { + options.cohort = filters.cohort; + } return async (dispatch) => { try { dispatch(fetchThreadsRequest({ courseId })); diff --git a/src/discussions/posts/post-editor/PostEditor.jsx b/src/discussions/posts/post-editor/PostEditor.jsx index 8606cba8..211d5014 100644 --- a/src/discussions/posts/post-editor/PostEditor.jsx +++ b/src/discussions/posts/post-editor/PostEditor.jsx @@ -161,7 +161,7 @@ function PostEditor({ }; useEffect(() => { - if (userIsPrivileged) { + if (userIsPrivileged && 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 4d080a7e..bc997171 100644 --- a/src/discussions/posts/post-editor/PostEditor.test.jsx +++ b/src/discussions/posts/post-editor/PostEditor.test.jsx @@ -137,7 +137,7 @@ describe('PostEditor', () => { ); }); - describe('chorting', () => { + describe('cohorting', () => { const dividedncw = ['ncw-topic-2']; const dividedcw = ['category-1-topic-2', 'category-2-topic-1', 'category-2-topic-2']; diff --git a/src/discussions/posts/post-filter-bar/PostFilterBar.jsx b/src/discussions/posts/post-filter-bar/PostFilterBar.jsx index 174f0c1f..d33ef9e0 100644 --- a/src/discussions/posts/post-filter-bar/PostFilterBar.jsx +++ b/src/discussions/posts/post-filter-bar/PostFilterBar.jsx @@ -1,18 +1,27 @@ -import React, { useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; +import { capitalize, isEmpty, toString } from 'lodash'; import { useDispatch, useSelector } from 'react-redux'; +import { useParams } from 'react-router-dom'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { Collapsible, Form, Icon } from '@edx/paragon'; -import { Check, Sort } from '@edx/paragon/icons'; +import { + Collapsible, Form, Icon, Spinner, +} from '@edx/paragon'; +import { Check, Tune } from '@edx/paragon/icons'; import { - PostsStatusFilter, ThreadOrdering, ThreadType, + PostsStatusFilter, RequestStatus, + ThreadOrdering, ThreadType, } from '../../../data/constants'; +import { selectCourseCohorts } from '../../cohorts/data/selectors'; +import { fetchCourseCohorts } from '../../cohorts/data/thunks'; import { selectUserIsPrivileged } from '../../data/selectors'; -import { setPostsTypeFilter, setSortedBy, setStatusFilter } from '../data'; +import { + setCohortFilter, setPostsTypeFilter, setSortedBy, setStatusFilter, +} from '../data'; import { selectThreadFilters, selectThreadSorting } from '../data/selectors'; import messages from './messages'; @@ -22,12 +31,16 @@ const ActionItem = ({ value, selected, }) => ( -