-
-
- navigate({ ...discussionsPath(Routes.LEARNERS.PATH, { courseId })(location) })}
- alt={intl.formatMessage(messages.back)}
- />
-
-
+
+
navigate({ ...discussionsPath(Routes.LEARNERS.PATH, { courseId })(location) })}
+ alt={intl.formatMessage(messages.back)}
+ />
+
{intl.formatMessage(messages.activityForLearner, { username: capitalize(username) })}
- {userHasBulkDeletePrivileges ? (
-
-
-
- )
- : ()}
+
@@ -154,22 +109,6 @@ const LearnerPostsView = () => {
)
)}
-
handleDeletePosts(isDeletingCourseOrOrg)}
- confirmButtonText={intl.formatMessage(messages.deletePostsConfirm)}
- confirmButtonVariant="danger"
- isDataLoading={!(learnerLoadingStatus === RequestStatus.SUCCESSFUL)}
- isConfirmButtonPending={bulkDeleting}
- pendingConfirmButtonText={intl.formatMessage(messages.deletePostConfirmPending)}
- />
);
};
diff --git a/src/discussions/learners/LearnerPostsView.test.jsx b/src/discussions/learners/LearnerPostsView.test.jsx
index dcca0656..5977d0f5 100644
--- a/src/discussions/learners/LearnerPostsView.test.jsx
+++ b/src/discussions/learners/LearnerPostsView.test.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import {
- fireEvent, render, screen, waitFor, within,
+ fireEvent, render, screen, waitFor,
} from '@testing-library/react';
import MockAdapter from 'axios-mock-adapter';
import { act } from 'react-dom/test-utils';
@@ -20,7 +20,7 @@ import executeThunk from '../../test-utils';
import { getCohortsApiUrl } from '../cohorts/data/api';
import fetchCourseCohorts from '../cohorts/data/thunks';
import DiscussionContext from '../common/context';
-import { deletePostsApiUrl, learnerPostsApiUrl } from './data/api';
+import { learnerPostsApiUrl } from './data/api';
import { fetchUserPosts } from './data/thunks';
import LearnerPostsView from './LearnerPostsView';
import { setUpPrivilages } from './test-utils';
@@ -220,128 +220,4 @@ describe('Learner Posts View', () => {
expect(loadMoreButton).not.toBeInTheDocument();
expect(container.querySelectorAll('.discussion-post')).toHaveLength(4);
});
-
- test('should display dropdown menu button for bulk delete user posts for privileged users', async () => {
- await setUpPrivilages(axiosMock, store, true, true);
- await renderComponent();
- expect(within(container).queryByRole('button', { name: /actions menu/i })).toBeInTheDocument();
- });
-
- test('should NOT display dropdown menu button for bulk delete user posts for other users', async () => {
- await setUpPrivilages(axiosMock, store, true, false);
- await renderComponent();
- expect(within(container).queryByRole('button', { name: /actions menu/i })).not.toBeInTheDocument();
- });
-
- test('should display confirmation dialog when delete course posts is clicked', async () => {
- await setUpPrivilages(axiosMock, store, true, true);
- axiosMock.onPost(deletePostsApiUrl(courseId, username, 'course', false))
- .reply(202, { thread_count: 2, comment_count: 3 });
- await renderComponent();
-
- const actionsButton = await screen.findByRole('button', { name: /actions menu/i });
- await act(async () => {
- fireEvent.click(actionsButton);
- });
-
- const deleteCourseItem = await screen.findByTestId('delete-course-posts');
- await act(async () => {
- fireEvent.click(deleteCourseItem);
- });
-
- await waitFor(() => {
- const dialog = screen.getByText('Are you sure you want to delete this user\'s discussion contributions?');
- expect(dialog).toBeInTheDocument();
- expect(screen.getByText('You are about to delete 5 discussion contributions by this user in this course. This includes all discussion threads, responses, and comments authored by them.')).toBeInTheDocument();
- expect(screen.getByText('This action cannot be undone.')).toBeInTheDocument();
- expect(screen.getByText('Cancel')).toBeInTheDocument();
- expect(screen.getByText('Delete')).toBeInTheDocument();
- });
- });
-
- test('should complete delete course posts flow and redirect', async () => {
- await setUpPrivilages(axiosMock, store, true, true);
- axiosMock.onPost(deletePostsApiUrl(courseId, username, 'course', false))
- .reply(202, { thread_count: 2, comment_count: 3 });
- axiosMock.onPost(deletePostsApiUrl(courseId, username, 'course', true))
- .reply(202, { thread_count: 0, comment_count: 0 });
- await renderComponent();
-
- const actionsButton = await screen.findByRole('button', { name: /actions menu/i });
- await act(async () => {
- fireEvent.click(actionsButton);
- });
-
- const deleteCourseItem = await screen.findByTestId('delete-course-posts');
- await act(async () => {
- fireEvent.click(deleteCourseItem);
- });
-
- await waitFor(() => {
- expect(screen.getByText('Are you sure you want to delete this user\'s discussion contributions?')).toBeInTheDocument();
- });
-
- const confirmButton = await screen.findByText('Delete');
- await act(async () => {
- fireEvent.click(confirmButton);
- });
-
- await waitFor(() => {
- expect(lastLocation.pathname.endsWith('/learners')).toBeTruthy();
- expect(screen.queryByText('Are you sure you want to delete this user\'s discussion contributions?')).not.toBeInTheDocument();
- });
- });
-
- test('should close confirmation dialog when cancel is clicked', async () => {
- await setUpPrivilages(axiosMock, store, true, true);
- axiosMock.onPost(deletePostsApiUrl(courseId, username, 'course', false))
- .reply(202, { thread_count: 2, comment_count: 3 });
- await renderComponent();
-
- const actionsButton = await screen.findByRole('button', { name: /actions menu/i });
- await act(async () => {
- fireEvent.click(actionsButton);
- });
-
- const deleteCourseItem = await screen.findByTestId('delete-course-posts');
- await act(async () => {
- fireEvent.click(deleteCourseItem);
- });
-
- await waitFor(() => {
- expect(screen.getByText('Are you sure you want to delete this user\'s discussion contributions?')).toBeInTheDocument();
- });
-
- const cancelButton = await screen.findByText('Cancel');
- await act(async () => {
- fireEvent.click(cancelButton);
- });
-
- await waitFor(() => {
- expect(screen.queryByText('Are you sure you want to delete this user\'s discussion contributions?')).not.toBeInTheDocument();
- });
- });
-
- test('should display confirmation dialog for org posts deletion', async () => {
- await setUpPrivilages(axiosMock, store, true, true);
- axiosMock.onPost(deletePostsApiUrl(courseId, username, 'org', false))
- .reply(202, { thread_count: 5, comment_count: 10 });
- await renderComponent();
-
- const actionsButton = await screen.findByRole('button', { name: /actions menu/i });
- await act(async () => {
- fireEvent.click(actionsButton);
- });
-
- const deleteOrgItem = await screen.findByTestId('delete-org-posts');
- await act(async () => {
- fireEvent.click(deleteOrgItem);
- });
-
- await waitFor(() => {
- expect(screen.getByText('Are you sure you want to delete this user\'s discussion contributions?')).toBeInTheDocument();
- expect(screen.getByText('You are about to delete 15 discussion contributions by this user across the organization. This includes all discussion threads, responses, and comments authored by them.')).toBeInTheDocument();
- expect(screen.getByText('This action cannot be undone.')).toBeInTheDocument();
- });
- });
});
diff --git a/src/discussions/learners/data/api.js b/src/discussions/learners/data/api.js
index 05121079..53a4d9c8 100644
--- a/src/discussions/learners/data/api.js
+++ b/src/discussions/learners/data/api.js
@@ -11,7 +11,6 @@ export const getCoursesApiUrl = () => `${getConfig().LMS_BASE_URL}/api/discussio
export const getUserProfileApiUrl = () => `${getConfig().LMS_BASE_URL}/api/user/v1/accounts`;
export const learnerPostsApiUrl = (courseId) => `${getCoursesApiUrl()}${courseId}/learner/`;
export const learnersApiUrl = (courseId) => `${getCoursesApiUrl()}${courseId}/activity_stats/`;
-export const deletePostsApiUrl = (courseId, username, courseOrOrg, execute) => `${getConfig().LMS_BASE_URL}/api/discussion/v1/bulk_delete_user_posts/${courseId}?username=${username}&course_or_org=${courseOrOrg}&execute=${execute}`;
/**
* Fetches all the learners in the given course.
@@ -83,23 +82,3 @@ export async function getUserPosts(courseId, {
.get(learnerPostsApiUrl(courseId), { params });
return data;
}
-
-/**
- * Deletes posts by a specific user in a course or organization
- * @param {string} courseId Course ID of the course
- * @param {string} username Username of the user whose posts are to be deleted
- * @param {string} courseOrOrg Can be 'course' or 'org' to specify deletion scope
- * @param {boolean} execute If true, deletes posts; if false, returns count of threads and comments
- * @returns API Response object in the format
- * {
- * thread_count: number,
- * comment_count: number
- * }
- */
-export async function deleteUserPostsApi(courseId, username, courseOrOrg, execute) {
- const { data } = await getAuthenticatedHttpClient().post(
- deletePostsApiUrl(courseId, username, courseOrOrg, execute),
- null,
- );
- return data;
-}
diff --git a/src/discussions/learners/data/api.test.jsx b/src/discussions/learners/data/api.test.jsx
index 444debaf..e03e883f 100644
--- a/src/discussions/learners/data/api.test.jsx
+++ b/src/discussions/learners/data/api.test.jsx
@@ -4,7 +4,7 @@ import { Factory } from 'rosie';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { initializeMockApp } from '@edx/frontend-platform/testing';
-import { setupDeleteUserPostsMockResponse, setupLearnerMockResponse, setupPostsMockResponse } from '../test-utils';
+import { setupLearnerMockResponse, setupPostsMockResponse } from '../test-utils';
import './__factories__';
@@ -80,25 +80,4 @@ describe('Learner api test cases', () => {
expect(threads.status).toEqual('denied');
});
-
- it.each([
- { courseOrOrg: 'course', execute: false, response: { comment_count: 3, thread_count: 2 } },
- { courseOrOrg: 'course', execute: true, response: { comment_count: 0, thread_count: 0 } },
- { courseOrOrg: 'org', execute: false, response: { comment_count: 3, thread_count: 2 } },
- { courseOrOrg: 'org', execute: true, response: { comment_count: 0, thread_count: 0 } },
- ])(
- 'Successfully fetches user post stats and bulk deletes user posts based on execute',
- async ({ courseOrOrg, execute, response }) => {
- const learners = await setupDeleteUserPostsMockResponse({ courseOrOrg, execute, response });
-
- expect(learners.status).toEqual('successful');
- expect(Object.values(learners.bulkDeleteStats)).toEqual(Object.values(response));
- },
- );
-
- it('Failed to bulk delete user posts', async () => {
- const learners = await setupPostsMockResponse({ statusCode: 400 });
-
- expect(learners.status).toEqual('failed');
- });
});
diff --git a/src/discussions/learners/data/constants.js b/src/discussions/learners/data/constants.js
deleted file mode 100644
index c957e256..00000000
--- a/src/discussions/learners/data/constants.js
+++ /dev/null
@@ -1,4 +0,0 @@
-export const BulkDeleteType = {
- COURSE: 'course',
- ORG: 'org',
-};
diff --git a/src/discussions/learners/data/selectors.js b/src/discussions/learners/data/selectors.js
index 10763445..bf3ea98e 100644
--- a/src/discussions/learners/data/selectors.js
+++ b/src/discussions/learners/data/selectors.js
@@ -16,5 +16,3 @@ export const selectLearnerNextPage = () => state => state.learners.nextPage;
export const selectLearnerAvatar = author => state => (
state.learners.learnerProfiles[author]?.profileImage?.imageUrlLarge
);
-
-export const selectBulkDeleteStats = () => state => state.learners.bulkDeleteStats;
diff --git a/src/discussions/learners/data/slices.js b/src/discussions/learners/data/slices.js
index 534fe850..cc94aaee 100644
--- a/src/discussions/learners/data/slices.js
+++ b/src/discussions/learners/data/slices.js
@@ -25,10 +25,6 @@ const learnersSlice = createSlice({
cohort: '',
},
usernameSearch: null,
- bulkDeleteStats: {
- commentCount: 0,
- threadCount: 0,
- },
},
reducers: {
fetchLearnersSuccess: (state, { payload }) => (
@@ -88,25 +84,6 @@ const learnersSlice = createSlice({
postFilter: payload,
}
),
- deleteUserPostsRequest: (state) => (
- {
- ...state,
- status: RequestStatus.IN_PROGRESS,
- }
- ),
- deleteUserPostsSuccess: (state, { payload }) => (
- {
- ...state,
- status: RequestStatus.SUCCESSFUL,
- bulkDeleteStats: payload,
- }
- ),
- deleteUserPostsFailed: (state) => (
- {
- ...state,
- status: RequestStatus.FAILED,
- }
- ),
},
});
@@ -118,9 +95,6 @@ export const {
setSortedBy,
setUsernameSearch,
setPostFilter,
- deleteUserPostsRequest,
- deleteUserPostsSuccess,
- deleteUserPostsFailed,
} = learnersSlice.actions;
export const learnersReducer = learnersSlice.reducer;
diff --git a/src/discussions/learners/data/thunks.js b/src/discussions/learners/data/thunks.js
index afc554b6..93603c9c 100644
--- a/src/discussions/learners/data/thunks.js
+++ b/src/discussions/learners/data/thunks.js
@@ -12,16 +12,8 @@ import {
} from '../../posts/data/slices';
import { normaliseThreads } from '../../posts/data/thunks';
import { getHttpErrorStatus } from '../../utils';
+import { getLearners, getUserPosts, getUserProfiles } from './api';
import {
- deleteUserPostsApi,
- getLearners,
- getUserPosts,
- getUserProfiles,
-} from './api';
-import {
- deleteUserPostsFailed,
- deleteUserPostsRequest,
- deleteUserPostsSuccess,
fetchLearnersDenied,
fetchLearnersFailed,
fetchLearnersRequest,
@@ -129,19 +121,3 @@ export function fetchUserPosts(courseId, {
}
};
}
-
-export const deleteUserPosts = (
- courseId,
- username,
- courseOrOrg,
- execute,
-) => async (dispatch) => {
- try {
- dispatch(deleteUserPostsRequest({ courseId, username }));
- const response = await deleteUserPostsApi(courseId, username, courseOrOrg, execute);
- dispatch(deleteUserPostsSuccess(camelCaseObject(response)));
- } catch (error) {
- dispatch(deleteUserPostsFailed());
- logError(error);
- }
-};
diff --git a/src/discussions/learners/messages.js b/src/discussions/learners/messages.js
index 38403e56..816730af 100644
--- a/src/discussions/learners/messages.js
+++ b/src/discussions/learners/messages.js
@@ -62,45 +62,6 @@ const messages = defineMessages({
defaultMessage: 'Posts',
description: 'Tooltip text for all posts icon',
},
- deleteCoursePosts: {
- id: 'discussions.learner.actions.deleteCoursePosts',
- defaultMessage: 'Delete user posts within this course',
- description: 'Action to delete user posts within a specific course',
- },
- deleteOrgPosts: {
- id: 'discussions.learner.actions.deleteOrgPosts',
- defaultMessage: 'Delete user posts within this organization',
- description: 'Action to delete user posts within the organization',
- },
- deletePostsTitle: {
- id: 'discussions.learner.deletePosts.title',
- defaultMessage: 'Are you sure you want to delete this user\'s discussion contributions?',
- description: 'Title for delete course posts confirmation dialog',
- },
- deletePostsDescription: {
- id: 'discussions.learner.deletePosts.description',
- defaultMessage: `{bulkType, select,
- course {You are about to delete {count, plural, one {# discussion contribution} other {# discussion contributions}} by this user in this course. This includes all discussion threads, responses, and comments authored by them.}
- org {You are about to delete {count, plural, one {# discussion contribution} other {# discussion contributions}} by this user across the organization. This includes all discussion threads, responses, and comments authored by them.}
- other {You are about to delete {count, plural, one {# discussion contribution} other {# discussion contributions}} by this user. This includes all discussion threads, responses, and comments authored by them.}
- }`,
- description: 'Description for delete posts confirmation dialog',
- },
- deletePostsConfirm: {
- id: 'discussions.learner.deletePosts.confirm',
- defaultMessage: 'Delete',
- description: 'Confirm button text for delete posts',
- },
- deletePostConfirmPending: {
- id: 'discussions.learner.deletePosts.confirm.pending',
- defaultMessage: 'Deleting',
- description: 'Pending state of confirm button text for delete posts',
- },
- deletePostsBoldDescription: {
- id: 'discussions.learner.deletePosts.boldDescription',
- defaultMessage: 'This action cannot be undone.',
- description: 'Bold disclaimer description for delete confirmation dialog',
- },
});
export default messages;
diff --git a/src/discussions/learners/test-utils.js b/src/discussions/learners/test-utils.js
index 253c4585..a14ba17e 100644
--- a/src/discussions/learners/test-utils.js
+++ b/src/discussions/learners/test-utils.js
@@ -7,13 +7,8 @@ import { initializeStore } from '../../store';
import executeThunk from '../../test-utils';
import { getDiscussionsConfigUrl } from '../data/api';
import fetchCourseConfig from '../data/thunks';
-import {
- deletePostsApiUrl,
- getUserProfileApiUrl,
- learnerPostsApiUrl,
- learnersApiUrl,
-} from './data/api';
-import { deleteUserPosts, fetchLearners, fetchUserPosts } from './data/thunks';
+import { getUserProfileApiUrl, learnerPostsApiUrl, learnersApiUrl } from './data/api';
+import { fetchLearners, fetchUserPosts } from './data/thunks';
const courseId = 'course-v1:edX+DemoX+Demo_Course';
@@ -59,26 +54,9 @@ export async function setupPostsMockResponse({
return store.getState().threads;
}
-export async function setupDeleteUserPostsMockResponse({
- username = 'abc123',
- courseOrOrg,
- statusCode = 202,
- execute,
- response,
-} = {}) {
- const store = initializeStore();
- const axiosMock = new MockAdapter(getAuthenticatedHttpClient());
-
- axiosMock.onPost(deletePostsApiUrl(courseId, username, courseOrOrg, execute)).reply(statusCode, response);
-
- await executeThunk(deleteUserPosts(courseId, username, courseOrOrg, execute), store.dispatch, store.getState);
- return store.getState().learners;
-}
-
-export async function setUpPrivilages(axiosMock, store, hasModerationPrivileges, hasBulkDeletePrivileges) {
+export async function setUpPrivilages(axiosMock, store, hasModerationPrivileges) {
axiosMock.onGet(getDiscussionsConfigUrl(courseId)).reply(200, {
hasModerationPrivileges,
- hasBulkDeletePrivileges,
});
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
diff --git a/src/discussions/learners/utils.js b/src/discussions/learners/utils.js
deleted file mode 100644
index 7f4cf4e7..00000000
--- a/src/discussions/learners/utils.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import { useMemo } from 'react';
-
-import { Delete } from '@openedx/paragon/icons';
-
-import { useIntl } from '@edx/frontend-platform/i18n';
-
-import { ContentActions } from '../../data/constants';
-import messages from './messages';
-
-export const LEARNER_ACTIONS_LIST = [
- {
- id: 'delete-course-posts',
- action: ContentActions.DELETE_COURSE_POSTS,
- icon: Delete,
- label: messages.deleteCoursePosts,
- },
- {
- id: 'delete-org-posts',
- action: ContentActions.DELETE_ORG_POSTS,
- icon: Delete,
- label: messages.deleteOrgPosts,
- },
-];
-
-export function useLearnerActions(userHasBulkDeletePrivileges = false) {
- const intl = useIntl();
-
- const actions = useMemo(() => {
- if (!userHasBulkDeletePrivileges) {
- return [];
- }
- return LEARNER_ACTIONS_LIST.map(action => ({
- ...action,
- label: {
- id: action.label.id,
- defaultMessage: intl.formatMessage(action.label),
- },
- }));
- }, [userHasBulkDeletePrivileges, intl]);
-
- return actions;
-}
diff --git a/src/index.scss b/src/index.scss
index 3b0bd2f0..2981b535 100755
--- a/src/index.scss
+++ b/src/index.scss
@@ -553,7 +553,7 @@ code {
.actions-dropdown-item {
padding: 12px 16px;
height: 48px !important;
- min-width: 195px !important
+ width: 195px !important
}
.font-xl {