From b76b003e0194fa3948799cbd0bd3135853cc9e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Ch=C3=A1vez?= Date: Tue, 2 Dec 2025 17:00:06 -0500 Subject: [PATCH] refactor: Migrate Help Urls from Redux store to React Query (#2714) --- src/help-urls/__mocks__/helpUrls.js | 62 ++++++++++++++--------------- src/help-urls/data/api.js | 10 ----- src/help-urls/data/api.ts | 46 +++++++++++++++++++++ src/help-urls/data/apiHooks.ts | 13 ++++++ src/help-urls/data/selectors.js | 13 ------ src/help-urls/data/slice.js | 27 ------------- src/help-urls/data/thunks.js | 23 ----------- src/help-urls/hooks.jsx | 22 ---------- src/help-urls/hooks.tsx | 22 ++++++++++ src/store.ts | 3 -- 10 files changed, 112 insertions(+), 129 deletions(-) delete mode 100644 src/help-urls/data/api.js create mode 100644 src/help-urls/data/api.ts create mode 100644 src/help-urls/data/apiHooks.ts delete mode 100644 src/help-urls/data/selectors.js delete mode 100644 src/help-urls/data/slice.js delete mode 100644 src/help-urls/data/thunks.js delete mode 100644 src/help-urls/hooks.jsx create mode 100644 src/help-urls/hooks.tsx diff --git a/src/help-urls/__mocks__/helpUrls.js b/src/help-urls/__mocks__/helpUrls.js index 958f74c17..cfde6b800 100644 --- a/src/help-urls/__mocks__/helpUrls.js +++ b/src/help-urls/__mocks__/helpUrls.js @@ -1,35 +1,35 @@ module.exports = { - default: 'https://docs.openedx.org/en/latest/educators/index.html', - home: 'https://docs.openedx.org/en/latest/educators/navigation/content_creation_management.html', - develop_course: 'https://docs.openedx.org/en/latest/educators/references/course_content_development.html', - outline: 'https://docs.openedx.org/en/latest/educators/concepts/open_edx_platform/about_course_outline.html', - unit: 'https://docs.openedx.org/en/latest/educators/concepts/open_edx_platform/about_course_units.html', - visibility: 'https://docs.openedx.org/en/latest/educators/references/controlling_content_visibility.html', - updates: 'https://docs.openedx.org/en/latest/educators/concepts/communication/about_course_updates_handouts.html', - pages: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/manage_custom_page.html', - files: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/manage_course_files.html', - textbooks: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/manage_textbooks.html', - schedule: 'https://docs.openedx.org/en/latest/educators/references/course_development/about_page.html', - grading: 'https://docs.openedx.org/en/latest/educators/concepts/grading/about_graded_subsections.html', - team_course: 'https://docs.openedx.org/en/latest/educators/references/course_development/course_team_roles.html#guide-to-course-team-roles', - team_library: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/library_access.html', advanced: 'https://docs.openedx.org/en/latest/educators/navigation/advanced_features.html', - checklist: 'https://docs.openedx.org/en/latest/educators/quickstarts/build_a_course.html#quick-start-build-a-course', - import_library: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/export_import_library.html', - import_course: 'https://docs.openedx.org/en/latest/educators/how-tos/releasing-course/import_course.html', - export_library: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/export_import_library.html', - export_course: 'https://docs.openedx.org/en/latest/educators/how-tos/releasing-course/export_course.html', - welcome: 'https://docs.openedx.org/en/latest/educators/quickstarts/build_a_course.html#quick-start-build-a-course', - login: 'https://docs.openedx.org/en/latest/educators/quickstarts/build_a_course.html#quick-start-build-a-course', - register: 'https://docs.openedx.org/en/latest/educators/quickstarts/build_a_course.html#quick-start-build-a-course', - content_libraries: 'https://docs.openedx.org/en/latest/educators/navigation/content_creation_management.html#work-with-content-libraries', - content_groups: 'https://docs.openedx.org/en/latest/educators/navigation/advanced_features.html#manage-course-cohorts', - enrollment_tracks: 'https://docs.openedx.org/en/latest/educators/how-tos/advanced_features/create_content_for_specific_enrollment_tracks.html', - group_configurations: 'https://docs.openedx.org/en/latest/educators/concepts/advanced_features/about_group_configurations.html', - container: 'https://docs.openedx.org/en/latest/educators/references/course_development/parent_child_components.html', - video: 'https://docs.openedx.org/en/latest/educators/navigation/content_creation_management.html#manage-video-components', certificates: 'https://docs.openedx.org/en/latest/educators/navigation/creating_course.html#set-up-course-certificates', - content_highlights: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/manage_course_highlight_emails.html#set-course-section-highlights', - image_accessibility: 'https://docs.openedx.org/en/latest/educators/references/accessibility/accessibility_best_practices_checklist.html#use-best-practices-for-describing-images', - social_sharing: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/social_sharing.html', + checklist: 'https://docs.openedx.org/en/latest/educators/quickstarts/build_a_course.html#quick-start-build-a-course', + container: 'https://docs.openedx.org/en/latest/educators/references/course_development/parent_child_components.html', + contentGroups: 'https://docs.openedx.org/en/latest/educators/navigation/advanced_features.html#manage-course-cohorts', + contentHighlights: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/manage_course_highlight_emails.html#set-course-section-highlights', + contentLibraries: 'https://docs.openedx.org/en/latest/educators/navigation/content_creation_management.html#work-with-content-libraries', + default: 'https://docs.openedx.org/en/latest/educators/index.html', + developCourse: 'https://docs.openedx.org/en/latest/educators/references/course_content_development.html', + enrollmentTracks: 'https://docs.openedx.org/en/latest/educators/how-tos/advanced_features/create_content_for_specific_enrollment_tracks.html', + exportCourse: 'https://docs.openedx.org/en/latest/educators/how-tos/releasing-course/export_course.html', + exportLibrary: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/export_import_library.html', + files: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/manage_course_files.html', + grading: 'https://docs.openedx.org/en/latest/educators/concepts/grading/about_graded_subsections.html', + groupConfigurations: 'https://docs.openedx.org/en/latest/educators/concepts/advanced_features/about_group_configurations.html', + home: 'https://docs.openedx.org/en/latest/educators/navigation/content_creation_management.html', + imageAccessibility: 'https://docs.openedx.org/en/latest/educators/references/imageAccessibility/image_accessibility.html', + importCourse: 'https://docs.openedx.org/en/latest/educators/how-tos/releasing-course/import_course.html', + importLibrary: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/export_import_library.html', + login: 'https://docs.openedx.org/en/latest/educators/quickstarts/build_a_course.html#quick-start-build-a-course', + outline: 'https://docs.openedx.org/en/latest/educators/concepts/open_edx_platform/about_course_outline.html', + pages: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/manage_custom_page.html', + register: 'https://docs.openedx.org/en/latest/educators/quickstarts/build_a_course.html#quick-start-build-a-course', + schedule: 'https://docs.openedx.org/en/latest/educators/references/course_development/about_page.html', + socialSharing: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/social_sharing.html', + teamCourse: 'https://docs.openedx.org/en/latest/educators/references/course_development/course_team_roles.html#guide-to-course-team-roles', + teamLibrary: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/library_access.html', + textbooks: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/manage_textbooks.html', + unit: 'https://docs.openedx.org/en/latest/educators/concepts/open_edx_platform/about_course_units.html', + updates: 'https://docs.openedx.org/en/latest/educators/concepts/communication/about_course_updates_handouts.html', + video: 'https://docs.openedx.org/en/latest/educators/navigation/content_creation_management.html#manage-video-components', + visibility: 'https://docs.openedx.org/en/latest/educators/references/controlling_content_visibility.html', + welcome: 'https://docs.openedx.org/en/latest/educators/quickstarts/build_a_course.html#quick-start-build-a-course', }; diff --git a/src/help-urls/data/api.js b/src/help-urls/data/api.js deleted file mode 100644 index 2dc9a855f..000000000 --- a/src/help-urls/data/api.js +++ /dev/null @@ -1,10 +0,0 @@ -import { camelCaseObject, getConfig } from '@edx/frontend-platform'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; - -export const getHelpUrlsApiUrl = () => `${getConfig().STUDIO_BASE_URL}/api/contentstore/v1/help_urls`; - -export async function getHelpUrls() { - const { data } = await getAuthenticatedHttpClient() - .get(getHelpUrlsApiUrl()); - return camelCaseObject(data); -} diff --git a/src/help-urls/data/api.ts b/src/help-urls/data/api.ts new file mode 100644 index 000000000..961721838 --- /dev/null +++ b/src/help-urls/data/api.ts @@ -0,0 +1,46 @@ +import { camelCaseObject, getConfig } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; + +export const getHelpUrlsApiUrl = () => `${getConfig().STUDIO_BASE_URL}/api/contentstore/v1/help_urls`; + +export interface HelpUrls { + advanced: string; + certificates: string; + checklist: string; + container: string; + contentGroups: string; + contentHighlights: string; + contentLibraries: string; + default: string; + developCourse: string; + enrollmentTracks: string; + exportCourse: string; + exportLibrary: string; + files: string; + grading: string; + groupConfigurations: string; + home: string; + imageAccessibility: string; + importCourse: string; + importLibrary: string; + login: string; + outline: string; + pages: string; + register: string; + schedule: string; + socialSharing: string; + teamCourse: string; + teamLibrary: string; + textbooks: string; + unit: string; + updates: string; + video: string; + visibility: string; + welcome: string; +} + +export async function getHelpUrls(): Promise { + const { data } = await getAuthenticatedHttpClient() + .get(getHelpUrlsApiUrl()); + return camelCaseObject(data); +} diff --git a/src/help-urls/data/apiHooks.ts b/src/help-urls/data/apiHooks.ts new file mode 100644 index 000000000..a58dfc86c --- /dev/null +++ b/src/help-urls/data/apiHooks.ts @@ -0,0 +1,13 @@ +import { useQuery } from '@tanstack/react-query'; + +import * as api from './api'; + +/** + * Hook to fetch all help urls + */ +export const useAllHelpUrls = () => ( + useQuery({ + queryKey: ['helpURLs'], + queryFn: api.getHelpUrls, + }) +); diff --git a/src/help-urls/data/selectors.js b/src/help-urls/data/selectors.js deleted file mode 100644 index cd88d4e72..000000000 --- a/src/help-urls/data/selectors.js +++ /dev/null @@ -1,13 +0,0 @@ -export const selectHelpUrlsByNames = (names) => (state) => { - const urlsDictionary = {}; - - names.forEach(name => { - urlsDictionary[name] = state.helpUrls.pages[name] || null; - }); - - return urlsDictionary; -}; - -export const getPages = (state) => state.helpUrls.pages; - -export const getLoadingHelpUrlsStatus = (state) => state.helpUrls.loadingHelpUrlsStatus; diff --git a/src/help-urls/data/slice.js b/src/help-urls/data/slice.js deleted file mode 100644 index 01c8821e5..000000000 --- a/src/help-urls/data/slice.js +++ /dev/null @@ -1,27 +0,0 @@ -/* eslint-disable no-param-reassign */ -import { createSlice } from '@reduxjs/toolkit'; - -const initialState = { - loadingHelpUrlsStatus: '', - pages: {}, -}; - -const slice = createSlice({ - name: 'helpUrls', - initialState, - reducers: { - updatePages: (state, { payload }) => { - state.pages = payload; - }, - updateLoadingHelpUrlsStatus: (state, { payload }) => { - state.loadingHelpUrlsStatus = payload.status; - }, - }, -}); - -export const { - updatePages, - updateLoadingHelpUrlsStatus, -} = slice.actions; - -export const { reducer } = slice; diff --git a/src/help-urls/data/thunks.js b/src/help-urls/data/thunks.js deleted file mode 100644 index 0f5153695..000000000 --- a/src/help-urls/data/thunks.js +++ /dev/null @@ -1,23 +0,0 @@ -import { RequestStatus } from '../../data/constants'; - -import { getHelpUrls } from './api'; -import { updateLoadingHelpUrlsStatus, updatePages } from './slice'; - -export function fetchHelpUrls() { - return async (dispatch) => { - dispatch(updateLoadingHelpUrlsStatus({ status: RequestStatus.IN_PROGRESS })); - - try { - const urls = await getHelpUrls(); - - dispatch(updatePages(urls)); - - dispatch(updateLoadingHelpUrlsStatus({ status: RequestStatus.SUCCESSFUL })); - return true; - } catch { - dispatch(updateLoadingHelpUrlsStatus({ status: RequestStatus.FAILED })); - - return false; - } - }; -} diff --git a/src/help-urls/hooks.jsx b/src/help-urls/hooks.jsx deleted file mode 100644 index d8d23f963..000000000 --- a/src/help-urls/hooks.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import { useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { isEmpty } from 'lodash'; - -import { fetchHelpUrls } from './data/thunks'; -import { getPages, selectHelpUrlsByNames } from './data/selectors'; - -const useHelpUrls = (tokenNames) => { - const dispatch = useDispatch(); - const helpTokens = useSelector(selectHelpUrlsByNames(tokenNames)); - const pages = useSelector(getPages); - - useEffect(() => { - if (isEmpty(pages)) { - dispatch(fetchHelpUrls()); - } - }, []); - - return helpTokens; -}; - -export { useHelpUrls }; diff --git a/src/help-urls/hooks.tsx b/src/help-urls/hooks.tsx new file mode 100644 index 000000000..eaef5dd26 --- /dev/null +++ b/src/help-urls/hooks.tsx @@ -0,0 +1,22 @@ +import { HelpUrls } from './data/api'; +import { useAllHelpUrls } from './data/apiHooks'; + +const useHelpUrls = (tokenNames: T & (keyof HelpUrls)[]): { + [K in T[number]]?: K extends keyof HelpUrls ? string : null +} => { + const { + data: pages, + } = useAllHelpUrls(); + + const urlsDictionary = {}; + + if (pages) { + tokenNames.forEach(name => { + urlsDictionary[name] = pages[name] || null; + }); + } + + return urlsDictionary; +}; + +export { useHelpUrls }; diff --git a/src/store.ts b/src/store.ts index 003e141dc..416884cd1 100644 --- a/src/store.ts +++ b/src/store.ts @@ -16,7 +16,6 @@ import { reducer as filesReducer } from './files-and-videos/files-page/data/slic import { reducer as courseTeamReducer } from './course-team/data/slice'; import { reducer as CourseUpdatesReducer } from './course-updates/data/slice'; import { reducer as processingNotificationReducer } from './generic/processing-notification/data/slice'; -import { reducer as helpUrlsReducer } from './help-urls/data/slice'; import { reducer as courseExportReducer } from './export-page/data/slice'; import { reducer as courseOptimizerReducer } from './optimizer-page/data/slice'; import { reducer as genericReducer } from './generic/data/slice'; @@ -50,7 +49,6 @@ export interface DeprecatedReduxState { courseTeam: Record; courseUpdates: Record; processingNotification: Record; - helpUrls: Record; courseExport: Record; courseOptimizer: Record; generic: Record; @@ -81,7 +79,6 @@ export default function initializeStore(preloadedState: Partial