diff --git a/src/course-outline/CourseOutline.test.jsx b/src/course-outline/CourseOutline.test.jsx index cfd019191..29ab59b91 100644 --- a/src/course-outline/CourseOutline.test.jsx +++ b/src/course-outline/CourseOutline.test.jsx @@ -27,7 +27,7 @@ import { RequestStatus } from '../data/constants'; import { fetchCourseBestPracticesQuery, fetchCourseLaunchQuery, - fetchCourseOutlineIndexQuery, + fetchCourseOutlineIndexQuery, syncDiscussionsTopics, updateCourseSectionHighlightsQuery, } from './data/thunk'; import initializeStore from '../store'; @@ -126,6 +126,10 @@ jest.mock('@dnd-kit/core', () => ({ closestCorners: jest.fn(), })); +jest.mock('./data/api', () => ({ + ...jest.requireActual('./data/api'), + createDiscussionsTopics: jest.fn().mockResolvedValue(undefined), +})); // eslint-disable-next-line no-promise-executor-return const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); @@ -173,6 +177,7 @@ describe('', () => { })) .reply(200, courseLaunchMock); await executeThunk(fetchCourseOutlineIndexQuery(courseId), store.dispatch); + await executeThunk(syncDiscussionsTopics(courseId), store.dispatch); }); afterEach(() => { diff --git a/src/course-outline/data/api.js b/src/course-outline/data/api.js index 2b2865c29..69bf8f018 100644 --- a/src/course-outline/data/api.js +++ b/src/course-outline/data/api.js @@ -59,6 +59,17 @@ export async function getCourseOutlineIndex(courseId) { return camelCaseObject(data); } +/** + * + * @param courseId + * @returns {Promise} + */ +export async function createDiscussionsTopics(courseId) { + const { data } = await getAuthenticatedHttpClient() + .post(`${getApiBaseUrl()}/api/discussions/v0/course/${courseId}/sync_discussion_topics`); + return camelCaseObject(data); +} + /** * Get course best practices. * @param {{courseId: string, excludeGraded: boolean, all: boolean}} options diff --git a/src/course-outline/data/selectors.js b/src/course-outline/data/selectors.js index 6ad81c7a4..4768d9959 100644 --- a/src/course-outline/data/selectors.js +++ b/src/course-outline/data/selectors.js @@ -11,3 +11,4 @@ export const getCustomRelativeDatesActiveFlag = (state) => state.courseOutline.i export const getProctoredExamsFlag = (state) => state.courseOutline.enableProctoredExams; export const getPasteFileNotices = (state) => state.courseOutline.pasteFileNotices; export const getErrors = (state) => state.courseOutline.errors; +export const getCreatedOn = (state) => state.courseOutline.createdOn; diff --git a/src/course-outline/data/slice.js b/src/course-outline/data/slice.js index 027555526..95d3c4235 100644 --- a/src/course-outline/data/slice.js +++ b/src/course-outline/data/slice.js @@ -47,6 +47,7 @@ const slice = createSlice({ }, enableProctoredExams: false, pasteFileNotices: {}, + createdOn: null, }, reducers: { fetchOutlineIndexSuccess: (state, { payload }) => { @@ -54,6 +55,7 @@ const slice = createSlice({ state.sectionsList = payload.courseStructure?.childInfo?.children || []; state.isCustomRelativeDatesActive = payload.isCustomRelativeDatesActive; state.enableProctoredExams = payload.courseStructure?.enableProctoredExams; + state.createdOn = payload.createdOn; }, updateOutlineIndexLoadingStatus: (state, { payload }) => { state.loadingStatus = { diff --git a/src/course-outline/data/thunk.js b/src/course-outline/data/thunk.js index 3091f3ec9..46f3ce4ba 100644 --- a/src/course-outline/data/thunk.js +++ b/src/course-outline/data/thunk.js @@ -30,7 +30,7 @@ import { setVideoSharingOption, setCourseItemOrderList, pasteBlock, - dismissNotification, + dismissNotification, createDiscussionsTopics, } from './api'; import { addSection, @@ -95,6 +95,17 @@ export function fetchCourseOutlineIndexQuery(courseId) { }; } +export function syncDiscussionsTopics(courseId) { + return async () => { + try { + await createDiscussionsTopics(courseId); + } catch (error) { + // eslint-disable-next-line no-console + console.log('There was an issue in discussion topic sync', error); + } + }; +} + export function fetchCourseLaunchQuery({ courseId, gradedOnly = true, diff --git a/src/course-outline/hooks.jsx b/src/course-outline/hooks.jsx index 55cdc69ad..25a9bb5be 100644 --- a/src/course-outline/hooks.jsx +++ b/src/course-outline/hooks.jsx @@ -4,6 +4,7 @@ import { useNavigate } from 'react-router-dom'; import { useToggle } from '@openedx/paragon'; import { getConfig } from '@edx/frontend-platform'; +import moment from 'moment'; import { getSavingStatus as getGenericSavingStatus } from '../generic/data/selectors'; import { getWaffleFlags } from '../data/selectors'; import { RequestStatus } from '../data/constants'; @@ -25,6 +26,7 @@ import { getCurrentSubsection, getCustomRelativeDatesActiveFlag, getErrors, + getCreatedOn, } from './data/selectors'; import { addNewSectionQuery, @@ -53,7 +55,7 @@ import { setUnitOrderListQuery, pasteClipboardContent, dismissNotificationQuery, - addUnitFromLibrary, + addUnitFromLibrary, syncDiscussionsTopics, } from './data/thunk'; const useCourseOutline = ({ courseId }) => { @@ -73,7 +75,7 @@ const useCourseOutline = ({ courseId }) => { mfeProctoredExamSettingsUrl, advanceSettingsUrl, } = useSelector(getOutlineIndexData); - + const createdOn = useSelector(getCreatedOn); const { outlineIndexLoadingStatus, reIndexLoadingStatus } = useSelector(getLoadingStatus); const statusBarData = useSelector(getStatusBarData); const savingStatus = useSelector(getSavingStatus); @@ -292,6 +294,12 @@ const useCourseOutline = ({ courseId }) => { dispatch(fetchCourseLaunchQuery({ courseId })); }, [courseId]); + useEffect(() => { + if (createdOn && moment(new Date(createdOn)).isAfter(moment().subtract(31, 'days'))) { + dispatch(syncDiscussionsTopics); + } + }, [createdOn]); + useEffect(() => { setShowSuccessAlert(reIndexLoadingStatus === RequestStatus.SUCCESSFUL); }, [reIndexLoadingStatus]);