Skip to content

Commit 3af0d15

Browse files
committed
feat: added soft delete functionality
1 parent 81b8149 commit 3af0d15

File tree

7 files changed

+48
-108
lines changed

7 files changed

+48
-108
lines changed

src/discussions/components/FilterBar.test.jsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import { Provider } from 'react-redux';
1111

1212
import { IntlProvider } from '@edx/frontend-platform/i18n';
1313

14-
import { threadsReducer } from '../../data/slices';
15-
import messages from '../../messages';
16-
import FilterBar from '../FilterBar';
14+
import messages from '../messages';
15+
import { threadsReducer } from '../posts/data/slices';
16+
import FilterBar from './FilterBar';
1717

1818
// Mock the soft delete service
1919
jest.mock('../../data/services/softDeleteService', () => ({
@@ -144,7 +144,9 @@ describe('FilterBar Component', () => {
144144

145145
test('shows loading state during bulk delete', async () => {
146146
const selectedThreadIds = ['thread1'];
147-
mockOnBulkAction.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100)));
147+
mockOnBulkAction.mockImplementation(() => new Promise((resolve) => {
148+
setTimeout(resolve, 100);
149+
}));
148150

149151
renderFilterBar({
150152
selectedThreadIds,
@@ -189,7 +191,9 @@ describe('FilterBar Component', () => {
189191

190192
test('shows loading state during bulk restore', async () => {
191193
const selectedThreadIds = ['thread1'];
192-
mockOnBulkAction.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100)));
194+
mockOnBulkAction.mockImplementation(() => new Promise((resolve) => {
195+
setTimeout(resolve, 100);
196+
}));
193197

194198
renderFilterBar({
195199
selectedThreadIds,

src/discussions/data/services/softDeleteService.js

Lines changed: 9 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,20 @@ const getDiscussionApiBaseUrl = () => `${getConfig().LMS_BASE_URL}/api/discussio
1010
/**
1111
* Soft delete a single thread by calling the DELETE endpoint
1212
* @param {string} threadId - The ID of the thread to soft delete
13-
* @param {string} userId - The ID of the user performing the action
14-
* @param {string} courseId - The course ID
1513
* @returns {Promise} API response
1614
*/
17-
export async function softDeleteThread(threadId, userId, courseId) {
15+
export async function softDeleteThread(threadId) {
1816
const url = `${getDiscussionApiBaseUrl()}/threads/${threadId}/`;
19-
console.log('[Service] softDeleteThread - About to DELETE:', url);
20-
console.log('[Service] LMS_BASE_URL:', getConfig().LMS_BASE_URL);
21-
2217
return getAuthenticatedHttpClient().delete(url);
2318
}
2419

2520
/**
2621
* Restore a soft deleted thread
2722
* @param {string} threadId - The ID of the thread to restore
28-
* @param {string} courseId - The course ID
2923
* @returns {Promise} API response
3024
*/
31-
export async function restoreThread(threadId, courseId) {
25+
export async function restoreThread(threadId) {
3226
const url = `${getDiscussionApiBaseUrl()}/threads/${threadId}/restore/`;
33-
console.log('[Service] restoreThread - About to POST:', url);
34-
console.log('[Service] LMS_BASE_URL:', getConfig().LMS_BASE_URL);
35-
3627
return getAuthenticatedHttpClient().post(url, {});
3728
}
3829

@@ -41,20 +32,12 @@ export async function restoreThread(threadId, courseId) {
4132
* NOTE: Currently implemented as sequential deletes.
4233
* TODO: Implement true bulk endpoint for better performance
4334
* @param {string[]} threadIds - Array of thread IDs to soft delete
44-
* @param {string} userId - The ID of the user performing the action
45-
* @param {string} courseId - The course ID
4635
* @returns {Promise} API response
4736
*/
48-
export async function bulkSoftDeleteThreads(threadIds, userId, courseId) {
49-
console.log('[Service] bulkSoftDeleteThreads called', { threadIds, userId, courseId });
50-
console.log('[Service] Processing', threadIds.length, 'threads');
37+
export async function bulkSoftDeleteThreads(threadIds) {
5138
const results = await Promise.allSettled(
52-
threadIds.map(threadId => softDeleteThread(threadId, userId, courseId)),
39+
threadIds.map(threadId => softDeleteThread(threadId)),
5340
);
54-
console.log('[Service] bulkSoftDeleteThreads completed', {
55-
successful: results.filter(r => r.status === 'fulfilled').length,
56-
failed: results.filter(r => r.status === 'rejected').length,
57-
});
5841

5942
return {
6043
successful: results.filter(r => r.status === 'fulfilled').length,
@@ -68,19 +51,12 @@ export async function bulkSoftDeleteThreads(threadIds, userId, courseId) {
6851
* NOTE: Currently implemented as sequential restores.
6952
* TODO: Implement true bulk endpoint for better performance
7053
* @param {string[]} threadIds - Array of thread IDs to restore
71-
* @param {string} courseId - The course ID
7254
* @returns {Promise} API response
7355
*/
74-
export async function bulkRestoreThreads(threadIds, courseId) {
75-
console.log('[Service] bulkRestoreThreads called', { threadIds, courseId });
76-
console.log('[Service] Processing', threadIds.length, 'threads');
56+
export async function bulkRestoreThreads(threadIds) {
7757
const results = await Promise.allSettled(
78-
threadIds.map(threadId => restoreThread(threadId, courseId)),
58+
threadIds.map(threadId => restoreThread(threadId)),
7959
);
80-
console.log('[Service] bulkRestoreThreads completed', {
81-
successful: results.filter(r => r.status === 'fulfilled').length,
82-
failed: results.filter(r => r.status === 'rejected').length,
83-
});
8460

8561
return {
8662
successful: results.filter(r => r.status === 'fulfilled').length,
@@ -117,17 +93,13 @@ export async function getDeletedThreads(courseId, options = {}) {
11793
/**
11894
* Search threads with optional deleted filter
11995
* @param {Object} searchParams - Search parameters including courseId
120-
* @param {boolean} includeDeleted - Whether to include deleted threads in search
12196
* @returns {Promise} API response
12297
*/
123-
export async function searchThreadsWithDeletedFilter(searchParams, includeDeleted = false) {
98+
export async function searchThreadsWithDeletedFilter(searchParams) {
12499
const url = `${getDiscussionApiBaseUrl()}/threads/`;
125100

126101
const params = {
127102
...searchParams,
128-
// If includeDeleted is false, we'll get only non-deleted threads (default behavior)
129-
// If true, we need to handle this differently - perhaps by fetching both and merging
130-
// For now, we use the standard thread list endpoint
131103
};
132104

133105
return getAuthenticatedHttpClient().get(url, { params });
@@ -136,28 +108,19 @@ export async function searchThreadsWithDeletedFilter(searchParams, includeDelete
136108
/**
137109
* Soft delete a single comment by calling the DELETE endpoint
138110
* @param {string} commentId - The ID of the comment to soft delete
139-
* @param {string} userId - The ID of the user performing the action
140-
* @param {string} courseId - The course ID
141111
* @returns {Promise} API response
142112
*/
143-
export async function softDeleteComment(commentId, userId, courseId) {
113+
export async function softDeleteComment(commentId) {
144114
const url = `${getDiscussionApiBaseUrl()}/comments/${commentId}/`;
145-
console.log('[Service] softDeleteComment - About to DELETE:', url);
146-
console.log('[Service] LMS_BASE_URL:', getConfig().LMS_BASE_URL);
147-
148115
return getAuthenticatedHttpClient().delete(url);
149116
}
150117

151118
/**
152119
* Restore a soft deleted comment
153120
* @param {string} commentId - The ID of the comment to restore
154-
* @param {string} courseId - The course ID
155121
* @returns {Promise} API response
156122
*/
157-
export async function restoreComment(commentId, courseId) {
123+
export async function restoreComment(commentId) {
158124
const url = `${getDiscussionApiBaseUrl()}/comments/${commentId}/restore/`;
159-
console.log('[Service] restoreComment - About to POST:', url);
160-
console.log('[Service] LMS_BASE_URL:', getConfig().LMS_BASE_URL);
161-
162125
return getAuthenticatedHttpClient().post(url, {});
163126
}

src/discussions/post-comments/comments/comment/Comment.jsx

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ import React, {
33
} from 'react';
44
import PropTypes from 'prop-types';
55

6-
import { Alert, Button, useToggle } from '@openedx/paragon';
6+
import { Button, useToggle } from '@openedx/paragon';
77
import { DeleteOutline } from '@openedx/paragon/icons';
88
import classNames from 'classnames';
99
import { useDispatch, useSelector } from 'react-redux';
1010

11-
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
1211
import { useIntl } from '@edx/frontend-platform/i18n';
1312
import { logError } from '@edx/frontend-platform/logging';
1413

@@ -110,19 +109,13 @@ const Comment = ({
110109
}, [abuseFlagged, id, showReportConfirmation]);
111110

112111
const handleDeleteConfirmation = useCallback(async () => {
113-
console.log('[Comment] handleDeleteConfirmation called', { commentId: id, courseId, threadId });
114112
try {
115-
const authenticatedUser = getAuthenticatedUser();
116-
console.log('[Comment] Authenticated user:', authenticatedUser);
117113
const { performSoftDeleteComment } = await import('../../data/thunks');
118-
console.log('[Comment] About to dispatch performSoftDeleteComment');
119-
const result = await dispatch(performSoftDeleteComment(id, authenticatedUser.userId || authenticatedUser.id, courseId));
120-
console.log('[Comment] performSoftDeleteComment result:', result);
114+
const result = await dispatch(performSoftDeleteComment(id));
121115
if (result.success) {
122116
await dispatch(fetchThread(threadId, courseId));
123117
}
124118
} catch (error) {
125-
console.error('[Comment] Error in handleDeleteConfirmation:', error);
126119
logError(error);
127120
}
128121
hideDeleteConfirmation();
@@ -146,18 +139,14 @@ const Comment = ({
146139
}, [showRestoreConfirmation]);
147140

148141
const handleRestoreConfirmation = useCallback(async () => {
149-
console.log('[Comment] handleRestoreConfirmation called', { commentId: id, courseId, threadId });
150142
try {
151143
const { performRestoreComment } = await import('../../data/thunks');
152-
console.log('[Comment] About to dispatch performRestoreComment');
153-
const result = await dispatch(performRestoreComment(id, courseId));
154-
console.log('[Comment] performRestoreComment result:', result);
144+
const result = await dispatch(performRestoreComment(id));
155145
if (result.success) {
156146
// Refresh the thread to reflect the change
157147
await dispatch(fetchThread(threadId, courseId));
158148
}
159149
} catch (error) {
160-
console.error('[Comment] Error in handleRestoreConfirmation:', error);
161150
logError(error);
162151
}
163152
hideRestoreConfirmation();
@@ -207,17 +196,25 @@ const Comment = ({
207196
>
208197
<Confirmation
209198
isOpen={isDeleting}
210-
title={intl.formatMessage(isNested ? messages.deleteCommentTitle : messages.deleteResponseTitle)}
211-
description={intl.formatMessage(isNested ? messages.deleteCommentDescription : messages.deleteResponseDescription)}
199+
title={intl.formatMessage(
200+
isNested ? messages.deleteCommentTitle : messages.deleteResponseTitle,
201+
)}
202+
description={intl.formatMessage(
203+
isNested ? messages.deleteCommentDescription : messages.deleteResponseDescription,
204+
)}
212205
onClose={hideDeleteConfirmation}
213206
confirmAction={handleDeleteConfirmation}
214207
closeButtonVariant="tertiary"
215208
confirmButtonText={intl.formatMessage(messages.deleteConfirmationDelete)}
216209
/>
217210
<Confirmation
218211
isOpen={isRestoring}
219-
title={intl.formatMessage(isNested ? messages.undeleteCommentTitle : messages.undeleteResponseTitle)}
220-
description={intl.formatMessage(isNested ? messages.undeleteCommentDescription : messages.undeleteResponseDescription)}
212+
title={intl.formatMessage(
213+
isNested ? messages.undeleteCommentTitle : messages.undeleteResponseTitle,
214+
)}
215+
description={intl.formatMessage(
216+
isNested ? messages.undeleteCommentDescription : messages.undeleteResponseDescription,
217+
)}
221218
onClose={hideRestoreConfirmation}
222219
confirmAction={handleRestoreConfirmation}
223220
closeButtonVariant="tertiary"

src/discussions/post-comments/comments/comment/Reply.jsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { Avatar, Badge, useToggle } from '@openedx/paragon';
77
import { useDispatch, useSelector } from 'react-redux';
88
import * as timeago from 'timeago.js';
99

10-
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
1110
import { useIntl } from '@edx/frontend-platform/i18n';
1211
import { logError } from '@edx/frontend-platform/logging';
1312

@@ -20,8 +19,8 @@ import DiscussionContext from '../../../common/context';
2019
import timeLocale from '../../../common/time-locale';
2120
import { ContentTypes } from '../../../data/constants';
2221
import { useAlertBannerVisible } from '../../../data/hooks';
23-
import { fetchThread } from '../../../data/thunks';
2422
import { selectAuthorAvatar, selectThread } from '../../../posts/data/selectors';
23+
import { fetchThread } from '../../../posts/data/thunks';
2524
import { selectCommentOrResponseById } from '../../data/selectors';
2625
import { editComment } from '../../data/thunks';
2726
import messages from '../../messages';
@@ -54,9 +53,8 @@ const Reply = ({ responseId }) => {
5453

5554
const handleDeleteConfirmation = useCallback(async () => {
5655
try {
57-
const authenticatedUser = getAuthenticatedUser();
5856
const { performSoftDeleteComment } = await import('../../data/thunks');
59-
const result = await dispatch(performSoftDeleteComment(id, authenticatedUser.userId || authenticatedUser.id, courseId));
57+
const result = await dispatch(performSoftDeleteComment(id));
6058
if (result.success) {
6159
await dispatch(fetchThread(threadId, courseId));
6260
}
@@ -98,7 +96,7 @@ const Reply = ({ responseId }) => {
9896
const handleRestoreConfirmation = useCallback(async () => {
9997
try {
10098
const { performRestoreComment } = await import('../../data/thunks');
101-
const result = await dispatch(performRestoreComment(id, courseId));
99+
const result = await dispatch(performRestoreComment(id));
102100
if (result.success) {
103101
await dispatch(fetchThread(threadId, courseId));
104102
}

src/discussions/post-comments/data/thunks.js

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -186,34 +186,26 @@ export function removeComment(commentId, threadId) {
186186
};
187187
}
188188

189-
export function performSoftDeleteComment(commentId, userId, courseId) {
189+
export function performSoftDeleteComment(commentId) {
190190
return async () => {
191-
console.log('[Thunk] performSoftDeleteComment called', { commentId, userId, courseId });
192191
try {
193192
const { softDeleteComment } = await import('../../data/services/softDeleteService');
194-
console.log('[Thunk] About to call softDeleteComment service');
195-
await softDeleteComment(commentId, userId, courseId);
196-
console.log('[Thunk] softDeleteComment service completed successfully');
193+
await softDeleteComment(commentId);
197194
return { success: true };
198195
} catch (error) {
199-
console.error('[Thunk] Error in performSoftDeleteComment:', error);
200196
logError(error);
201197
return { success: false, error: error.message };
202198
}
203199
};
204200
}
205201

206-
export function performRestoreComment(commentId, courseId) {
202+
export function performRestoreComment(commentId) {
207203
return async () => {
208-
console.log('[Thunk] performRestoreComment called', { commentId, courseId });
209204
try {
210205
const { restoreComment } = await import('../../data/services/softDeleteService');
211-
console.log('[Thunk] About to call restoreComment service');
212-
await restoreComment(commentId, courseId);
213-
console.log('[Thunk] restoreComment service completed successfully');
206+
await restoreComment(commentId);
214207
return { success: true };
215208
} catch (error) {
216-
console.error('[Thunk] Error in performRestoreComment:', error);
217209
logError(error);
218210
return { success: false, error: error.message };
219211
}

src/discussions/posts/data/thunks.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
import { setContentCreationRateLimited } from '../../data/slices';
88
import { getHttpErrorStatus } from '../../utils';
99
import {
10-
deleteThread,
1110
getThread,
1211
getThreads,
1312
postThread,
@@ -308,13 +307,13 @@ export function updateExistingThread(threadId, {
308307
};
309308
}
310309

311-
export function removeThread(threadId, courseId, userId) {
310+
export function removeThread(threadId) {
312311
return async (dispatch) => {
313312
try {
314313
dispatch(deleteThreadRequest({ threadId }));
315314
// Use soft delete instead of hard delete
316315
const { softDeleteThread } = await import('../../data/services/softDeleteService');
317-
await softDeleteThread(threadId, userId, courseId);
316+
await softDeleteThread(threadId);
318317
dispatch(deleteThreadSuccess({ threadId }));
319318
} catch (error) {
320319
if (getHttpErrorStatus(error) === 403) {

0 commit comments

Comments
 (0)