Files
frontend-app-discussions/src/discussions/posts/data/thunks.js

302 lines
7.6 KiB
JavaScript

/* eslint-disable import/prefer-default-export */
import { camelCaseObject } from '@edx/frontend-platform';
import { logError } from '@edx/frontend-platform/logging';
import {
PostsStatusFilter, ThreadType,
} from '../../../data/constants';
import { getHttpErrorStatus } from '../../utils';
import {
deleteThread, getThread, getThreads, postThread, updateThread,
} from './api';
import {
deleteThreadDenied,
deleteThreadFailed,
deleteThreadRequest,
deleteThreadSuccess,
fetchThreadByDirectLinkSuccess,
fetchThreadDenied,
fetchThreadFailed,
fetchThreadRequest,
fetchThreadsDenied,
fetchThreadsFailed,
fetchThreadsRequest,
fetchThreadsSuccess,
fetchThreadSuccess,
postThreadDenied,
postThreadFailed,
postThreadRequest,
postThreadSuccess,
updateThreadAsRead,
updateThreadDenied,
updateThreadFailed,
updateThreadRequest,
updateThreadSuccess,
} from './slices';
/**
* Filters to apply to a thread/posts query.
* @typedef {Object} ThreadFilter
* @property {PostsStatusFilter} status
* @property {ThreadType} postType
*/
/**
* Normalises raw data returned by threads API by mapping threads to id and
* mapping topic ids to threads in them.
* @param data
* @param {[string]?} topicIds
* @returns {{pagination, threadsById: {}, threadsInTopic: {}, avatars: {}}}
*/
export function normaliseThreads(data, topicIds = null) {
const normalized = {};
let threads;
if ('results' in data) {
threads = data.results;
normalized.pagination = data.pagination;
} else {
threads = [data];
}
const threadsInTopic = {};
const threadsById = {};
let avatars = {};
const ids = [];
if (topicIds) {
topicIds.forEach(topicId => {
threadsInTopic[topicId] = [];
});
}
threads.forEach(
thread => {
const { topicId, id } = thread;
ids.push(id);
if (!threadsInTopic[topicId]) {
threadsInTopic[topicId] = [];
}
if (!threadsInTopic[topicId].includes(id)) {
threadsInTopic[topicId].push(id);
}
threadsById[id] = thread;
avatars = { ...avatars, ...thread.users };
},
);
return {
ids, threadsById, threadsInTopic, avatars, ...normalized,
};
}
/**
* Fetches the threads for the course specified va the threadIds.
* @param {string} courseId The course ID for the course to fetch data for.
* @param {?string} author The author whose posts need to be viewed.
* @param {[string]} topicIds List of topics to limit threads to
* @param {ThreadOrdering} orderBy The results will be sorted on this basis.
* @param {ThreadFilter} filters The set of filters to apply to the thread.
* @param {number} page Page to fetch
* @param {boolean} countFlagged
* @returns {(function(*): Promise<void>)|*}
*/
export function fetchThreads(courseId, {
topicIds,
orderBy,
author = null,
filters = {},
page = 1,
isFilterChanged,
countFlagged,
} = {}) {
const options = {
orderBy,
topicIds,
page,
author,
countFlagged,
};
if (filters.status === PostsStatusFilter.FOLLOWING) {
options.following = true;
}
if (filters.status === PostsStatusFilter.UNREAD) {
options.view = 'unread';
}
if (filters.status === PostsStatusFilter.UNANSWERED) {
options.view = 'unanswered';
}
if (filters.status === PostsStatusFilter.UNRESPONDED) {
options.view = 'unresponded';
}
if (filters.status === PostsStatusFilter.REPORTED) {
options.flagged = true;
}
if (filters.postType !== ThreadType.ALL) {
options.threadType = filters.postType;
}
if (filters.search) {
options.textSearch = filters.search;
}
if (filters.cohort) {
options.cohort = filters.cohort;
}
return async (dispatch) => {
try {
dispatch(fetchThreadsRequest({ courseId }));
const data = await getThreads(courseId, options);
const normalisedData = normaliseThreads(camelCaseObject(data), topicIds);
dispatch(fetchThreadsSuccess({
...normalisedData, page, author, textSearchRewrite: data.text_search_rewrite, isFilterChanged,
}));
} catch (error) {
if (getHttpErrorStatus(error) === 403) {
dispatch(fetchThreadsDenied());
} else {
dispatch(fetchThreadsFailed());
}
logError(error);
}
};
}
export function fetchThread(threadId, courseId, isDirectLinkPost = false) {
return async (dispatch) => {
try {
dispatch(fetchThreadRequest({ threadId }));
const data = await getThread(threadId, courseId);
if (isDirectLinkPost) {
dispatch(fetchThreadByDirectLinkSuccess({ ...normaliseThreads(camelCaseObject(data)), page: 1 }));
} else {
dispatch(fetchThreadSuccess(normaliseThreads(camelCaseObject(data))));
}
} catch (error) {
if (getHttpErrorStatus(error) === 403 || getHttpErrorStatus(error) === 404) {
dispatch(fetchThreadDenied());
} else {
dispatch(fetchThreadFailed());
}
logError(error);
}
};
}
export function markThreadAsRead(threadId) {
return async (dispatch) => {
try {
dispatch(updateThreadAsRead({ threadId }));
dispatch(updateThreadRequest({ threadId, read: true }));
const data = await updateThread(threadId, { read: true });
dispatch(updateThreadSuccess(camelCaseObject(data)));
} catch (error) {
if (getHttpErrorStatus(error) === 403) {
dispatch(updateThreadDenied());
} else {
dispatch(updateThreadFailed());
}
logError(error);
}
};
}
export function createNewThread({
courseId,
topicId,
type,
title,
content,
following,
anonymous,
anonymousToPeers,
cohort,
enableInContextSidebar,
}) {
return async (dispatch) => {
try {
dispatch(postThreadRequest({
courseId,
topicId,
type,
title,
content,
following,
anonymous,
anonymousToPeers,
cohort,
}));
const data = await postThread(courseId, topicId, type, title, content, {
cohort,
following,
anonymous,
anonymousToPeers,
}, enableInContextSidebar);
dispatch(postThreadSuccess(camelCaseObject(data)));
} catch (error) {
if (getHttpErrorStatus(error) === 403) {
dispatch(postThreadDenied());
} else {
dispatch(postThreadFailed());
}
logError(error);
}
};
}
export function updateExistingThread(threadId, {
flagged, voted, read, topicId, type, title, content, following, closed, pinned, closeReasonCode, editReasonCode,
}) {
return async (dispatch) => {
try {
dispatch(updateThreadRequest({
threadId,
flagged,
voted,
read,
topicId,
type,
title,
content,
following,
closed,
pinned,
editReasonCode,
closeReasonCode,
}));
const data = await updateThread(threadId, {
flagged,
voted,
read,
topicId,
type,
title,
content,
following,
closed,
pinned,
editReasonCode,
closeReasonCode,
});
dispatch(updateThreadSuccess(camelCaseObject(data)));
} catch (error) {
if (getHttpErrorStatus(error) === 403) {
dispatch(updateThreadDenied());
} else {
dispatch(updateThreadFailed());
}
logError(error);
}
};
}
export function removeThread(threadId) {
return async (dispatch) => {
try {
dispatch(deleteThreadRequest({ threadId }));
await deleteThread(threadId);
dispatch(deleteThreadSuccess({ threadId }));
} catch (error) {
if (getHttpErrorStatus(error) === 403) {
dispatch(deleteThreadDenied());
} else {
dispatch(deleteThreadFailed());
}
logError(error);
}
};
}