From 4191ecb5d0047b49e0534bb3bef48b33812887ec Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Mon, 27 May 2024 20:13:28 +0300 Subject: [PATCH 01/88] feat: Add lib v2/legacy tabs in studio home When lib mode is set to "mixed", both "Libraries" and "Legacy Libraries" tabs are show in the Studio Home. When "Libraries" is clicked, v2 libraries are fetched, when "Legacy Libraries" is clicked, v1 libraries are fetched. When lib mode is set to "v1 only" or "v2 only", only one tab "Libraries" is show and only the respective libraries are fetched when the tab is clicked. --- src/studio-home/StudioHome.jsx | 9 ++- src/studio-home/data/api.js | 5 ++ src/studio-home/data/apiHooks.ts | 13 +++++ .../tabs-section/TabsSection.test.jsx | 12 ++-- src/studio-home/tabs-section/index.jsx | 47 ++++++++++----- .../tabs-section/libraries-v2-tab/index.tsx | 58 +++++++++++++++++++ src/studio-home/tabs-section/messages.js | 4 ++ src/studio-home/tabs-section/utils.js | 10 +++- 8 files changed, 134 insertions(+), 24 deletions(-) create mode 100644 src/studio-home/data/apiHooks.ts create mode 100644 src/studio-home/tabs-section/libraries-v2-tab/index.tsx diff --git a/src/studio-home/StudioHome.jsx b/src/studio-home/StudioHome.jsx index 8348aaca34..acc5cd1174 100644 --- a/src/studio-home/StudioHome.jsx +++ b/src/studio-home/StudioHome.jsx @@ -18,6 +18,7 @@ import Header from '../header'; import SubHeader from '../generic/sub-header/SubHeader'; import HomeSidebar from './home-sidebar'; import TabsSection from './tabs-section'; +import { isMixedOrV2LibrariesMode } from './tabs-section/utils'; import OrganizationSection from './organization-section'; import VerifyEmailLayout from './verify-email-layout'; import CreateNewCourseForm from './create-new-course-form'; @@ -43,12 +44,14 @@ const StudioHome = ({ intl }) => { dispatch, } = useStudioHome(isPaginationCoursesEnabled); + // TODO: this should be a flag in the backend + const LIB_MODE = 'mixed'; + const { userIsActive, studioShortName, studioRequestEmail, libraryAuthoringMfeUrl, - redirectToLibraryAuthoringMfe, } = studioHomeData; function getHeaderButtons() { @@ -79,8 +82,8 @@ const StudioHome = ({ intl }) => { } let libraryHref = `${getConfig().STUDIO_BASE_URL}/home_library`; - if (redirectToLibraryAuthoringMfe) { - libraryHref = `${libraryAuthoringMfeUrl}/create`; + if (isMixedOrV2LibrariesMode(LIB_MODE)) { + libraryHref = `${libraryAuthoringMfeUrl}create`; } headerButtons.push( diff --git a/src/studio-home/data/api.js b/src/studio-home/data/api.js index 1fefe2981a..0c09601d11 100644 --- a/src/studio-home/data/api.js +++ b/src/studio-home/data/api.js @@ -40,6 +40,11 @@ export async function getStudioHomeLibraries() { return camelCaseObject(data); } +export async function getStudioHomeLibrariesV2() { + const { data } = await getAuthenticatedHttpClient().get(`${getApiBaseUrl()}/api/libraries/v2/`); + return camelCaseObject(data); +} + /** * Handle course notification requests. * @param {string} url diff --git a/src/studio-home/data/apiHooks.ts b/src/studio-home/data/apiHooks.ts new file mode 100644 index 0000000000..7285874c64 --- /dev/null +++ b/src/studio-home/data/apiHooks.ts @@ -0,0 +1,13 @@ +import { useQuery } from '@tanstack/react-query'; + +import { getStudioHomeLibrariesV2 } from './api'; + +/** + * Builds the query to fetch list of V2 Libraries + */ +export const useListStudioHomeV2Libraries = () => ( + useQuery({ + queryKey: ['listV2Libraries'], + queryFn: () => getStudioHomeLibrariesV2(), + }) +); diff --git a/src/studio-home/tabs-section/TabsSection.test.jsx b/src/studio-home/tabs-section/TabsSection.test.jsx index fdc955d8df..ea5929aeec 100644 --- a/src/studio-home/tabs-section/TabsSection.test.jsx +++ b/src/studio-home/tabs-section/TabsSection.test.jsx @@ -80,7 +80,7 @@ describe('', () => { expect(screen.getByText(tabMessages.coursesTabTitle.defaultMessage)).toBeInTheDocument(); - expect(screen.getByText(tabMessages.librariesTabTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(tabMessages.legacyLibrariesTabTitle.defaultMessage)).toBeInTheDocument(); expect(screen.getByText(tabMessages.archivedTabTitle.defaultMessage)).toBeInTheDocument(); }); @@ -222,7 +222,7 @@ describe('', () => { expect(screen.getByText(tabMessages.coursesTabTitle.defaultMessage)).toBeInTheDocument(); - expect(screen.getByText(tabMessages.librariesTabTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(tabMessages.legacyLibrariesTabTitle.defaultMessage)).toBeInTheDocument(); expect(screen.queryByText(tabMessages.archivedTabTitle.defaultMessage)).toBeNull(); }); @@ -236,7 +236,7 @@ describe('', () => { await executeThunk(fetchStudioHomeData(), store.dispatch); await executeThunk(fetchLibraryData(), store.dispatch); - const librariesTab = screen.getByText(tabMessages.librariesTabTitle.defaultMessage); + const librariesTab = screen.getByText(tabMessages.legacyLibrariesTabTitle.defaultMessage); await act(async () => { fireEvent.click(librariesTab); }); @@ -257,7 +257,7 @@ describe('', () => { await executeThunk(fetchStudioHomeData(), store.dispatch); expect(screen.getByText(tabMessages.coursesTabTitle.defaultMessage)).toBeInTheDocument(); - expect(screen.queryByText(tabMessages.librariesTabTitle.defaultMessage)).toBeNull(); + expect(screen.queryByText(tabMessages.legacyLibrariesTabTitle.defaultMessage)).toBeNull(); }); it('should redirect to library authoring mfe', async () => { @@ -268,7 +268,7 @@ describe('', () => { axiosMock.onGet(getStudioHomeApiUrl()).reply(200, data); await executeThunk(fetchStudioHomeData(), store.dispatch); - const librariesTab = screen.getByText(tabMessages.librariesTabTitle.defaultMessage); + const librariesTab = screen.getByText(tabMessages.legacyLibrariesTabTitle.defaultMessage); fireEvent.click(librariesTab); waitFor(() => { @@ -283,7 +283,7 @@ describe('', () => { await executeThunk(fetchStudioHomeData(), store.dispatch); await executeThunk(fetchLibraryData(), store.dispatch); - const librariesTab = screen.getByText(tabMessages.librariesTabTitle.defaultMessage); + const librariesTab = screen.getByText(tabMessages.legacyLibrariesTabTitle.defaultMessage); await act(async () => { fireEvent.click(librariesTab); }); diff --git a/src/studio-home/tabs-section/index.jsx b/src/studio-home/tabs-section/index.jsx index 1409766c47..789bb2bea1 100644 --- a/src/studio-home/tabs-section/index.jsx +++ b/src/studio-home/tabs-section/index.jsx @@ -9,10 +9,12 @@ import { useNavigate } from 'react-router-dom'; import { getLoadingStatuses, getStudioHomeData } from '../data/selectors'; import messages from './messages'; import LibrariesTab from './libraries-tab'; +import LibrariesV2Tab from './libraries-v2-tab/index.tsx'; import ArchivedTab from './archived-tab'; import CoursesTab from './courses-tab'; import { RequestStatus } from '../../data/constants'; import { fetchLibraryData } from '../data/thunks'; +import { isMixedOrV1LibrariesMode, isMixedOrV2LibrariesMode } from './utils'; const TabsSection = ({ intl, @@ -23,9 +25,14 @@ const TabsSection = ({ isPaginationCoursesEnabled, }) => { const navigate = useNavigate(); + + // TODO: this should be a flag in the backend + const LIB_MODE = 'mixed'; + const TABS_LIST = { courses: 'courses', libraries: 'libraries', + legacyLibraries: 'legacyLibraries', archived: 'archived', taxonomies: 'taxonomies', }; @@ -87,21 +94,37 @@ const TabsSection = ({ } if (librariesEnabled) { - tabs.push( - - {!redirectToLibraryAuthoringMfe && ( + if (isMixedOrV2LibrariesMode(LIB_MODE)) { + tabs.push( + + + , + ); + } + + if (isMixedOrV1LibrariesMode(LIB_MODE)) { + tabs.push( + - )} - , - ); + , + ); + } } if (getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true') { @@ -118,9 +141,7 @@ const TabsSection = ({ }, [archivedCourses, librariesEnabled, showNewCourseContainer, isLoadingCourses, isLoadingLibraries]); const handleSelectTab = (tab) => { - if (tab === TABS_LIST.libraries && redirectToLibraryAuthoringMfe) { - window.location.assign(libraryAuthoringMfeUrl); - } else if (tab === TABS_LIST.libraries && !redirectToLibraryAuthoringMfe) { + if (tab === TABS_LIST.legacyLibraries) { dispatch(fetchLibraryData()); } else if (tab === TABS_LIST.taxonomies) { navigate('/taxonomies'); diff --git a/src/studio-home/tabs-section/libraries-v2-tab/index.tsx b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx new file mode 100644 index 0000000000..1e14ffef6c --- /dev/null +++ b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { Icon, Row } from '@openedx/paragon'; +import { useIntl } from '@edx/frontend-platform/i18n'; + +import { useListStudioHomeV2Libraries } from '../../data/apiHooks'; +import { LoadingSpinner } from '../../../generic/Loading'; +import AlertMessage from '../../../generic/alert-message'; +import CardItem from '../../card-item'; +import messages from '../messages'; + +const LibrariesV2Tab = () => { + const intl = useIntl(); + const { + data, + isLoading, + isError, + } = useListStudioHomeV2Libraries(); + + if (isLoading) { + return ( + + + + ); + } + + return ( + isError ? ( + + + {intl.formatMessage(messages.librariesTabErrorMessage)} + + )} + /> + ) : ( +
+ {data.map(({ org, slug, title }) => ( + + ))} +
+ ) + ); +}; + + +export default LibrariesV2Tab; diff --git a/src/studio-home/tabs-section/messages.js b/src/studio-home/tabs-section/messages.js index 5ae2e139b2..e1ad0fd44f 100644 --- a/src/studio-home/tabs-section/messages.js +++ b/src/studio-home/tabs-section/messages.js @@ -21,6 +21,10 @@ const messages = defineMessages({ id: 'course-authoring.studio-home.libraries.tab.title', defaultMessage: 'Libraries', }, + legacyLibrariesTabTitle: { + id: 'course-authoring.studio-home.legacy.libraries.tab.title', + defaultMessage: 'Legacy Libraries', + }, archivedTabTitle: { id: 'course-authoring.studio-home.archived.tab.title', defaultMessage: 'Archived courses', diff --git a/src/studio-home/tabs-section/utils.js b/src/studio-home/tabs-section/utils.js index 5d3822b8ed..e7dea1ad69 100644 --- a/src/studio-home/tabs-section/utils.js +++ b/src/studio-home/tabs-section/utils.js @@ -8,5 +8,11 @@ const sortAlphabeticallyArray = (arr) => [...arr] .sort((firstArrayData, secondArrayData) => firstArrayData .displayName.localeCompare(secondArrayData.displayName)); -// eslint-disable-next-line import/prefer-default-export -export { sortAlphabeticallyArray }; +const isMixedOrV1LibrariesMode = (libMode) => ['mixed', 'v1 only'].includes(libMode); +const isMixedOrV2LibrariesMode = (libMode) => ['mixed', 'v2 only'].includes(libMode); + +export { + sortAlphabeticallyArray, + isMixedOrV1LibrariesMode, + isMixedOrV2LibrariesMode, +}; From 15c678b8fa6248dfba857bca098d3426e3878341 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Tue, 28 May 2024 17:31:08 +0300 Subject: [PATCH 02/88] feat: Add `LIBRARY_MODE` config variable This is to switch between different library modes. --- .env | 1 + .env.development | 1 + .env.test | 1 + README.rst | 16 ++++++++++++++++ .../feature-v2-and-legacy-libs.png | Bin 0 -> 246316 bytes src/index.jsx | 1 + src/studio-home/StudioHome.jsx | 5 ++--- src/studio-home/tabs-section/index.jsx | 11 ++++------- 8 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 docs/readme-images/feature-v2-and-legacy-libs.png diff --git a/.env b/.env index ce17454708..4235461134 100644 --- a/.env +++ b/.env @@ -43,3 +43,4 @@ AI_TRANSLATIONS_BASE_URL='' ENABLE_HOME_PAGE_COURSE_API_V2=false ENABLE_CHECKLIST_QUALITY='' ENABLE_GRADING_METHOD_IN_PROBLEMS=false +LIBRARY_MODE="v1 only" diff --git a/.env.development b/.env.development index 983ce9674f..5547e8ffec 100644 --- a/.env.development +++ b/.env.development @@ -46,3 +46,4 @@ AI_TRANSLATIONS_BASE_URL='http://localhost:18760' ENABLE_HOME_PAGE_COURSE_API_V2=false ENABLE_CHECKLIST_QUALITY=true ENABLE_GRADING_METHOD_IN_PROBLEMS=false +LIBRARY_MODE="mixed" diff --git a/.env.test b/.env.test index 28240ad2ff..0f73517968 100644 --- a/.env.test +++ b/.env.test @@ -37,3 +37,4 @@ INVITE_STUDENTS_EMAIL_TO="someone@domain.com" ENABLE_HOME_PAGE_COURSE_API_V2=true ENABLE_CHECKLIST_QUALITY=true ENABLE_GRADING_METHOD_IN_PROBLEMS=false +LIBRARY_MODE="mixed" diff --git a/README.rst b/README.rst index 3847453ea3..6f1de194b4 100644 --- a/README.rst +++ b/README.rst @@ -264,6 +264,22 @@ In additional to the standard settings, the following local configuration items Tagging/Taxonomy functionality. +Feature: Libraries V2/Legacy Tabs +================================= + +.. image:: ./docs/readme-images/feature-v2-and-legacy-libs.png + +Configuration +------------- + +In additional to the standard settings, the following local configurations can be set to switch between different library modes: + +* ``LIBRARY_MODE``: can be set to ``mixed`` (default for development), ``v1 only`` (default for production) and ``v2 only``. + + * ``mixed``: Shows 2 tabs, "Libraries" that lists the v2 libraries and "Legacy Libraries" that lists the v1 libraries. When creating a new library in this mode it will create a new v2 library. + * ``v1 only``: Shows only 1 tab, "Libraries" that lists v1 libraries only. When creating a new library in this mode it will create a new v1 library. + * ``v2 only``: Shows only 1 tab, "Libraries" that lists v2 libraries only. When creating a new library in this mode it will create a new v2 library. + Developing ********** diff --git a/docs/readme-images/feature-v2-and-legacy-libs.png b/docs/readme-images/feature-v2-and-legacy-libs.png new file mode 100644 index 0000000000000000000000000000000000000000..c8fd363655f7e4fc0b026bc9c7f9faf0df045e7a GIT binary patch literal 246316 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
-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

wa;w%Q#(cB+nr}wMmuC0|@ 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 { dispatch, } = useStudioHome(isPaginationCoursesEnabled); - // TODO: this should be a flag in the backend - const LIB_MODE = 'mixed'; + const libMode = getConfig().LIBRARY_MODE; const { userIsActive, @@ -82,7 +81,7 @@ const StudioHome = ({ intl }) => { } let libraryHref = `${getConfig().STUDIO_BASE_URL}/home_library`; - if (isMixedOrV2LibrariesMode(LIB_MODE)) { + if (isMixedOrV2LibrariesMode(libMode)) { libraryHref = `${libraryAuthoringMfeUrl}create`; } diff --git a/src/studio-home/tabs-section/index.jsx b/src/studio-home/tabs-section/index.jsx index 789bb2bea1..997796913f 100644 --- a/src/studio-home/tabs-section/index.jsx +++ b/src/studio-home/tabs-section/index.jsx @@ -25,10 +25,7 @@ const TabsSection = ({ isPaginationCoursesEnabled, }) => { const navigate = useNavigate(); - - // TODO: this should be a flag in the backend - const LIB_MODE = 'mixed'; - + const libMode = getConfig().LIBRARY_MODE; const TABS_LIST = { courses: 'courses', libraries: 'libraries', @@ -94,7 +91,7 @@ const TabsSection = ({ } if (librariesEnabled) { - if (isMixedOrV2LibrariesMode(LIB_MODE)) { + if (isMixedOrV2LibrariesMode(libMode)) { tabs.push( Date: Tue, 28 May 2024 21:23:39 +0300 Subject: [PATCH 03/88] feat: Add url paths/navigation for each tab The path updates when selecting tabs, when accessing the url with the path directly it will open its respective tab. Navigating using the browser back/forward buttons is also supported. --- src/index.jsx | 2 ++ src/studio-home/data/api.js | 4 +++ src/studio-home/tabs-section/index.jsx | 48 +++++++++++++++++++++++--- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/index.jsx b/src/index.jsx index e3d21096a3..f17c0563ab 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -52,6 +52,8 @@ const App = () => { createRoutesFromElements( } /> + } /> + } /> } /> } /> {getConfig().ENABLE_ACCESSIBILITY_PAGE === 'true' && ( diff --git a/src/studio-home/data/api.js b/src/studio-home/data/api.js index 0c09601d11..69e0487fff 100644 --- a/src/studio-home/data/api.js +++ b/src/studio-home/data/api.js @@ -40,6 +40,10 @@ export async function getStudioHomeLibraries() { return camelCaseObject(data); } +/** + * Get's studio home v2 Libraries. + * @returns {Promise} + */ export async function getStudioHomeLibrariesV2() { const { data } = await getAuthenticatedHttpClient().get(`${getApiBaseUrl()}/api/libraries/v2/`); return camelCaseObject(data); diff --git a/src/studio-home/tabs-section/index.jsx b/src/studio-home/tabs-section/index.jsx index 997796913f..089f9bc842 100644 --- a/src/studio-home/tabs-section/index.jsx +++ b/src/studio-home/tabs-section/index.jsx @@ -1,10 +1,10 @@ -import React, { useMemo, useState } from 'react'; +import React, { useMemo, useState, useEffect } from 'react'; import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import { Tab, Tabs } from '@openedx/paragon'; import { getConfig } from '@edx/frontend-platform'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useLocation } from 'react-router-dom'; import { getLoadingStatuses, getStudioHomeData } from '../data/selectors'; import messages from './messages'; @@ -25,6 +25,7 @@ const TabsSection = ({ isPaginationCoursesEnabled, }) => { const navigate = useNavigate(); + const { pathname } = useLocation(); const libMode = getConfig().LIBRARY_MODE; const TABS_LIST = { courses: 'courses', @@ -33,7 +34,37 @@ const TabsSection = ({ archived: 'archived', taxonomies: 'taxonomies', }; - const [tabKey, setTabKey] = useState(TABS_LIST.courses); + + const initTabKeyState = (pname) => { + if (pname.includes('/libraries')) { + return isMixedOrV2LibrariesMode(libMode) + ? TABS_LIST.libraries + : TABS_LIST.legacyLibraries; + } + + if (pname.includes('/legacy-libraries')) { + return TABS_LIST.legacyLibraries; + } + + // Default to courses tab + return TABS_LIST.courses; + }; + + const [tabKey, setTabKey] = useState(initTabKeyState(pathname)); + + // This is needed to handle navigating using the back/forward buttons in the browser + useEffect(() => { + // Handle special case when navigating directly to /legacy-libraries or /libraries in `v1 only` mode + // we need to call dispatch to fetch library data + if ( + (isMixedOrV1LibrariesMode(libMode) && pathname.includes('/libraries')) + || pathname.includes('/legacy-libraries') + ) { + dispatch(fetchLibraryData()); + } + setTabKey(initTabKeyState(pathname)); + }, [pathname]); + const { libraryAuthoringMfeUrl, redirectToLibraryAuthoringMfe, @@ -138,8 +169,17 @@ const TabsSection = ({ }, [archivedCourses, librariesEnabled, showNewCourseContainer, isLoadingCourses, isLoadingLibraries]); const handleSelectTab = (tab) => { - if (tab === TABS_LIST.legacyLibraries) { + if (tab === TABS_LIST.courses) { + navigate('/home'); + } else if (tab === TABS_LIST.legacyLibraries) { dispatch(fetchLibraryData()); + navigate( + libMode === 'v1 only' + ? '/libraries' + : '/legacy-libraries', + ); + } else if (tab === TABS_LIST.libraries) { + navigate('/libraries'); } else if (tab === TABS_LIST.taxonomies) { navigate('/taxonomies'); } From 515cc71d5acba5cf36ad4a3f19f568c087233122 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Thu, 30 May 2024 14:52:53 +0300 Subject: [PATCH 04/88] feat: LibraryV2 redirect to lib mfe or placeholder --- src/index.jsx | 2 ++ .../tabs-section/LibraryV2Placeholder.tsx | 36 +++++++++++++++++++ src/studio-home/tabs-section/index.jsx | 5 ++- .../tabs-section/libraries-v2-tab/index.tsx | 23 +++++++++--- src/studio-home/tabs-section/messages.js | 8 +++++ 5 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 src/studio-home/tabs-section/LibraryV2Placeholder.tsx diff --git a/src/index.jsx b/src/index.jsx index f17c0563ab..8bd2d4ef06 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -23,6 +23,7 @@ import initializeStore from './store'; import CourseAuthoringRoutes from './CourseAuthoringRoutes'; import Head from './head/Head'; import { StudioHome } from './studio-home'; +import LibraryV2Placeholder from './studio-home/tabs-section/LibraryV2Placeholder.tsx'; import CourseRerun from './course-rerun'; import { TaxonomyLayout, TaxonomyDetailPage, TaxonomyListPage } from './taxonomy'; import { ContentTagsDrawer } from './content-tags-drawer'; @@ -54,6 +55,7 @@ const App = () => { } /> } /> } /> + } /> } /> } /> {getConfig().ENABLE_ACCESSIBILITY_PAGE === 'true' && ( diff --git a/src/studio-home/tabs-section/LibraryV2Placeholder.tsx b/src/studio-home/tabs-section/LibraryV2Placeholder.tsx new file mode 100644 index 0000000000..ba47ee8899 --- /dev/null +++ b/src/studio-home/tabs-section/LibraryV2Placeholder.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { Container } from '@openedx/paragon'; +import { StudioFooter } from '@edx/frontend-component-footer'; +import { useIntl } from '@edx/frontend-platform/i18n'; + +import Header from '../../header'; +import SubHeader from '../../generic/sub-header/SubHeader'; +import messages from './messages'; + + +const LibraryV2Placeholder = () => { + const intl = useIntl(); + + return ( + <> +
+ +
+
+
+ +
+
+
+

{intl.formatMessage(messages.libraryV2PlaceholderBody)}

+
+
+
+ + + ); +}; + +export default LibraryV2Placeholder; diff --git a/src/studio-home/tabs-section/index.jsx b/src/studio-home/tabs-section/index.jsx index 089f9bc842..aa9d1aa0e2 100644 --- a/src/studio-home/tabs-section/index.jsx +++ b/src/studio-home/tabs-section/index.jsx @@ -129,7 +129,10 @@ const TabsSection = ({ eventKey={TABS_LIST.libraries} title={intl.formatMessage(messages.librariesTabTitle)} > - + , ); } diff --git a/src/studio-home/tabs-section/libraries-v2-tab/index.tsx b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx index 1e14ffef6c..98888f79a8 100644 --- a/src/studio-home/tabs-section/libraries-v2-tab/index.tsx +++ b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { Icon, Row } from '@openedx/paragon'; import { useIntl } from '@edx/frontend-platform/i18n'; @@ -8,7 +9,10 @@ import AlertMessage from '../../../generic/alert-message'; import CardItem from '../../card-item'; import messages from '../messages'; -const LibrariesV2Tab = () => { +const LibrariesV2Tab = ({ + libraryAuthoringMfeUrl, + redirectToLibraryAuthoringMfe, +}) => { const intl = useIntl(); const { data, @@ -24,6 +28,14 @@ const LibrariesV2Tab = () => { ); } + const libURL = (id: string): string => ( + libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe + ? `${libraryAuthoringMfeUrl}library/${id}` + // Redirection to the placeholder is done in the MFE rather than + // through the backend i.e. redirection from cms, because this this will probably change + : `${window.location.origin}/course-authoring/library/${id}` + ); + return ( isError ? ( { /> ) : (
- {data.map(({ org, slug, title }) => ( + {data.map(({ id, org, slug, title }) => ( ))}
@@ -54,5 +65,9 @@ const LibrariesV2Tab = () => { ); }; +LibrariesV2Tab.propTypes = { + libraryAuthoringMfeUrl: PropTypes.string.isRequired, + redirectToLibraryAuthoringMfe: PropTypes.bool.isRequired, +}; export default LibrariesV2Tab; diff --git a/src/studio-home/tabs-section/messages.js b/src/studio-home/tabs-section/messages.js index e1ad0fd44f..0ed614f55a 100644 --- a/src/studio-home/tabs-section/messages.js +++ b/src/studio-home/tabs-section/messages.js @@ -50,6 +50,14 @@ const messages = defineMessages({ defaultMessage: 'Taxonomies', description: 'Title of Taxonomies tab on the home page', }, + libraryV2PlaceholderTitle: { + id: 'course-authoring.studio-home.libraries.placeholder.title', + defaultMessage: 'Library V2 Placeholder', + }, + libraryV2PlaceholderBody: { + id: 'course-authoring.studio-home.libraries.placeholder.body', + defaultMessage: 'This is a placeholder page, as the Library Authoring MFE is not enabled.', + }, }); export default messages; From 9c6e1e6fc7d4206146993edd042ae846b90be217 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Thu, 30 May 2024 18:56:20 +0300 Subject: [PATCH 05/88] feat: Add pagination support for lib v2s --- src/studio-home/data/api.js | 19 ++++++++-- src/studio-home/data/apiHooks.ts | 14 +++++-- .../tabs-section/libraries-v2-tab/index.tsx | 38 ++++++++++++++++--- 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/studio-home/data/api.js b/src/studio-home/data/api.js index 69e0487fff..2124f6fed7 100644 --- a/src/studio-home/data/api.js +++ b/src/studio-home/data/api.js @@ -42,10 +42,23 @@ export async function getStudioHomeLibraries() { /** * Get's studio home v2 Libraries. - * @returns {Promise} + * @param {object} customParams - Additional custom paramaters for the API request. + * @param {string} [customParams.type] - (optional) Library type, default `complex` + * @param {number} [customParams.page] - (optional) Page number of results + * @param {number} [customParams.pageSize] - (optional) The number of results on each page, default `50` + * @param {boolean} [customParams.pagination] - (optional) Whether pagination is supported, default `true` + * @returns {Promise} - A Promise that resolves to the response data container the studio home v2 libraries. */ -export async function getStudioHomeLibrariesV2() { - const { data } = await getAuthenticatedHttpClient().get(`${getApiBaseUrl()}/api/libraries/v2/`); +export async function getStudioHomeLibrariesV2(customParams) { + // Set default params if not passed in + const customParamsDefaults = { + type: customParams.type || 'complex', + page: customParams.page || 1, + pageSize: customParams.pageSize || 50, + pagination: customParams.pagination !== undefined ? customParams.pagination : true, + }; + const customParamsFormat = snakeCaseObject(customParamsDefaults); + const { data } = await getAuthenticatedHttpClient().get(`${getApiBaseUrl()}/api/libraries/v2/`, { params: customParamsFormat }); return camelCaseObject(data); } diff --git a/src/studio-home/data/apiHooks.ts b/src/studio-home/data/apiHooks.ts index 7285874c64..79929f040f 100644 --- a/src/studio-home/data/apiHooks.ts +++ b/src/studio-home/data/apiHooks.ts @@ -2,12 +2,20 @@ import { useQuery } from '@tanstack/react-query'; import { getStudioHomeLibrariesV2 } from './api'; + +interface CustomParams { + type?: string, + page?: number, + pageSize?: number, + pagination?: boolean, +} + /** * Builds the query to fetch list of V2 Libraries */ -export const useListStudioHomeV2Libraries = () => ( +export const useListStudioHomeV2Libraries = (customParams: CustomParams) => ( useQuery({ - queryKey: ['listV2Libraries'], - queryFn: () => getStudioHomeLibrariesV2(), + queryKey: ['listV2Libraries', customParams], + queryFn: () => getStudioHomeLibrariesV2(customParams), }) ); diff --git a/src/studio-home/tabs-section/libraries-v2-tab/index.tsx b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx index 98888f79a8..c26527edd8 100644 --- a/src/studio-home/tabs-section/libraries-v2-tab/index.tsx +++ b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import { Icon, Row } from '@openedx/paragon'; +import { Icon, Row, Pagination } from '@openedx/paragon'; import { useIntl } from '@edx/frontend-platform/i18n'; import { useListStudioHomeV2Libraries } from '../../data/apiHooks'; @@ -14,11 +14,18 @@ const LibrariesV2Tab = ({ redirectToLibraryAuthoringMfe, }) => { const intl = useIntl(); + + const [currentPage, setCurrentPage] = useState(1); + + const handlePageSelect = (page) => { + setCurrentPage(page); + }; + const { data, isLoading, isError, - } = useListStudioHomeV2Libraries(); + } = useListStudioHomeV2Libraries({page: currentPage}); if (isLoading) { return ( @@ -49,8 +56,19 @@ const LibrariesV2Tab = ({ )} /> ) : ( -
- {data.map(({ id, org, slug, title }) => ( +
+
+ {/* Temporary div to add spacing. This will be replaced with lib search/filters */} +
+

+ {intl.formatMessage(messages.coursesPaginationInfo, { + length: data.results.length, + total: data.count, + })} +

+
+ + {data.results.map(({ id, org, slug, title }) => ( ))} + + {data.numPages > 1 && + + }
) ); From da189c1d6d1687b5a6971ab8604fd852481c4a6b Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Mon, 3 Jun 2024 16:50:53 +0300 Subject: [PATCH 06/88] fix: Redirect to placeholder create lib in v2/mixed disabled mfe --- src/studio-home/StudioHome.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/studio-home/StudioHome.jsx b/src/studio-home/StudioHome.jsx index 2d68af9c25..52be27a60f 100644 --- a/src/studio-home/StudioHome.jsx +++ b/src/studio-home/StudioHome.jsx @@ -51,6 +51,7 @@ const StudioHome = ({ intl }) => { studioShortName, studioRequestEmail, libraryAuthoringMfeUrl, + redirectToLibraryAuthoringMfe, } = studioHomeData; function getHeaderButtons() { @@ -82,7 +83,9 @@ const StudioHome = ({ intl }) => { let libraryHref = `${getConfig().STUDIO_BASE_URL}/home_library`; if (isMixedOrV2LibrariesMode(libMode)) { - libraryHref = `${libraryAuthoringMfeUrl}create`; + libraryHref = libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe + ? `${libraryAuthoringMfeUrl}create` + : `${window.location.origin}/course-authoring/library/create`; } headerButtons.push( From 85d9ff215c9d3980a50aacd4c92ce8092bcd0014 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Mon, 3 Jun 2024 17:03:01 +0300 Subject: [PATCH 07/88] temp: This removes TS code to get tests to run This commit is temporary as the current frontend build system in tests doesnt support TS syntax. That should be fixed soon, and this commit should be removed. --- src/studio-home/data/apiHooks.ts | 9 +-------- src/studio-home/tabs-section/libraries-v2-tab/index.tsx | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/studio-home/data/apiHooks.ts b/src/studio-home/data/apiHooks.ts index 79929f040f..ec163e5732 100644 --- a/src/studio-home/data/apiHooks.ts +++ b/src/studio-home/data/apiHooks.ts @@ -3,17 +3,10 @@ import { useQuery } from '@tanstack/react-query'; import { getStudioHomeLibrariesV2 } from './api'; -interface CustomParams { - type?: string, - page?: number, - pageSize?: number, - pagination?: boolean, -} - /** * Builds the query to fetch list of V2 Libraries */ -export const useListStudioHomeV2Libraries = (customParams: CustomParams) => ( +export const useListStudioHomeV2Libraries = (customParams) => ( useQuery({ queryKey: ['listV2Libraries', customParams], queryFn: () => getStudioHomeLibrariesV2(customParams), diff --git a/src/studio-home/tabs-section/libraries-v2-tab/index.tsx b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx index c26527edd8..a659dcc1fa 100644 --- a/src/studio-home/tabs-section/libraries-v2-tab/index.tsx +++ b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx @@ -35,7 +35,7 @@ const LibrariesV2Tab = ({ ); } - const libURL = (id: string): string => ( + const libURL = (id) => ( libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe ? `${libraryAuthoringMfeUrl}library/${id}` // Redirection to the placeholder is done in the MFE rather than From c6b7bf8380f2623f2dc53f55d2be73341c9b8d61 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Mon, 3 Jun 2024 18:35:29 +0300 Subject: [PATCH 08/88] test: Update existing tests to support changes --- src/setupTest.js | 1 + src/studio-home/StudioHome.test.jsx | 46 ++++++++++++++----- src/studio-home/__mocks__/studioHomeMock.js | 2 +- .../tabs-section/TabsSection.test.jsx | 39 +++++++++++++--- 4 files changed, 69 insertions(+), 19 deletions(-) diff --git a/src/setupTest.js b/src/setupTest.js index 35b1c9ebe2..f0f7f6a435 100755 --- a/src/setupTest.js +++ b/src/setupTest.js @@ -48,6 +48,7 @@ mergeConfig({ ENABLE_TEAM_TYPE_SETTING: process.env.ENABLE_TEAM_TYPE_SETTING === 'true', ENABLE_CHECKLIST_QUALITY: process.env.ENABLE_CHECKLIST_QUALITY || 'true', STUDIO_BASE_URL: process.env.STUDIO_BASE_URL || null, + LIBRARY_MODE: process.env.LIBRARY_MODE || 'v1 only', }, 'CourseAuthoringConfig'); class ResizeObserver { diff --git a/src/studio-home/StudioHome.test.jsx b/src/studio-home/StudioHome.test.jsx index 7286acda0f..49ca600e5d 100644 --- a/src/studio-home/StudioHome.test.jsx +++ b/src/studio-home/StudioHome.test.jsx @@ -1,6 +1,8 @@ import React from 'react'; import { useSelector } from 'react-redux'; -import { initializeMockApp } from '@edx/frontend-platform'; +import { MemoryRouter, Routes, Route } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { initializeMockApp, getConfig, setConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; import { AppProvider } from '@edx/frontend-platform/react'; @@ -23,7 +25,6 @@ import { StudioHome } from '.'; let axiosMock; let store; -const mockPathname = '/foo-bar'; const { studioShortName, studioRequestEmail, @@ -34,17 +35,29 @@ jest.mock('react-redux', () => ({ useSelector: jest.fn(), })); -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useLocation: () => ({ - pathname: mockPathname, - }), -})); +const queryClient = new QueryClient(); const RootWrapper = () => ( - + - + + + + } + /> + } + /> + } + /> + + + ); @@ -145,7 +158,18 @@ describe('', async () => { }); describe('render new library button', () => { - it('href should include home_library', async () => { + beforeEach(() => { + setConfig({ + ...getConfig(), + LIBRARY_MODE: 'mixed', + }); + }); + + it('href should include home_library when in "v1 only" lib mode', async () => { + setConfig({ + ...getConfig(), + LIBRARY_MODE: 'v1 only', + }); useSelector.mockReturnValue({ ...studioHomeMock, courseCreatorStatus: COURSE_CREATOR_STATES.granted, diff --git a/src/studio-home/__mocks__/studioHomeMock.js b/src/studio-home/__mocks__/studioHomeMock.js index 5385201e52..4f66cc116f 100644 --- a/src/studio-home/__mocks__/studioHomeMock.js +++ b/src/studio-home/__mocks__/studioHomeMock.js @@ -62,7 +62,7 @@ module.exports = { }, ], librariesEnabled: true, - libraryAuthoringMfeUrl: 'http://localhost:3001', + libraryAuthoringMfeUrl: 'http://localhost:3001/', optimizationEnabled: false, redirectToLibraryAuthoringMfe: false, requestCourseCreatorUrl: '/request_course_creator', diff --git a/src/studio-home/tabs-section/TabsSection.test.jsx b/src/studio-home/tabs-section/TabsSection.test.jsx index ea5929aeec..945322dcd5 100644 --- a/src/studio-home/tabs-section/TabsSection.test.jsx +++ b/src/studio-home/tabs-section/TabsSection.test.jsx @@ -1,4 +1,6 @@ import React from 'react'; +import { MemoryRouter, Routes, Route } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { getConfig, initializeMockApp, setConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { @@ -34,15 +36,38 @@ const libraryApiLink = `${getApiBaseUrl()}/api/contentstore/v1/home/libraries`; const mockDispatch = jest.fn(); +const queryClient = new QueryClient(); + +const tabSectionComponent = (overrideProps) => ( + +); + const RootWrapper = (overrideProps) => ( - + - + + + + + + + + + ); From 14933d2ce093c4396469c62b9e307275940fd947 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Mon, 3 Jun 2024 19:04:16 +0300 Subject: [PATCH 09/88] temp: Rename .tsx -> .jsx & .ts -> .js for tests This is a temporary commit since there are currently no webpack loaders that support tsx files in the test running. This commit should be removed once that is fixed upstream. --- src/index.jsx | 2 +- src/studio-home/data/{apiHooks.ts => apiHooks.js} | 0 .../{LibraryV2Placeholder.tsx => LibraryV2Placeholder.jsx} | 0 src/studio-home/tabs-section/index.jsx | 2 +- .../tabs-section/libraries-v2-tab/{index.tsx => index.jsx} | 0 5 files changed, 2 insertions(+), 2 deletions(-) rename src/studio-home/data/{apiHooks.ts => apiHooks.js} (100%) rename src/studio-home/tabs-section/{LibraryV2Placeholder.tsx => LibraryV2Placeholder.jsx} (100%) rename src/studio-home/tabs-section/libraries-v2-tab/{index.tsx => index.jsx} (100%) diff --git a/src/index.jsx b/src/index.jsx index 8bd2d4ef06..93fe3c3f4c 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -23,7 +23,7 @@ import initializeStore from './store'; import CourseAuthoringRoutes from './CourseAuthoringRoutes'; import Head from './head/Head'; import { StudioHome } from './studio-home'; -import LibraryV2Placeholder from './studio-home/tabs-section/LibraryV2Placeholder.tsx'; +import LibraryV2Placeholder from './studio-home/tabs-section/LibraryV2Placeholder'; import CourseRerun from './course-rerun'; import { TaxonomyLayout, TaxonomyDetailPage, TaxonomyListPage } from './taxonomy'; import { ContentTagsDrawer } from './content-tags-drawer'; diff --git a/src/studio-home/data/apiHooks.ts b/src/studio-home/data/apiHooks.js similarity index 100% rename from src/studio-home/data/apiHooks.ts rename to src/studio-home/data/apiHooks.js diff --git a/src/studio-home/tabs-section/LibraryV2Placeholder.tsx b/src/studio-home/tabs-section/LibraryV2Placeholder.jsx similarity index 100% rename from src/studio-home/tabs-section/LibraryV2Placeholder.tsx rename to src/studio-home/tabs-section/LibraryV2Placeholder.jsx diff --git a/src/studio-home/tabs-section/index.jsx b/src/studio-home/tabs-section/index.jsx index aa9d1aa0e2..703a4e6f04 100644 --- a/src/studio-home/tabs-section/index.jsx +++ b/src/studio-home/tabs-section/index.jsx @@ -9,7 +9,7 @@ import { useNavigate, useLocation } from 'react-router-dom'; import { getLoadingStatuses, getStudioHomeData } from '../data/selectors'; import messages from './messages'; import LibrariesTab from './libraries-tab'; -import LibrariesV2Tab from './libraries-v2-tab/index.tsx'; +import LibrariesV2Tab from './libraries-v2-tab/index'; import ArchivedTab from './archived-tab'; import CoursesTab from './courses-tab'; import { RequestStatus } from '../../data/constants'; diff --git a/src/studio-home/tabs-section/libraries-v2-tab/index.tsx b/src/studio-home/tabs-section/libraries-v2-tab/index.jsx similarity index 100% rename from src/studio-home/tabs-section/libraries-v2-tab/index.tsx rename to src/studio-home/tabs-section/libraries-v2-tab/index.jsx From 8b9626887eb13d0df0f77d4b13b9e19c6f875ea1 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Mon, 3 Jun 2024 19:24:05 +0300 Subject: [PATCH 10/88] fix: Fix lint issues --- src/studio-home/data/apiHooks.js | 5 +- .../tabs-section/LibraryV2Placeholder.jsx | 1 - .../tabs-section/libraries-v2-tab/index.jsx | 47 +++++++++++-------- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/studio-home/data/apiHooks.js b/src/studio-home/data/apiHooks.js index ec163e5732..92575bf717 100644 --- a/src/studio-home/data/apiHooks.js +++ b/src/studio-home/data/apiHooks.js @@ -2,13 +2,14 @@ import { useQuery } from '@tanstack/react-query'; import { getStudioHomeLibrariesV2 } from './api'; - /** * Builds the query to fetch list of V2 Libraries */ -export const useListStudioHomeV2Libraries = (customParams) => ( +const useListStudioHomeV2Libraries = (customParams) => ( useQuery({ queryKey: ['listV2Libraries', customParams], queryFn: () => getStudioHomeLibrariesV2(customParams), }) ); + +export default useListStudioHomeV2Libraries; diff --git a/src/studio-home/tabs-section/LibraryV2Placeholder.jsx b/src/studio-home/tabs-section/LibraryV2Placeholder.jsx index ba47ee8899..6844515bd9 100644 --- a/src/studio-home/tabs-section/LibraryV2Placeholder.jsx +++ b/src/studio-home/tabs-section/LibraryV2Placeholder.jsx @@ -7,7 +7,6 @@ import Header from '../../header'; import SubHeader from '../../generic/sub-header/SubHeader'; import messages from './messages'; - const LibraryV2Placeholder = () => { const intl = useIntl(); diff --git a/src/studio-home/tabs-section/libraries-v2-tab/index.jsx b/src/studio-home/tabs-section/libraries-v2-tab/index.jsx index a659dcc1fa..9060493dd1 100644 --- a/src/studio-home/tabs-section/libraries-v2-tab/index.jsx +++ b/src/studio-home/tabs-section/libraries-v2-tab/index.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { Icon, Row, Pagination } from '@openedx/paragon'; import { useIntl } from '@edx/frontend-platform/i18n'; -import { useListStudioHomeV2Libraries } from '../../data/apiHooks'; +import useListStudioHomeV2Libraries from '../../data/apiHooks'; import { LoadingSpinner } from '../../../generic/Loading'; import AlertMessage from '../../../generic/alert-message'; import CardItem from '../../card-item'; @@ -25,7 +25,7 @@ const LibrariesV2Tab = ({ data, isLoading, isError, - } = useListStudioHomeV2Libraries({page: currentPage}); + } = useListStudioHomeV2Libraries({ page: currentPage }); if (isLoading) { return ( @@ -68,25 +68,32 @@ const LibrariesV2Tab = ({

- {data.results.map(({ id, org, slug, title }) => ( - - ))} + { + data.results.map(({ + id, org, slug, title, + }) => ( + + )) + } - {data.numPages > 1 && - + { + data.numPages > 1 + && ( + + ) }
) From f8db85358bd87578bfa4d5c5aaf8cb2632d045dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Tue, 4 Jun 2024 14:56:13 -0300 Subject: [PATCH 11/88] feat: library home page bare bones --- src/CourseAuthoringPage.jsx | 33 +---- src/header/Header.jsx | 51 +++---- src/index.jsx | 4 +- src/library-authoring/EmptyStates.jsx | 21 +++ .../LibraryAuthoringPage.jsx | 131 ++++++++++++++++++ src/library-authoring/LibraryCollections.jsx | 19 +++ src/library-authoring/LibraryComponents.jsx | 35 +++++ src/library-authoring/LibraryHome.jsx | 61 ++++++++ src/library-authoring/data/api.ts | 12 ++ src/library-authoring/data/apiHook.ts | 56 ++++++++ src/library-authoring/data/types.ts | 17 +++ src/library-authoring/index.ts | 3 + src/library-authoring/messages.ts | 31 +++++ src/search-modal/data/apiHooks.js | 7 +- src/search-modal/index.ts | 3 + 15 files changed, 428 insertions(+), 56 deletions(-) create mode 100644 src/library-authoring/EmptyStates.jsx create mode 100644 src/library-authoring/LibraryAuthoringPage.jsx create mode 100644 src/library-authoring/LibraryCollections.jsx create mode 100644 src/library-authoring/LibraryComponents.jsx create mode 100644 src/library-authoring/LibraryHome.jsx create mode 100644 src/library-authoring/data/api.ts create mode 100644 src/library-authoring/data/apiHook.ts create mode 100644 src/library-authoring/data/types.ts create mode 100644 src/library-authoring/index.ts create mode 100644 src/library-authoring/messages.ts create mode 100644 src/search-modal/index.ts diff --git a/src/CourseAuthoringPage.jsx b/src/CourseAuthoringPage.jsx index c4281a8c13..eaa16c49c2 100644 --- a/src/CourseAuthoringPage.jsx +++ b/src/CourseAuthoringPage.jsx @@ -15,29 +15,6 @@ import { getCourseAppsApiStatus } from './pages-and-resources/data/selectors'; import { RequestStatus } from './data/constants'; import Loading from './generic/Loading'; -const AppHeader = ({ - courseNumber, courseOrg, courseTitle, courseId, -}) => ( -
-); - -AppHeader.propTypes = { - courseId: PropTypes.string.isRequired, - courseNumber: PropTypes.string, - courseOrg: PropTypes.string, - courseTitle: PropTypes.string.isRequired, -}; - -AppHeader.defaultProps = { - courseNumber: null, - courseOrg: null, -}; - const CourseAuthoringPage = ({ courseId, children }) => { const dispatch = useDispatch(); @@ -74,11 +51,11 @@ const CourseAuthoringPage = ({ courseId, children }) => { This functionality will be removed in TNL-9591 */} {inProgress ? !isEditor && : (!isEditor && ( - ) )} diff --git a/src/header/Header.jsx b/src/header/Header.jsx index 7cc1adcb08..8e15d32292 100644 --- a/src/header/Header.jsx +++ b/src/header/Header.jsx @@ -6,16 +6,17 @@ import { useIntl } from '@edx/frontend-platform/i18n'; import { StudioHeader } from '@edx/frontend-component-header'; import { useToggle } from '@openedx/paragon'; -import SearchModal from '../search-modal/SearchModal'; +import { SearchModal } from '../search-modal'; import { getContentMenuItems, getSettingMenuItems, getToolsMenuItems } from './utils'; import messages from './messages'; const Header = ({ - courseId, - courseOrg, - courseNumber, - courseTitle, + contentId, + org, + number, + title, isHiddenMainMenu, + isLibrary, }) => { const intl = useIntl(); @@ -23,40 +24,40 @@ const Header = ({ const studioBaseUrl = getConfig().STUDIO_BASE_URL; const meiliSearchEnabled = [true, 'true'].includes(getConfig().MEILISEARCH_ENABLED); - const mainMenuDropdowns = [ + const mainMenuDropdowns = !isLibrary ? [ { id: `${intl.formatMessage(messages['header.links.content'])}-dropdown-menu`, buttonTitle: intl.formatMessage(messages['header.links.content']), - items: getContentMenuItems({ studioBaseUrl, courseId, intl }), + items: getContentMenuItems({ studioBaseUrl, courseId: contentId, intl }), }, { id: `${intl.formatMessage(messages['header.links.settings'])}-dropdown-menu`, buttonTitle: intl.formatMessage(messages['header.links.settings']), - items: getSettingMenuItems({ studioBaseUrl, courseId, intl }), + items: getSettingMenuItems({ studioBaseUrl, courseId: contentId, intl }), }, { id: `${intl.formatMessage(messages['header.links.tools'])}-dropdown-menu`, buttonTitle: intl.formatMessage(messages['header.links.tools']), - items: getToolsMenuItems({ studioBaseUrl, courseId, intl }), + items: getToolsMenuItems({ studioBaseUrl, courseId: contentId, intl }), }, - ]; - const outlineLink = `${studioBaseUrl}/course/${courseId}`; + ] : []; + const outlineLink = !isLibrary ? `${studioBaseUrl}/course/${contentId}` : `${studioBaseUrl}/library/${contentId}`; return ( <> { meiliSearchEnabled && ( )} @@ -65,19 +66,21 @@ const Header = ({ }; Header.propTypes = { - courseId: PropTypes.string, - courseNumber: PropTypes.string, - courseOrg: PropTypes.string, - courseTitle: PropTypes.string, + contentId: PropTypes.string, + number: PropTypes.string, + org: PropTypes.string, + title: PropTypes.string, isHiddenMainMenu: PropTypes.bool, + isLibrary: PropTypes.bool, }; Header.defaultProps = { - courseId: '', - courseNumber: '', - courseOrg: '', - courseTitle: '', + contentId: '', + number: '', + org: '', + title: '', isHiddenMainMenu: false, + isLibrary: false, }; export default Header; diff --git a/src/index.jsx b/src/index.jsx index 93fe3c3f4c..588689aae7 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -19,11 +19,11 @@ import { initializeHotjar } from '@edx/frontend-enterprise-hotjar'; import { logError } from '@edx/frontend-platform/logging'; import messages from './i18n'; +import { LibraryAuthoringPage } from './library-authoring'; import initializeStore from './store'; import CourseAuthoringRoutes from './CourseAuthoringRoutes'; import Head from './head/Head'; import { StudioHome } from './studio-home'; -import LibraryV2Placeholder from './studio-home/tabs-section/LibraryV2Placeholder'; import CourseRerun from './course-rerun'; import { TaxonomyLayout, TaxonomyDetailPage, TaxonomyListPage } from './taxonomy'; import { ContentTagsDrawer } from './content-tags-drawer'; @@ -55,7 +55,7 @@ const App = () => { } /> } /> } /> - } /> + } /> } /> } /> {getConfig().ENABLE_ACCESSIBILITY_PAGE === 'true' && ( diff --git a/src/library-authoring/EmptyStates.jsx b/src/library-authoring/EmptyStates.jsx new file mode 100644 index 0000000000..6f54dc810b --- /dev/null +++ b/src/library-authoring/EmptyStates.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; +import { + Button, Stack, +} from '@openedx/paragon'; +import { Add } from '@openedx/paragon/icons'; + +import messages from './messages'; + +export const NoComponents = () => ( + +
You have not added any content to this library yet.
+ +
+); + +export const NoSearchResults = () => ( +
+ +
+); diff --git a/src/library-authoring/LibraryAuthoringPage.jsx b/src/library-authoring/LibraryAuthoringPage.jsx new file mode 100644 index 0000000000..9c075ada88 --- /dev/null +++ b/src/library-authoring/LibraryAuthoringPage.jsx @@ -0,0 +1,131 @@ +// @ts-check +/* eslint-disable react/prop-types */ +import React, { useEffect } from 'react'; +import { StudioFooter } from '@edx/frontend-component-footer'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { + Container, Icon, IconButton, SearchField, Tab, Tabs, +} from '@openedx/paragon'; +import { InfoOutline } from '@openedx/paragon/icons'; +import { + Routes, Route, useLocation, useNavigate, useParams, +} from 'react-router-dom'; + +import Loading from '../generic/Loading'; +import SubHeader from '../generic/sub-header/SubHeader'; +import Header from '../header'; +import NotFoundAlert from '../generic/NotFoundAlert'; +import LibraryComponents from './LibraryComponents'; +import LibraryCollections from './LibraryCollections'; +import LibraryHome from './LibraryHome'; +import { useContentLibrary } from './data/apiHook'; +import messages from './messages'; + +const TAB_LIST = { + home: '', + components: 'components', + collections: 'collections', +}; + +const SubHeaderTitle = ({ title }) => ( + <> + {title} + {}} className="mr-2" /> + +); + +/** + * @type {React.FC} + */ +const LibraryAuthoringPage = () => { + const intl = useIntl(); + const location = useLocation(); + const navigate = useNavigate(); + const [tabKey, setTabKey] = React.useState(TAB_LIST.home); + const [searchKeywords, setSearchKeywords] = React.useState(''); + + const { libraryId } = useParams(); + + const { data: libraryData, isLoading } = useContentLibrary(libraryId); + + useEffect(() => { + const currentPath = location.pathname.split('/').pop(); + if (currentPath && Object.values(TAB_LIST).includes(currentPath)) { + setTabKey(currentPath); + } else { + setTabKey(TAB_LIST.home); + } + }, [location]); + + if (isLoading) { + return ; + } + + if (!libraryId || !libraryData) { + return ; + } + + /** Handle tab change + * @param {string} key + */ + const handleTabChange = (key) => { + setTabKey(key); + navigate(key); + }; + + return ( + <> +
+ + } + subtitle={intl.formatMessage(messages.headingSubtitle)} + /> + setSearchKeywords(value)} + onChange={(value) => setSearchKeywords(value)} + className="w-50" + /> + + + + + + + } + /> + } + /> + } + /> + } + /> + + + + + ); +}; + +export default LibraryAuthoringPage; diff --git a/src/library-authoring/LibraryCollections.jsx b/src/library-authoring/LibraryCollections.jsx new file mode 100644 index 0000000000..5292b10f40 --- /dev/null +++ b/src/library-authoring/LibraryCollections.jsx @@ -0,0 +1,19 @@ +// @ts-check +/* eslint-disable react/prop-types */ +import React from 'react'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; + +import messages from './messages'; + +/** + * @type {React.FC} + */ +const LibraryCollections = () => ( +
+ +
+); + +export default LibraryCollections; diff --git a/src/library-authoring/LibraryComponents.jsx b/src/library-authoring/LibraryComponents.jsx new file mode 100644 index 0000000000..a48fbfe645 --- /dev/null +++ b/src/library-authoring/LibraryComponents.jsx @@ -0,0 +1,35 @@ +// @ts-check +/* eslint-disable react/prop-types */ +import React from 'react'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; + +import { NoComponents, NoSearchResults } from './EmptyStates'; +import { useLibraryComponentCount } from './data/apiHook'; +import messages from './messages'; + +/** + * @type {React.FC<{ + * libraryId: string, + * filter: { + * searchKeywords: string, + * }, + * }>} + */ +const LibraryComponents = ({ libraryId, filter: { searchKeywords } }) => { + const { componentCount, collectionCount } = useLibraryComponentCount(libraryId, searchKeywords); + + if (componentCount === 0) { + return searchKeywords === '' ? : ; + } + + return ( +
+ +
+ ); +}; + +export default LibraryComponents; diff --git a/src/library-authoring/LibraryHome.jsx b/src/library-authoring/LibraryHome.jsx new file mode 100644 index 0000000000..e2c16862ca --- /dev/null +++ b/src/library-authoring/LibraryHome.jsx @@ -0,0 +1,61 @@ +// @ts-check +/* eslint-disable react/prop-types */ +import React from 'react'; +import { + Card, Stack, +} from '@openedx/paragon'; + +import { NoComponents, NoSearchResults } from './EmptyStates'; +import LibraryCollections from './LibraryCollections'; +import LibraryComponents from './LibraryComponents'; +import { useLibraryComponentCount } from './data/apiHook'; + +/** + * @type {React.FC<{ + * title: string, + * children: React.ReactNode, + * }>} + */ +const Section = ({ title, children }) => ( + + + + {children} + + +); + +/** + * @type {React.FC<{ + * libraryId: string, + * filter: { + * searchKeywords: string, + * }, + * }>} + */ +const LibraryHome = ({ libraryId, filter }) => { + const { searchKeywords } = filter; + const { componentCount, collectionCount } = useLibraryComponentCount(libraryId, searchKeywords); + + if (componentCount === 0) { + return searchKeywords === '' ? : ; + } + + return ( + +
+ Recently modified components and collections will be displayed here. +
+
+ +
+
+ +
+
+ ); +}; + +export default LibraryHome; diff --git a/src/library-authoring/data/api.ts b/src/library-authoring/data/api.ts new file mode 100644 index 0000000000..ff0dd3dec5 --- /dev/null +++ b/src/library-authoring/data/api.ts @@ -0,0 +1,12 @@ +// @ts-check +import type { ContentLibrary } from './types'; +import { camelCaseObject, getConfig } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; + +const getApiBaseUrl = (): string => getConfig().STUDIO_BASE_URL; +const getContentLibraryApiUrl = (libraryId: string) => `${getApiBaseUrl()}/api/libraries/v2/${libraryId}/`; + +export async function getContentLibrary(libraryId: string): Promise { + const { data } = await getAuthenticatedHttpClient().get(getContentLibraryApiUrl(libraryId)); + return camelCaseObject(data); +} diff --git a/src/library-authoring/data/apiHook.ts b/src/library-authoring/data/apiHook.ts new file mode 100644 index 0000000000..2b1516ee22 --- /dev/null +++ b/src/library-authoring/data/apiHook.ts @@ -0,0 +1,56 @@ +// @ts-check +import React, { useEffect } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { MeiliSearch } from 'meilisearch'; + +import { useContentSearchConnection, useContentSearchResults } from '../../search-modal'; +import { getContentLibrary } from './api'; + +/** + * Hook to fetch a content library by its ID. + */ +export const useContentLibrary = (libraryId?: string) => { + if (!libraryId) { + return { + data: undefined, + error: 'No library ID provided', + } + } + + return useQuery({ + queryKey: ['contentLibrary', libraryId], + queryFn: () => getContentLibrary(libraryId), + }); +}; + + +export const useLibraryComponentCount = (libraryId: string, searchKeywords: string) => { + // Meilisearch code to get Collection and Component counts + const { data: connectionDetails } = useContentSearchConnection(); + + const indexName = connectionDetails?.indexName; + const client = React.useMemo(() => { + if (connectionDetails?.apiKey === undefined || connectionDetails?.url === undefined) { + return undefined; + } + return new MeiliSearch({ host: connectionDetails.url, apiKey: connectionDetails.apiKey }); + }, [connectionDetails?.apiKey, connectionDetails?.url]); + + const libFilter = `context_key = "${libraryId}"`; + + const { totalHits: componentCount } = useContentSearchResults({ + client, + indexName, + searchKeywords, + extraFilter: [libFilter], // ToDo: Add filter for components when collection is implemented + }); + + const collectionCount = 0; // ToDo: Implement collections count + + return { + componentCount, + collectionCount, + }; +} + + diff --git a/src/library-authoring/data/types.ts b/src/library-authoring/data/types.ts new file mode 100644 index 0000000000..1af41a8b1f --- /dev/null +++ b/src/library-authoring/data/types.ts @@ -0,0 +1,17 @@ +export type ContentLibrary = { + id: string; + type: string; + org: string; + slug: string; + title: string; + description: string; + numBlocks: number; + version: number; + lastPublished: Date | null; + allowLti: boolean; + allowPublicLearning: boolean; + allowPublicRead: boolean; + hasUnpublishedChanges: boolean; + hasUnpublishedDeletes: boolean; + license: string; +} diff --git a/src/library-authoring/index.ts b/src/library-authoring/index.ts new file mode 100644 index 0000000000..05cd9d1e61 --- /dev/null +++ b/src/library-authoring/index.ts @@ -0,0 +1,3 @@ +// @ts-check +// eslint-disable-next-line import/prefer-default-export +export { default as LibraryAuthoringPage } from './LibraryAuthoringPage'; diff --git a/src/library-authoring/messages.ts b/src/library-authoring/messages.ts new file mode 100644 index 0000000000..1a48fdeaf4 --- /dev/null +++ b/src/library-authoring/messages.ts @@ -0,0 +1,31 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + headingSubtitle: { + id: 'course-authoring.library-authoring.heading-subtitle', + defaultMessage: 'Content library', + description: 'The page heading for the library page.', + }, + searchPlaceholder: { + id: 'course-authoring.library-authoring.search', + defaultMessage: 'Search...', + description: 'Placeholder for search field', + }, + noSearchResults: { + id: 'course-authoring.library-authoring.no-search-results', + defaultMessage: 'No matching components found in this library.', + description: 'Message displayed when no search results are found', + }, + componentsTempPlaceholder: { + id: 'course-authoring.library-authoring.components-temp-placeholder', + defaultMessage: 'There are {componentCount} components in this library', + description: 'Temp placeholder for the component container. This will be replaced with the actual component list.', + }, + collectionsTempPlaceholder: { + id: 'course-authoring.library-authoring.collections-temp-placeholder', + defaultMessage: 'Coming soon!', + description: 'Temp placeholder for the collections container. This will be replaced with the actual collection list.', + }, +}); + +export default messages; diff --git a/src/search-modal/data/apiHooks.js b/src/search-modal/data/apiHooks.js index 02488635da..59a07c425a 100644 --- a/src/search-modal/data/apiHooks.js +++ b/src/search-modal/data/apiHooks.js @@ -34,8 +34,8 @@ export const useContentSearchConnection = () => ( * @param {string} [context.indexName] Which search index contains the content data * @param {import('meilisearch').Filter} [context.extraFilter] Other filters to apply to the search, e.g. course ID * @param {string} context.searchKeywords The keywords that the user is searching for, if any - * @param {string[]} context.blockTypesFilter Only search for these block types (e.g. ["html", "problem"]) - * @param {string[]} context.tagsFilter Required tags (all must match), e.g. ["Difficulty > Hard", "Subject > Math"] + * @param {string[]} [context.blockTypesFilter] Only search for these block types (e.g. ["html", "problem"]) + * @param {string[]} [context.tagsFilter] Required tags (all must match), e.g. ["Difficulty > Hard", "Subject > Math"] */ export const useContentSearchResults = ({ client, @@ -45,6 +45,9 @@ export const useContentSearchResults = ({ blockTypesFilter, tagsFilter, }) => { + blockTypesFilter ??= []; // eslint-disable-line no-param-reassign -- default value for optional parameter + tagsFilter ??= []; // eslint-disable-line no-param-reassign -- Default value for optional parameter + const query = useInfiniteQuery({ enabled: client !== undefined && indexName !== undefined, queryKey: [ diff --git a/src/search-modal/index.ts b/src/search-modal/index.ts new file mode 100644 index 0000000000..190635618d --- /dev/null +++ b/src/search-modal/index.ts @@ -0,0 +1,3 @@ +// @ts-check +export { default as SearchModal } from './SearchModal'; +export { useContentSearchConnection, useContentSearchResults } from './data/apiHooks'; From 7a8488d8101509bd797e19db44ff220376dacddd Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Tue, 4 Jun 2024 22:58:51 +0300 Subject: [PATCH 12/88] test: Add tests for new functionality --- src/studio-home/__mocks__/index.js | 2 +- .../listStudioHomeV2LibrariesMock.js | 44 ++++ src/studio-home/data/api.test.js | 24 ++- .../factories/mockApiResponses.jsx | 47 +++- .../tabs-section/LibraryV2Placeholder.jsx | 1 + .../tabs-section/TabsSection.test.jsx | 201 +++++++++++++++++- 6 files changed, 309 insertions(+), 10 deletions(-) create mode 100644 src/studio-home/__mocks__/listStudioHomeV2LibrariesMock.js diff --git a/src/studio-home/__mocks__/index.js b/src/studio-home/__mocks__/index.js index 92461eb0bb..af2a85b390 100644 --- a/src/studio-home/__mocks__/index.js +++ b/src/studio-home/__mocks__/index.js @@ -1,2 +1,2 @@ -// eslint-disable-next-line import/prefer-default-export export { default as studioHomeMock } from './studioHomeMock'; +export { default as listStudioHomeV2LibrariesMock } from './listStudioHomeV2LibrariesMock'; diff --git a/src/studio-home/__mocks__/listStudioHomeV2LibrariesMock.js b/src/studio-home/__mocks__/listStudioHomeV2LibrariesMock.js new file mode 100644 index 0000000000..02257a9744 --- /dev/null +++ b/src/studio-home/__mocks__/listStudioHomeV2LibrariesMock.js @@ -0,0 +1,44 @@ +module.exports = { + next: null, + previous: null, + count: 2, + num_pages: 1, + current_page: 1, + start: 0, + results: [ + { + id: 'lib:SampleTaxonomyOrg1:AL1', + type: 'complex', + org: 'SampleTaxonomyOrg1', + slug: 'AL1', + title: 'Another Library 2', + description: '', + num_blocks: 0, + version: 0, + last_published: null, + allow_lti: false, + allow_public_learning: false, + allow_public_read: false, + has_unpublished_changes: false, + has_unpublished_deletes: false, + license: '', + }, + { + id: 'lib:SampleTaxonomyOrg1:TL1', + type: 'complex', + org: 'SampleTaxonomyOrg1', + slug: 'TL1', + title: 'Test Library 1', + description: '', + num_blocks: 0, + version: 0, + last_published: null, + allow_lti: false, + allow_public_learning: false, + allow_public_read: false, + has_unpublished_changes: false, + has_unpublished_deletes: false, + license: '', + }, + ], +}; diff --git a/src/studio-home/data/api.test.js b/src/studio-home/data/api.test.js index 593a2730de..66f6ee279f 100644 --- a/src/studio-home/data/api.test.js +++ b/src/studio-home/data/api.test.js @@ -13,8 +13,14 @@ import { getStudioHomeCourses, getStudioHomeCoursesV2, getStudioHomeLibraries, + getStudioHomeLibrariesV2, } from './api'; -import { generateGetStudioCoursesApiResponse, generateGetStudioHomeDataApiResponse, generateGetStuioHomeLibrariesApiResponse } from '../factories/mockApiResponses'; +import { + generateGetStudioCoursesApiResponse, + generateGetStudioHomeDataApiResponse, + generateGetStudioHomeLibrariesApiResponse, + generateGetStudioHomeLibrariesV2ApiResponse, +} from '../factories/mockApiResponses'; let axiosMock; @@ -64,11 +70,21 @@ describe('studio-home api calls', () => { expect(result).toEqual(expected); }); - it('should get studio libraries data', async () => { + it('should get studio v1 libraries data', async () => { const apiLink = `${getApiBaseUrl()}/api/contentstore/v1/home/libraries`; - axiosMock.onGet(apiLink).reply(200, generateGetStuioHomeLibrariesApiResponse()); + axiosMock.onGet(apiLink).reply(200, generateGetStudioHomeLibrariesApiResponse()); const result = await getStudioHomeLibraries(); - const expected = generateGetStuioHomeLibrariesApiResponse(); + const expected = generateGetStudioHomeLibrariesApiResponse(); + + expect(axiosMock.history.get[0].url).toEqual(apiLink); + expect(result).toEqual(expected); + }); + + it('should get studio v2 libraries data', async () => { + const apiLink = `${getApiBaseUrl()}/api/libraries/v2/`; + axiosMock.onGet(apiLink).reply(200, generateGetStudioHomeLibrariesV2ApiResponse()); + const result = await getStudioHomeLibrariesV2({}); + const expected = generateGetStudioHomeLibrariesV2ApiResponse(); expect(axiosMock.history.get[0].url).toEqual(apiLink); expect(result).toEqual(expected); diff --git a/src/studio-home/factories/mockApiResponses.jsx b/src/studio-home/factories/mockApiResponses.jsx index 30615ba8d5..5d75f9f592 100644 --- a/src/studio-home/factories/mockApiResponses.jsx +++ b/src/studio-home/factories/mockApiResponses.jsx @@ -112,7 +112,7 @@ export const generateGetStudioCoursesApiResponseV2 = () => ({ }, }); -export const generateGetStuioHomeLibrariesApiResponse = () => ({ +export const generateGetStudioHomeLibrariesApiResponse = () => ({ libraries: [ { displayName: 'MBA', @@ -125,6 +125,51 @@ export const generateGetStuioHomeLibrariesApiResponse = () => ({ ], }); +export const generateGetStudioHomeLibrariesV2ApiResponse = () => ({ + next: null, + previous: null, + count: 2, + numPages: 1, + currentPage: 1, + start: 0, + results: [ + { + id: 'lib:SampleTaxonomyOrg1:AL1', + type: 'complex', + org: 'SampleTaxonomyOrg1', + slug: 'AL1', + title: 'Another Library 2', + description: '', + numBlocks: 0, + version: 0, + lastPublished: null, + allowLti: false, + allowPublicLearning: false, + allowpublicRead: false, + hasUnpublishedChanges: false, + hasUnpublishedDeletes: false, + license: '', + }, + { + id: 'lib:SampleTaxonomyOrg1:TL1', + type: 'complex', + org: 'SampleTaxonomyOrg1', + slug: 'TL1', + title: 'Test Library 1', + description: '', + numBlocks: 0, + version: 0, + lastPublished: null, + allowLti: false, + allowPublicLearning: false, + allowPublicRead: false, + hasUnpublishedChanges: false, + hasUnpublishedDeletes: false, + license: '', + }, + ], +}); + export const generateNewVideoApiResponse = () => ({ files: [{ edx_video_id: 'mOckID4', diff --git a/src/studio-home/tabs-section/LibraryV2Placeholder.jsx b/src/studio-home/tabs-section/LibraryV2Placeholder.jsx index 6844515bd9..6b13853a2c 100644 --- a/src/studio-home/tabs-section/LibraryV2Placeholder.jsx +++ b/src/studio-home/tabs-section/LibraryV2Placeholder.jsx @@ -7,6 +7,7 @@ import Header from '../../header'; import SubHeader from '../../generic/sub-header/SubHeader'; import messages from './messages'; +/* istanbul ignore next */ const LibraryV2Placeholder = () => { const intl = useIntl(); diff --git a/src/studio-home/tabs-section/TabsSection.test.jsx b/src/studio-home/tabs-section/TabsSection.test.jsx index 945322dcd5..54741ebbb1 100644 --- a/src/studio-home/tabs-section/TabsSection.test.jsx +++ b/src/studio-home/tabs-section/TabsSection.test.jsx @@ -11,7 +11,7 @@ import { AppProvider } from '@edx/frontend-platform/react'; import MockAdapter from 'axios-mock-adapter'; import initializeStore from '../../store'; -import { studioHomeMock } from '../__mocks__'; +import { studioHomeMock, listStudioHomeV2LibrariesMock } from '../__mocks__'; import messages from '../messages'; import tabMessages from './messages'; import TabsSection from '.'; @@ -20,12 +20,32 @@ import { generateGetStudioHomeDataApiResponse, generateGetStudioCoursesApiResponse, generateGetStudioCoursesApiResponseV2, - generateGetStuioHomeLibrariesApiResponse, + generateGetStudioHomeLibrariesApiResponse, } from '../factories/mockApiResponses'; import { getApiBaseUrl, getStudioHomeApiUrl } from '../data/api'; import { executeThunk } from '../../utils'; import { fetchLibraryData, fetchStudioHomeData } from '../data/thunks'; +import useListStudioHomeV2Libraries from '../data/apiHooks'; + +jest.mock('../data/apiHooks', () => ({ + // Since only useListStudioHomeV2Libraries is exported as default + __esModule: true, + default: jest.fn(() => ({ + data: { + next: null, + previous: null, + count: 2, + num_pages: 1, + current_page: 1, + start: 0, + results: [], + }, + isLoading: false, + isError: false, + })), +})); + const { studioShortName } = studioHomeMock; let axiosMock; @@ -84,6 +104,10 @@ describe('', () => { }); store = initializeStore(initialState); axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + setConfig({ + ...getConfig(), + LIBRARY_MODE: 'mixed', + }); }); it('should render all tabs correctly', async () => { @@ -105,11 +129,47 @@ describe('', () => { expect(screen.getByText(tabMessages.coursesTabTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(tabMessages.librariesTabTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(tabMessages.legacyLibrariesTabTitle.defaultMessage)).toBeInTheDocument(); expect(screen.getByText(tabMessages.archivedTabTitle.defaultMessage)).toBeInTheDocument(); }); + it('should render only 1 library tab when "v1 only" lib mode', async () => { + setConfig({ + ...getConfig(), + LIBRARY_MODE: 'v1 only', + }); + + const data = generateGetStudioHomeDataApiResponse(); + + render(); + axiosMock.onGet(getStudioHomeApiUrl()).reply(200, data); + await executeThunk(fetchStudioHomeData(), store.dispatch); + + expect(screen.getByText(tabMessages.librariesTabTitle.defaultMessage)).toBeInTheDocument(); + + expect(screen.queryByText(tabMessages.legacyLibrariesTabTitle.defaultMessage)).not.toBeInTheDocument(); + }); + + it('should render only 1 library tab when "v2 only" lib mode', async () => { + setConfig({ + ...getConfig(), + LIBRARY_MODE: 'v2 only', + }); + + const data = generateGetStudioHomeDataApiResponse(); + + render(); + axiosMock.onGet(getStudioHomeApiUrl()).reply(200, data); + await executeThunk(fetchStudioHomeData(), store.dispatch); + + expect(screen.getByText(tabMessages.librariesTabTitle.defaultMessage)).toBeInTheDocument(); + + expect(screen.queryByText(tabMessages.legacyLibrariesTabTitle.defaultMessage)).not.toBeInTheDocument(); + }); + describe('course tab', () => { it('should render specific course details', async () => { render(); @@ -181,6 +241,46 @@ describe('', () => { const pagination = screen.queryByRole('navigation'); expect(pagination).not.toBeInTheDocument(); }); + + it('should set the url path to "/home" when switching away then back to courses tab', async () => { + const data = generateGetStudioCoursesApiResponseV2(); + data.results.courses = []; + render(); + axiosMock.onGet(getStudioHomeApiUrl()).reply(200, generateGetStudioHomeDataApiResponse()); + axiosMock.onGet(courseApiLinkV2).reply(200, data); + await executeThunk(fetchStudioHomeData(), store.dispatch); + + // confirm the url path is initially /home + waitFor(() => { + expect(window.location.href).toContain('/home'); + }); + + // switch to libraries tab + axiosMock.onGet(libraryApiLink).reply(200, generateGetStudioHomeLibrariesApiResponse()); + await executeThunk(fetchLibraryData(), store.dispatch); + const librariesTab = screen.getByText(tabMessages.legacyLibrariesTabTitle.defaultMessage); + await act(async () => { + fireEvent.click(librariesTab); + }); + + // confirm that the url path has changed + expect(librariesTab).toHaveClass('active'); + waitFor(() => { + expect(window.location.href).toContain('/legacy-libraries'); + }); + + // switch back to courses tab + const coursesTab = screen.getByText(tabMessages.coursesTabTitle.defaultMessage); + await act(async () => { + fireEvent.click(coursesTab); + }); + + // confirm that the url path is /home + expect(coursesTab).toHaveClass('active'); + waitFor(() => { + expect(window.location.href).toContain('/home'); + }); + }); }); describe('taxonomies tab', () => { @@ -247,6 +347,8 @@ describe('', () => { expect(screen.getByText(tabMessages.coursesTabTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(tabMessages.librariesTabTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(tabMessages.legacyLibrariesTabTitle.defaultMessage)).toBeInTheDocument(); expect(screen.queryByText(tabMessages.archivedTabTitle.defaultMessage)).toBeNull(); @@ -254,10 +356,10 @@ describe('', () => { }); describe('library tab', () => { - it('should switch to Libraries tab and render specific library details', async () => { + it('should switch to Legacy Libraries tab and render specific v1 library details', async () => { render(); axiosMock.onGet(getStudioHomeApiUrl()).reply(200, generateGetStudioHomeDataApiResponse()); - axiosMock.onGet(libraryApiLink).reply(200, generateGetStuioHomeLibrariesApiResponse()); + axiosMock.onGet(libraryApiLink).reply(200, generateGetStudioHomeLibrariesApiResponse()); await executeThunk(fetchStudioHomeData(), store.dispatch); await executeThunk(fetchLibraryData(), store.dispatch); @@ -273,6 +375,97 @@ describe('', () => { expect(screen.getByText(`${studioHomeMock.libraries[0].org} / ${studioHomeMock.libraries[0].number}`)).toBeVisible(); }); + it('should switch to Libraries tab and render specific v2 library details', async () => { + useListStudioHomeV2Libraries.mockReturnValue({ + data: listStudioHomeV2LibrariesMock, + isLoading: false, + isError: false, + }); + + render(); + axiosMock.onGet(getStudioHomeApiUrl()).reply(200, generateGetStudioHomeDataApiResponse()); + await executeThunk(fetchStudioHomeData(), store.dispatch); + + const librariesTab = screen.getByText(tabMessages.librariesTabTitle.defaultMessage); + await act(async () => { + fireEvent.click(librariesTab); + }); + + expect(librariesTab).toHaveClass('active'); + + expect(screen.getByText('Showing 2 of 2')).toBeVisible(); + + expect(screen.getByText(listStudioHomeV2LibrariesMock.results[0].title)).toBeVisible(); + expect(screen.getByText( + `${listStudioHomeV2LibrariesMock.results[0].org} / ${listStudioHomeV2LibrariesMock.results[0].slug}`, + )).toBeVisible(); + + expect(screen.getByText(listStudioHomeV2LibrariesMock.results[1].title)).toBeVisible(); + expect(screen.getByText( + `${listStudioHomeV2LibrariesMock.results[1].org} / ${listStudioHomeV2LibrariesMock.results[1].slug}`, + )).toBeVisible(); + }); + + it('should switch to Libraries tab and render specific v1 library details ("v1 only" mode)', async () => { + setConfig({ + ...getConfig(), + LIBRARY_MODE: 'v1 only', + }); + + render(); + axiosMock.onGet(getStudioHomeApiUrl()).reply(200, generateGetStudioHomeDataApiResponse()); + axiosMock.onGet(libraryApiLink).reply(200, generateGetStudioHomeLibrariesApiResponse()); + await executeThunk(fetchStudioHomeData(), store.dispatch); + await executeThunk(fetchLibraryData(), store.dispatch); + + const librariesTab = screen.getByText(tabMessages.librariesTabTitle.defaultMessage); + await act(async () => { + fireEvent.click(librariesTab); + }); + + expect(librariesTab).toHaveClass('active'); + + expect(screen.getByText(studioHomeMock.libraries[0].displayName)).toBeVisible(); + + expect(screen.getByText(`${studioHomeMock.libraries[0].org} / ${studioHomeMock.libraries[0].number}`)).toBeVisible(); + }); + + it('should switch to Libraries tab and render specific v2 library details ("v2 only" mode)', async () => { + setConfig({ + ...getConfig(), + LIBRARY_MODE: 'v2 only', + }); + + useListStudioHomeV2Libraries.mockReturnValue({ + data: listStudioHomeV2LibrariesMock, + isLoading: false, + isError: false, + }); + + render(); + axiosMock.onGet(getStudioHomeApiUrl()).reply(200, generateGetStudioHomeDataApiResponse()); + await executeThunk(fetchStudioHomeData(), store.dispatch); + + const librariesTab = screen.getByText(tabMessages.librariesTabTitle.defaultMessage); + await act(async () => { + fireEvent.click(librariesTab); + }); + + expect(librariesTab).toHaveClass('active'); + + expect(screen.getByText('Showing 2 of 2')).toBeVisible(); + + expect(screen.getByText(listStudioHomeV2LibrariesMock.results[0].title)).toBeVisible(); + expect(screen.getByText( + `${listStudioHomeV2LibrariesMock.results[0].org} / ${listStudioHomeV2LibrariesMock.results[0].slug}`, + )).toBeVisible(); + + expect(screen.getByText(listStudioHomeV2LibrariesMock.results[1].title)).toBeVisible(); + expect(screen.getByText( + `${listStudioHomeV2LibrariesMock.results[1].org} / ${listStudioHomeV2LibrariesMock.results[1].slug}`, + )).toBeVisible(); + }); + it('should hide Libraries tab when libraries are disabled', async () => { const data = generateGetStudioHomeDataApiResponse(); data.librariesEnabled = false; From 7842ce029fa770c5ae4e9f6021a81cc1b8c4c9e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Wed, 5 Jun 2024 10:57:46 -0300 Subject: [PATCH 13/88] fix: update search modal for new library urls --- src/header/Header.jsx | 2 +- src/search-modal/SearchResult.jsx | 26 +++++++++++++------------- src/search-modal/SearchUI.test.jsx | 18 +++++++++++------- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/header/Header.jsx b/src/header/Header.jsx index 8e15d32292..6865a3db96 100644 --- a/src/header/Header.jsx +++ b/src/header/Header.jsx @@ -52,7 +52,7 @@ const Header = ({ isHiddenMainMenu={isHiddenMainMenu} mainMenuDropdowns={mainMenuDropdowns} outlineLink={outlineLink} - searchButtonAction={meiliSearchEnabled && !isLibrary ? openSearchModal : undefined} + searchButtonAction={meiliSearchEnabled ? openSearchModal : undefined} /> { meiliSearchEnabled && ( { const { closeSearchModal } = useSearchContext(); const { libraryAuthoringMfeUrl, redirectToLibraryAuthoringMfe } = useSelector(getStudioHomeData); - const { usageKey } = hit; - - const noRedirectUrl = usageKey.startsWith('lb:') && !redirectToLibraryAuthoringMfe; - /** * Returns the URL for the context of the hit */ @@ -149,10 +144,16 @@ const SearchResult = ({ hit }) => { return `/${urlSuffix}`; } - if (usageKey.startsWith('lb:')) { - if (redirectToLibraryAuthoringMfe) { - return getLibraryHitUrl(hit, libraryAuthoringMfeUrl); + if (contextKey.startsWith('lib:')) { + const urlSuffix = getLibraryComponentUrlSuffix(hit); + if (libraryAuthoringMfeUrl) { + return `${libraryAuthoringMfeUrl}${urlSuffix}`; } + + if (newWindow) { + return `${getPath(getConfig().PUBLIC_PATH)}${urlSuffix}`; + } + return `/${urlSuffix}`; } // No context URL for this hit (e.g. a library without library authoring mfe) @@ -206,12 +207,12 @@ const SearchResult = ({ hit }) => { return ( @@ -230,7 +231,6 @@ const SearchResult = ({ hit }) => { diff --git a/src/search-modal/SearchUI.test.jsx b/src/search-modal/SearchUI.test.jsx index c653807dfb..0c46908c08 100644 --- a/src/search-modal/SearchUI.test.jsx +++ b/src/search-modal/SearchUI.test.jsx @@ -344,9 +344,10 @@ describe('', () => { window.location = location; }); - test('click lib component result doesnt navigates to the context withou libraryAuthoringMfe', async () => { + test('click lib component result navigates to course-authoring/library without libraryAuthoringMfe', async () => { const data = generateGetStudioHomeDataApiResponse(); data.redirectToLibraryAuthoringMfe = false; + data.libraryAuthoringMfeUrl = ''; axiosMock.onGet(getStudioHomeApiUrl()).reply(200, data); await executeThunk(fetchStudioHomeData(), store.dispatch); @@ -356,18 +357,21 @@ describe('', () => { const resultItem = await findByRole('button', { name: /Library Content/ }); // Clicking the "Open in new window" button should open the result in a new window: - const { open, location } = window; + const { open } = window; window.open = jest.fn(); fireEvent.click(within(resultItem).getByRole('button', { name: 'Open in new window' })); - expect(window.open).not.toHaveBeenCalled(); + + expect(window.open).toHaveBeenCalledWith( + '/library/lib:org1:libafter1', + '_blank', + ); window.open = open; - // @ts-ignore - window.location = { href: '' }; // Clicking in the result should navigate to the result's URL: fireEvent.click(resultItem); - expect(window.location.href === location.href); - window.location = location; + expect(mockNavigate).toHaveBeenCalledWith( + '/library/lib:org1:libafter1', + ); }); }); From 462cda93f674abe8941f66c3ab1ef7f6a26d5e9e Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Mon, 27 May 2024 20:13:28 +0300 Subject: [PATCH 14/88] feat: Add lib v2/legacy tabs in studio home When lib mode is set to "mixed", both "Libraries" and "Legacy Libraries" tabs are show in the Studio Home. When "Libraries" is clicked, v2 libraries are fetched, when "Legacy Libraries" is clicked, v1 libraries are fetched. When lib mode is set to "v1 only" or "v2 only", only one tab "Libraries" is show and only the respective libraries are fetched when the tab is clicked. --- src/studio-home/StudioHome.jsx | 9 ++- src/studio-home/data/api.js | 5 ++ src/studio-home/data/apiHooks.ts | 13 +++++ .../tabs-section/TabsSection.test.jsx | 12 ++-- src/studio-home/tabs-section/index.jsx | 47 ++++++++++----- .../tabs-section/libraries-v2-tab/index.tsx | 58 +++++++++++++++++++ src/studio-home/tabs-section/messages.js | 4 ++ src/studio-home/tabs-section/utils.js | 10 +++- 8 files changed, 134 insertions(+), 24 deletions(-) create mode 100644 src/studio-home/data/apiHooks.ts create mode 100644 src/studio-home/tabs-section/libraries-v2-tab/index.tsx diff --git a/src/studio-home/StudioHome.jsx b/src/studio-home/StudioHome.jsx index 8348aaca34..acc5cd1174 100644 --- a/src/studio-home/StudioHome.jsx +++ b/src/studio-home/StudioHome.jsx @@ -18,6 +18,7 @@ import Header from '../header'; import SubHeader from '../generic/sub-header/SubHeader'; import HomeSidebar from './home-sidebar'; import TabsSection from './tabs-section'; +import { isMixedOrV2LibrariesMode } from './tabs-section/utils'; import OrganizationSection from './organization-section'; import VerifyEmailLayout from './verify-email-layout'; import CreateNewCourseForm from './create-new-course-form'; @@ -43,12 +44,14 @@ const StudioHome = ({ intl }) => { dispatch, } = useStudioHome(isPaginationCoursesEnabled); + // TODO: this should be a flag in the backend + const LIB_MODE = 'mixed'; + const { userIsActive, studioShortName, studioRequestEmail, libraryAuthoringMfeUrl, - redirectToLibraryAuthoringMfe, } = studioHomeData; function getHeaderButtons() { @@ -79,8 +82,8 @@ const StudioHome = ({ intl }) => { } let libraryHref = `${getConfig().STUDIO_BASE_URL}/home_library`; - if (redirectToLibraryAuthoringMfe) { - libraryHref = `${libraryAuthoringMfeUrl}/create`; + if (isMixedOrV2LibrariesMode(LIB_MODE)) { + libraryHref = `${libraryAuthoringMfeUrl}create`; } headerButtons.push( diff --git a/src/studio-home/data/api.js b/src/studio-home/data/api.js index 1fefe2981a..0c09601d11 100644 --- a/src/studio-home/data/api.js +++ b/src/studio-home/data/api.js @@ -40,6 +40,11 @@ export async function getStudioHomeLibraries() { return camelCaseObject(data); } +export async function getStudioHomeLibrariesV2() { + const { data } = await getAuthenticatedHttpClient().get(`${getApiBaseUrl()}/api/libraries/v2/`); + return camelCaseObject(data); +} + /** * Handle course notification requests. * @param {string} url diff --git a/src/studio-home/data/apiHooks.ts b/src/studio-home/data/apiHooks.ts new file mode 100644 index 0000000000..7285874c64 --- /dev/null +++ b/src/studio-home/data/apiHooks.ts @@ -0,0 +1,13 @@ +import { useQuery } from '@tanstack/react-query'; + +import { getStudioHomeLibrariesV2 } from './api'; + +/** + * Builds the query to fetch list of V2 Libraries + */ +export const useListStudioHomeV2Libraries = () => ( + useQuery({ + queryKey: ['listV2Libraries'], + queryFn: () => getStudioHomeLibrariesV2(), + }) +); diff --git a/src/studio-home/tabs-section/TabsSection.test.jsx b/src/studio-home/tabs-section/TabsSection.test.jsx index fdc955d8df..ea5929aeec 100644 --- a/src/studio-home/tabs-section/TabsSection.test.jsx +++ b/src/studio-home/tabs-section/TabsSection.test.jsx @@ -80,7 +80,7 @@ describe('', () => { expect(screen.getByText(tabMessages.coursesTabTitle.defaultMessage)).toBeInTheDocument(); - expect(screen.getByText(tabMessages.librariesTabTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(tabMessages.legacyLibrariesTabTitle.defaultMessage)).toBeInTheDocument(); expect(screen.getByText(tabMessages.archivedTabTitle.defaultMessage)).toBeInTheDocument(); }); @@ -222,7 +222,7 @@ describe('', () => { expect(screen.getByText(tabMessages.coursesTabTitle.defaultMessage)).toBeInTheDocument(); - expect(screen.getByText(tabMessages.librariesTabTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(tabMessages.legacyLibrariesTabTitle.defaultMessage)).toBeInTheDocument(); expect(screen.queryByText(tabMessages.archivedTabTitle.defaultMessage)).toBeNull(); }); @@ -236,7 +236,7 @@ describe('', () => { await executeThunk(fetchStudioHomeData(), store.dispatch); await executeThunk(fetchLibraryData(), store.dispatch); - const librariesTab = screen.getByText(tabMessages.librariesTabTitle.defaultMessage); + const librariesTab = screen.getByText(tabMessages.legacyLibrariesTabTitle.defaultMessage); await act(async () => { fireEvent.click(librariesTab); }); @@ -257,7 +257,7 @@ describe('', () => { await executeThunk(fetchStudioHomeData(), store.dispatch); expect(screen.getByText(tabMessages.coursesTabTitle.defaultMessage)).toBeInTheDocument(); - expect(screen.queryByText(tabMessages.librariesTabTitle.defaultMessage)).toBeNull(); + expect(screen.queryByText(tabMessages.legacyLibrariesTabTitle.defaultMessage)).toBeNull(); }); it('should redirect to library authoring mfe', async () => { @@ -268,7 +268,7 @@ describe('', () => { axiosMock.onGet(getStudioHomeApiUrl()).reply(200, data); await executeThunk(fetchStudioHomeData(), store.dispatch); - const librariesTab = screen.getByText(tabMessages.librariesTabTitle.defaultMessage); + const librariesTab = screen.getByText(tabMessages.legacyLibrariesTabTitle.defaultMessage); fireEvent.click(librariesTab); waitFor(() => { @@ -283,7 +283,7 @@ describe('', () => { await executeThunk(fetchStudioHomeData(), store.dispatch); await executeThunk(fetchLibraryData(), store.dispatch); - const librariesTab = screen.getByText(tabMessages.librariesTabTitle.defaultMessage); + const librariesTab = screen.getByText(tabMessages.legacyLibrariesTabTitle.defaultMessage); await act(async () => { fireEvent.click(librariesTab); }); diff --git a/src/studio-home/tabs-section/index.jsx b/src/studio-home/tabs-section/index.jsx index 1409766c47..789bb2bea1 100644 --- a/src/studio-home/tabs-section/index.jsx +++ b/src/studio-home/tabs-section/index.jsx @@ -9,10 +9,12 @@ import { useNavigate } from 'react-router-dom'; import { getLoadingStatuses, getStudioHomeData } from '../data/selectors'; import messages from './messages'; import LibrariesTab from './libraries-tab'; +import LibrariesV2Tab from './libraries-v2-tab/index.tsx'; import ArchivedTab from './archived-tab'; import CoursesTab from './courses-tab'; import { RequestStatus } from '../../data/constants'; import { fetchLibraryData } from '../data/thunks'; +import { isMixedOrV1LibrariesMode, isMixedOrV2LibrariesMode } from './utils'; const TabsSection = ({ intl, @@ -23,9 +25,14 @@ const TabsSection = ({ isPaginationCoursesEnabled, }) => { const navigate = useNavigate(); + + // TODO: this should be a flag in the backend + const LIB_MODE = 'mixed'; + const TABS_LIST = { courses: 'courses', libraries: 'libraries', + legacyLibraries: 'legacyLibraries', archived: 'archived', taxonomies: 'taxonomies', }; @@ -87,21 +94,37 @@ const TabsSection = ({ } if (librariesEnabled) { - tabs.push( - - {!redirectToLibraryAuthoringMfe && ( + if (isMixedOrV2LibrariesMode(LIB_MODE)) { + tabs.push( + + + , + ); + } + + if (isMixedOrV1LibrariesMode(LIB_MODE)) { + tabs.push( + - )} - , - ); + , + ); + } } if (getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true') { @@ -118,9 +141,7 @@ const TabsSection = ({ }, [archivedCourses, librariesEnabled, showNewCourseContainer, isLoadingCourses, isLoadingLibraries]); const handleSelectTab = (tab) => { - if (tab === TABS_LIST.libraries && redirectToLibraryAuthoringMfe) { - window.location.assign(libraryAuthoringMfeUrl); - } else if (tab === TABS_LIST.libraries && !redirectToLibraryAuthoringMfe) { + if (tab === TABS_LIST.legacyLibraries) { dispatch(fetchLibraryData()); } else if (tab === TABS_LIST.taxonomies) { navigate('/taxonomies'); diff --git a/src/studio-home/tabs-section/libraries-v2-tab/index.tsx b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx new file mode 100644 index 0000000000..1e14ffef6c --- /dev/null +++ b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { Icon, Row } from '@openedx/paragon'; +import { useIntl } from '@edx/frontend-platform/i18n'; + +import { useListStudioHomeV2Libraries } from '../../data/apiHooks'; +import { LoadingSpinner } from '../../../generic/Loading'; +import AlertMessage from '../../../generic/alert-message'; +import CardItem from '../../card-item'; +import messages from '../messages'; + +const LibrariesV2Tab = () => { + const intl = useIntl(); + const { + data, + isLoading, + isError, + } = useListStudioHomeV2Libraries(); + + if (isLoading) { + return ( + + + + ); + } + + return ( + isError ? ( + + + {intl.formatMessage(messages.librariesTabErrorMessage)} + + )} + /> + ) : ( +
+ {data.map(({ org, slug, title }) => ( + + ))} +
+ ) + ); +}; + + +export default LibrariesV2Tab; diff --git a/src/studio-home/tabs-section/messages.js b/src/studio-home/tabs-section/messages.js index 5ae2e139b2..e1ad0fd44f 100644 --- a/src/studio-home/tabs-section/messages.js +++ b/src/studio-home/tabs-section/messages.js @@ -21,6 +21,10 @@ const messages = defineMessages({ id: 'course-authoring.studio-home.libraries.tab.title', defaultMessage: 'Libraries', }, + legacyLibrariesTabTitle: { + id: 'course-authoring.studio-home.legacy.libraries.tab.title', + defaultMessage: 'Legacy Libraries', + }, archivedTabTitle: { id: 'course-authoring.studio-home.archived.tab.title', defaultMessage: 'Archived courses', diff --git a/src/studio-home/tabs-section/utils.js b/src/studio-home/tabs-section/utils.js index 5d3822b8ed..e7dea1ad69 100644 --- a/src/studio-home/tabs-section/utils.js +++ b/src/studio-home/tabs-section/utils.js @@ -8,5 +8,11 @@ const sortAlphabeticallyArray = (arr) => [...arr] .sort((firstArrayData, secondArrayData) => firstArrayData .displayName.localeCompare(secondArrayData.displayName)); -// eslint-disable-next-line import/prefer-default-export -export { sortAlphabeticallyArray }; +const isMixedOrV1LibrariesMode = (libMode) => ['mixed', 'v1 only'].includes(libMode); +const isMixedOrV2LibrariesMode = (libMode) => ['mixed', 'v2 only'].includes(libMode); + +export { + sortAlphabeticallyArray, + isMixedOrV1LibrariesMode, + isMixedOrV2LibrariesMode, +}; From be8b2f4ce476771dcfa936f8984ed38d7ccb47a5 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Tue, 28 May 2024 17:31:08 +0300 Subject: [PATCH 15/88] feat: Add `LIBRARY_MODE` config variable This is to switch between different library modes. --- .env | 1 + .env.development | 1 + .env.test | 1 + README.rst | 16 ++++++++++++++++ .../feature-v2-and-legacy-libs.png | Bin 0 -> 246316 bytes src/index.jsx | 1 + src/studio-home/StudioHome.jsx | 5 ++--- src/studio-home/tabs-section/index.jsx | 11 ++++------- 8 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 docs/readme-images/feature-v2-and-legacy-libs.png diff --git a/.env b/.env index ce17454708..4235461134 100644 --- a/.env +++ b/.env @@ -43,3 +43,4 @@ AI_TRANSLATIONS_BASE_URL='' ENABLE_HOME_PAGE_COURSE_API_V2=false ENABLE_CHECKLIST_QUALITY='' ENABLE_GRADING_METHOD_IN_PROBLEMS=false +LIBRARY_MODE="v1 only" diff --git a/.env.development b/.env.development index 983ce9674f..5547e8ffec 100644 --- a/.env.development +++ b/.env.development @@ -46,3 +46,4 @@ AI_TRANSLATIONS_BASE_URL='http://localhost:18760' ENABLE_HOME_PAGE_COURSE_API_V2=false ENABLE_CHECKLIST_QUALITY=true ENABLE_GRADING_METHOD_IN_PROBLEMS=false +LIBRARY_MODE="mixed" diff --git a/.env.test b/.env.test index 28240ad2ff..0f73517968 100644 --- a/.env.test +++ b/.env.test @@ -37,3 +37,4 @@ INVITE_STUDENTS_EMAIL_TO="someone@domain.com" ENABLE_HOME_PAGE_COURSE_API_V2=true ENABLE_CHECKLIST_QUALITY=true ENABLE_GRADING_METHOD_IN_PROBLEMS=false +LIBRARY_MODE="mixed" diff --git a/README.rst b/README.rst index 3847453ea3..6f1de194b4 100644 --- a/README.rst +++ b/README.rst @@ -264,6 +264,22 @@ In additional to the standard settings, the following local configuration items Tagging/Taxonomy functionality. +Feature: Libraries V2/Legacy Tabs +================================= + +.. image:: ./docs/readme-images/feature-v2-and-legacy-libs.png + +Configuration +------------- + +In additional to the standard settings, the following local configurations can be set to switch between different library modes: + +* ``LIBRARY_MODE``: can be set to ``mixed`` (default for development), ``v1 only`` (default for production) and ``v2 only``. + + * ``mixed``: Shows 2 tabs, "Libraries" that lists the v2 libraries and "Legacy Libraries" that lists the v1 libraries. When creating a new library in this mode it will create a new v2 library. + * ``v1 only``: Shows only 1 tab, "Libraries" that lists v1 libraries only. When creating a new library in this mode it will create a new v1 library. + * ``v2 only``: Shows only 1 tab, "Libraries" that lists v2 libraries only. When creating a new library in this mode it will create a new v2 library. + Developing ********** diff --git a/docs/readme-images/feature-v2-and-legacy-libs.png b/docs/readme-images/feature-v2-and-legacy-libs.png new file mode 100644 index 0000000000000000000000000000000000000000..c8fd363655f7e4fc0b026bc9c7f9faf0df045e7a GIT binary patch literal 246316 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
-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

wa;w%Q#(cB+nr}wMmuC0|@ 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 { dispatch, } = useStudioHome(isPaginationCoursesEnabled); - // TODO: this should be a flag in the backend - const LIB_MODE = 'mixed'; + const libMode = getConfig().LIBRARY_MODE; const { userIsActive, @@ -82,7 +81,7 @@ const StudioHome = ({ intl }) => { } let libraryHref = `${getConfig().STUDIO_BASE_URL}/home_library`; - if (isMixedOrV2LibrariesMode(LIB_MODE)) { + if (isMixedOrV2LibrariesMode(libMode)) { libraryHref = `${libraryAuthoringMfeUrl}create`; } diff --git a/src/studio-home/tabs-section/index.jsx b/src/studio-home/tabs-section/index.jsx index 789bb2bea1..997796913f 100644 --- a/src/studio-home/tabs-section/index.jsx +++ b/src/studio-home/tabs-section/index.jsx @@ -25,10 +25,7 @@ const TabsSection = ({ isPaginationCoursesEnabled, }) => { const navigate = useNavigate(); - - // TODO: this should be a flag in the backend - const LIB_MODE = 'mixed'; - + const libMode = getConfig().LIBRARY_MODE; const TABS_LIST = { courses: 'courses', libraries: 'libraries', @@ -94,7 +91,7 @@ const TabsSection = ({ } if (librariesEnabled) { - if (isMixedOrV2LibrariesMode(LIB_MODE)) { + if (isMixedOrV2LibrariesMode(libMode)) { tabs.push( Date: Tue, 28 May 2024 21:23:39 +0300 Subject: [PATCH 16/88] feat: Add url paths/navigation for each tab The path updates when selecting tabs, when accessing the url with the path directly it will open its respective tab. Navigating using the browser back/forward buttons is also supported. --- src/index.jsx | 2 ++ src/studio-home/data/api.js | 4 +++ src/studio-home/tabs-section/index.jsx | 48 +++++++++++++++++++++++--- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/index.jsx b/src/index.jsx index e3d21096a3..f17c0563ab 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -52,6 +52,8 @@ const App = () => { createRoutesFromElements( } /> + } /> + } /> } /> } /> {getConfig().ENABLE_ACCESSIBILITY_PAGE === 'true' && ( diff --git a/src/studio-home/data/api.js b/src/studio-home/data/api.js index 0c09601d11..69e0487fff 100644 --- a/src/studio-home/data/api.js +++ b/src/studio-home/data/api.js @@ -40,6 +40,10 @@ export async function getStudioHomeLibraries() { return camelCaseObject(data); } +/** + * Get's studio home v2 Libraries. + * @returns {Promise} + */ export async function getStudioHomeLibrariesV2() { const { data } = await getAuthenticatedHttpClient().get(`${getApiBaseUrl()}/api/libraries/v2/`); return camelCaseObject(data); diff --git a/src/studio-home/tabs-section/index.jsx b/src/studio-home/tabs-section/index.jsx index 997796913f..089f9bc842 100644 --- a/src/studio-home/tabs-section/index.jsx +++ b/src/studio-home/tabs-section/index.jsx @@ -1,10 +1,10 @@ -import React, { useMemo, useState } from 'react'; +import React, { useMemo, useState, useEffect } from 'react'; import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import { Tab, Tabs } from '@openedx/paragon'; import { getConfig } from '@edx/frontend-platform'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useLocation } from 'react-router-dom'; import { getLoadingStatuses, getStudioHomeData } from '../data/selectors'; import messages from './messages'; @@ -25,6 +25,7 @@ const TabsSection = ({ isPaginationCoursesEnabled, }) => { const navigate = useNavigate(); + const { pathname } = useLocation(); const libMode = getConfig().LIBRARY_MODE; const TABS_LIST = { courses: 'courses', @@ -33,7 +34,37 @@ const TabsSection = ({ archived: 'archived', taxonomies: 'taxonomies', }; - const [tabKey, setTabKey] = useState(TABS_LIST.courses); + + const initTabKeyState = (pname) => { + if (pname.includes('/libraries')) { + return isMixedOrV2LibrariesMode(libMode) + ? TABS_LIST.libraries + : TABS_LIST.legacyLibraries; + } + + if (pname.includes('/legacy-libraries')) { + return TABS_LIST.legacyLibraries; + } + + // Default to courses tab + return TABS_LIST.courses; + }; + + const [tabKey, setTabKey] = useState(initTabKeyState(pathname)); + + // This is needed to handle navigating using the back/forward buttons in the browser + useEffect(() => { + // Handle special case when navigating directly to /legacy-libraries or /libraries in `v1 only` mode + // we need to call dispatch to fetch library data + if ( + (isMixedOrV1LibrariesMode(libMode) && pathname.includes('/libraries')) + || pathname.includes('/legacy-libraries') + ) { + dispatch(fetchLibraryData()); + } + setTabKey(initTabKeyState(pathname)); + }, [pathname]); + const { libraryAuthoringMfeUrl, redirectToLibraryAuthoringMfe, @@ -138,8 +169,17 @@ const TabsSection = ({ }, [archivedCourses, librariesEnabled, showNewCourseContainer, isLoadingCourses, isLoadingLibraries]); const handleSelectTab = (tab) => { - if (tab === TABS_LIST.legacyLibraries) { + if (tab === TABS_LIST.courses) { + navigate('/home'); + } else if (tab === TABS_LIST.legacyLibraries) { dispatch(fetchLibraryData()); + navigate( + libMode === 'v1 only' + ? '/libraries' + : '/legacy-libraries', + ); + } else if (tab === TABS_LIST.libraries) { + navigate('/libraries'); } else if (tab === TABS_LIST.taxonomies) { navigate('/taxonomies'); } From 4ffd65195d1605cf486d4df0e8c51bc2b731bc87 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Thu, 30 May 2024 14:52:53 +0300 Subject: [PATCH 17/88] feat: LibraryV2 redirect to lib mfe or placeholder --- src/index.jsx | 2 ++ .../tabs-section/LibraryV2Placeholder.tsx | 36 +++++++++++++++++++ src/studio-home/tabs-section/index.jsx | 5 ++- .../tabs-section/libraries-v2-tab/index.tsx | 23 +++++++++--- src/studio-home/tabs-section/messages.js | 8 +++++ 5 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 src/studio-home/tabs-section/LibraryV2Placeholder.tsx diff --git a/src/index.jsx b/src/index.jsx index f17c0563ab..8bd2d4ef06 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -23,6 +23,7 @@ import initializeStore from './store'; import CourseAuthoringRoutes from './CourseAuthoringRoutes'; import Head from './head/Head'; import { StudioHome } from './studio-home'; +import LibraryV2Placeholder from './studio-home/tabs-section/LibraryV2Placeholder.tsx'; import CourseRerun from './course-rerun'; import { TaxonomyLayout, TaxonomyDetailPage, TaxonomyListPage } from './taxonomy'; import { ContentTagsDrawer } from './content-tags-drawer'; @@ -54,6 +55,7 @@ const App = () => { } /> } /> } /> + } /> } /> } /> {getConfig().ENABLE_ACCESSIBILITY_PAGE === 'true' && ( diff --git a/src/studio-home/tabs-section/LibraryV2Placeholder.tsx b/src/studio-home/tabs-section/LibraryV2Placeholder.tsx new file mode 100644 index 0000000000..ba47ee8899 --- /dev/null +++ b/src/studio-home/tabs-section/LibraryV2Placeholder.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { Container } from '@openedx/paragon'; +import { StudioFooter } from '@edx/frontend-component-footer'; +import { useIntl } from '@edx/frontend-platform/i18n'; + +import Header from '../../header'; +import SubHeader from '../../generic/sub-header/SubHeader'; +import messages from './messages'; + + +const LibraryV2Placeholder = () => { + const intl = useIntl(); + + return ( + <> +
+ +
+
+
+ +
+
+
+

{intl.formatMessage(messages.libraryV2PlaceholderBody)}

+
+
+
+ + + ); +}; + +export default LibraryV2Placeholder; diff --git a/src/studio-home/tabs-section/index.jsx b/src/studio-home/tabs-section/index.jsx index 089f9bc842..aa9d1aa0e2 100644 --- a/src/studio-home/tabs-section/index.jsx +++ b/src/studio-home/tabs-section/index.jsx @@ -129,7 +129,10 @@ const TabsSection = ({ eventKey={TABS_LIST.libraries} title={intl.formatMessage(messages.librariesTabTitle)} > - + , ); } diff --git a/src/studio-home/tabs-section/libraries-v2-tab/index.tsx b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx index 1e14ffef6c..98888f79a8 100644 --- a/src/studio-home/tabs-section/libraries-v2-tab/index.tsx +++ b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { Icon, Row } from '@openedx/paragon'; import { useIntl } from '@edx/frontend-platform/i18n'; @@ -8,7 +9,10 @@ import AlertMessage from '../../../generic/alert-message'; import CardItem from '../../card-item'; import messages from '../messages'; -const LibrariesV2Tab = () => { +const LibrariesV2Tab = ({ + libraryAuthoringMfeUrl, + redirectToLibraryAuthoringMfe, +}) => { const intl = useIntl(); const { data, @@ -24,6 +28,14 @@ const LibrariesV2Tab = () => { ); } + const libURL = (id: string): string => ( + libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe + ? `${libraryAuthoringMfeUrl}library/${id}` + // Redirection to the placeholder is done in the MFE rather than + // through the backend i.e. redirection from cms, because this this will probably change + : `${window.location.origin}/course-authoring/library/${id}` + ); + return ( isError ? ( { /> ) : (
- {data.map(({ org, slug, title }) => ( + {data.map(({ id, org, slug, title }) => ( ))}
@@ -54,5 +65,9 @@ const LibrariesV2Tab = () => { ); }; +LibrariesV2Tab.propTypes = { + libraryAuthoringMfeUrl: PropTypes.string.isRequired, + redirectToLibraryAuthoringMfe: PropTypes.bool.isRequired, +}; export default LibrariesV2Tab; diff --git a/src/studio-home/tabs-section/messages.js b/src/studio-home/tabs-section/messages.js index e1ad0fd44f..0ed614f55a 100644 --- a/src/studio-home/tabs-section/messages.js +++ b/src/studio-home/tabs-section/messages.js @@ -50,6 +50,14 @@ const messages = defineMessages({ defaultMessage: 'Taxonomies', description: 'Title of Taxonomies tab on the home page', }, + libraryV2PlaceholderTitle: { + id: 'course-authoring.studio-home.libraries.placeholder.title', + defaultMessage: 'Library V2 Placeholder', + }, + libraryV2PlaceholderBody: { + id: 'course-authoring.studio-home.libraries.placeholder.body', + defaultMessage: 'This is a placeholder page, as the Library Authoring MFE is not enabled.', + }, }); export default messages; From 7f97243f65750e2de0542023b1b144ed9fcee129 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Thu, 30 May 2024 18:56:20 +0300 Subject: [PATCH 18/88] feat: Add pagination support for lib v2s --- src/studio-home/data/api.js | 19 ++++++++-- src/studio-home/data/apiHooks.ts | 14 +++++-- .../tabs-section/libraries-v2-tab/index.tsx | 38 ++++++++++++++++--- 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/studio-home/data/api.js b/src/studio-home/data/api.js index 69e0487fff..2124f6fed7 100644 --- a/src/studio-home/data/api.js +++ b/src/studio-home/data/api.js @@ -42,10 +42,23 @@ export async function getStudioHomeLibraries() { /** * Get's studio home v2 Libraries. - * @returns {Promise} + * @param {object} customParams - Additional custom paramaters for the API request. + * @param {string} [customParams.type] - (optional) Library type, default `complex` + * @param {number} [customParams.page] - (optional) Page number of results + * @param {number} [customParams.pageSize] - (optional) The number of results on each page, default `50` + * @param {boolean} [customParams.pagination] - (optional) Whether pagination is supported, default `true` + * @returns {Promise} - A Promise that resolves to the response data container the studio home v2 libraries. */ -export async function getStudioHomeLibrariesV2() { - const { data } = await getAuthenticatedHttpClient().get(`${getApiBaseUrl()}/api/libraries/v2/`); +export async function getStudioHomeLibrariesV2(customParams) { + // Set default params if not passed in + const customParamsDefaults = { + type: customParams.type || 'complex', + page: customParams.page || 1, + pageSize: customParams.pageSize || 50, + pagination: customParams.pagination !== undefined ? customParams.pagination : true, + }; + const customParamsFormat = snakeCaseObject(customParamsDefaults); + const { data } = await getAuthenticatedHttpClient().get(`${getApiBaseUrl()}/api/libraries/v2/`, { params: customParamsFormat }); return camelCaseObject(data); } diff --git a/src/studio-home/data/apiHooks.ts b/src/studio-home/data/apiHooks.ts index 7285874c64..79929f040f 100644 --- a/src/studio-home/data/apiHooks.ts +++ b/src/studio-home/data/apiHooks.ts @@ -2,12 +2,20 @@ import { useQuery } from '@tanstack/react-query'; import { getStudioHomeLibrariesV2 } from './api'; + +interface CustomParams { + type?: string, + page?: number, + pageSize?: number, + pagination?: boolean, +} + /** * Builds the query to fetch list of V2 Libraries */ -export const useListStudioHomeV2Libraries = () => ( +export const useListStudioHomeV2Libraries = (customParams: CustomParams) => ( useQuery({ - queryKey: ['listV2Libraries'], - queryFn: () => getStudioHomeLibrariesV2(), + queryKey: ['listV2Libraries', customParams], + queryFn: () => getStudioHomeLibrariesV2(customParams), }) ); diff --git a/src/studio-home/tabs-section/libraries-v2-tab/index.tsx b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx index 98888f79a8..c26527edd8 100644 --- a/src/studio-home/tabs-section/libraries-v2-tab/index.tsx +++ b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import { Icon, Row } from '@openedx/paragon'; +import { Icon, Row, Pagination } from '@openedx/paragon'; import { useIntl } from '@edx/frontend-platform/i18n'; import { useListStudioHomeV2Libraries } from '../../data/apiHooks'; @@ -14,11 +14,18 @@ const LibrariesV2Tab = ({ redirectToLibraryAuthoringMfe, }) => { const intl = useIntl(); + + const [currentPage, setCurrentPage] = useState(1); + + const handlePageSelect = (page) => { + setCurrentPage(page); + }; + const { data, isLoading, isError, - } = useListStudioHomeV2Libraries(); + } = useListStudioHomeV2Libraries({page: currentPage}); if (isLoading) { return ( @@ -49,8 +56,19 @@ const LibrariesV2Tab = ({ )} /> ) : ( -
- {data.map(({ id, org, slug, title }) => ( +
+
+ {/* Temporary div to add spacing. This will be replaced with lib search/filters */} +
+

+ {intl.formatMessage(messages.coursesPaginationInfo, { + length: data.results.length, + total: data.count, + })} +

+
+ + {data.results.map(({ id, org, slug, title }) => ( ))} + + {data.numPages > 1 && + + }
) ); From c86b85a4eb77bb94e0f8e0a8ae7f4e16dfa9fcbc Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Mon, 3 Jun 2024 16:50:53 +0300 Subject: [PATCH 19/88] fix: Redirect to placeholder create lib in v2/mixed disabled mfe --- src/studio-home/StudioHome.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/studio-home/StudioHome.jsx b/src/studio-home/StudioHome.jsx index 2d68af9c25..52be27a60f 100644 --- a/src/studio-home/StudioHome.jsx +++ b/src/studio-home/StudioHome.jsx @@ -51,6 +51,7 @@ const StudioHome = ({ intl }) => { studioShortName, studioRequestEmail, libraryAuthoringMfeUrl, + redirectToLibraryAuthoringMfe, } = studioHomeData; function getHeaderButtons() { @@ -82,7 +83,9 @@ const StudioHome = ({ intl }) => { let libraryHref = `${getConfig().STUDIO_BASE_URL}/home_library`; if (isMixedOrV2LibrariesMode(libMode)) { - libraryHref = `${libraryAuthoringMfeUrl}create`; + libraryHref = libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe + ? `${libraryAuthoringMfeUrl}create` + : `${window.location.origin}/course-authoring/library/create`; } headerButtons.push( From efbc625aaa03a51e0f010ae25a7821b0404b854a Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Mon, 3 Jun 2024 17:03:01 +0300 Subject: [PATCH 20/88] temp: This removes TS code to get tests to run This commit is temporary as the current frontend build system in tests doesnt support TS syntax. That should be fixed soon, and this commit should be removed. --- src/studio-home/data/apiHooks.ts | 9 +-------- src/studio-home/tabs-section/libraries-v2-tab/index.tsx | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/studio-home/data/apiHooks.ts b/src/studio-home/data/apiHooks.ts index 79929f040f..ec163e5732 100644 --- a/src/studio-home/data/apiHooks.ts +++ b/src/studio-home/data/apiHooks.ts @@ -3,17 +3,10 @@ import { useQuery } from '@tanstack/react-query'; import { getStudioHomeLibrariesV2 } from './api'; -interface CustomParams { - type?: string, - page?: number, - pageSize?: number, - pagination?: boolean, -} - /** * Builds the query to fetch list of V2 Libraries */ -export const useListStudioHomeV2Libraries = (customParams: CustomParams) => ( +export const useListStudioHomeV2Libraries = (customParams) => ( useQuery({ queryKey: ['listV2Libraries', customParams], queryFn: () => getStudioHomeLibrariesV2(customParams), diff --git a/src/studio-home/tabs-section/libraries-v2-tab/index.tsx b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx index c26527edd8..a659dcc1fa 100644 --- a/src/studio-home/tabs-section/libraries-v2-tab/index.tsx +++ b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx @@ -35,7 +35,7 @@ const LibrariesV2Tab = ({ ); } - const libURL = (id: string): string => ( + const libURL = (id) => ( libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe ? `${libraryAuthoringMfeUrl}library/${id}` // Redirection to the placeholder is done in the MFE rather than From 21da6f84f922c0834d4a06e958ef89161a43b01a Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Mon, 3 Jun 2024 18:35:29 +0300 Subject: [PATCH 21/88] test: Update existing tests to support changes --- src/setupTest.js | 1 + src/studio-home/StudioHome.test.jsx | 46 ++++++++++++++----- src/studio-home/__mocks__/studioHomeMock.js | 2 +- .../tabs-section/TabsSection.test.jsx | 39 +++++++++++++--- 4 files changed, 69 insertions(+), 19 deletions(-) diff --git a/src/setupTest.js b/src/setupTest.js index 35b1c9ebe2..f0f7f6a435 100755 --- a/src/setupTest.js +++ b/src/setupTest.js @@ -48,6 +48,7 @@ mergeConfig({ ENABLE_TEAM_TYPE_SETTING: process.env.ENABLE_TEAM_TYPE_SETTING === 'true', ENABLE_CHECKLIST_QUALITY: process.env.ENABLE_CHECKLIST_QUALITY || 'true', STUDIO_BASE_URL: process.env.STUDIO_BASE_URL || null, + LIBRARY_MODE: process.env.LIBRARY_MODE || 'v1 only', }, 'CourseAuthoringConfig'); class ResizeObserver { diff --git a/src/studio-home/StudioHome.test.jsx b/src/studio-home/StudioHome.test.jsx index 7286acda0f..49ca600e5d 100644 --- a/src/studio-home/StudioHome.test.jsx +++ b/src/studio-home/StudioHome.test.jsx @@ -1,6 +1,8 @@ import React from 'react'; import { useSelector } from 'react-redux'; -import { initializeMockApp } from '@edx/frontend-platform'; +import { MemoryRouter, Routes, Route } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { initializeMockApp, getConfig, setConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; import { AppProvider } from '@edx/frontend-platform/react'; @@ -23,7 +25,6 @@ import { StudioHome } from '.'; let axiosMock; let store; -const mockPathname = '/foo-bar'; const { studioShortName, studioRequestEmail, @@ -34,17 +35,29 @@ jest.mock('react-redux', () => ({ useSelector: jest.fn(), })); -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useLocation: () => ({ - pathname: mockPathname, - }), -})); +const queryClient = new QueryClient(); const RootWrapper = () => ( - + - + + + + } + /> + } + /> + } + /> + + + ); @@ -145,7 +158,18 @@ describe('', async () => { }); describe('render new library button', () => { - it('href should include home_library', async () => { + beforeEach(() => { + setConfig({ + ...getConfig(), + LIBRARY_MODE: 'mixed', + }); + }); + + it('href should include home_library when in "v1 only" lib mode', async () => { + setConfig({ + ...getConfig(), + LIBRARY_MODE: 'v1 only', + }); useSelector.mockReturnValue({ ...studioHomeMock, courseCreatorStatus: COURSE_CREATOR_STATES.granted, diff --git a/src/studio-home/__mocks__/studioHomeMock.js b/src/studio-home/__mocks__/studioHomeMock.js index 5385201e52..4f66cc116f 100644 --- a/src/studio-home/__mocks__/studioHomeMock.js +++ b/src/studio-home/__mocks__/studioHomeMock.js @@ -62,7 +62,7 @@ module.exports = { }, ], librariesEnabled: true, - libraryAuthoringMfeUrl: 'http://localhost:3001', + libraryAuthoringMfeUrl: 'http://localhost:3001/', optimizationEnabled: false, redirectToLibraryAuthoringMfe: false, requestCourseCreatorUrl: '/request_course_creator', diff --git a/src/studio-home/tabs-section/TabsSection.test.jsx b/src/studio-home/tabs-section/TabsSection.test.jsx index ea5929aeec..945322dcd5 100644 --- a/src/studio-home/tabs-section/TabsSection.test.jsx +++ b/src/studio-home/tabs-section/TabsSection.test.jsx @@ -1,4 +1,6 @@ import React from 'react'; +import { MemoryRouter, Routes, Route } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { getConfig, initializeMockApp, setConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { @@ -34,15 +36,38 @@ const libraryApiLink = `${getApiBaseUrl()}/api/contentstore/v1/home/libraries`; const mockDispatch = jest.fn(); +const queryClient = new QueryClient(); + +const tabSectionComponent = (overrideProps) => ( + +); + const RootWrapper = (overrideProps) => ( - + - + + + + + + + + + ); From 79e6516b32cc6b03e5438f5c33138a20d72bea24 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Mon, 3 Jun 2024 19:04:16 +0300 Subject: [PATCH 22/88] temp: Rename .tsx -> .jsx & .ts -> .js for tests This is a temporary commit since there are currently no webpack loaders that support tsx files in the test running. This commit should be removed once that is fixed upstream. --- src/index.jsx | 2 +- src/studio-home/data/{apiHooks.ts => apiHooks.js} | 0 .../{LibraryV2Placeholder.tsx => LibraryV2Placeholder.jsx} | 0 src/studio-home/tabs-section/index.jsx | 2 +- .../tabs-section/libraries-v2-tab/{index.tsx => index.jsx} | 0 5 files changed, 2 insertions(+), 2 deletions(-) rename src/studio-home/data/{apiHooks.ts => apiHooks.js} (100%) rename src/studio-home/tabs-section/{LibraryV2Placeholder.tsx => LibraryV2Placeholder.jsx} (100%) rename src/studio-home/tabs-section/libraries-v2-tab/{index.tsx => index.jsx} (100%) diff --git a/src/index.jsx b/src/index.jsx index 8bd2d4ef06..93fe3c3f4c 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -23,7 +23,7 @@ import initializeStore from './store'; import CourseAuthoringRoutes from './CourseAuthoringRoutes'; import Head from './head/Head'; import { StudioHome } from './studio-home'; -import LibraryV2Placeholder from './studio-home/tabs-section/LibraryV2Placeholder.tsx'; +import LibraryV2Placeholder from './studio-home/tabs-section/LibraryV2Placeholder'; import CourseRerun from './course-rerun'; import { TaxonomyLayout, TaxonomyDetailPage, TaxonomyListPage } from './taxonomy'; import { ContentTagsDrawer } from './content-tags-drawer'; diff --git a/src/studio-home/data/apiHooks.ts b/src/studio-home/data/apiHooks.js similarity index 100% rename from src/studio-home/data/apiHooks.ts rename to src/studio-home/data/apiHooks.js diff --git a/src/studio-home/tabs-section/LibraryV2Placeholder.tsx b/src/studio-home/tabs-section/LibraryV2Placeholder.jsx similarity index 100% rename from src/studio-home/tabs-section/LibraryV2Placeholder.tsx rename to src/studio-home/tabs-section/LibraryV2Placeholder.jsx diff --git a/src/studio-home/tabs-section/index.jsx b/src/studio-home/tabs-section/index.jsx index aa9d1aa0e2..703a4e6f04 100644 --- a/src/studio-home/tabs-section/index.jsx +++ b/src/studio-home/tabs-section/index.jsx @@ -9,7 +9,7 @@ import { useNavigate, useLocation } from 'react-router-dom'; import { getLoadingStatuses, getStudioHomeData } from '../data/selectors'; import messages from './messages'; import LibrariesTab from './libraries-tab'; -import LibrariesV2Tab from './libraries-v2-tab/index.tsx'; +import LibrariesV2Tab from './libraries-v2-tab/index'; import ArchivedTab from './archived-tab'; import CoursesTab from './courses-tab'; import { RequestStatus } from '../../data/constants'; diff --git a/src/studio-home/tabs-section/libraries-v2-tab/index.tsx b/src/studio-home/tabs-section/libraries-v2-tab/index.jsx similarity index 100% rename from src/studio-home/tabs-section/libraries-v2-tab/index.tsx rename to src/studio-home/tabs-section/libraries-v2-tab/index.jsx From 262cb3fdf8578b937bcb3233a7d53c8afcf53cd1 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Mon, 3 Jun 2024 19:24:05 +0300 Subject: [PATCH 23/88] fix: Fix lint issues --- src/studio-home/data/apiHooks.js | 5 +- .../tabs-section/LibraryV2Placeholder.jsx | 1 - .../tabs-section/libraries-v2-tab/index.jsx | 47 +++++++++++-------- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/studio-home/data/apiHooks.js b/src/studio-home/data/apiHooks.js index ec163e5732..92575bf717 100644 --- a/src/studio-home/data/apiHooks.js +++ b/src/studio-home/data/apiHooks.js @@ -2,13 +2,14 @@ import { useQuery } from '@tanstack/react-query'; import { getStudioHomeLibrariesV2 } from './api'; - /** * Builds the query to fetch list of V2 Libraries */ -export const useListStudioHomeV2Libraries = (customParams) => ( +const useListStudioHomeV2Libraries = (customParams) => ( useQuery({ queryKey: ['listV2Libraries', customParams], queryFn: () => getStudioHomeLibrariesV2(customParams), }) ); + +export default useListStudioHomeV2Libraries; diff --git a/src/studio-home/tabs-section/LibraryV2Placeholder.jsx b/src/studio-home/tabs-section/LibraryV2Placeholder.jsx index ba47ee8899..6844515bd9 100644 --- a/src/studio-home/tabs-section/LibraryV2Placeholder.jsx +++ b/src/studio-home/tabs-section/LibraryV2Placeholder.jsx @@ -7,7 +7,6 @@ import Header from '../../header'; import SubHeader from '../../generic/sub-header/SubHeader'; import messages from './messages'; - const LibraryV2Placeholder = () => { const intl = useIntl(); diff --git a/src/studio-home/tabs-section/libraries-v2-tab/index.jsx b/src/studio-home/tabs-section/libraries-v2-tab/index.jsx index a659dcc1fa..9060493dd1 100644 --- a/src/studio-home/tabs-section/libraries-v2-tab/index.jsx +++ b/src/studio-home/tabs-section/libraries-v2-tab/index.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { Icon, Row, Pagination } from '@openedx/paragon'; import { useIntl } from '@edx/frontend-platform/i18n'; -import { useListStudioHomeV2Libraries } from '../../data/apiHooks'; +import useListStudioHomeV2Libraries from '../../data/apiHooks'; import { LoadingSpinner } from '../../../generic/Loading'; import AlertMessage from '../../../generic/alert-message'; import CardItem from '../../card-item'; @@ -25,7 +25,7 @@ const LibrariesV2Tab = ({ data, isLoading, isError, - } = useListStudioHomeV2Libraries({page: currentPage}); + } = useListStudioHomeV2Libraries({ page: currentPage }); if (isLoading) { return ( @@ -68,25 +68,32 @@ const LibrariesV2Tab = ({

- {data.results.map(({ id, org, slug, title }) => ( - - ))} + { + data.results.map(({ + id, org, slug, title, + }) => ( + + )) + } - {data.numPages > 1 && - + { + data.numPages > 1 + && ( + + ) }
) From 1ea229fc85d351a2610cd07085e33ac0728d3c12 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Tue, 4 Jun 2024 22:58:51 +0300 Subject: [PATCH 24/88] test: Add tests for new functionality --- src/studio-home/__mocks__/index.js | 2 +- .../listStudioHomeV2LibrariesMock.js | 44 ++++ src/studio-home/data/api.test.js | 24 ++- .../factories/mockApiResponses.jsx | 47 +++- .../tabs-section/LibraryV2Placeholder.jsx | 1 + .../tabs-section/TabsSection.test.jsx | 201 +++++++++++++++++- 6 files changed, 309 insertions(+), 10 deletions(-) create mode 100644 src/studio-home/__mocks__/listStudioHomeV2LibrariesMock.js diff --git a/src/studio-home/__mocks__/index.js b/src/studio-home/__mocks__/index.js index 92461eb0bb..af2a85b390 100644 --- a/src/studio-home/__mocks__/index.js +++ b/src/studio-home/__mocks__/index.js @@ -1,2 +1,2 @@ -// eslint-disable-next-line import/prefer-default-export export { default as studioHomeMock } from './studioHomeMock'; +export { default as listStudioHomeV2LibrariesMock } from './listStudioHomeV2LibrariesMock'; diff --git a/src/studio-home/__mocks__/listStudioHomeV2LibrariesMock.js b/src/studio-home/__mocks__/listStudioHomeV2LibrariesMock.js new file mode 100644 index 0000000000..02257a9744 --- /dev/null +++ b/src/studio-home/__mocks__/listStudioHomeV2LibrariesMock.js @@ -0,0 +1,44 @@ +module.exports = { + next: null, + previous: null, + count: 2, + num_pages: 1, + current_page: 1, + start: 0, + results: [ + { + id: 'lib:SampleTaxonomyOrg1:AL1', + type: 'complex', + org: 'SampleTaxonomyOrg1', + slug: 'AL1', + title: 'Another Library 2', + description: '', + num_blocks: 0, + version: 0, + last_published: null, + allow_lti: false, + allow_public_learning: false, + allow_public_read: false, + has_unpublished_changes: false, + has_unpublished_deletes: false, + license: '', + }, + { + id: 'lib:SampleTaxonomyOrg1:TL1', + type: 'complex', + org: 'SampleTaxonomyOrg1', + slug: 'TL1', + title: 'Test Library 1', + description: '', + num_blocks: 0, + version: 0, + last_published: null, + allow_lti: false, + allow_public_learning: false, + allow_public_read: false, + has_unpublished_changes: false, + has_unpublished_deletes: false, + license: '', + }, + ], +}; diff --git a/src/studio-home/data/api.test.js b/src/studio-home/data/api.test.js index 593a2730de..66f6ee279f 100644 --- a/src/studio-home/data/api.test.js +++ b/src/studio-home/data/api.test.js @@ -13,8 +13,14 @@ import { getStudioHomeCourses, getStudioHomeCoursesV2, getStudioHomeLibraries, + getStudioHomeLibrariesV2, } from './api'; -import { generateGetStudioCoursesApiResponse, generateGetStudioHomeDataApiResponse, generateGetStuioHomeLibrariesApiResponse } from '../factories/mockApiResponses'; +import { + generateGetStudioCoursesApiResponse, + generateGetStudioHomeDataApiResponse, + generateGetStudioHomeLibrariesApiResponse, + generateGetStudioHomeLibrariesV2ApiResponse, +} from '../factories/mockApiResponses'; let axiosMock; @@ -64,11 +70,21 @@ describe('studio-home api calls', () => { expect(result).toEqual(expected); }); - it('should get studio libraries data', async () => { + it('should get studio v1 libraries data', async () => { const apiLink = `${getApiBaseUrl()}/api/contentstore/v1/home/libraries`; - axiosMock.onGet(apiLink).reply(200, generateGetStuioHomeLibrariesApiResponse()); + axiosMock.onGet(apiLink).reply(200, generateGetStudioHomeLibrariesApiResponse()); const result = await getStudioHomeLibraries(); - const expected = generateGetStuioHomeLibrariesApiResponse(); + const expected = generateGetStudioHomeLibrariesApiResponse(); + + expect(axiosMock.history.get[0].url).toEqual(apiLink); + expect(result).toEqual(expected); + }); + + it('should get studio v2 libraries data', async () => { + const apiLink = `${getApiBaseUrl()}/api/libraries/v2/`; + axiosMock.onGet(apiLink).reply(200, generateGetStudioHomeLibrariesV2ApiResponse()); + const result = await getStudioHomeLibrariesV2({}); + const expected = generateGetStudioHomeLibrariesV2ApiResponse(); expect(axiosMock.history.get[0].url).toEqual(apiLink); expect(result).toEqual(expected); diff --git a/src/studio-home/factories/mockApiResponses.jsx b/src/studio-home/factories/mockApiResponses.jsx index 30615ba8d5..5d75f9f592 100644 --- a/src/studio-home/factories/mockApiResponses.jsx +++ b/src/studio-home/factories/mockApiResponses.jsx @@ -112,7 +112,7 @@ export const generateGetStudioCoursesApiResponseV2 = () => ({ }, }); -export const generateGetStuioHomeLibrariesApiResponse = () => ({ +export const generateGetStudioHomeLibrariesApiResponse = () => ({ libraries: [ { displayName: 'MBA', @@ -125,6 +125,51 @@ export const generateGetStuioHomeLibrariesApiResponse = () => ({ ], }); +export const generateGetStudioHomeLibrariesV2ApiResponse = () => ({ + next: null, + previous: null, + count: 2, + numPages: 1, + currentPage: 1, + start: 0, + results: [ + { + id: 'lib:SampleTaxonomyOrg1:AL1', + type: 'complex', + org: 'SampleTaxonomyOrg1', + slug: 'AL1', + title: 'Another Library 2', + description: '', + numBlocks: 0, + version: 0, + lastPublished: null, + allowLti: false, + allowPublicLearning: false, + allowpublicRead: false, + hasUnpublishedChanges: false, + hasUnpublishedDeletes: false, + license: '', + }, + { + id: 'lib:SampleTaxonomyOrg1:TL1', + type: 'complex', + org: 'SampleTaxonomyOrg1', + slug: 'TL1', + title: 'Test Library 1', + description: '', + numBlocks: 0, + version: 0, + lastPublished: null, + allowLti: false, + allowPublicLearning: false, + allowPublicRead: false, + hasUnpublishedChanges: false, + hasUnpublishedDeletes: false, + license: '', + }, + ], +}); + export const generateNewVideoApiResponse = () => ({ files: [{ edx_video_id: 'mOckID4', diff --git a/src/studio-home/tabs-section/LibraryV2Placeholder.jsx b/src/studio-home/tabs-section/LibraryV2Placeholder.jsx index 6844515bd9..6b13853a2c 100644 --- a/src/studio-home/tabs-section/LibraryV2Placeholder.jsx +++ b/src/studio-home/tabs-section/LibraryV2Placeholder.jsx @@ -7,6 +7,7 @@ import Header from '../../header'; import SubHeader from '../../generic/sub-header/SubHeader'; import messages from './messages'; +/* istanbul ignore next */ const LibraryV2Placeholder = () => { const intl = useIntl(); diff --git a/src/studio-home/tabs-section/TabsSection.test.jsx b/src/studio-home/tabs-section/TabsSection.test.jsx index 945322dcd5..54741ebbb1 100644 --- a/src/studio-home/tabs-section/TabsSection.test.jsx +++ b/src/studio-home/tabs-section/TabsSection.test.jsx @@ -11,7 +11,7 @@ import { AppProvider } from '@edx/frontend-platform/react'; import MockAdapter from 'axios-mock-adapter'; import initializeStore from '../../store'; -import { studioHomeMock } from '../__mocks__'; +import { studioHomeMock, listStudioHomeV2LibrariesMock } from '../__mocks__'; import messages from '../messages'; import tabMessages from './messages'; import TabsSection from '.'; @@ -20,12 +20,32 @@ import { generateGetStudioHomeDataApiResponse, generateGetStudioCoursesApiResponse, generateGetStudioCoursesApiResponseV2, - generateGetStuioHomeLibrariesApiResponse, + generateGetStudioHomeLibrariesApiResponse, } from '../factories/mockApiResponses'; import { getApiBaseUrl, getStudioHomeApiUrl } from '../data/api'; import { executeThunk } from '../../utils'; import { fetchLibraryData, fetchStudioHomeData } from '../data/thunks'; +import useListStudioHomeV2Libraries from '../data/apiHooks'; + +jest.mock('../data/apiHooks', () => ({ + // Since only useListStudioHomeV2Libraries is exported as default + __esModule: true, + default: jest.fn(() => ({ + data: { + next: null, + previous: null, + count: 2, + num_pages: 1, + current_page: 1, + start: 0, + results: [], + }, + isLoading: false, + isError: false, + })), +})); + const { studioShortName } = studioHomeMock; let axiosMock; @@ -84,6 +104,10 @@ describe('', () => { }); store = initializeStore(initialState); axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + setConfig({ + ...getConfig(), + LIBRARY_MODE: 'mixed', + }); }); it('should render all tabs correctly', async () => { @@ -105,11 +129,47 @@ describe('', () => { expect(screen.getByText(tabMessages.coursesTabTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(tabMessages.librariesTabTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(tabMessages.legacyLibrariesTabTitle.defaultMessage)).toBeInTheDocument(); expect(screen.getByText(tabMessages.archivedTabTitle.defaultMessage)).toBeInTheDocument(); }); + it('should render only 1 library tab when "v1 only" lib mode', async () => { + setConfig({ + ...getConfig(), + LIBRARY_MODE: 'v1 only', + }); + + const data = generateGetStudioHomeDataApiResponse(); + + render(); + axiosMock.onGet(getStudioHomeApiUrl()).reply(200, data); + await executeThunk(fetchStudioHomeData(), store.dispatch); + + expect(screen.getByText(tabMessages.librariesTabTitle.defaultMessage)).toBeInTheDocument(); + + expect(screen.queryByText(tabMessages.legacyLibrariesTabTitle.defaultMessage)).not.toBeInTheDocument(); + }); + + it('should render only 1 library tab when "v2 only" lib mode', async () => { + setConfig({ + ...getConfig(), + LIBRARY_MODE: 'v2 only', + }); + + const data = generateGetStudioHomeDataApiResponse(); + + render(); + axiosMock.onGet(getStudioHomeApiUrl()).reply(200, data); + await executeThunk(fetchStudioHomeData(), store.dispatch); + + expect(screen.getByText(tabMessages.librariesTabTitle.defaultMessage)).toBeInTheDocument(); + + expect(screen.queryByText(tabMessages.legacyLibrariesTabTitle.defaultMessage)).not.toBeInTheDocument(); + }); + describe('course tab', () => { it('should render specific course details', async () => { render(); @@ -181,6 +241,46 @@ describe('', () => { const pagination = screen.queryByRole('navigation'); expect(pagination).not.toBeInTheDocument(); }); + + it('should set the url path to "/home" when switching away then back to courses tab', async () => { + const data = generateGetStudioCoursesApiResponseV2(); + data.results.courses = []; + render(); + axiosMock.onGet(getStudioHomeApiUrl()).reply(200, generateGetStudioHomeDataApiResponse()); + axiosMock.onGet(courseApiLinkV2).reply(200, data); + await executeThunk(fetchStudioHomeData(), store.dispatch); + + // confirm the url path is initially /home + waitFor(() => { + expect(window.location.href).toContain('/home'); + }); + + // switch to libraries tab + axiosMock.onGet(libraryApiLink).reply(200, generateGetStudioHomeLibrariesApiResponse()); + await executeThunk(fetchLibraryData(), store.dispatch); + const librariesTab = screen.getByText(tabMessages.legacyLibrariesTabTitle.defaultMessage); + await act(async () => { + fireEvent.click(librariesTab); + }); + + // confirm that the url path has changed + expect(librariesTab).toHaveClass('active'); + waitFor(() => { + expect(window.location.href).toContain('/legacy-libraries'); + }); + + // switch back to courses tab + const coursesTab = screen.getByText(tabMessages.coursesTabTitle.defaultMessage); + await act(async () => { + fireEvent.click(coursesTab); + }); + + // confirm that the url path is /home + expect(coursesTab).toHaveClass('active'); + waitFor(() => { + expect(window.location.href).toContain('/home'); + }); + }); }); describe('taxonomies tab', () => { @@ -247,6 +347,8 @@ describe('', () => { expect(screen.getByText(tabMessages.coursesTabTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(tabMessages.librariesTabTitle.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(tabMessages.legacyLibrariesTabTitle.defaultMessage)).toBeInTheDocument(); expect(screen.queryByText(tabMessages.archivedTabTitle.defaultMessage)).toBeNull(); @@ -254,10 +356,10 @@ describe('', () => { }); describe('library tab', () => { - it('should switch to Libraries tab and render specific library details', async () => { + it('should switch to Legacy Libraries tab and render specific v1 library details', async () => { render(); axiosMock.onGet(getStudioHomeApiUrl()).reply(200, generateGetStudioHomeDataApiResponse()); - axiosMock.onGet(libraryApiLink).reply(200, generateGetStuioHomeLibrariesApiResponse()); + axiosMock.onGet(libraryApiLink).reply(200, generateGetStudioHomeLibrariesApiResponse()); await executeThunk(fetchStudioHomeData(), store.dispatch); await executeThunk(fetchLibraryData(), store.dispatch); @@ -273,6 +375,97 @@ describe('', () => { expect(screen.getByText(`${studioHomeMock.libraries[0].org} / ${studioHomeMock.libraries[0].number}`)).toBeVisible(); }); + it('should switch to Libraries tab and render specific v2 library details', async () => { + useListStudioHomeV2Libraries.mockReturnValue({ + data: listStudioHomeV2LibrariesMock, + isLoading: false, + isError: false, + }); + + render(); + axiosMock.onGet(getStudioHomeApiUrl()).reply(200, generateGetStudioHomeDataApiResponse()); + await executeThunk(fetchStudioHomeData(), store.dispatch); + + const librariesTab = screen.getByText(tabMessages.librariesTabTitle.defaultMessage); + await act(async () => { + fireEvent.click(librariesTab); + }); + + expect(librariesTab).toHaveClass('active'); + + expect(screen.getByText('Showing 2 of 2')).toBeVisible(); + + expect(screen.getByText(listStudioHomeV2LibrariesMock.results[0].title)).toBeVisible(); + expect(screen.getByText( + `${listStudioHomeV2LibrariesMock.results[0].org} / ${listStudioHomeV2LibrariesMock.results[0].slug}`, + )).toBeVisible(); + + expect(screen.getByText(listStudioHomeV2LibrariesMock.results[1].title)).toBeVisible(); + expect(screen.getByText( + `${listStudioHomeV2LibrariesMock.results[1].org} / ${listStudioHomeV2LibrariesMock.results[1].slug}`, + )).toBeVisible(); + }); + + it('should switch to Libraries tab and render specific v1 library details ("v1 only" mode)', async () => { + setConfig({ + ...getConfig(), + LIBRARY_MODE: 'v1 only', + }); + + render(); + axiosMock.onGet(getStudioHomeApiUrl()).reply(200, generateGetStudioHomeDataApiResponse()); + axiosMock.onGet(libraryApiLink).reply(200, generateGetStudioHomeLibrariesApiResponse()); + await executeThunk(fetchStudioHomeData(), store.dispatch); + await executeThunk(fetchLibraryData(), store.dispatch); + + const librariesTab = screen.getByText(tabMessages.librariesTabTitle.defaultMessage); + await act(async () => { + fireEvent.click(librariesTab); + }); + + expect(librariesTab).toHaveClass('active'); + + expect(screen.getByText(studioHomeMock.libraries[0].displayName)).toBeVisible(); + + expect(screen.getByText(`${studioHomeMock.libraries[0].org} / ${studioHomeMock.libraries[0].number}`)).toBeVisible(); + }); + + it('should switch to Libraries tab and render specific v2 library details ("v2 only" mode)', async () => { + setConfig({ + ...getConfig(), + LIBRARY_MODE: 'v2 only', + }); + + useListStudioHomeV2Libraries.mockReturnValue({ + data: listStudioHomeV2LibrariesMock, + isLoading: false, + isError: false, + }); + + render(); + axiosMock.onGet(getStudioHomeApiUrl()).reply(200, generateGetStudioHomeDataApiResponse()); + await executeThunk(fetchStudioHomeData(), store.dispatch); + + const librariesTab = screen.getByText(tabMessages.librariesTabTitle.defaultMessage); + await act(async () => { + fireEvent.click(librariesTab); + }); + + expect(librariesTab).toHaveClass('active'); + + expect(screen.getByText('Showing 2 of 2')).toBeVisible(); + + expect(screen.getByText(listStudioHomeV2LibrariesMock.results[0].title)).toBeVisible(); + expect(screen.getByText( + `${listStudioHomeV2LibrariesMock.results[0].org} / ${listStudioHomeV2LibrariesMock.results[0].slug}`, + )).toBeVisible(); + + expect(screen.getByText(listStudioHomeV2LibrariesMock.results[1].title)).toBeVisible(); + expect(screen.getByText( + `${listStudioHomeV2LibrariesMock.results[1].org} / ${listStudioHomeV2LibrariesMock.results[1].slug}`, + )).toBeVisible(); + }); + it('should hide Libraries tab when libraries are disabled', async () => { const data = generateGetStudioHomeDataApiResponse(); data.librariesEnabled = false; From a0a30b7f31dd3ad9a0f518ac0932143ea81b0e14 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Thu, 6 Jun 2024 17:47:47 +0300 Subject: [PATCH 25/88] refactor: Change /legacy-libraries -> /libraries-v1 --- src/index.jsx | 2 +- src/studio-home/StudioHome.test.jsx | 2 +- .../tabs-section/TabsSection.test.jsx | 4 ++-- src/studio-home/tabs-section/index.jsx | 17 ++++++++--------- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/index.jsx b/src/index.jsx index 93fe3c3f4c..f881441df9 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -54,7 +54,7 @@ const App = () => { } /> } /> - } /> + } /> } /> } /> } /> diff --git a/src/studio-home/StudioHome.test.jsx b/src/studio-home/StudioHome.test.jsx index 49ca600e5d..4f11d3d4c1 100644 --- a/src/studio-home/StudioHome.test.jsx +++ b/src/studio-home/StudioHome.test.jsx @@ -52,7 +52,7 @@ const RootWrapper = () => ( element={} /> } /> diff --git a/src/studio-home/tabs-section/TabsSection.test.jsx b/src/studio-home/tabs-section/TabsSection.test.jsx index 54741ebbb1..fce235f7b7 100644 --- a/src/studio-home/tabs-section/TabsSection.test.jsx +++ b/src/studio-home/tabs-section/TabsSection.test.jsx @@ -82,7 +82,7 @@ const RootWrapper = (overrideProps) => ( element={tabSectionComponent(overrideProps)} /> @@ -266,7 +266,7 @@ describe('', () => { // confirm that the url path has changed expect(librariesTab).toHaveClass('active'); waitFor(() => { - expect(window.location.href).toContain('/legacy-libraries'); + expect(window.location.href).toContain('/libraries-v1'); }); // switch back to courses tab diff --git a/src/studio-home/tabs-section/index.jsx b/src/studio-home/tabs-section/index.jsx index 703a4e6f04..5f3085bb9c 100644 --- a/src/studio-home/tabs-section/index.jsx +++ b/src/studio-home/tabs-section/index.jsx @@ -36,16 +36,16 @@ const TabsSection = ({ }; const initTabKeyState = (pname) => { + if (pname.includes('/libraries-v1')) { + return TABS_LIST.legacyLibraries; + } + if (pname.includes('/libraries')) { return isMixedOrV2LibrariesMode(libMode) ? TABS_LIST.libraries : TABS_LIST.legacyLibraries; } - if (pname.includes('/legacy-libraries')) { - return TABS_LIST.legacyLibraries; - } - // Default to courses tab return TABS_LIST.courses; }; @@ -54,11 +54,10 @@ const TabsSection = ({ // This is needed to handle navigating using the back/forward buttons in the browser useEffect(() => { - // Handle special case when navigating directly to /legacy-libraries or /libraries in `v1 only` mode + // Handle special case when navigating directly to /libraries-v1 or /libraries in `v1 only` mode // we need to call dispatch to fetch library data - if ( - (isMixedOrV1LibrariesMode(libMode) && pathname.includes('/libraries')) - || pathname.includes('/legacy-libraries') + if (pathname.includes('/libraries-v1') + || (isMixedOrV1LibrariesMode(libMode) && pathname.includes('/libraries')) ) { dispatch(fetchLibraryData()); } @@ -179,7 +178,7 @@ const TabsSection = ({ navigate( libMode === 'v1 only' ? '/libraries' - : '/legacy-libraries', + : '/libraries-v1', ); } else if (tab === TABS_LIST.libraries) { navigate('/libraries'); From 4deaea9a1f3d70aff3d175035f97947c5e996618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Thu, 6 Jun 2024 14:53:16 -0300 Subject: [PATCH 26/88] fix: add i18n messages --- src/library-authoring/EmptyStates.jsx | 6 ++++-- src/library-authoring/LibraryHome.jsx | 4 +++- src/library-authoring/messages.ts | 15 +++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/library-authoring/EmptyStates.jsx b/src/library-authoring/EmptyStates.jsx index 6f54dc810b..d7b718c71d 100644 --- a/src/library-authoring/EmptyStates.jsx +++ b/src/library-authoring/EmptyStates.jsx @@ -9,8 +9,10 @@ import messages from './messages'; export const NoComponents = () => ( -
You have not added any content to this library yet.
- + +
); diff --git a/src/library-authoring/LibraryHome.jsx b/src/library-authoring/LibraryHome.jsx index e2c16862ca..4331a0b54d 100644 --- a/src/library-authoring/LibraryHome.jsx +++ b/src/library-authoring/LibraryHome.jsx @@ -1,6 +1,7 @@ // @ts-check /* eslint-disable react/prop-types */ import React from 'react'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; import { Card, Stack, } from '@openedx/paragon'; @@ -9,6 +10,7 @@ import { NoComponents, NoSearchResults } from './EmptyStates'; import LibraryCollections from './LibraryCollections'; import LibraryComponents from './LibraryComponents'; import { useLibraryComponentCount } from './data/apiHook'; +import messages from './messages'; /** * @type {React.FC<{ @@ -46,7 +48,7 @@ const LibraryHome = ({ libraryId, filter }) => { return (
- Recently modified components and collections will be displayed here. +
diff --git a/src/library-authoring/messages.ts b/src/library-authoring/messages.ts index 1a48fdeaf4..0d6c497d3a 100644 --- a/src/library-authoring/messages.ts +++ b/src/library-authoring/messages.ts @@ -16,6 +16,16 @@ const messages = defineMessages({ defaultMessage: 'No matching components found in this library.', description: 'Message displayed when no search results are found', }, + noComponents: { + id: 'course-authoring.library-authoring.no-components', + defaultMessage: 'You have not added any content to this library yet.', + description: 'Message displayed when the library is empty', + }, + addComponent: { + id: 'course-authoring.library-authoring.add-component', + defaultMessage: 'Add component', + description: 'Button text to add a new component', + }, componentsTempPlaceholder: { id: 'course-authoring.library-authoring.components-temp-placeholder', defaultMessage: 'There are {componentCount} components in this library', @@ -26,6 +36,11 @@ const messages = defineMessages({ defaultMessage: 'Coming soon!', description: 'Temp placeholder for the collections container. This will be replaced with the actual collection list.', }, + recentComponentsTempPlaceholder: { + id: 'course-authoring.library-authoring.recent-components-temp-placeholder', + defaultMessage: 'Recently modified components and collections will be displayed here.', + description: 'Temp placeholder for the recent components container. This will be replaced with the actual list.', + }, }); export default messages; From c2bdecfae35bea464a439f3043faddd305c76f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Thu, 6 Jun 2024 15:14:24 -0300 Subject: [PATCH 27/88] fix: libraryAuthoring enabled check --- src/search-modal/SearchResult.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search-modal/SearchResult.jsx b/src/search-modal/SearchResult.jsx index abf5746cbf..0d763b82ac 100644 --- a/src/search-modal/SearchResult.jsx +++ b/src/search-modal/SearchResult.jsx @@ -146,7 +146,7 @@ const SearchResult = ({ hit }) => { if (contextKey.startsWith('lib:')) { const urlSuffix = getLibraryComponentUrlSuffix(hit); - if (libraryAuthoringMfeUrl) { + if (redirectToLibraryAuthoringMfe && libraryAuthoringMfeUrl) { return `${libraryAuthoringMfeUrl}${urlSuffix}`; } From a24b3ba35bc129248a4983d724b680a7b7e83b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Thu, 6 Jun 2024 15:47:26 -0300 Subject: [PATCH 28/88] fix: add Create Library placeholder --- src/index.jsx | 3 +- src/library-authoring/CreateLibrary.jsx | 31 ++++++++++++++++ src/library-authoring/data/apiHook.ts | 1 + src/library-authoring/index.ts | 1 + src/library-authoring/messages.ts | 10 ++++++ .../tabs-section/LibraryV2Placeholder.jsx | 36 ------------------- src/studio-home/tabs-section/messages.js | 8 ----- 7 files changed, 45 insertions(+), 45 deletions(-) create mode 100644 src/library-authoring/CreateLibrary.jsx delete mode 100644 src/studio-home/tabs-section/LibraryV2Placeholder.jsx diff --git a/src/index.jsx b/src/index.jsx index 588689aae7..2d3a7c271f 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -19,7 +19,7 @@ import { initializeHotjar } from '@edx/frontend-enterprise-hotjar'; import { logError } from '@edx/frontend-platform/logging'; import messages from './i18n'; -import { LibraryAuthoringPage } from './library-authoring'; +import { CreateLibrary, LibraryAuthoringPage } from './library-authoring'; import initializeStore from './store'; import CourseAuthoringRoutes from './CourseAuthoringRoutes'; import Head from './head/Head'; @@ -55,6 +55,7 @@ const App = () => { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/library-authoring/CreateLibrary.jsx b/src/library-authoring/CreateLibrary.jsx new file mode 100644 index 0000000000..b75c23a4c0 --- /dev/null +++ b/src/library-authoring/CreateLibrary.jsx @@ -0,0 +1,31 @@ +// @ts-check +/* eslint-disable react/prop-types */ +import React from 'react'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; +import { Container } from '@openedx/paragon'; + +import Header from '../header'; +import SubHeader from '../generic/sub-header/SubHeader'; + +import messages from './messages'; + +/** + * @type {React.FC} + */ +const CreateLibrary = () => ( + <> +
+ + } + /> +
+ +
+
+ +); + +export default CreateLibrary; diff --git a/src/library-authoring/data/apiHook.ts b/src/library-authoring/data/apiHook.ts index 2b1516ee22..53509fbe3b 100644 --- a/src/library-authoring/data/apiHook.ts +++ b/src/library-authoring/data/apiHook.ts @@ -14,6 +14,7 @@ export const useContentLibrary = (libraryId?: string) => { return { data: undefined, error: 'No library ID provided', + isLoading: false, } } diff --git a/src/library-authoring/index.ts b/src/library-authoring/index.ts index 05cd9d1e61..69831c4ed9 100644 --- a/src/library-authoring/index.ts +++ b/src/library-authoring/index.ts @@ -1,3 +1,4 @@ // @ts-check // eslint-disable-next-line import/prefer-default-export export { default as LibraryAuthoringPage } from './LibraryAuthoringPage'; +export { default as CreateLibrary } from './CreateLibrary'; diff --git a/src/library-authoring/messages.ts b/src/library-authoring/messages.ts index 0d6c497d3a..6a09703b64 100644 --- a/src/library-authoring/messages.ts +++ b/src/library-authoring/messages.ts @@ -41,6 +41,16 @@ const messages = defineMessages({ defaultMessage: 'Recently modified components and collections will be displayed here.', description: 'Temp placeholder for the recent components container. This will be replaced with the actual list.', }, + createLibrary: { + id: 'course-authoring.library-authoring.create-library', + defaultMessage: 'Create library', + description: 'Header for the create library form', + }, + createLibraryTempPlaceholder: { + id: 'course-authoring.library-authoring.create-library-temp-placeholder', + defaultMessage: 'This is a placeholder for the create library form. This will be replaced with the actual form.', + description: 'Temp placeholder for the create library container. This will be replaced with the new library form.', + }, }); export default messages; diff --git a/src/studio-home/tabs-section/LibraryV2Placeholder.jsx b/src/studio-home/tabs-section/LibraryV2Placeholder.jsx deleted file mode 100644 index 6b13853a2c..0000000000 --- a/src/studio-home/tabs-section/LibraryV2Placeholder.jsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import { Container } from '@openedx/paragon'; -import { StudioFooter } from '@edx/frontend-component-footer'; -import { useIntl } from '@edx/frontend-platform/i18n'; - -import Header from '../../header'; -import SubHeader from '../../generic/sub-header/SubHeader'; -import messages from './messages'; - -/* istanbul ignore next */ -const LibraryV2Placeholder = () => { - const intl = useIntl(); - - return ( - <> -
- -
-
-
- -
-
-
-

{intl.formatMessage(messages.libraryV2PlaceholderBody)}

-
-
-
- - - ); -}; - -export default LibraryV2Placeholder; diff --git a/src/studio-home/tabs-section/messages.js b/src/studio-home/tabs-section/messages.js index 0ed614f55a..e1ad0fd44f 100644 --- a/src/studio-home/tabs-section/messages.js +++ b/src/studio-home/tabs-section/messages.js @@ -50,14 +50,6 @@ const messages = defineMessages({ defaultMessage: 'Taxonomies', description: 'Title of Taxonomies tab on the home page', }, - libraryV2PlaceholderTitle: { - id: 'course-authoring.studio-home.libraries.placeholder.title', - defaultMessage: 'Library V2 Placeholder', - }, - libraryV2PlaceholderBody: { - id: 'course-authoring.studio-home.libraries.placeholder.body', - defaultMessage: 'This is a placeholder page, as the Library Authoring MFE is not enabled.', - }, }); export default messages; From 28597411042e5c2fadf47cc8c7ef669725a4405f Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Thu, 6 Jun 2024 22:04:30 +0300 Subject: [PATCH 29/88] refactor: Remove hardcoded mfe path --- src/studio-home/StudioHome.jsx | 4 ++-- src/studio-home/card-item/index.jsx | 23 ++++++++++++++++--- .../tabs-section/libraries-v2-tab/index.jsx | 5 ++-- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/studio-home/StudioHome.jsx b/src/studio-home/StudioHome.jsx index 52be27a60f..fa1affd2e1 100644 --- a/src/studio-home/StudioHome.jsx +++ b/src/studio-home/StudioHome.jsx @@ -10,7 +10,7 @@ import { import { Add as AddIcon, Error } from '@openedx/paragon/icons'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { StudioFooter } from '@edx/frontend-component-footer'; -import { getConfig } from '@edx/frontend-platform'; +import { getConfig, getPath } from '@edx/frontend-platform'; import Loading from '../generic/Loading'; import InternetConnectionAlert from '../generic/internet-connection-alert'; @@ -85,7 +85,7 @@ const StudioHome = ({ intl }) => { if (isMixedOrV2LibrariesMode(libMode)) { libraryHref = libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe ? `${libraryAuthoringMfeUrl}create` - : `${window.location.origin}/course-authoring/library/create`; + : `${getPath(getConfig().PUBLIC_PATH)}library/create`; } headerButtons.push( diff --git a/src/studio-home/card-item/index.jsx b/src/studio-home/card-item/index.jsx index f495794d80..1ee4045390 100644 --- a/src/studio-home/card-item/index.jsx +++ b/src/studio-home/card-item/index.jsx @@ -10,7 +10,7 @@ import { } from '@openedx/paragon'; import { MoreHoriz } from '@openedx/paragon/icons'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { getConfig } from '@edx/frontend-platform'; +import { getConfig, getPath } from '@edx/frontend-platform'; import { COURSE_CREATOR_STATES } from '../../constants'; import { getStudioHomeData } from '../data/selectors'; @@ -35,7 +35,24 @@ const CardItem = ({ courseCreatorStatus, rerunCreatorStatus, } = useSelector(getStudioHomeData); - const courseUrl = () => new URL(url, getConfig().STUDIO_BASE_URL); + const destinationUrl = () => { + if (isLibraries) { + // This case is for the library authoring MFE + if (url.startsWith('http')) { + return new URL(url); + } + + if (url.includes(getPath(getConfig().PUBLIC_PATH))) { + // Redirection to the placeholder is done in the MFE rather than + // through the backend i.e. redirection from cms, because this this will probably change, + // hence why we use the MFE's origin + return new URL(url, window.location.origin); + } + } + + return new URL(url, getConfig().STUDIO_BASE_URL); + }; + const subtitle = isLibraries ? `${org} / ${number}` : `${org} / ${number} / ${run}`; const readOnlyItem = !(lmsLink || rerunLink || url); const showActions = !(readOnlyItem || isLibraries); @@ -51,7 +68,7 @@ const CardItem = ({ title={!readOnlyItem ? ( {hasDisplayName} diff --git a/src/studio-home/tabs-section/libraries-v2-tab/index.jsx b/src/studio-home/tabs-section/libraries-v2-tab/index.jsx index 9060493dd1..ae0c1aecaf 100644 --- a/src/studio-home/tabs-section/libraries-v2-tab/index.jsx +++ b/src/studio-home/tabs-section/libraries-v2-tab/index.jsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; import { Icon, Row, Pagination } from '@openedx/paragon'; import { useIntl } from '@edx/frontend-platform/i18n'; +import { getConfig, getPath } from '@edx/frontend-platform'; import useListStudioHomeV2Libraries from '../../data/apiHooks'; import { LoadingSpinner } from '../../../generic/Loading'; @@ -38,9 +39,7 @@ const LibrariesV2Tab = ({ const libURL = (id) => ( libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe ? `${libraryAuthoringMfeUrl}library/${id}` - // Redirection to the placeholder is done in the MFE rather than - // through the backend i.e. redirection from cms, because this this will probably change - : `${window.location.origin}/course-authoring/library/${id}` + : `${getPath(getConfig().PUBLIC_PATH)}library/${id}` ); return ( From d853f29ffe45b37019283881d366dde2511333bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Thu, 6 Jun 2024 16:16:27 -0300 Subject: [PATCH 30/88] refactor: rename .ts files to .js --- src/library-authoring/LibraryComponents.jsx | 2 +- src/library-authoring/data/{api.ts => api.js} | 0 src/library-authoring/data/{apiHook.ts => apiHook.js} | 0 src/library-authoring/data/{types.ts => types.js} | 0 src/library-authoring/{index.ts => index.js} | 0 src/library-authoring/{messages.ts => messages.js} | 0 src/search-modal/{index.ts => index.js} | 0 7 files changed, 1 insertion(+), 1 deletion(-) rename src/library-authoring/data/{api.ts => api.js} (100%) rename src/library-authoring/data/{apiHook.ts => apiHook.js} (100%) rename src/library-authoring/data/{types.ts => types.js} (100%) rename src/library-authoring/{index.ts => index.js} (100%) rename src/library-authoring/{messages.ts => messages.js} (100%) rename src/search-modal/{index.ts => index.js} (100%) diff --git a/src/library-authoring/LibraryComponents.jsx b/src/library-authoring/LibraryComponents.jsx index a48fbfe645..3ce00c4fda 100644 --- a/src/library-authoring/LibraryComponents.jsx +++ b/src/library-authoring/LibraryComponents.jsx @@ -16,7 +16,7 @@ import messages from './messages'; * }>} */ const LibraryComponents = ({ libraryId, filter: { searchKeywords } }) => { - const { componentCount, collectionCount } = useLibraryComponentCount(libraryId, searchKeywords); + const { componentCount } = useLibraryComponentCount(libraryId, searchKeywords); if (componentCount === 0) { return searchKeywords === '' ? : ; diff --git a/src/library-authoring/data/api.ts b/src/library-authoring/data/api.js similarity index 100% rename from src/library-authoring/data/api.ts rename to src/library-authoring/data/api.js diff --git a/src/library-authoring/data/apiHook.ts b/src/library-authoring/data/apiHook.js similarity index 100% rename from src/library-authoring/data/apiHook.ts rename to src/library-authoring/data/apiHook.js diff --git a/src/library-authoring/data/types.ts b/src/library-authoring/data/types.js similarity index 100% rename from src/library-authoring/data/types.ts rename to src/library-authoring/data/types.js diff --git a/src/library-authoring/index.ts b/src/library-authoring/index.js similarity index 100% rename from src/library-authoring/index.ts rename to src/library-authoring/index.js diff --git a/src/library-authoring/messages.ts b/src/library-authoring/messages.js similarity index 100% rename from src/library-authoring/messages.ts rename to src/library-authoring/messages.js diff --git a/src/search-modal/index.ts b/src/search-modal/index.js similarity index 100% rename from src/search-modal/index.ts rename to src/search-modal/index.js From 567dcb48f08d7bb43ea389bda9ee3dd9c1c22966 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Thu, 6 Jun 2024 23:56:56 +0300 Subject: [PATCH 31/88] feat: Add function to construct Lib Auth MFE URL --- src/search-modal/SearchResult.jsx | 3 +- src/studio-home/StudioHome.jsx | 3 +- src/studio-home/StudioHome.test.jsx | 6 ++-- src/studio-home/__mocks__/studioHomeMock.js | 2 +- .../tabs-section/libraries-v2-tab/index.jsx | 3 +- src/utils.js | 24 +++++++++++++++ src/utils.test.js | 29 ++++++++++++++++++- 7 files changed, 63 insertions(+), 7 deletions(-) diff --git a/src/search-modal/SearchResult.jsx b/src/search-modal/SearchResult.jsx index 634c12d016..893452fe63 100644 --- a/src/search-modal/SearchResult.jsx +++ b/src/search-modal/SearchResult.jsx @@ -16,6 +16,7 @@ import { import { useSelector } from 'react-redux'; import { useNavigate } from 'react-router-dom'; +import { constructLibraryAuthoringURL } from '../utils'; import { COMPONENT_TYPE_ICON_MAP, TYPE_ICONS_MAP } from '../course-unit/constants'; import { getStudioHomeData } from '../studio-home/data/selectors'; import { useSearchContext } from './manager/SearchManager'; @@ -41,7 +42,7 @@ function getItemIcon(blockType) { */ function getLibraryHitUrl(hit, libraryAuthoringMfeUrl) { const { contextKey } = hit; - return `${libraryAuthoringMfeUrl}library/${contextKey}`; + return constructLibraryAuthoringURL(libraryAuthoringMfeUrl, contextKey); } /** diff --git a/src/studio-home/StudioHome.jsx b/src/studio-home/StudioHome.jsx index fa1affd2e1..2e59b78ed2 100644 --- a/src/studio-home/StudioHome.jsx +++ b/src/studio-home/StudioHome.jsx @@ -12,6 +12,7 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { StudioFooter } from '@edx/frontend-component-footer'; import { getConfig, getPath } from '@edx/frontend-platform'; +import { constructLibraryAuthoringURL } from '../utils'; import Loading from '../generic/Loading'; import InternetConnectionAlert from '../generic/internet-connection-alert'; import Header from '../header'; @@ -84,7 +85,7 @@ const StudioHome = ({ intl }) => { let libraryHref = `${getConfig().STUDIO_BASE_URL}/home_library`; if (isMixedOrV2LibrariesMode(libMode)) { libraryHref = libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe - ? `${libraryAuthoringMfeUrl}create` + ? constructLibraryAuthoringURL(libraryAuthoringMfeUrl, 'create') : `${getPath(getConfig().PUBLIC_PATH)}library/create`; } diff --git a/src/studio-home/StudioHome.test.jsx b/src/studio-home/StudioHome.test.jsx index 4f11d3d4c1..a5799085dd 100644 --- a/src/studio-home/StudioHome.test.jsx +++ b/src/studio-home/StudioHome.test.jsx @@ -14,7 +14,7 @@ import MockAdapter from 'axios-mock-adapter'; import initializeStore from '../store'; import { RequestStatus } from '../data/constants'; import { COURSE_CREATOR_STATES } from '../constants'; -import { executeThunk } from '../utils'; +import { executeThunk, constructLibraryAuthoringURL } from '../utils'; import { studioHomeMock } from './__mocks__'; import { getStudioHomeApiUrl } from './data/api'; import { fetchStudioHomeData } from './data/thunks'; @@ -191,7 +191,9 @@ describe('', async () => { const { getByTestId } = render(); const createNewLibraryButton = getByTestId('new-library-button'); - expect(createNewLibraryButton.getAttribute('href')).toBe(`${libraryAuthoringMfeUrl}/create`); + expect(createNewLibraryButton.getAttribute('href')).toBe( + `${constructLibraryAuthoringURL(libraryAuthoringMfeUrl, 'create')}`, + ); }); }); diff --git a/src/studio-home/__mocks__/studioHomeMock.js b/src/studio-home/__mocks__/studioHomeMock.js index 4f66cc116f..5385201e52 100644 --- a/src/studio-home/__mocks__/studioHomeMock.js +++ b/src/studio-home/__mocks__/studioHomeMock.js @@ -62,7 +62,7 @@ module.exports = { }, ], librariesEnabled: true, - libraryAuthoringMfeUrl: 'http://localhost:3001/', + libraryAuthoringMfeUrl: 'http://localhost:3001', optimizationEnabled: false, redirectToLibraryAuthoringMfe: false, requestCourseCreatorUrl: '/request_course_creator', diff --git a/src/studio-home/tabs-section/libraries-v2-tab/index.jsx b/src/studio-home/tabs-section/libraries-v2-tab/index.jsx index ae0c1aecaf..317da1a93e 100644 --- a/src/studio-home/tabs-section/libraries-v2-tab/index.jsx +++ b/src/studio-home/tabs-section/libraries-v2-tab/index.jsx @@ -4,6 +4,7 @@ import { Icon, Row, Pagination } from '@openedx/paragon'; import { useIntl } from '@edx/frontend-platform/i18n'; import { getConfig, getPath } from '@edx/frontend-platform'; +import { constructLibraryAuthoringURL } from '../../../utils'; import useListStudioHomeV2Libraries from '../../data/apiHooks'; import { LoadingSpinner } from '../../../generic/Loading'; import AlertMessage from '../../../generic/alert-message'; @@ -38,7 +39,7 @@ const LibrariesV2Tab = ({ const libURL = (id) => ( libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe - ? `${libraryAuthoringMfeUrl}library/${id}` + ? constructLibraryAuthoringURL(libraryAuthoringMfeUrl, `library/${id}`) : `${getPath(getConfig().PUBLIC_PATH)}library/${id}` ); diff --git a/src/utils.js b/src/utils.js index d4bc8f6ff3..2abb63e5be 100644 --- a/src/utils.js +++ b/src/utils.js @@ -301,3 +301,27 @@ export const getFileSizeToClosestByte = (fileSize) => { const fileSizeFixedDecimal = Number.parseFloat(size).toFixed(2); return `${fileSizeFixedDecimal} ${units[divides]}`; }; + +/** + * Constructs library authoring MFE URL with correct slashes + * @param {string} libraryAuthoringMfeUrl - the base library authoring MFE url + * @param {string} path - the library authoring MFE url path + * @returns {string} - the correct internal route path + */ +export const constructLibraryAuthoringURL = (libraryAuthoringMfeUrl, path) => { + // Remove '/' at the beginning of path if any + const trimmedPath = path.startsWith('/') + ? path.slice(1, path.length) + : path; + + let constructedUrl = libraryAuthoringMfeUrl; + // Remove trailing `/` from base if found + if (libraryAuthoringMfeUrl.endsWith('/')) { + constructedUrl = constructedUrl.slice(0, -1); + } + + // Add the `/` and path to url + constructedUrl = `${constructedUrl}/${trimmedPath}`; + + return constructedUrl; +}; diff --git a/src/utils.test.js b/src/utils.test.js index e4aada849f..a5b12d6c37 100644 --- a/src/utils.test.js +++ b/src/utils.test.js @@ -1,6 +1,6 @@ import { getConfig, getPath } from '@edx/frontend-platform'; -import { getFileSizeToClosestByte, createCorrectInternalRoute } from './utils'; +import { getFileSizeToClosestByte, createCorrectInternalRoute, constructLibraryAuthoringURL } from './utils'; jest.mock('@edx/frontend-platform', () => ({ getConfig: jest.fn(), @@ -78,3 +78,30 @@ describe('FilesAndUploads utils', () => { }); }); }); + +describe('constructLibraryAuthoringURL', () => { + it('should construct URL given no trailing `/` in base and no starting `/` in path', () => { + const libraryAuthoringMfeUrl = 'http://localhost:3001'; + const path = 'example'; + const constructedURL = constructLibraryAuthoringURL(libraryAuthoringMfeUrl, path); + expect(constructedURL).toEqual('http://localhost:3001/example'); + }); + it('should construct URL given a trailing `/` in base and no starting `/` in path', () => { + const libraryAuthoringMfeUrl = 'http://localhost:3001/'; + const path = 'example'; + const constructedURL = constructLibraryAuthoringURL(libraryAuthoringMfeUrl, path); + expect(constructedURL).toEqual('http://localhost:3001/example'); + }); + it('should construct URL with no trailing `/` in base and a starting `/` in path', () => { + const libraryAuthoringMfeUrl = 'http://localhost:3001'; + const path = '/example'; + const constructedURL = constructLibraryAuthoringURL(libraryAuthoringMfeUrl, path); + expect(constructedURL).toEqual('http://localhost:3001/example'); + }); + it('should construct URL with a trailing `/` in base and a starting `/` in path', () => { + const libraryAuthoringMfeUrl = 'http://localhost:3001/'; + const path = '/example'; + const constructedURL = constructLibraryAuthoringURL(libraryAuthoringMfeUrl, path); + expect(constructedURL).toEqual('http://localhost:3001/example'); + }); +}); From 094086e05f948cecb3437f89026a959ce0ff45a7 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Fri, 7 Jun 2024 00:19:47 +0300 Subject: [PATCH 32/88] feat: Make URL /library-v1 when referencing legacy --- src/studio-home/tabs-section/index.jsx | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/studio-home/tabs-section/index.jsx b/src/studio-home/tabs-section/index.jsx index 5f3085bb9c..75a3ae12ba 100644 --- a/src/studio-home/tabs-section/index.jsx +++ b/src/studio-home/tabs-section/index.jsx @@ -54,11 +54,9 @@ const TabsSection = ({ // This is needed to handle navigating using the back/forward buttons in the browser useEffect(() => { - // Handle special case when navigating directly to /libraries-v1 or /libraries in `v1 only` mode + // Handle special case when navigating directly to /libraries-v1 // we need to call dispatch to fetch library data - if (pathname.includes('/libraries-v1') - || (isMixedOrV1LibrariesMode(libMode) && pathname.includes('/libraries')) - ) { + if (pathname.includes('/libraries-v1')) { dispatch(fetchLibraryData()); } setTabKey(initTabKeyState(pathname)); @@ -175,11 +173,7 @@ const TabsSection = ({ navigate('/home'); } else if (tab === TABS_LIST.legacyLibraries) { dispatch(fetchLibraryData()); - navigate( - libMode === 'v1 only' - ? '/libraries' - : '/libraries-v1', - ); + navigate('/libraries-v1'); } else if (tab === TABS_LIST.libraries) { navigate('/libraries'); } else if (tab === TABS_LIST.taxonomies) { From a95c99000009c1c8b655de17d67495003372002b Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Fri, 7 Jun 2024 01:24:37 +0300 Subject: [PATCH 33/88] fix: Add missing part of path --- src/search-modal/SearchResult.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search-modal/SearchResult.jsx b/src/search-modal/SearchResult.jsx index 893452fe63..939043ef73 100644 --- a/src/search-modal/SearchResult.jsx +++ b/src/search-modal/SearchResult.jsx @@ -42,7 +42,7 @@ function getItemIcon(blockType) { */ function getLibraryHitUrl(hit, libraryAuthoringMfeUrl) { const { contextKey } = hit; - return constructLibraryAuthoringURL(libraryAuthoringMfeUrl, contextKey); + return constructLibraryAuthoringURL(libraryAuthoringMfeUrl, `library/${contextKey}`); } /** From e3ebc55532bae01884cd4c556653ad43d2adf90c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Thu, 6 Jun 2024 19:56:20 -0300 Subject: [PATCH 34/88] fix: type and lint errors --- src/library-authoring/data/api.js | 21 ++++++++++++++---- src/library-authoring/data/apiHook.js | 31 +++++++++++---------------- src/library-authoring/data/types.js | 17 --------------- src/library-authoring/data/types.mjs | 18 ++++++++++++++++ src/search-modal/SearchResult.jsx | 2 +- 5 files changed, 49 insertions(+), 40 deletions(-) delete mode 100644 src/library-authoring/data/types.js create mode 100644 src/library-authoring/data/types.mjs diff --git a/src/library-authoring/data/api.js b/src/library-authoring/data/api.js index ff0dd3dec5..c97760b868 100644 --- a/src/library-authoring/data/api.js +++ b/src/library-authoring/data/api.js @@ -1,12 +1,25 @@ // @ts-check -import type { ContentLibrary } from './types'; import { camelCaseObject, getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -const getApiBaseUrl = (): string => getConfig().STUDIO_BASE_URL; -const getContentLibraryApiUrl = (libraryId: string) => `${getApiBaseUrl()}/api/libraries/v2/${libraryId}/`; +const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL; +/** + * Get the URL for the content library API. + * @param {string} libraryId - The ID of the library to fetch. + */ +const getContentLibraryApiUrl = (libraryId) => `${getApiBaseUrl()}/api/libraries/v2/${libraryId}/`; + +/** + * Fetch a content library by its ID. + * @param {string} [libraryId] - The ID of the library to fetch. + * @returns {Promise} + */ +/* eslint-disable import/prefer-default-export */ +export async function getContentLibrary(libraryId) { + if (!libraryId) { + throw new Error('libraryId is required'); + } -export async function getContentLibrary(libraryId: string): Promise { const { data } = await getAuthenticatedHttpClient().get(getContentLibraryApiUrl(libraryId)); return camelCaseObject(data); } diff --git a/src/library-authoring/data/apiHook.js b/src/library-authoring/data/apiHook.js index 53509fbe3b..8f11e49a4e 100644 --- a/src/library-authoring/data/apiHook.js +++ b/src/library-authoring/data/apiHook.js @@ -1,5 +1,5 @@ // @ts-check -import React, { useEffect } from 'react'; +import React from 'react'; import { useQuery } from '@tanstack/react-query'; import { MeiliSearch } from 'meilisearch'; @@ -8,24 +8,21 @@ import { getContentLibrary } from './api'; /** * Hook to fetch a content library by its ID. + * @param {string} [libraryId] - The ID of the library to fetch. */ -export const useContentLibrary = (libraryId?: string) => { - if (!libraryId) { - return { - data: undefined, - error: 'No library ID provided', - isLoading: false, - } - } - - return useQuery({ +export const useContentLibrary = (libraryId) => ( + useQuery({ queryKey: ['contentLibrary', libraryId], queryFn: () => getContentLibrary(libraryId), - }); -}; - + }) +); -export const useLibraryComponentCount = (libraryId: string, searchKeywords: string) => { +/** + * Hook to fetch the count of components and collections in a library. + * @param {string} libraryId - The ID of the library to fetch. + * @param {string} searchKeywords - Keywords to search for. + */ +export const useLibraryComponentCount = (libraryId, searchKeywords) => { // Meilisearch code to get Collection and Component counts const { data: connectionDetails } = useContentSearchConnection(); @@ -52,6 +49,4 @@ export const useLibraryComponentCount = (libraryId: string, searchKeywords: stri componentCount, collectionCount, }; -} - - +}; diff --git a/src/library-authoring/data/types.js b/src/library-authoring/data/types.js deleted file mode 100644 index 1af41a8b1f..0000000000 --- a/src/library-authoring/data/types.js +++ /dev/null @@ -1,17 +0,0 @@ -export type ContentLibrary = { - id: string; - type: string; - org: string; - slug: string; - title: string; - description: string; - numBlocks: number; - version: number; - lastPublished: Date | null; - allowLti: boolean; - allowPublicLearning: boolean; - allowPublicRead: boolean; - hasUnpublishedChanges: boolean; - hasUnpublishedDeletes: boolean; - license: string; -} diff --git a/src/library-authoring/data/types.mjs b/src/library-authoring/data/types.mjs new file mode 100644 index 0000000000..34486525d7 --- /dev/null +++ b/src/library-authoring/data/types.mjs @@ -0,0 +1,18 @@ +/** + * @typedef {Object} ContentLibrary + * @property {string} id + * @property {string} type + * @property {string} org + * @property {string} slug + * @property {string} title + * @property {string} description + * @property {number} numBlocks + * @property {number} version + * @property {Date | null} lastPublished + * @property {boolean} allowLti + * @property {boolean} allowPublicLearning + * @property {boolean} allowPublicRead + * @property {boolean} hasUnpublishedChanges + * @property {boolean} hasUnpublishedDeletes + * @property {string} license + */ diff --git a/src/search-modal/SearchResult.jsx b/src/search-modal/SearchResult.jsx index 0d763b82ac..051aa3c852 100644 --- a/src/search-modal/SearchResult.jsx +++ b/src/search-modal/SearchResult.jsx @@ -156,7 +156,7 @@ const SearchResult = ({ hit }) => { return `/${urlSuffix}`; } - // No context URL for this hit (e.g. a library without library authoring mfe) + // istanbul ignore next - This case should never be reached return undefined; }, [libraryAuthoringMfeUrl, redirectToLibraryAuthoringMfe, hit]); From 8ed168d3902aa4af0401fb73d4e641da4bad508e Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Fri, 7 Jun 2024 02:35:12 +0300 Subject: [PATCH 35/88] fix: Issue with destinationUrl --- src/studio-home/card-item/index.jsx | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/studio-home/card-item/index.jsx b/src/studio-home/card-item/index.jsx index 1ee4045390..3fd85ffb7e 100644 --- a/src/studio-home/card-item/index.jsx +++ b/src/studio-home/card-item/index.jsx @@ -35,23 +35,14 @@ const CardItem = ({ courseCreatorStatus, rerunCreatorStatus, } = useSelector(getStudioHomeData); - const destinationUrl = () => { - if (isLibraries) { - // This case is for the library authoring MFE - if (url.startsWith('http')) { - return new URL(url); - } - - if (url.includes(getPath(getConfig().PUBLIC_PATH))) { - // Redirection to the placeholder is done in the MFE rather than - // through the backend i.e. redirection from cms, because this this will probably change, - // hence why we use the MFE's origin - return new URL(url, window.location.origin); - } - } - - return new URL(url, getConfig().STUDIO_BASE_URL); - }; + const destinationUrl = () => ( + isLibraries && url.includes(getPath(getConfig().PUBLIC_PATH)) + // Redirection to the placeholder is done in the MFE rather than + // through the backend i.e. redirection from cms, because this this will probably change, + // hence why we use the MFE's origin + ? new URL(url, window.location.origin) + : new URL(url, getConfig().STUDIO_BASE_URL) + ); const subtitle = isLibraries ? `${org} / ${number}` : `${org} / ${number} / ${run}`; const readOnlyItem = !(lmsLink || rerunLink || url); From beda37f829cbdd43204144ecd2f0217d09d8f669 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Fri, 7 Jun 2024 02:35:43 +0300 Subject: [PATCH 36/88] test: Add checks for Tab.eventKey in tests --- src/studio-home/tabs-section/TabsSection.test.jsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/studio-home/tabs-section/TabsSection.test.jsx b/src/studio-home/tabs-section/TabsSection.test.jsx index fce235f7b7..90f47d5e85 100644 --- a/src/studio-home/tabs-section/TabsSection.test.jsx +++ b/src/studio-home/tabs-section/TabsSection.test.jsx @@ -149,6 +149,10 @@ describe('', () => { await executeThunk(fetchStudioHomeData(), store.dispatch); expect(screen.getByText(tabMessages.librariesTabTitle.defaultMessage)).toBeInTheDocument(); + const librariesTab = screen.getByRole('tab', { name: tabMessages.librariesTabTitle.defaultMessage }); + expect(librariesTab).toBeInTheDocument(); + // Check Tab.eventKey + expect(librariesTab).toHaveAttribute('data-rb-event-key', 'legacyLibraries'); expect(screen.queryByText(tabMessages.legacyLibrariesTabTitle.defaultMessage)).not.toBeInTheDocument(); }); @@ -166,6 +170,10 @@ describe('', () => { await executeThunk(fetchStudioHomeData(), store.dispatch); expect(screen.getByText(tabMessages.librariesTabTitle.defaultMessage)).toBeInTheDocument(); + const librariesTab = screen.getByRole('tab', { name: tabMessages.librariesTabTitle.defaultMessage }); + expect(librariesTab).toBeInTheDocument(); + // Check Tab.eventKey + expect(librariesTab).toHaveAttribute('data-rb-event-key', 'libraries'); expect(screen.queryByText(tabMessages.legacyLibrariesTabTitle.defaultMessage)).not.toBeInTheDocument(); }); From 2e3fa438afe5dec1753174f6e8db9c68974793e6 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Fri, 7 Jun 2024 03:28:32 +0300 Subject: [PATCH 37/88] fix: Revert card item url changes to keep simple --- src/studio-home/StudioHome.jsx | 5 ++++- src/studio-home/card-item/index.jsx | 12 ++---------- .../tabs-section/libraries-v2-tab/index.jsx | 5 ++++- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/studio-home/StudioHome.jsx b/src/studio-home/StudioHome.jsx index 2e59b78ed2..4953c6f3ae 100644 --- a/src/studio-home/StudioHome.jsx +++ b/src/studio-home/StudioHome.jsx @@ -86,7 +86,10 @@ const StudioHome = ({ intl }) => { if (isMixedOrV2LibrariesMode(libMode)) { libraryHref = libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe ? constructLibraryAuthoringURL(libraryAuthoringMfeUrl, 'create') - : `${getPath(getConfig().PUBLIC_PATH)}library/create`; + // Redirection to the placeholder is done in the MFE rather than + // through the backend i.e. redirection from cms, because this this will probably change, + // hence why we use the MFE's origin + : `${window.location.origin}${getPath(getConfig().PUBLIC_PATH)}library/create`; } headerButtons.push( diff --git a/src/studio-home/card-item/index.jsx b/src/studio-home/card-item/index.jsx index 3fd85ffb7e..0b06e66ac3 100644 --- a/src/studio-home/card-item/index.jsx +++ b/src/studio-home/card-item/index.jsx @@ -10,7 +10,7 @@ import { } from '@openedx/paragon'; import { MoreHoriz } from '@openedx/paragon/icons'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { getConfig, getPath } from '@edx/frontend-platform'; +import { getConfig } from '@edx/frontend-platform'; import { COURSE_CREATOR_STATES } from '../../constants'; import { getStudioHomeData } from '../data/selectors'; @@ -35,15 +35,7 @@ const CardItem = ({ courseCreatorStatus, rerunCreatorStatus, } = useSelector(getStudioHomeData); - const destinationUrl = () => ( - isLibraries && url.includes(getPath(getConfig().PUBLIC_PATH)) - // Redirection to the placeholder is done in the MFE rather than - // through the backend i.e. redirection from cms, because this this will probably change, - // hence why we use the MFE's origin - ? new URL(url, window.location.origin) - : new URL(url, getConfig().STUDIO_BASE_URL) - ); - + const destinationUrl = () => new URL(url, getConfig().STUDIO_BASE_URL); const subtitle = isLibraries ? `${org} / ${number}` : `${org} / ${number} / ${run}`; const readOnlyItem = !(lmsLink || rerunLink || url); const showActions = !(readOnlyItem || isLibraries); diff --git a/src/studio-home/tabs-section/libraries-v2-tab/index.jsx b/src/studio-home/tabs-section/libraries-v2-tab/index.jsx index 317da1a93e..c3b58df554 100644 --- a/src/studio-home/tabs-section/libraries-v2-tab/index.jsx +++ b/src/studio-home/tabs-section/libraries-v2-tab/index.jsx @@ -40,7 +40,10 @@ const LibrariesV2Tab = ({ const libURL = (id) => ( libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe ? constructLibraryAuthoringURL(libraryAuthoringMfeUrl, `library/${id}`) - : `${getPath(getConfig().PUBLIC_PATH)}library/${id}` + // Redirection to the placeholder is done in the MFE rather than + // through the backend i.e. redirection from cms, because this this will probably change, + // hence why we use the MFE's origin + : `${window.location.origin}${getPath(getConfig().PUBLIC_PATH)}library/${id}` ); return ( From 72edfacbc8439ca1ad81830e27177b996c32fc86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Fri, 7 Jun 2024 15:59:09 -0300 Subject: [PATCH 38/88] fix: add tests --- src/library-authoring/CreateLibrary.jsx | 4 +- src/library-authoring/EmptyStates.jsx | 1 + .../LibraryAuthoringPage.jsx | 10 +- .../LibraryAuthoringPage.test.jsx | 237 ++++++++++++++++++ src/library-authoring/data/api.js | 3 +- src/library-authoring/messages.js | 5 + 6 files changed, 252 insertions(+), 8 deletions(-) create mode 100644 src/library-authoring/LibraryAuthoringPage.test.jsx diff --git a/src/library-authoring/CreateLibrary.jsx b/src/library-authoring/CreateLibrary.jsx index b75c23a4c0..738e9fb769 100644 --- a/src/library-authoring/CreateLibrary.jsx +++ b/src/library-authoring/CreateLibrary.jsx @@ -9,9 +9,7 @@ import SubHeader from '../generic/sub-header/SubHeader'; import messages from './messages'; -/** - * @type {React.FC} - */ +/* istanbul ignore next This is only a placeholder component */ const CreateLibrary = () => ( <>
diff --git a/src/library-authoring/EmptyStates.jsx b/src/library-authoring/EmptyStates.jsx index d7b718c71d..54e3b8019d 100644 --- a/src/library-authoring/EmptyStates.jsx +++ b/src/library-authoring/EmptyStates.jsx @@ -1,3 +1,4 @@ +// @ts-check import React from 'react'; import { FormattedMessage } from '@edx/frontend-platform/i18n'; import { diff --git a/src/library-authoring/LibraryAuthoringPage.jsx b/src/library-authoring/LibraryAuthoringPage.jsx index 9c075ada88..0536e4faab 100644 --- a/src/library-authoring/LibraryAuthoringPage.jsx +++ b/src/library-authoring/LibraryAuthoringPage.jsx @@ -2,7 +2,7 @@ /* eslint-disable react/prop-types */ import React, { useEffect } from 'react'; import { StudioFooter } from '@edx/frontend-component-footer'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import { Container, Icon, IconButton, SearchField, Tab, Tabs, } from '@openedx/paragon'; @@ -30,7 +30,12 @@ const TAB_LIST = { const SubHeaderTitle = ({ title }) => ( <> {title} - {}} className="mr-2" /> + } + className="mr-2" + /> ); @@ -90,7 +95,6 @@ const LibraryAuthoringPage = () => { setSearchKeywords(value)} onChange={(value) => setSearchKeywords(value)} className="w-50" /> diff --git a/src/library-authoring/LibraryAuthoringPage.test.jsx b/src/library-authoring/LibraryAuthoringPage.test.jsx new file mode 100644 index 0000000000..59f510f2fd --- /dev/null +++ b/src/library-authoring/LibraryAuthoringPage.test.jsx @@ -0,0 +1,237 @@ +// @ts-check +import React from 'react'; +import MockAdapter from 'axios-mock-adapter'; +import { initializeMockApp } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; +import { AppProvider } from '@edx/frontend-platform/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { fireEvent, render, waitFor } from '@testing-library/react'; +import fetchMock from 'fetch-mock-jest'; + +import initializeStore from '../store'; +import { getContentSearchConfigUrl } from '../search-modal/data/api'; +import mockResult from '../search-modal/__mocks__/search-result.json'; +import mockEmptyResult from '../search-modal/__mocks__/empty-search-result.json'; +import LibraryAuthoringPage from './LibraryAuthoringPage'; +import { getContentLibraryApiUrl } from './data/api'; + +let store; +const mockUseParams = jest.fn(); +let axiosMock; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), // use actual for all non-hook parts + useParams: () => mockUseParams(), +})); + +const searchEndpoint = 'http://mock.meilisearch.local/multi-search'; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, +}); + +const returnEmptyResult = (_url, req) => { + const requestData = JSON.parse(req.body?.toString() ?? ''); + const query = requestData?.queries[0]?.q ?? ''; + // We have to replace the query (search keywords) in the mock results with the actual query, + // because otherwise we may have an inconsistent state that causes more queries and unexpected results. + mockEmptyResult.results[0].query = query; + // And fake the required '_formatted' fields; it contains the highlighting ... around matched words + // eslint-disable-next-line no-underscore-dangle, no-param-reassign + mockEmptyResult.results[0]?.hits.forEach((hit) => { hit._formatted = { ...hit }; }); + return mockEmptyResult; +}; + +const libraryData = { + id: 'lib:org1:lib1', + type: 'complex', + org: 'org1', + slug: 'lib1', + title: 'lib1', + description: 'lib1', + numBlocks: 2, + version: 0, + lastPublished: null, + allowLti: false, + allowPublic_learning: false, + allowPublic_read: false, + hasUnpublished_changes: true, + hasUnpublished_deletes: false, + license: '', +}; + +const RootWrapper = () => ( + + + + + + + +); + +describe('', () => { + beforeEach(() => { + initializeMockApp({ + authenticatedUser: { + userId: 3, + username: 'abc123', + administrator: true, + roles: [], + }, + }); + store = initializeStore(); + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + mockUseParams.mockReturnValue({ libraryId: '1' }); + + // The API method to get the Meilisearch connection details uses Axios: + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + axiosMock.onGet(getContentSearchConfigUrl()).reply(200, { + url: 'http://mock.meilisearch.local', + index_name: 'studio', + api_key: 'test-key', + }); + // + // The Meilisearch client-side API uses fetch, not Axios. + fetchMock.post(searchEndpoint, (_url, req) => { + const requestData = JSON.parse(req.body?.toString() ?? ''); + const query = requestData?.queries[0]?.q ?? ''; + // We have to replace the query (search keywords) in the mock results with the actual query, + // because otherwise Instantsearch will update the UI and change the query, + // leading to unexpected results in the test cases. + mockResult.results[0].query = query; + // And fake the required '_formatted' fields; it contains the highlighting ... around matched words + // eslint-disable-next-line no-underscore-dangle, no-param-reassign + mockResult.results[0]?.hits.forEach((hit) => { hit._formatted = { ...hit }; }); + return mockResult; + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + axiosMock.restore(); + fetchMock.mockReset(); + queryClient.clear(); + }); + + it('shows the spinner before the query is complete', () => { + mockUseParams.mockReturnValue({ libraryId: '1' }); + // @ts-ignore Use unresolved promise to keep the Loading visible + axiosMock.onGet(getContentLibraryApiUrl('1')).reply(() => new Promise()); + const { getByRole } = render(); + const spinner = getByRole('status'); + expect(spinner.textContent).toEqual('Loading...'); + }); + + it('shows an error component if no library returned', async () => { + mockUseParams.mockReturnValue({ libraryId: 'invalid' }); + axiosMock.onGet(getContentLibraryApiUrl('invalid')).reply(400); + + const { findByTestId } = render(); + + expect(await findByTestId('notFoundAlert')).toBeInTheDocument(); + }); + + it('shows an error component if no library param', async () => { + mockUseParams.mockReturnValue({ libraryId: '' }); + + const { findByTestId } = render(); + + expect(await findByTestId('notFoundAlert')).toBeInTheDocument(); + }); + + it('show library data', async () => { + mockUseParams.mockReturnValue({ libraryId: libraryData.id }); + axiosMock.onGet(getContentLibraryApiUrl(libraryData.id)).reply(200, libraryData); + + const { + getByRole, getByText, queryByText, + } = render(); + + // Ensure the search endpoint is called + await waitFor(() => { expect(fetchMock).toHaveFetchedTimes(1, searchEndpoint, 'post'); }); + + expect(getByText('Content library')).toBeInTheDocument(); + expect(getByText(libraryData.title)).toBeInTheDocument(); + + expect(queryByText('You have not added any content to this library yet.')).not.toBeInTheDocument(); + + expect(getByText('Recently Modified')).toBeInTheDocument(); + expect(getByText('Collections (0)')).toBeInTheDocument(); + expect(getByText('Components (6)')).toBeInTheDocument(); + expect(getByText('There are 6 components in this library')).toBeInTheDocument(); + + // Navigate to the components tab + fireEvent.click(getByRole('tab', { name: 'Components' })); + expect(queryByText('Recently Modified')).not.toBeInTheDocument(); + expect(queryByText('Collections (0)')).not.toBeInTheDocument(); + expect(queryByText('Components (6)')).not.toBeInTheDocument(); + expect(getByText('There are 6 components in this library')).toBeInTheDocument(); + + // Navigate to the collections tab + fireEvent.click(getByRole('tab', { name: 'Collections' })); + expect(queryByText('Recently Modified')).not.toBeInTheDocument(); + expect(queryByText('Collections (0)')).not.toBeInTheDocument(); + expect(queryByText('Components (6)')).not.toBeInTheDocument(); + expect(queryByText('There are 6 components in this library')).not.toBeInTheDocument(); + expect(getByText('Coming soon!')).toBeInTheDocument(); + + // Go back to Home tab + // This step is necessary to avoid the url change leak to other tests + fireEvent.click(getByRole('tab', { name: 'Home' })); + expect(getByText('Recently Modified')).toBeInTheDocument(); + expect(getByText('Collections (0)')).toBeInTheDocument(); + expect(getByText('Components (6)')).toBeInTheDocument(); + expect(getByText('There are 6 components in this library')).toBeInTheDocument(); + }); + + it('show library without components', async () => { + mockUseParams.mockReturnValue({ libraryId: libraryData.id }); + axiosMock.onGet(getContentLibraryApiUrl(libraryData.id)).reply(200, libraryData); + fetchMock.post(searchEndpoint, returnEmptyResult, { overwriteRoutes: true }); + + const { findByText, getByText } = render(); + + expect(await findByText('Content library')).toBeInTheDocument(); + expect(await findByText(libraryData.title)).toBeInTheDocument(); + + // Ensure the search endpoint is called + await waitFor(() => { expect(fetchMock).toHaveFetchedTimes(1, searchEndpoint, 'post'); }); + + expect(getByText('You have not added any content to this library yet.')).toBeInTheDocument(); + }); + + it('show library without search results', async () => { + mockUseParams.mockReturnValue({ libraryId: libraryData.id }); + axiosMock.onGet(getContentLibraryApiUrl(libraryData.id)).reply(200, libraryData); + fetchMock.post(searchEndpoint, returnEmptyResult, { overwriteRoutes: true }); + + const { findByText, getByRole, getByText } = render(); + + expect(await findByText('Content library')).toBeInTheDocument(); + expect(await findByText(libraryData.title)).toBeInTheDocument(); + + // Ensure the search endpoint is called + await waitFor(() => { expect(fetchMock).toHaveFetchedTimes(1, searchEndpoint, 'post'); }); + + fireEvent.change(getByRole('searchbox'), { target: { value: 'noresults' } }); + + // Ensure the search endpoint is called again + await waitFor(() => { expect(fetchMock).toHaveFetchedTimes(2, searchEndpoint, 'post'); }); + + expect(getByText('No matching components found in this library.')).toBeInTheDocument(); + + // Navigate to the components tab + fireEvent.click(getByRole('tab', { name: 'Components' })); + expect(getByText('No matching components found in this library.')).toBeInTheDocument(); + + // Go back to Home tab + // This step is necessary to avoid the url change leak to other tests + fireEvent.click(getByRole('tab', { name: 'Home' })); + }); +}); diff --git a/src/library-authoring/data/api.js b/src/library-authoring/data/api.js index c97760b868..9fae35d947 100644 --- a/src/library-authoring/data/api.js +++ b/src/library-authoring/data/api.js @@ -7,14 +7,13 @@ const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL; * Get the URL for the content library API. * @param {string} libraryId - The ID of the library to fetch. */ -const getContentLibraryApiUrl = (libraryId) => `${getApiBaseUrl()}/api/libraries/v2/${libraryId}/`; +export const getContentLibraryApiUrl = (libraryId) => `${getApiBaseUrl()}/api/libraries/v2/${libraryId}/`; /** * Fetch a content library by its ID. * @param {string} [libraryId] - The ID of the library to fetch. * @returns {Promise} */ -/* eslint-disable import/prefer-default-export */ export async function getContentLibrary(libraryId) { if (!libraryId) { throw new Error('libraryId is required'); diff --git a/src/library-authoring/messages.js b/src/library-authoring/messages.js index 6a09703b64..5be2437a98 100644 --- a/src/library-authoring/messages.js +++ b/src/library-authoring/messages.js @@ -6,6 +6,11 @@ const messages = defineMessages({ defaultMessage: 'Content library', description: 'The page heading for the library page.', }, + headingInfoAlt: { + id: 'course-authoring.library-authoring.heading-info-alt', + defaultMessage: 'Info', + description: 'Alt text for the info icon next to the page heading.', + }, searchPlaceholder: { id: 'course-authoring.library-authoring.search', defaultMessage: 'Search...', From 91443e9d75baa925f18d6461647dfbf75f362a99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Fri, 7 Jun 2024 17:13:06 -0300 Subject: [PATCH 39/88] fix: removing unused file --- src/studio-home/__mocks__/studioHomeMock.js | 2 +- .../tabs-section/LibraryV2Placeholder.jsx | 36 ------------------- webpack.dev-tutor.config.js | 0 3 files changed, 1 insertion(+), 37 deletions(-) delete mode 100644 src/studio-home/tabs-section/LibraryV2Placeholder.jsx create mode 100755 webpack.dev-tutor.config.js diff --git a/src/studio-home/__mocks__/studioHomeMock.js b/src/studio-home/__mocks__/studioHomeMock.js index 4f66cc116f..5385201e52 100644 --- a/src/studio-home/__mocks__/studioHomeMock.js +++ b/src/studio-home/__mocks__/studioHomeMock.js @@ -62,7 +62,7 @@ module.exports = { }, ], librariesEnabled: true, - libraryAuthoringMfeUrl: 'http://localhost:3001/', + libraryAuthoringMfeUrl: 'http://localhost:3001', optimizationEnabled: false, redirectToLibraryAuthoringMfe: false, requestCourseCreatorUrl: '/request_course_creator', diff --git a/src/studio-home/tabs-section/LibraryV2Placeholder.jsx b/src/studio-home/tabs-section/LibraryV2Placeholder.jsx deleted file mode 100644 index 6b13853a2c..0000000000 --- a/src/studio-home/tabs-section/LibraryV2Placeholder.jsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import { Container } from '@openedx/paragon'; -import { StudioFooter } from '@edx/frontend-component-footer'; -import { useIntl } from '@edx/frontend-platform/i18n'; - -import Header from '../../header'; -import SubHeader from '../../generic/sub-header/SubHeader'; -import messages from './messages'; - -/* istanbul ignore next */ -const LibraryV2Placeholder = () => { - const intl = useIntl(); - - return ( - <> -
- -
-
-
- -
-
-
-

{intl.formatMessage(messages.libraryV2PlaceholderBody)}

-
-
-
- - - ); -}; - -export default LibraryV2Placeholder; diff --git a/webpack.dev-tutor.config.js b/webpack.dev-tutor.config.js new file mode 100755 index 0000000000..e69de29bb2 From a29cf7e45038e10a2c5cbe313f1202f0be8c0a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Fri, 7 Jun 2024 17:25:36 -0300 Subject: [PATCH 40/88] fix: add ts-check --- src/library-authoring/messages.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/library-authoring/messages.js b/src/library-authoring/messages.js index 5be2437a98..ff985aa62c 100644 --- a/src/library-authoring/messages.js +++ b/src/library-authoring/messages.js @@ -1,3 +1,4 @@ +// @ts-check import { defineMessages } from '@edx/frontend-platform/i18n'; const messages = defineMessages({ From 4deab763fc52676c3807ffe57bb128a50ecf5c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Fri, 7 Jun 2024 17:37:24 -0300 Subject: [PATCH 41/88] fix: removing deleted file references --- src/index.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/index.jsx b/src/index.jsx index 283d2544aa..bf7ee9c423 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -24,7 +24,6 @@ import initializeStore from './store'; import CourseAuthoringRoutes from './CourseAuthoringRoutes'; import Head from './head/Head'; import { StudioHome } from './studio-home'; -import LibraryV2Placeholder from './studio-home/tabs-section/LibraryV2Placeholder'; import CourseRerun from './course-rerun'; import { TaxonomyLayout, TaxonomyDetailPage, TaxonomyListPage } from './taxonomy'; import { ContentTagsDrawer } from './content-tags-drawer'; From e8bca34a8656bcae26a6d5de45bc5b7b39c6b062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Fri, 7 Jun 2024 18:04:09 -0300 Subject: [PATCH 42/88] chore: trigger CI From 388c40e2ffd072d8c1fe770f777d8d7bea9bfe3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Thu, 13 Jun 2024 15:34:16 -0300 Subject: [PATCH 43/88] fix: fixes from review --- src/library-authoring/LibraryAuthoringPage.jsx | 1 + src/library-authoring/LibraryAuthoringPage.test.jsx | 2 +- webpack.dev-tutor.config.js | 0 3 files changed, 2 insertions(+), 1 deletion(-) delete mode 100755 webpack.dev-tutor.config.js diff --git a/src/library-authoring/LibraryAuthoringPage.jsx b/src/library-authoring/LibraryAuthoringPage.jsx index 0536e4faab..1a70bfb491 100644 --- a/src/library-authoring/LibraryAuthoringPage.jsx +++ b/src/library-authoring/LibraryAuthoringPage.jsx @@ -96,6 +96,7 @@ const LibraryAuthoringPage = () => { value={searchKeywords} placeholder={intl.formatMessage(messages.searchPlaceholder)} onChange={(value) => setSearchKeywords(value)} + onSubmit={() => {}} className="w-50" /> ', () => { index_name: 'studio', api_key: 'test-key', }); - // + // The Meilisearch client-side API uses fetch, not Axios. fetchMock.post(searchEndpoint, (_url, req) => { const requestData = JSON.parse(req.body?.toString() ?? ''); diff --git a/webpack.dev-tutor.config.js b/webpack.dev-tutor.config.js deleted file mode 100755 index e69de29bb2..0000000000 From b8279765365f2fc746ab747fa22bdaca2727e4a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Fri, 14 Jun 2024 10:40:24 -0300 Subject: [PATCH 44/88] feat: create library form --- src/library-authoring/CreateLibrary.jsx | 29 ----- .../create-library/CreateLibrary.jsx | 110 ++++++++++++++++++ src/library-authoring/create-library/hooks.js | 69 +++++++++++ src/library-authoring/create-library/index.js | 2 + .../create-library/messages.js | 79 +++++++++++++ src/library-authoring/index.js | 2 +- src/library-authoring/messages.js | 10 -- src/search-modal/data/apiHooks.js | 4 +- 8 files changed, 263 insertions(+), 42 deletions(-) delete mode 100644 src/library-authoring/CreateLibrary.jsx create mode 100644 src/library-authoring/create-library/CreateLibrary.jsx create mode 100644 src/library-authoring/create-library/hooks.js create mode 100644 src/library-authoring/create-library/index.js create mode 100644 src/library-authoring/create-library/messages.js diff --git a/src/library-authoring/CreateLibrary.jsx b/src/library-authoring/CreateLibrary.jsx deleted file mode 100644 index 738e9fb769..0000000000 --- a/src/library-authoring/CreateLibrary.jsx +++ /dev/null @@ -1,29 +0,0 @@ -// @ts-check -/* eslint-disable react/prop-types */ -import React from 'react'; -import { FormattedMessage } from '@edx/frontend-platform/i18n'; -import { Container } from '@openedx/paragon'; - -import Header from '../header'; -import SubHeader from '../generic/sub-header/SubHeader'; - -import messages from './messages'; - -/* istanbul ignore next This is only a placeholder component */ -const CreateLibrary = () => ( - <> -
- - } - /> -
- -
-
- -); - -export default CreateLibrary; diff --git a/src/library-authoring/create-library/CreateLibrary.jsx b/src/library-authoring/create-library/CreateLibrary.jsx new file mode 100644 index 0000000000..b8d43c9819 --- /dev/null +++ b/src/library-authoring/create-library/CreateLibrary.jsx @@ -0,0 +1,110 @@ +// @ts-check +/* eslint-disable react/prop-types */ +import React from 'react'; +import { TypeaheadDropdown } from '@edx/frontend-lib-content-components'; +import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; +import { Container, Form } from '@openedx/paragon'; +import classNames from 'classnames'; + +import Header from '../../header'; +import SubHeader from '../../generic/sub-header/SubHeader'; +import { useCreateLibrary } from './hooks'; +import messages from './messages'; + +const CreateLibrary = () => { + const intl = useIntl(); + const initialValues = { + title: '', + org: '', + slug: '', + }; + const { + values, + errors, + hasErrorField, + handleChange, + handleBlur, + organizationListData, + } = useCreateLibrary(initialValues); + + const newLibraryFields = [ + { + label: intl.formatMessage(messages.titleLabel), + helpText: intl.formatMessage(messages.titleHelp), + name: 'title', + value: values.title, + placeholder: intl.formatMessage(messages.titlePlaceholder), + }, + { + label: intl.formatMessage(messages.orgLabel), + helpText: intl.formatMessage(messages.orgHelp), + name: 'org', + value: values.org, + options: organizationListData, + placeholder: intl.formatMessage(messages.orgPlaceholder), + }, + { + label: intl.formatMessage(messages.slugLabel), + helpText: intl.formatMessage(messages.slugHelp), + name: 'slug', + value: values.slug, + placeholder: intl.formatMessage(messages.slugPlaceholder), + }, + ]; + + return ( + <> +
+ + } + /> + {newLibraryFields.map((field) => ( + + {field.label} + {field.name !== 'org' ? ( + + ) : ( + + )} + {field.helpText} + {hasErrorField(field.name) && ( + + {errors[field.name]} + + )} + + ))} + + + ); +}; + +export default CreateLibrary; diff --git a/src/library-authoring/create-library/hooks.js b/src/library-authoring/create-library/hooks.js new file mode 100644 index 0000000000..1f203a7d23 --- /dev/null +++ b/src/library-authoring/create-library/hooks.js @@ -0,0 +1,69 @@ +// @ts-check +import { useIntl } from '@edx/frontend-platform/i18n'; +import { useFormik } from 'formik'; +import * as Yup from 'yup'; + +import { useOrganizationListData } from '../../generic/data/apiHooks'; +import messages from './messages'; + +// eslint-disable-next-line import/prefer-default-export +export const useCreateLibrary = (initialValues) => { + const intl = useIntl(); + + const specialCharsRule = /^[a-zA-Z0-9_\-.'*~\s]+$/; + const noSpaceRule = /^\S*$/; + const validSlugIdRegex = /^[a-zA-Z\d]+(?:[\w -]*[a-zA-Z\d]+)*$/; + + const validationSchema = Yup.object().shape({ + displayName: Yup.string().required( + intl.formatMessage(messages.requiredFieldError), + ), + org: Yup.string() + .required(intl.formatMessage(messages.requiredFieldError)) + .matches( + specialCharsRule, + intl.formatMessage(messages.disallowedCharsError), + ) + .matches(noSpaceRule, intl.formatMessage(messages.noSpaceError)), + slug: Yup.string() + .required(intl.formatMessage(messages.requiredFieldError)) + .matches( + validSlugIdRegex, + intl.formatMessage(messages.invalidSlugError), + ), + title: Yup.string() + .required(intl.formatMessage(messages.requiredFieldError)), + }); + + const { + data: organizationListData, + isSuccess: isOrganizationListLoaded, + } = useOrganizationListData(); + + const onSubmit = async (values) => { + console.log('values', values); + }; + + const { + values, errors, touched, handleChange, handleBlur, setFieldValue, + } = useFormik({ + initialValues, + enableReinitialize: true, + validateOnBlur: false, + validationSchema, + onSubmit, + }); + + const hasErrorField = (fieldName) => !!errors[fieldName] && !!touched[fieldName]; + const isFormInvalid = !!Object.keys(errors).length; + + return { + organizationListData, + hasErrorField, + isFormInvalid, + handleChange, + handleBlur, + values, + errors, + }; +}; diff --git a/src/library-authoring/create-library/index.js b/src/library-authoring/create-library/index.js new file mode 100644 index 0000000000..dedcc16dcb --- /dev/null +++ b/src/library-authoring/create-library/index.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as CreateLibrary } from './CreateLibrary'; diff --git a/src/library-authoring/create-library/messages.js b/src/library-authoring/create-library/messages.js new file mode 100644 index 0000000000..33801ac56f --- /dev/null +++ b/src/library-authoring/create-library/messages.js @@ -0,0 +1,79 @@ +// @ts-check +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + createLibrary: { + id: 'course-authoring.library-authoring.create-library', + defaultMessage: 'Create new library', + description: 'Header for the create library form', + }, + titleLabel: { + id: 'course-authoring.library-authoring.create-library.form.title.label', + defaultMessage: 'Library name', + description: 'Label for the title field.', + }, + titlePlaceholder: { + id: 'course-authoring.library-authoring.create-library.form.title.placeholder', + defaultMessage: 'e.g. Computer Science Problems', + description: 'Placeholder text for the title field.', + }, + titleHelp: { + id: 'course-authoring.library-authoring.create-library.form.title.help', + defaultMessage: 'The name for your library', + description: 'Help text for the title field.', + }, + orgLabel: { + id: 'course-authoring.library-authoring.create-library.form.org.label', + defaultMessage: 'Organization', + description: 'Label for the organization field.', + }, + orgPlaceholder: { + id: 'course-authoring.library-authoring.create-library.form.org.placeholder', + defaultMessage: 'e.g. UniversityX or OrganizationX', + description: 'Placeholder text for the organization field.', + }, + orgHelp: { + id: 'course-authoring.library-authoring.create-library.form.org.help', + defaultMessage: 'The public organization name for your library. This cannot be changed.', + description: 'Help text for the organization field.', + }, + slugLabel: { + id: 'course-authoring.library-authoring.create-library.form.slug.label', + defaultMessage: 'Library ID', + description: 'Label for the slug field.', + }, + slugPlaceholder: { + id: 'course-authoring.library-authoring.create-library.form.slug.placeholder', + defaultMessage: 'e.g. CSPROB', + description: 'Placeholder text for the slug field.', + }, + slugHelp: { + id: 'course-authoring.library-authoring.create-library.form.slug.help', + defaultMessage: `The unique code that identifies this library. Note: This is + part of your library URL, so no spaces or special characters are allowed. + This cannot be changed.`, + description: 'Help text for the slug field.', + }, + invalidSlugError: { + id: 'course-authoring.library-authoring.create-library.form.invalid-slug.error', + defaultMessage: 'Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or hyphens.', + description: 'Text to display when slug id has invalid symbols.', + }, + requiredFieldError: { + id: 'course-authoring.library-authoring.create-library.form.required.error', + defaultMessage: 'Required field.', + description: 'Error message to display when a required field is missing.', + }, + disallowedCharsError: { + id: 'course-authoring.library-authoring.create-library.form.disallowed-chars.error', + defaultMessage: 'Please do not use any spaces or special characters in this field.', + description: 'Error message to display when a field contains disallowed characters.', + }, + noSpaceError: { + id: 'course-authoring.library-authoring.create-library.form.no-space.error', + defaultMessage: 'Please do not use any spaces in this field.', + description: 'Error message to display when a field contains spaces.', + }, +}); + +export default messages; diff --git a/src/library-authoring/index.js b/src/library-authoring/index.js index 69831c4ed9..da0dfbabda 100644 --- a/src/library-authoring/index.js +++ b/src/library-authoring/index.js @@ -1,4 +1,4 @@ // @ts-check // eslint-disable-next-line import/prefer-default-export export { default as LibraryAuthoringPage } from './LibraryAuthoringPage'; -export { default as CreateLibrary } from './CreateLibrary'; +export { CreateLibrary } from './create-library'; diff --git a/src/library-authoring/messages.js b/src/library-authoring/messages.js index ff985aa62c..acb35dfb67 100644 --- a/src/library-authoring/messages.js +++ b/src/library-authoring/messages.js @@ -47,16 +47,6 @@ const messages = defineMessages({ defaultMessage: 'Recently modified components and collections will be displayed here.', description: 'Temp placeholder for the recent components container. This will be replaced with the actual list.', }, - createLibrary: { - id: 'course-authoring.library-authoring.create-library', - defaultMessage: 'Create library', - description: 'Header for the create library form', - }, - createLibraryTempPlaceholder: { - id: 'course-authoring.library-authoring.create-library-temp-placeholder', - defaultMessage: 'This is a placeholder for the create library form. This will be replaced with the actual form.', - description: 'Temp placeholder for the create library container. This will be replaced with the new library form.', - }, }); export default messages; diff --git a/src/search-modal/data/apiHooks.js b/src/search-modal/data/apiHooks.js index 59a07c425a..aecd3d7bbe 100644 --- a/src/search-modal/data/apiHooks.js +++ b/src/search-modal/data/apiHooks.js @@ -42,8 +42,8 @@ export const useContentSearchResults = ({ indexName, extraFilter, searchKeywords, - blockTypesFilter, - tagsFilter, + blockTypesFilter = [], + tagsFilter = [], }) => { blockTypesFilter ??= []; // eslint-disable-line no-param-reassign -- default value for optional parameter tagsFilter ??= []; // eslint-disable-line no-param-reassign -- Default value for optional parameter From 7d6096e2cea78aa79497305058d019fda31e5a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Fri, 14 Jun 2024 10:41:18 -0300 Subject: [PATCH 45/88] fix: fix default parameter syntax --- src/search-modal/data/apiHooks.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/search-modal/data/apiHooks.js b/src/search-modal/data/apiHooks.js index 59a07c425a..eb64696fc8 100644 --- a/src/search-modal/data/apiHooks.js +++ b/src/search-modal/data/apiHooks.js @@ -42,12 +42,9 @@ export const useContentSearchResults = ({ indexName, extraFilter, searchKeywords, - blockTypesFilter, - tagsFilter, + blockTypesFilter = [], + tagsFilter = [], }) => { - blockTypesFilter ??= []; // eslint-disable-line no-param-reassign -- default value for optional parameter - tagsFilter ??= []; // eslint-disable-line no-param-reassign -- Default value for optional parameter - const query = useInfiniteQuery({ enabled: client !== undefined && indexName !== undefined, queryKey: [ From c63bc2fbf586a791770b3b63e3262c5c6bd542ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Tue, 18 Jun 2024 18:56:16 -0300 Subject: [PATCH 46/88] fix: new library redirect --- src/studio-home/StudioHome.jsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/studio-home/StudioHome.jsx b/src/studio-home/StudioHome.jsx index 4953c6f3ae..92b26f37f9 100644 --- a/src/studio-home/StudioHome.jsx +++ b/src/studio-home/StudioHome.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { Button, Container, @@ -11,6 +11,7 @@ import { Add as AddIcon, Error } from '@openedx/paragon/icons'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { StudioFooter } from '@edx/frontend-component-footer'; import { getConfig, getPath } from '@edx/frontend-platform'; +import { useLocation } from 'react-router-dom'; import { constructLibraryAuthoringURL } from '../utils'; import Loading from '../generic/Loading'; @@ -19,7 +20,7 @@ import Header from '../header'; import SubHeader from '../generic/sub-header/SubHeader'; import HomeSidebar from './home-sidebar'; import TabsSection from './tabs-section'; -import { isMixedOrV2LibrariesMode } from './tabs-section/utils'; +import { isMixedOrV1LibrariesMode, isMixedOrV2LibrariesMode } from './tabs-section/utils'; import OrganizationSection from './organization-section'; import VerifyEmailLayout from './verify-email-layout'; import CreateNewCourseForm from './create-new-course-form'; @@ -28,6 +29,8 @@ import { useStudioHome } from './hooks'; import AlertMessage from '../generic/alert-message'; const StudioHome = ({ intl }) => { + const location = useLocation(); + const isPaginationCoursesEnabled = getConfig().ENABLE_HOME_PAGE_COURSE_API_V2; const { isLoadingPage, @@ -47,6 +50,9 @@ const StudioHome = ({ intl }) => { const libMode = getConfig().LIBRARY_MODE; + const v1LibraryTab = isMixedOrV1LibrariesMode(libMode) && location?.pathname.split('/').pop() === 'libraries-v1'; + console.log('v1LibraryTab', v1LibraryTab); + const { userIsActive, studioShortName, @@ -55,7 +61,7 @@ const StudioHome = ({ intl }) => { redirectToLibraryAuthoringMfe, } = studioHomeData; - function getHeaderButtons() { + const getHeaderButtons = useCallback(() => { const headerButtons = []; if (isFailedLoadingPage || !userIsActive) { @@ -83,7 +89,7 @@ const StudioHome = ({ intl }) => { } let libraryHref = `${getConfig().STUDIO_BASE_URL}/home_library`; - if (isMixedOrV2LibrariesMode(libMode)) { + if (isMixedOrV2LibrariesMode(libMode) && !v1LibraryTab) { libraryHref = libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe ? constructLibraryAuthoringURL(libraryAuthoringMfeUrl, 'create') // Redirection to the placeholder is done in the MFE rather than @@ -91,6 +97,7 @@ const StudioHome = ({ intl }) => { // hence why we use the MFE's origin : `${window.location.origin}${getPath(getConfig().PUBLIC_PATH)}library/create`; } + console.log('libraryHref', libraryHref); headerButtons.push( diff --git a/src/generic/clipboard/paste-component/components/WhatsInClipboard.jsx b/src/generic/clipboard/paste-component/components/WhatsInClipboard.jsx index aca6d3f0cc..22d4d0ca4c 100644 --- a/src/generic/clipboard/paste-component/components/WhatsInClipboard.jsx +++ b/src/generic/clipboard/paste-component/components/WhatsInClipboard.jsx @@ -34,7 +34,7 @@ const WhatsInClipboard = ({ />

togglePopover(isOpen); const renderPopover = (props) => ( -

+
= ({ appliedFilters, ...props }) => { const [isOpen, open, close] = useToggle(false); - const [target, setTarget] = React.useState(null); + const [target, setTarget] = React.useState(null); return ( <> diff --git a/src/search-modal/SearchResult.tsx b/src/search-modal/SearchResult.tsx index 77f806be4a..57584cefe5 100644 --- a/src/search-modal/SearchResult.tsx +++ b/src/search-modal/SearchResult.tsx @@ -22,13 +22,13 @@ import type { ContentHit } from './data/api'; import Highlight from './Highlight'; import messages from './messages'; -const STRUCTURAL_TYPE_ICONS: Record = { +const STRUCTURAL_TYPE_ICONS: Record = { vertical: TYPE_ICONS_MAP.vertical, sequential: Folder, chapter: Folder, }; -function getItemIcon(blockType: string): React.ReactElement { +function getItemIcon(blockType: string): React.ComponentType { return STRUCTURAL_TYPE_ICONS[blockType] ?? COMPONENT_TYPE_ICON_MAP[blockType] ?? Article; } From 83489b098315fe2e8c6408a8d6d48b3ae9d45ed4 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Mon, 8 Jul 2024 17:35:43 +0300 Subject: [PATCH 78/88] feat: Add filters/sorting for the libraries v2 tab on studio home (#1117) --- src/library/data/api.ts | 66 ++++++++ src/studio-home/data/api.js | 24 +-- src/studio-home/data/api.test.js | 2 +- .../data/{apiHooks.js => apiHooks.ts} | 5 +- .../libraries-v2-tab/{index.jsx => index.tsx} | 86 +++++++---- .../libraries-v2-filters/index.test.tsx | 144 ++++++++++++++++++ .../libraries-v2-filters/index.tsx | 109 +++++++++++++ .../libraries-v2-filter-menu/index.tsx | 58 +++++++ .../libraries-v2-order-filter-menu/index.tsx | 55 +++++++ .../messages.ts | 26 ++++ src/studio-home/tabs-section/messages.js | 12 ++ 11 files changed, 535 insertions(+), 52 deletions(-) create mode 100644 src/library/data/api.ts rename src/studio-home/data/{apiHooks.js => apiHooks.ts} (57%) rename src/studio-home/tabs-section/libraries-v2-tab/{index.jsx => index.tsx} (54%) create mode 100644 src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/index.test.tsx create mode 100644 src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/index.tsx create mode 100644 src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/libraries-v2-filter-menu/index.tsx create mode 100644 src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/libraries-v2-order-filter-menu/index.tsx create mode 100644 src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/libraries-v2-order-filter-menu/messages.ts diff --git a/src/library/data/api.ts b/src/library/data/api.ts new file mode 100644 index 0000000000..2a31db309e --- /dev/null +++ b/src/library/data/api.ts @@ -0,0 +1,66 @@ +import { camelCaseObject, snakeCaseObject, getConfig } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; + +export const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL; + +export interface LibraryV2 { + id: string, + type: string, + org: string, + slug: string, + title: string, + description: string, + numBlocks: number, + version: number, + lastPublished: string | null, + allowLti: boolean, + allowPublicLearning: boolean, + allowPublicRead: boolean, + hasUnpublishedChanges: boolean, + hasUnpublishedDeletes: boolean, + license: string, +} + +export interface LibrariesV2Response { + next: string | null, + previous: string | null, + count: number, + numPages: number, + currentPage: number, + start: number, + results: LibraryV2[], +} + +/* Additional custom parameters for the API request. */ +export interface GetLibrariesV2CustomParams { + /* (optional) Library type, default `complex` */ + type?: string, + /* (optional) Page number of results */ + page?: number, + /* (optional) The number of results on each page, default `50` */ + pageSize?: number, + /* (optional) Whether pagination is supported, default `true` */ + pagination?: boolean, + /* (optional) Library field to order results by. Prefix with '-' for descending */ + order?: string, + /* (optional) Search query to filter v2 Libraries by */ + search?: string, +} + +/** + * Get's studio home v2 Libraries. + */ +export async function getStudioHomeLibrariesV2(customParams: GetLibrariesV2CustomParams): Promise { + // Set default params if not passed in + const customParamsDefaults = { + type: customParams.type || 'complex', + page: customParams.page || 1, + pageSize: customParams.pageSize || 50, + pagination: customParams.pagination !== undefined ? customParams.pagination : true, + order: customParams.order || 'title', + textSearch: customParams.search, + }; + const customParamsFormat = snakeCaseObject(customParamsDefaults); + const { data } = await getAuthenticatedHttpClient().get(`${getApiBaseUrl()}/api/libraries/v2/`, { params: customParamsFormat }); + return camelCaseObject(data); +} diff --git a/src/studio-home/data/api.js b/src/studio-home/data/api.js index 2124f6fed7..630bb52b6c 100644 --- a/src/studio-home/data/api.js +++ b/src/studio-home/data/api.js @@ -1,3 +1,4 @@ +// @ts-check import { camelCaseObject, snakeCaseObject, getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; @@ -8,7 +9,6 @@ export const getCourseNotificationUrl = (url) => new URL(url, getApiBaseUrl()).h /** * Get's studio home data. - * @param {string} search * @returns {Promise} */ export async function getStudioHomeData() { @@ -40,28 +40,6 @@ export async function getStudioHomeLibraries() { return camelCaseObject(data); } -/** - * Get's studio home v2 Libraries. - * @param {object} customParams - Additional custom paramaters for the API request. - * @param {string} [customParams.type] - (optional) Library type, default `complex` - * @param {number} [customParams.page] - (optional) Page number of results - * @param {number} [customParams.pageSize] - (optional) The number of results on each page, default `50` - * @param {boolean} [customParams.pagination] - (optional) Whether pagination is supported, default `true` - * @returns {Promise} - A Promise that resolves to the response data container the studio home v2 libraries. - */ -export async function getStudioHomeLibrariesV2(customParams) { - // Set default params if not passed in - const customParamsDefaults = { - type: customParams.type || 'complex', - page: customParams.page || 1, - pageSize: customParams.pageSize || 50, - pagination: customParams.pagination !== undefined ? customParams.pagination : true, - }; - const customParamsFormat = snakeCaseObject(customParamsDefaults); - const { data } = await getAuthenticatedHttpClient().get(`${getApiBaseUrl()}/api/libraries/v2/`, { params: customParamsFormat }); - return camelCaseObject(data); -} - /** * Handle course notification requests. * @param {string} url diff --git a/src/studio-home/data/api.test.js b/src/studio-home/data/api.test.js index 66f6ee279f..5d255d7fb2 100644 --- a/src/studio-home/data/api.test.js +++ b/src/studio-home/data/api.test.js @@ -13,8 +13,8 @@ import { getStudioHomeCourses, getStudioHomeCoursesV2, getStudioHomeLibraries, - getStudioHomeLibrariesV2, } from './api'; +import { getStudioHomeLibrariesV2 } from '../../library/data/api'; import { generateGetStudioCoursesApiResponse, generateGetStudioHomeDataApiResponse, diff --git a/src/studio-home/data/apiHooks.js b/src/studio-home/data/apiHooks.ts similarity index 57% rename from src/studio-home/data/apiHooks.js rename to src/studio-home/data/apiHooks.ts index 92575bf717..99e9606fb3 100644 --- a/src/studio-home/data/apiHooks.js +++ b/src/studio-home/data/apiHooks.ts @@ -1,14 +1,15 @@ import { useQuery } from '@tanstack/react-query'; -import { getStudioHomeLibrariesV2 } from './api'; +import { GetLibrariesV2CustomParams, getStudioHomeLibrariesV2 } from '../../library/data/api'; /** * Builds the query to fetch list of V2 Libraries */ -const useListStudioHomeV2Libraries = (customParams) => ( +const useListStudioHomeV2Libraries = (customParams: GetLibrariesV2CustomParams) => ( useQuery({ queryKey: ['listV2Libraries', customParams], queryFn: () => getStudioHomeLibrariesV2(customParams), + keepPreviousData: true, }) ); diff --git a/src/studio-home/tabs-section/libraries-v2-tab/index.jsx b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx similarity index 54% rename from src/studio-home/tabs-section/libraries-v2-tab/index.jsx rename to src/studio-home/tabs-section/libraries-v2-tab/index.tsx index c3b58df554..8678a69ea8 100644 --- a/src/studio-home/tabs-section/libraries-v2-tab/index.jsx +++ b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx @@ -1,8 +1,14 @@ import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import { Icon, Row, Pagination } from '@openedx/paragon'; +import { + Icon, + Row, + Pagination, + Alert, + Button, +} from '@openedx/paragon'; import { useIntl } from '@edx/frontend-platform/i18n'; import { getConfig, getPath } from '@edx/frontend-platform'; +import { Error } from '@openedx/paragon/icons'; import { constructLibraryAuthoringURL } from '../../../utils'; import useListStudioHomeV2Libraries from '../../data/apiHooks'; @@ -10,26 +16,38 @@ import { LoadingSpinner } from '../../../generic/Loading'; import AlertMessage from '../../../generic/alert-message'; import CardItem from '../../card-item'; import messages from '../messages'; +import LibrariesV2Filters from './libraries-v2-filters'; -const LibrariesV2Tab = ({ +const LibrariesV2Tab: React.FC<{ + libraryAuthoringMfeUrl: string, + redirectToLibraryAuthoringMfe: boolean +}> = ({ libraryAuthoringMfeUrl, redirectToLibraryAuthoringMfe, }) => { const intl = useIntl(); const [currentPage, setCurrentPage] = useState(1); + const [filterParams, setFilterParams] = useState({}); - const handlePageSelect = (page) => { + const isFiltered = Object.keys(filterParams).length > 0; + + const handlePageSelect = (page: number) => { setCurrentPage(page); }; + const handleClearFilters = () => { + setFilterParams({}); + setCurrentPage(1); + }; + const { data, isLoading, isError, - } = useListStudioHomeV2Libraries({ page: currentPage }); + } = useListStudioHomeV2Libraries({ page: currentPage, ...filterParams }); - if (isLoading) { + if (isLoading && !isFiltered) { return ( @@ -37,7 +55,7 @@ const LibrariesV2Tab = ({ ); } - const libURL = (id) => ( + const libURL = (id: string) => ( libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe ? constructLibraryAuthoringURL(libraryAuthoringMfeUrl, `library/${id}`) // Redirection to the placeholder is done in the MFE rather than @@ -46,6 +64,8 @@ const LibrariesV2Tab = ({ : `${window.location.origin}${getPath(getConfig().PUBLIC_PATH)}library/${id}` ); + const hasV2Libraries = !isLoading && ((data!.results.length || 0) > 0); + return ( isError ? (
- {/* Temporary div to add spacing. This will be replaced with lib search/filters */} -
-

- {intl.formatMessage(messages.coursesPaginationInfo, { - length: data.results.length, - total: data.count, - })} -

+ + { !isLoading + && ( +

+ {intl.formatMessage(messages.coursesPaginationInfo, { + length: data!.results.length, + total: data!.count, + })} +

+ )}
- { - data.results.map(({ + { hasV2Libraries + ? data!.results.map(({ id, org, slug, title, }) => ( - )) - } + )) : isFiltered && !isLoading && ( + + + {intl.formatMessage(messages.librariesV2TabLibraryNotFoundAlertTitle)} + +

+ {intl.formatMessage(messages.librariesV2TabLibraryNotFoundAlertMessage)} +

+ +
+ )} { - data.numPages > 1 + hasV2Libraries && (data!.numPages || 0) > 1 && ( @@ -103,9 +142,4 @@ const LibrariesV2Tab = ({ ); }; -LibrariesV2Tab.propTypes = { - libraryAuthoringMfeUrl: PropTypes.string.isRequired, - redirectToLibraryAuthoringMfe: PropTypes.bool.isRequired, -}; - export default LibrariesV2Tab; diff --git a/src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/index.test.tsx b/src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/index.test.tsx new file mode 100644 index 0000000000..ca78fa570f --- /dev/null +++ b/src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/index.test.tsx @@ -0,0 +1,144 @@ +import React from 'react'; +import { + screen, fireEvent, render, waitFor, +} from '@testing-library/react'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; + +import LibrariesV2Filters, { LibrariesV2FiltersProps } from '.'; + +describe('LibrariesV2Filters', () => { + const setFilterParamsMock = jest.fn(); + const setCurrentPageMock = jest.fn(); + + const IntlProviderWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( + + {children} + + ); + + const renderComponent = (overrideProps: Partial = {}) => render( + + + , + ); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render search field and order filter', () => { + renderComponent(); + const searchInput = screen.getByRole('searchbox'); + expect(searchInput).toBeInTheDocument(); + const orderFilter = screen.getByText('Name A-Z'); + expect(orderFilter).toBeInTheDocument(); + }); + + it('should call setFilterParams and setCurrentPage when search input changes', async () => { + renderComponent(); + const searchInput = screen.getByRole('searchbox'); + fireEvent.change(searchInput, { target: { value: 'test' } }); + await waitFor(() => expect(setFilterParamsMock).toHaveBeenCalled()); + await waitFor(() => expect(setCurrentPageMock).toHaveBeenCalled()); + }); + + it('should call setFilterParams and setCurrentPage when a menu item order menu is selected', async () => { + renderComponent(); + const libraryV2OrderMenuFilter = screen.getByText('Name A-Z'); + fireEvent.click(libraryV2OrderMenuFilter); + const newestLibV2sMenuItem = screen.getByText('Newest'); + fireEvent.click(newestLibV2sMenuItem); + expect(setFilterParamsMock).toHaveBeenCalled(); + expect(setCurrentPageMock).toHaveBeenCalled(); + }); + + it('should clear the search input when filters cleared', async () => { + const { rerender } = renderComponent({ isFiltered: true }); + const searchInput = screen.getByRole('searchbox'); + fireEvent.change(searchInput, { target: { value: 'test' } }); + await waitFor(() => expect(setFilterParamsMock).toHaveBeenCalled()); + await waitFor(() => expect(setCurrentPageMock).toHaveBeenCalled()); + + rerender( + + + , + ); + + await waitFor(() => expect((screen.getByRole('searchbox') as HTMLInputElement).value).toBe('')); + }); + + it('should update states with the correct parameters when a order menu item is selected', () => { + renderComponent(); + const libraryV2OrderMenuFilter = screen.getByText('Name A-Z'); + fireEvent.click(libraryV2OrderMenuFilter); + const oldestLibV2sMenuItem = screen.getByText('Oldest'); + fireEvent.click(oldestLibV2sMenuItem); + + // Check that setFilterParams is called with the correct payload + expect(setFilterParamsMock).toHaveBeenCalledWith(expect.objectContaining({ + search: undefined, + order: 'created', + })); + + // Check that setCurrentPage is called with `1` + expect(setCurrentPageMock).toHaveBeenCalledWith(1); + }); + + it('should call setFilterParams after debounce delay when the search input changes', async () => { + renderComponent(); + const searchInput = screen.getByRole('searchbox'); + fireEvent.change(searchInput, { target: { value: 'test' } }); + await waitFor(() => expect(setFilterParamsMock).toHaveBeenCalled(), { timeout: 500 }); + expect(setFilterParamsMock).toHaveBeenCalledWith(expect.anything()); + }); + + it('should not call setFilterParams with only spaces when search only spaces', async () => { + renderComponent(); + const searchInput = screen.getByRole('searchbox'); + fireEvent.change(searchInput, { target: { value: ' ' } }); + + await waitFor(() => expect(setFilterParamsMock).not.toHaveBeenCalledWith(expect.objectContaining({ + search: ' ', + order: 'created', + })), { timeout: 500 }); + }); + + it('should display the loading spinner when isLoading is true', () => { + renderComponent({ isLoading: true }); + const spinner = screen.getByText('Loading...'); + expect(spinner).toBeInTheDocument(); + }); + + it('should not display the loading spinner when isLoading is false', () => { + renderComponent({ isLoading: false }); + const spinner = screen.queryByText('Loading...'); + expect(spinner).not.toBeInTheDocument(); + }); + + it('should clear the search input and call dispatch when the reset button is clicked', async () => { + renderComponent(); + const searchInput = screen.getByRole('searchbox') as HTMLInputElement; + fireEvent.change(searchInput, { target: { value: 'test' } }); + const form = searchInput.closest('form'); + if (!form) { + throw new Error('Form not found'); + } + const resetButton = form.querySelector('button'); + if (!resetButton || !(resetButton instanceof HTMLButtonElement)) { + throw new Error('Reset button not found'); + } + fireEvent.click(resetButton); + expect(searchInput.value).toBe(''); + }); +}); diff --git a/src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/index.tsx b/src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/index.tsx new file mode 100644 index 0000000000..0af40ddf92 --- /dev/null +++ b/src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/index.tsx @@ -0,0 +1,109 @@ +/* eslint-disable react/require-default-props */ +import React, { useState, useCallback, useEffect } from 'react'; +import { SearchField } from '@openedx/paragon'; +import { useIntl } from '@edx/frontend-platform/i18n'; + +import { LoadingSpinner } from '../../../../generic/Loading'; +import LibrariesV2OrderFilterMenu from './libraries-v2-order-filter-menu'; +import messages from '../../messages'; + +export interface LibrariesV2FiltersProps { + isLoading?: boolean; + isFiltered?: boolean; + filterParams: { search?: string | undefined, order?: string }; + setFilterParams: React.Dispatch>; + setCurrentPage: React.Dispatch>; +} + +const LibrariesV2Filters: React.FC = ({ + isLoading = false, + isFiltered = false, + filterParams, + setFilterParams, + setCurrentPage, +}) => { + const intl = useIntl(); + + const [search, setSearch] = useState(''); + const [order, setOrder] = useState('title'); + + // Reset search & order when filters cleared + useEffect(() => { + if (!isFiltered) { + setSearch(filterParams.search); + setOrder('title'); + } + }, [isFiltered, setSearch, search, setOrder, filterParams.search]); + + const getOrderFromFilterType = (filterType: string) => { + const orders = { + sortLibrariesV2AZ: 'title', + sortLibrariesV2ZA: '-title', + sortLibrariesV2Newest: '-created', + sortLibrariesV2Oldest: 'created', + }; + + // Default to 'A-Z` if invalid filtertype + return orders[filterType] || 'title'; + }; + + const getFilterTypeData = (baseFilters: { search: string | undefined; order: string; }) => ({ + sortLibrariesV2AZ: { ...baseFilters, order: 'title' }, + sortLibrariesV2ZA: { ...baseFilters, order: '-title' }, + sortLibrariesV2Newest: { ...baseFilters, order: '-created' }, + sortLibrariesV2Oldest: { ...baseFilters, order: 'created' }, + }); + + const handleMenuFilterItemSelected = (filterType: string) => { + setOrder(getOrderFromFilterType(filterType)); + + const baseFilters = { + search, + order, + }; + + const menuFilterParams = getFilterTypeData(baseFilters); + const filterParamsFormat = menuFilterParams[filterType] || baseFilters; + + setFilterParams(filterParamsFormat); + setCurrentPage(1); + }; + + const handleSearchLibrariesV2 = useCallback((searchValue: string) => { + const valueFormatted = searchValue.trim(); + const updatedFilterParams = { + search: valueFormatted.length > 0 ? valueFormatted : undefined, + order, + }; + + // Check if the search is different from the current search and it's not only spaces + if (valueFormatted !== search || valueFormatted) { + setSearch(valueFormatted); + setFilterParams(updatedFilterParams); + setCurrentPage(1); + } + }, [order, search]); + + return ( +
+
+ {}} + onChange={handleSearchLibrariesV2} + value={search} + className="mr-4" + placeholder={intl.formatMessage(messages.librariesV2TabLibrarySearchPlaceholder)} + /> + {isLoading && ( + + + + )} +
+ + +
+ ); +}; + +export default LibrariesV2Filters; diff --git a/src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/libraries-v2-filter-menu/index.tsx b/src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/libraries-v2-filter-menu/index.tsx new file mode 100644 index 0000000000..bdc52f5bf1 --- /dev/null +++ b/src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/libraries-v2-filter-menu/index.tsx @@ -0,0 +1,58 @@ +import React, { useState, useEffect } from 'react'; +import { Icon, Dropdown } from '@openedx/paragon'; +import { Check } from '@openedx/paragon/icons'; + +const LibrariesV2FilterMenu: React.FC<{ + id: string; + menuItems: { id: string, name: string, value: string }[]; + onItemMenuSelected: (value: string) => void; + defaultItemSelectedText: string; + isFiltered: boolean; +}> = ({ + id: idProp, + menuItems = [], + onItemMenuSelected, + defaultItemSelectedText = '', + isFiltered, +}) => { + const [itemMenuSelected, setItemMenuSelected] = useState(defaultItemSelectedText); + const handleOrderSelected = (name: string, value: string) => { + setItemMenuSelected(name); + onItemMenuSelected(value); + }; + + const libraryV2OrderSelectedIcon = (itemValue: string) => (itemValue === itemMenuSelected ? ( + + ) : null); + + useEffect(() => { + if (!isFiltered) { + setItemMenuSelected(defaultItemSelectedText); + } + }, [isFiltered]); + + return ( + + + {itemMenuSelected} + + + {menuItems.map(({ id, name, value }) => ( + handleOrderSelected(name, value)} + > + {name} {libraryV2OrderSelectedIcon(name)} + + ))} + + + ); +}; + +export default LibrariesV2FilterMenu; diff --git a/src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/libraries-v2-order-filter-menu/index.tsx b/src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/libraries-v2-order-filter-menu/index.tsx new file mode 100644 index 0000000000..00e5794f23 --- /dev/null +++ b/src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/libraries-v2-order-filter-menu/index.tsx @@ -0,0 +1,55 @@ +import React, { useMemo } from 'react'; +import { useIntl } from '@edx/frontend-platform/i18n'; + +import messages from './messages'; + +import LibrariesV2FilterMenu from '../libraries-v2-filter-menu'; + +const LibrariesV2OrderFilterMenu: React.FC<{ + onItemMenuSelected: (value: string) => void; + isFiltered: boolean; +}> = ({ onItemMenuSelected, isFiltered }) => { + const intl = useIntl(); + + const libraryV2Orders = useMemo( + () => [ + { + id: 'sort-libraries-v2-az', + name: intl.formatMessage(messages.librariesV2OrderFilterMenuAscendantLibrariesV2), + value: 'sortLibrariesV2AZ', + }, + { + id: 'sort-libraries-v2-za', + name: intl.formatMessage(messages.librariesV2OrderFilterMenuDescendantLibrariesV2), + value: 'sortLibrariesV2ZA', + }, + { + id: 'sort-libraries-v2-newest', + name: intl.formatMessage(messages.librariesV2OrderFilterMenuNewestLibrariesV2), + value: 'sortLibrariesV2Newest', + }, + { + id: 'sort-libraries-v2-oldest', + name: intl.formatMessage(messages.librariesV2OrderFilterMenuOldestLibrariesV2), + value: 'sortLibrariesV2Oldest', + }, + ], + [intl], + ); + + const handleLibraryV2OrderSelected = (libraryV2Order: string) => { + onItemMenuSelected(libraryV2Order); + }; + + return ( + + ); +}; + +export default LibrariesV2OrderFilterMenu; diff --git a/src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/libraries-v2-order-filter-menu/messages.ts b/src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/libraries-v2-order-filter-menu/messages.ts new file mode 100644 index 0000000000..0826a32eae --- /dev/null +++ b/src/studio-home/tabs-section/libraries-v2-tab/libraries-v2-filters/libraries-v2-order-filter-menu/messages.ts @@ -0,0 +1,26 @@ +import { defineMessages as _defineMessages } from '@edx/frontend-platform/i18n'; +import type { defineMessages as defineMessagesType } from 'react-intl'; + +// frontend-platform currently doesn't provide types... do it ourselves. +const defineMessages = _defineMessages as typeof defineMessagesType; + +const messages = defineMessages({ + librariesV2OrderFilterMenuAscendantLibrariesV2: { + id: 'course-authoring.studio-home.libraries.tab.order-filter-menu.ascendant-librariesv2', + defaultMessage: 'Name A-Z', + }, + librariesV2OrderFilterMenuDescendantLibrariesV2: { + id: 'course-authoring.studio-home.libraries.tab.order-filter-menu.descendant-librariesv2', + defaultMessage: 'Name Z-A', + }, + librariesV2OrderFilterMenuNewestLibrariesV2: { + id: 'course-authoring.studio-home.libraries.tab.order-filter-menu.newest-librariesv2', + defaultMessage: 'Newest', + }, + librariesV2OrderFilterMenuOldestLibrariesV2: { + id: 'course-authoring.studio-home.libraries.tab.order-filter-menu.oldest-librariesv2', + defaultMessage: 'Oldest', + }, +}); + +export default messages; diff --git a/src/studio-home/tabs-section/messages.js b/src/studio-home/tabs-section/messages.js index 0ed614f55a..076f9c8eb7 100644 --- a/src/studio-home/tabs-section/messages.js +++ b/src/studio-home/tabs-section/messages.js @@ -58,6 +58,18 @@ const messages = defineMessages({ id: 'course-authoring.studio-home.libraries.placeholder.body', defaultMessage: 'This is a placeholder page, as the Library Authoring MFE is not enabled.', }, + librariesV2TabLibrarySearchPlaceholder: { + id: 'course-authoring.studio-home.libraries.tab.library.search-placeholder', + defaultMessage: 'Search', + }, + librariesV2TabLibraryNotFoundAlertTitle: { + id: 'course-authoring.studio-home.libraries.tab.library.not.found.alert.title', + defaultMessage: 'We could not find any result', + }, + librariesV2TabLibraryNotFoundAlertMessage: { + id: 'course-authoring.studio-home.libraries.tab.library.not.found.alert.message', + defaultMessage: 'There are no libraries with the current filters.', + }, }); export default messages; From 6a99b4853f763c9899f6ecc7fec5ce8ced93f508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Mon, 8 Jul 2024 17:58:13 +0200 Subject: [PATCH 79/88] fix: merging errors --- src/library-authoring/CreateLibrary.tsx | 27 ------------------- .../create-library/CreateLibrary.tsx | 1 - 2 files changed, 28 deletions(-) delete mode 100644 src/library-authoring/CreateLibrary.tsx diff --git a/src/library-authoring/CreateLibrary.tsx b/src/library-authoring/CreateLibrary.tsx deleted file mode 100644 index 227f14dbe5..0000000000 --- a/src/library-authoring/CreateLibrary.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import { FormattedMessage } from '@edx/frontend-platform/i18n'; -import { Container } from '@openedx/paragon'; - -import Header from '../header'; -import SubHeader from '../generic/sub-header/SubHeader'; - -import messages from './messages'; - -/* istanbul ignore next This is only a placeholder component */ -const CreateLibrary = () => ( - <> -
- - } - /> -
- -
-
- -); - -export default CreateLibrary; diff --git a/src/library-authoring/create-library/CreateLibrary.tsx b/src/library-authoring/create-library/CreateLibrary.tsx index 4bede0742a..b31966ff2b 100644 --- a/src/library-authoring/create-library/CreateLibrary.tsx +++ b/src/library-authoring/create-library/CreateLibrary.tsx @@ -21,7 +21,6 @@ import type { CreateContentLibraryArgs } from './data/api'; import { useCreateLibraryV2 } from './data/apiHooks'; import messages from './messages'; - const CreateLibrary = () => { const intl = useIntl(); const navigate = useNavigate(); From 63e7593c9e147817238a44444dce3d5736198eed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Tue, 9 Jul 2024 16:16:38 +0200 Subject: [PATCH 80/88] fix: rename contentId -> contextId and add typings --- src/CourseAuthoringPage.jsx | 2 +- src/header/Header.tsx | 16 ++++++++-------- .../LibraryAuthoringPage.test.tsx | 12 ++++++------ src/library-authoring/LibraryAuthoringPage.tsx | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/CourseAuthoringPage.jsx b/src/CourseAuthoringPage.jsx index eaa16c49c2..5d3d5e37b3 100644 --- a/src/CourseAuthoringPage.jsx +++ b/src/CourseAuthoringPage.jsx @@ -55,7 +55,7 @@ const CourseAuthoringPage = ({ courseId, children }) => { number={courseNumber} org={courseOrg} title={courseTitle} - contentId={courseId} + contextId={courseId} /> ) )} diff --git a/src/header/Header.tsx b/src/header/Header.tsx index d32c4de66a..79c09756f9 100644 --- a/src/header/Header.tsx +++ b/src/header/Header.tsx @@ -10,7 +10,7 @@ import { getContentMenuItems, getSettingMenuItems, getToolsMenuItems } from './u import messages from './messages'; interface HeaderProps { - contentId?: string, + contextId?: string, number?: string, org?: string, title?: string, @@ -19,7 +19,7 @@ interface HeaderProps { } const Header = ({ - contentId = '', + contextId = '', org = '', number = '', title = '', @@ -36,20 +36,20 @@ const Header = ({ { id: `${intl.formatMessage(messages['header.links.content'])}-dropdown-menu`, buttonTitle: intl.formatMessage(messages['header.links.content']), - items: getContentMenuItems({ studioBaseUrl, courseId: contentId, intl }), + items: getContentMenuItems({ studioBaseUrl, courseId: contextId, intl }), }, { id: `${intl.formatMessage(messages['header.links.settings'])}-dropdown-menu`, buttonTitle: intl.formatMessage(messages['header.links.settings']), - items: getSettingMenuItems({ studioBaseUrl, courseId: contentId, intl }), + items: getSettingMenuItems({ studioBaseUrl, courseId: contextId, intl }), }, { id: `${intl.formatMessage(messages['header.links.tools'])}-dropdown-menu`, buttonTitle: intl.formatMessage(messages['header.links.tools']), - items: getToolsMenuItems({ studioBaseUrl, courseId: contentId, intl }), + items: getToolsMenuItems({ studioBaseUrl, courseId: contextId, intl }), }, ] : []; - const outlineLink = !isLibrary ? `${studioBaseUrl}/course/${contentId}` : `/course-authoring/library/${contentId}`; + const outlineLink = !isLibrary ? `${studioBaseUrl}/course/${contextId}` : `/course-authoring/library/${contextId}`; return ( <> @@ -60,12 +60,12 @@ const Header = ({ isHiddenMainMenu={isHiddenMainMenu} mainMenuDropdowns={mainMenuDropdowns} outlineLink={outlineLink} - searchButtonAction={meiliSearchEnabled ? openSearchModal : undefined} + searchButtonAction={meiliSearchEnabled && !isLibrary ? openSearchModal : undefined} /> { meiliSearchEnabled && ( )} diff --git a/src/library-authoring/LibraryAuthoringPage.test.tsx b/src/library-authoring/LibraryAuthoringPage.test.tsx index db7e815a10..958e3e4486 100644 --- a/src/library-authoring/LibraryAuthoringPage.test.tsx +++ b/src/library-authoring/LibraryAuthoringPage.test.tsx @@ -13,7 +13,7 @@ import { getContentSearchConfigUrl } from '../search-modal/data/api'; import mockResult from '../search-modal/__mocks__/search-result.json'; import mockEmptyResult from '../search-modal/__mocks__/empty-search-result.json'; import LibraryAuthoringPage from './LibraryAuthoringPage'; -import { getContentLibraryApiUrl } from './data/api'; +import { getContentLibraryApiUrl, type ContentLibrary } from './data/api'; let store; const mockUseParams = jest.fn(); @@ -46,7 +46,7 @@ const returnEmptyResult = (_url, req) => { return mockEmptyResult; }; -const libraryData = { +const libraryData: ContentLibrary = { id: 'lib:org1:lib1', type: 'complex', org: 'org1', @@ -57,10 +57,10 @@ const libraryData = { version: 0, lastPublished: null, allowLti: false, - allowPublic_learning: false, - allowPublic_read: false, - hasUnpublished_changes: true, - hasUnpublished_deletes: false, + allowPublicLearning: false, + allowPublicRead: false, + hasUnpublishedChanges: true, + hasUnpublishedDeletes: false, license: '', }; diff --git a/src/library-authoring/LibraryAuthoringPage.tsx b/src/library-authoring/LibraryAuthoringPage.tsx index 8a6c449407..f45ef11e20 100644 --- a/src/library-authoring/LibraryAuthoringPage.tsx +++ b/src/library-authoring/LibraryAuthoringPage.tsx @@ -79,7 +79,7 @@ const LibraryAuthoringPage = () => { number={libraryData.slug} title={libraryData.title} org={libraryData.org} - contentId={libraryId} + contextId={libraryId} isLibrary /> From 5f4ebbd71617782feb01446f2303aef5c7a7359e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Tue, 9 Jul 2024 16:41:53 +0200 Subject: [PATCH 81/88] refactor: renaming index.js -> index.ts --- src/search-modal/{index.js => index.ts} | 1 - 1 file changed, 1 deletion(-) rename src/search-modal/{index.js => index.ts} (91%) diff --git a/src/search-modal/index.js b/src/search-modal/index.ts similarity index 91% rename from src/search-modal/index.js rename to src/search-modal/index.ts index 190635618d..570b22db47 100644 --- a/src/search-modal/index.js +++ b/src/search-modal/index.ts @@ -1,3 +1,2 @@ -// @ts-check export { default as SearchModal } from './SearchModal'; export { useContentSearchConnection, useContentSearchResults } from './data/apiHooks'; From 01d4b85205bdca78e0e49c5454f2939dd15bf337 Mon Sep 17 00:00:00 2001 From: Hunia Fatima Date: Wed, 10 Jul 2024 11:05:43 +0500 Subject: [PATCH 82/88] perf: lockfile version check workflow file updated (#1107) Co-authored-by: Hunia Fatima --- .github/workflows/lockfileversion-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lockfileversion-check.yml b/.github/workflows/lockfileversion-check.yml index 916dcb40d2..fadf1e26ce 100644 --- a/.github/workflows/lockfileversion-check.yml +++ b/.github/workflows/lockfileversion-check.yml @@ -10,4 +10,4 @@ on: jobs: version-check: - uses: openedx/.github/.github/workflows/lockfileversion-check-v3.yml@master + uses: openedx/.github/.github/workflows/lockfile-check.yml@master From 09822c2937f87434809c52f313c475f678cabe7e Mon Sep 17 00:00:00 2001 From: edX requirements bot <49161187+edx-requirements-bot@users.noreply.github.com> Date: Wed, 10 Jul 2024 04:24:44 -0400 Subject: [PATCH 83/88] chore: update browserslist DB (#443) --- package-lock.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 330871fb21..8c782430e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8044,9 +8044,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001606", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001606.tgz", - "integrity": "sha512-LPbwnW4vfpJId225pwjZJOgX1m9sGfbw/RKJvw/t0QhYOOaTXHvkjVGFGPpvwEzufrjvTlsULnVTxdy4/6cqkg==", + "version": "1.0.30001640", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz", + "integrity": "sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==", "funding": [ { "type": "opencollective", @@ -8060,7 +8060,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/cast-array": { "version": "1.0.1", From a7fe9997d255339cd72c1944071955250f78642d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Wed, 10 Jul 2024 11:24:20 +0200 Subject: [PATCH 84/88] feat: add types to createLibraryV2 api --- src/library-authoring/LibraryAuthoringPage.test.tsx | 1 + src/library-authoring/create-library/data/api.ts | 4 +++- src/library-authoring/data/api.ts | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/library-authoring/LibraryAuthoringPage.test.tsx b/src/library-authoring/LibraryAuthoringPage.test.tsx index 958e3e4486..26073ee238 100644 --- a/src/library-authoring/LibraryAuthoringPage.test.tsx +++ b/src/library-authoring/LibraryAuthoringPage.test.tsx @@ -62,6 +62,7 @@ const libraryData: ContentLibrary = { hasUnpublishedChanges: true, hasUnpublishedDeletes: false, license: '', + canEditLibrary: false, }; const RootWrapper = () => ( diff --git a/src/library-authoring/create-library/data/api.ts b/src/library-authoring/create-library/data/api.ts index b5c945db01..b529de5e9d 100644 --- a/src/library-authoring/create-library/data/api.ts +++ b/src/library-authoring/create-library/data/api.ts @@ -1,6 +1,8 @@ import { camelCaseObject, getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import type { ContentLibrary } from '../../data/api'; + const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL; /** @@ -17,7 +19,7 @@ export interface CreateContentLibraryArgs { /** * Create a new library */ -export async function createLibraryV2(data: CreateContentLibraryArgs) { +export async function createLibraryV2(data: CreateContentLibraryArgs): Promise { const client = getAuthenticatedHttpClient(); const url = getContentLibraryV2CreateApiUrl(); diff --git a/src/library-authoring/data/api.ts b/src/library-authoring/data/api.ts index 95126d8269..651473e466 100644 --- a/src/library-authoring/data/api.ts +++ b/src/library-authoring/data/api.ts @@ -23,6 +23,7 @@ export interface ContentLibrary { hasUnpublishedChanges: boolean; hasUnpublishedDeletes: boolean; license: string; + canEditLibrary: boolean; } /** From 9a232047aa2fe5d2f2669d9658db048ca2fdbd10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Wed, 10 Jul 2024 11:24:47 +0200 Subject: [PATCH 85/88] refactor: change code to use useMutate values instead of states --- .../create-library/CreateLibrary.tsx | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/src/library-authoring/create-library/CreateLibrary.tsx b/src/library-authoring/create-library/CreateLibrary.tsx index b31966ff2b..97eb2c0d89 100644 --- a/src/library-authoring/create-library/CreateLibrary.tsx +++ b/src/library-authoring/create-library/CreateLibrary.tsx @@ -1,6 +1,6 @@ -import React, { useState } from 'react'; +import React from 'react'; import { StudioFooter } from '@edx/frontend-component-footer'; -import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@edx/frontend-platform/i18n'; import { Alert, Container, @@ -17,21 +17,30 @@ import FormikControl from '../../generic/FormikControl'; import FormikErrorFeedback from '../../generic/FormikErrorFeedback'; import { useOrganizationListData } from '../../generic/data/apiHooks'; import SubHeader from '../../generic/sub-header/SubHeader'; -import type { CreateContentLibraryArgs } from './data/api'; import { useCreateLibraryV2 } from './data/apiHooks'; import messages from './messages'; +const ErrorAlert = ({ error }: { error: any }) => ( + + {error?.message} +
+ {error?.response?.data && JSON.stringify(error.response.data)} +
+); + const CreateLibrary = () => { const intl = useIntl(); const navigate = useNavigate(); - const [apiError, setApiError] = useState(); - const { noSpaceRule, specialCharsRule } = REGEX_RULES; const validSlugIdRegex = /^[a-zA-Z\d]+(?:[\w-]*[a-zA-Z\d]+)*$/; const { - mutateAsync, + mutate, + data, + isLoading, + isError, + error, } = useCreateLibraryV2(); const { @@ -39,12 +48,16 @@ const CreateLibrary = () => { isLoading: isOrganizationListLoading, } = useOrganizationListData(); + if (data) { + navigate(`/library/${data.id}`); + } + return ( <>
} + title={intl.formatMessage(messages.createLibrary)} /> { ), }) } - onSubmit={async (values: CreateContentLibraryArgs) => { - setApiError(undefined); - try { - const data = await mutateAsync(values); - navigate(`/library/${data.id}`); - } catch (error: any) { - setApiError(( - <> - {error.message} -
- {error.response?.data && JSON.stringify(error.response.data)} - - )); - } - }} + onSubmit={(values) => mutate(values)} > {(formikProps) => ( @@ -123,7 +122,7 @@ const CreateLibrary = () => { type="submit" variant="primary" className="action btn-primary" - state={formikProps.isSubmitting ? 'disabled' : 'enabled'} + state={isLoading ? 'disabled' : 'enabled'} disabledStates={['disabled']} labels={{ enabled: intl.formatMessage(messages.createLibraryButton), @@ -133,11 +132,7 @@ const CreateLibrary = () => { )}
- {apiError && ( - - {apiError} - - )} + {isError && ()}
From 117b4f10e7db4d1f04a30987ee99eace1fcbeb5c Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Wed, 10 Jul 2024 05:07:29 -0700 Subject: [PATCH 86/88] chore: remove core-js and regenerator-runtime (#1032) --- package-lock.json | 8 +------- package.json | 2 -- src/index.jsx | 3 --- src/setupTest.js | 5 ----- 4 files changed, 1 insertion(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8c782430e9..8380429e4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,7 +41,6 @@ "@reduxjs/toolkit": "1.9.7", "@tanstack/react-query": "4.36.1", "classnames": "2.5.1", - "core-js": "3.37.1", "email-validator": "2.0.4", "file-saver": "^2.0.5", "formik": "2.4.6", @@ -64,7 +63,6 @@ "react-textarea-autosize": "^8.5.3", "react-transition-group": "4.4.5", "redux": "4.0.5", - "regenerator-runtime": "0.13.7", "start": "^5.1.0", "universal-cookie": "^4.0.4", "uuid": "^3.4.0", @@ -8518,6 +8516,7 @@ "version": "3.37.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", + "dev": true, "hasInstallScript": true, "funding": { "type": "opencollective", @@ -20109,11 +20108,6 @@ "node": ">=4" } }, - "node_modules/regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - }, "node_modules/regenerator-transform": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", diff --git a/package.json b/package.json index 288ced20b9..89ce687a3e 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,6 @@ "@reduxjs/toolkit": "1.9.7", "@tanstack/react-query": "4.36.1", "classnames": "2.5.1", - "core-js": "3.37.1", "email-validator": "2.0.4", "file-saver": "^2.0.5", "formik": "2.4.6", @@ -92,7 +91,6 @@ "react-textarea-autosize": "^8.5.3", "react-transition-group": "4.4.5", "redux": "4.0.5", - "regenerator-runtime": "0.13.7", "start": "^5.1.0", "universal-cookie": "^4.0.4", "uuid": "^3.4.0", diff --git a/src/index.jsx b/src/index.jsx index f881441df9..84aa71ffea 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -1,6 +1,3 @@ -import 'core-js/stable'; -import 'regenerator-runtime/runtime'; - import { APP_INIT_ERROR, APP_READY, subscribe, initialize, mergeConfig, getConfig, getPath, } from '@edx/frontend-platform'; diff --git a/src/setupTest.js b/src/setupTest.js index f0f7f6a435..ae5c7aca23 100755 --- a/src/setupTest.js +++ b/src/setupTest.js @@ -1,11 +1,6 @@ -import 'core-js/stable'; -import 'regenerator-runtime/runtime'; import '@testing-library/jest-dom'; import '@testing-library/jest-dom/extend-expect'; -/* eslint-disable import/no-extraneous-dependencies */ -import 'babel-polyfill'; - import { mergeConfig } from '@edx/frontend-platform'; /* need to mock window for tinymce on import, as it is JSDOM incompatible */ From f60ddb579e86f40202be45e1d869707da0cbeb33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Wed, 10 Jul 2024 14:20:00 +0200 Subject: [PATCH 87/88] feat: library home page ("bare bones") (#1076) --- src/CourseAuthoringPage.jsx | 33 +-- src/header/{Header.jsx => Header.tsx} | 69 +++-- src/index.jsx | 5 +- src/library-authoring/CreateLibrary.tsx | 27 ++ src/library-authoring/EmptyStates.tsx | 23 ++ .../LibraryAuthoringPage.test.tsx | 236 ++++++++++++++++++ .../LibraryAuthoringPage.tsx | 124 +++++++++ src/library-authoring/LibraryCollections.tsx | 14 ++ src/library-authoring/LibraryComponents.tsx | 32 +++ src/library-authoring/LibraryHome.tsx | 55 ++++ src/library-authoring/data/api.ts | 38 +++ src/library-authoring/data/apiHook.ts | 48 ++++ src/library-authoring/index.ts | 2 + src/library-authoring/messages.ts | 95 +++++++ src/search-modal/SearchModal.tsx | 3 +- src/search-modal/SearchResult.tsx | 27 +- src/search-modal/SearchUI.test.tsx | 18 +- src/search-modal/SearchUI.tsx | 2 +- src/search-modal/data/apiHooks.ts | 8 +- src/search-modal/index.ts | 2 + src/studio-home/StudioHome.jsx | 41 +-- src/studio-home/StudioHome.test.jsx | 39 ++- .../tabs-section/LibraryV2Placeholder.jsx | 36 --- 23 files changed, 830 insertions(+), 147 deletions(-) rename src/header/{Header.jsx => Header.tsx} (57%) create mode 100644 src/library-authoring/CreateLibrary.tsx create mode 100644 src/library-authoring/EmptyStates.tsx create mode 100644 src/library-authoring/LibraryAuthoringPage.test.tsx create mode 100644 src/library-authoring/LibraryAuthoringPage.tsx create mode 100644 src/library-authoring/LibraryCollections.tsx create mode 100644 src/library-authoring/LibraryComponents.tsx create mode 100644 src/library-authoring/LibraryHome.tsx create mode 100644 src/library-authoring/data/api.ts create mode 100644 src/library-authoring/data/apiHook.ts create mode 100644 src/library-authoring/index.ts create mode 100644 src/library-authoring/messages.ts create mode 100644 src/search-modal/index.ts delete mode 100644 src/studio-home/tabs-section/LibraryV2Placeholder.jsx diff --git a/src/CourseAuthoringPage.jsx b/src/CourseAuthoringPage.jsx index c4281a8c13..5d3d5e37b3 100644 --- a/src/CourseAuthoringPage.jsx +++ b/src/CourseAuthoringPage.jsx @@ -15,29 +15,6 @@ import { getCourseAppsApiStatus } from './pages-and-resources/data/selectors'; import { RequestStatus } from './data/constants'; import Loading from './generic/Loading'; -const AppHeader = ({ - courseNumber, courseOrg, courseTitle, courseId, -}) => ( -
-); - -AppHeader.propTypes = { - courseId: PropTypes.string.isRequired, - courseNumber: PropTypes.string, - courseOrg: PropTypes.string, - courseTitle: PropTypes.string.isRequired, -}; - -AppHeader.defaultProps = { - courseNumber: null, - courseOrg: null, -}; - const CourseAuthoringPage = ({ courseId, children }) => { const dispatch = useDispatch(); @@ -74,11 +51,11 @@ const CourseAuthoringPage = ({ courseId, children }) => { This functionality will be removed in TNL-9591 */} {inProgress ? !isEditor && : (!isEditor && ( - ) )} diff --git a/src/header/Header.jsx b/src/header/Header.tsx similarity index 57% rename from src/header/Header.jsx rename to src/header/Header.tsx index 7cc1adcb08..42dc5f0469 100644 --- a/src/header/Header.jsx +++ b/src/header/Header.tsx @@ -1,62 +1,75 @@ -// @ts-check +/* eslint-disable react/require-default-props */ import React from 'react'; -import PropTypes from 'prop-types'; import { getConfig } from '@edx/frontend-platform'; import { useIntl } from '@edx/frontend-platform/i18n'; import { StudioHeader } from '@edx/frontend-component-header'; import { useToggle } from '@openedx/paragon'; +import { generatePath, useHref } from 'react-router-dom'; -import SearchModal from '../search-modal/SearchModal'; +import { SearchModal } from '../search-modal'; import { getContentMenuItems, getSettingMenuItems, getToolsMenuItems } from './utils'; import messages from './messages'; +interface HeaderProps { + contextId?: string, + number?: string, + org?: string, + title?: string, + isHiddenMainMenu?: boolean, + isLibrary?: boolean, +} + const Header = ({ - courseId, - courseOrg, - courseNumber, - courseTitle, - isHiddenMainMenu, -}) => { + contextId = '', + org = '', + number = '', + title = '', + isHiddenMainMenu = false, + isLibrary = false, +}: HeaderProps) => { const intl = useIntl(); + const libraryHref = useHref('/library/:libraryId'); const [isShowSearchModalOpen, openSearchModal, closeSearchModal] = useToggle(false); const studioBaseUrl = getConfig().STUDIO_BASE_URL; const meiliSearchEnabled = [true, 'true'].includes(getConfig().MEILISEARCH_ENABLED); - const mainMenuDropdowns = [ + const mainMenuDropdowns = !isLibrary ? [ { id: `${intl.formatMessage(messages['header.links.content'])}-dropdown-menu`, buttonTitle: intl.formatMessage(messages['header.links.content']), - items: getContentMenuItems({ studioBaseUrl, courseId, intl }), + items: getContentMenuItems({ studioBaseUrl, courseId: contextId, intl }), }, { id: `${intl.formatMessage(messages['header.links.settings'])}-dropdown-menu`, buttonTitle: intl.formatMessage(messages['header.links.settings']), - items: getSettingMenuItems({ studioBaseUrl, courseId, intl }), + items: getSettingMenuItems({ studioBaseUrl, courseId: contextId, intl }), }, { id: `${intl.formatMessage(messages['header.links.tools'])}-dropdown-menu`, buttonTitle: intl.formatMessage(messages['header.links.tools']), - items: getToolsMenuItems({ studioBaseUrl, courseId, intl }), + items: getToolsMenuItems({ studioBaseUrl, courseId: contextId, intl }), }, - ]; - const outlineLink = `${studioBaseUrl}/course/${courseId}`; + ] : []; + const outlineLink = !isLibrary + ? `${studioBaseUrl}/course/${contextId}` + : generatePath(libraryHref, { libraryId: contextId }); return ( <> { meiliSearchEnabled && ( )} @@ -64,20 +77,4 @@ const Header = ({ ); }; -Header.propTypes = { - courseId: PropTypes.string, - courseNumber: PropTypes.string, - courseOrg: PropTypes.string, - courseTitle: PropTypes.string, - isHiddenMainMenu: PropTypes.bool, -}; - -Header.defaultProps = { - courseId: '', - courseNumber: '', - courseOrg: '', - courseTitle: '', - isHiddenMainMenu: false, -}; - export default Header; diff --git a/src/index.jsx b/src/index.jsx index 84aa71ffea..a0959ac192 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -16,11 +16,11 @@ import { initializeHotjar } from '@edx/frontend-enterprise-hotjar'; import { logError } from '@edx/frontend-platform/logging'; import messages from './i18n'; +import { CreateLibrary, LibraryAuthoringPage } from './library-authoring'; import initializeStore from './store'; import CourseAuthoringRoutes from './CourseAuthoringRoutes'; import Head from './head/Head'; import { StudioHome } from './studio-home'; -import LibraryV2Placeholder from './studio-home/tabs-section/LibraryV2Placeholder'; import CourseRerun from './course-rerun'; import { TaxonomyLayout, TaxonomyDetailPage, TaxonomyListPage } from './taxonomy'; import { ContentTagsDrawer } from './content-tags-drawer'; @@ -52,7 +52,8 @@ const App = () => { } /> } /> } /> - } /> + } /> + } /> } /> } /> {getConfig().ENABLE_ACCESSIBILITY_PAGE === 'true' && ( diff --git a/src/library-authoring/CreateLibrary.tsx b/src/library-authoring/CreateLibrary.tsx new file mode 100644 index 0000000000..227f14dbe5 --- /dev/null +++ b/src/library-authoring/CreateLibrary.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; +import { Container } from '@openedx/paragon'; + +import Header from '../header'; +import SubHeader from '../generic/sub-header/SubHeader'; + +import messages from './messages'; + +/* istanbul ignore next This is only a placeholder component */ +const CreateLibrary = () => ( + <> +
+ + } + /> +
+ +
+
+ +); + +export default CreateLibrary; diff --git a/src/library-authoring/EmptyStates.tsx b/src/library-authoring/EmptyStates.tsx new file mode 100644 index 0000000000..d7b718c71d --- /dev/null +++ b/src/library-authoring/EmptyStates.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; +import { + Button, Stack, +} from '@openedx/paragon'; +import { Add } from '@openedx/paragon/icons'; + +import messages from './messages'; + +export const NoComponents = () => ( + + + + +); + +export const NoSearchResults = () => ( +
+ +
+); diff --git a/src/library-authoring/LibraryAuthoringPage.test.tsx b/src/library-authoring/LibraryAuthoringPage.test.tsx new file mode 100644 index 0000000000..958e3e4486 --- /dev/null +++ b/src/library-authoring/LibraryAuthoringPage.test.tsx @@ -0,0 +1,236 @@ +import React from 'react'; +import MockAdapter from 'axios-mock-adapter'; +import { initializeMockApp } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; +import { AppProvider } from '@edx/frontend-platform/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { fireEvent, render, waitFor } from '@testing-library/react'; +import fetchMock from 'fetch-mock-jest'; + +import initializeStore from '../store'; +import { getContentSearchConfigUrl } from '../search-modal/data/api'; +import mockResult from '../search-modal/__mocks__/search-result.json'; +import mockEmptyResult from '../search-modal/__mocks__/empty-search-result.json'; +import LibraryAuthoringPage from './LibraryAuthoringPage'; +import { getContentLibraryApiUrl, type ContentLibrary } from './data/api'; + +let store; +const mockUseParams = jest.fn(); +let axiosMock; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), // use actual for all non-hook parts + useParams: () => mockUseParams(), +})); + +const searchEndpoint = 'http://mock.meilisearch.local/multi-search'; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, +}); + +const returnEmptyResult = (_url, req) => { + const requestData = JSON.parse(req.body?.toString() ?? ''); + const query = requestData?.queries[0]?.q ?? ''; + // We have to replace the query (search keywords) in the mock results with the actual query, + // because otherwise we may have an inconsistent state that causes more queries and unexpected results. + mockEmptyResult.results[0].query = query; + // And fake the required '_formatted' fields; it contains the highlighting ... around matched words + // eslint-disable-next-line no-underscore-dangle, no-param-reassign + mockEmptyResult.results[0]?.hits.forEach((hit) => { hit._formatted = { ...hit }; }); + return mockEmptyResult; +}; + +const libraryData: ContentLibrary = { + id: 'lib:org1:lib1', + type: 'complex', + org: 'org1', + slug: 'lib1', + title: 'lib1', + description: 'lib1', + numBlocks: 2, + version: 0, + lastPublished: null, + allowLti: false, + allowPublicLearning: false, + allowPublicRead: false, + hasUnpublishedChanges: true, + hasUnpublishedDeletes: false, + license: '', +}; + +const RootWrapper = () => ( + + + + + + + +); + +describe('', () => { + beforeEach(() => { + initializeMockApp({ + authenticatedUser: { + userId: 3, + username: 'abc123', + administrator: true, + roles: [], + }, + }); + store = initializeStore(); + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + mockUseParams.mockReturnValue({ libraryId: '1' }); + + // The API method to get the Meilisearch connection details uses Axios: + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + axiosMock.onGet(getContentSearchConfigUrl()).reply(200, { + url: 'http://mock.meilisearch.local', + index_name: 'studio', + api_key: 'test-key', + }); + + // The Meilisearch client-side API uses fetch, not Axios. + fetchMock.post(searchEndpoint, (_url, req) => { + const requestData = JSON.parse(req.body?.toString() ?? ''); + const query = requestData?.queries[0]?.q ?? ''; + // We have to replace the query (search keywords) in the mock results with the actual query, + // because otherwise Instantsearch will update the UI and change the query, + // leading to unexpected results in the test cases. + mockResult.results[0].query = query; + // And fake the required '_formatted' fields; it contains the highlighting ... around matched words + // eslint-disable-next-line no-underscore-dangle, no-param-reassign + mockResult.results[0]?.hits.forEach((hit) => { hit._formatted = { ...hit }; }); + return mockResult; + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + axiosMock.restore(); + fetchMock.mockReset(); + queryClient.clear(); + }); + + it('shows the spinner before the query is complete', () => { + mockUseParams.mockReturnValue({ libraryId: '1' }); + // @ts-ignore Use unresolved promise to keep the Loading visible + axiosMock.onGet(getContentLibraryApiUrl('1')).reply(() => new Promise()); + const { getByRole } = render(); + const spinner = getByRole('status'); + expect(spinner.textContent).toEqual('Loading...'); + }); + + it('shows an error component if no library returned', async () => { + mockUseParams.mockReturnValue({ libraryId: 'invalid' }); + axiosMock.onGet(getContentLibraryApiUrl('invalid')).reply(400); + + const { findByTestId } = render(); + + expect(await findByTestId('notFoundAlert')).toBeInTheDocument(); + }); + + it('shows an error component if no library param', async () => { + mockUseParams.mockReturnValue({ libraryId: '' }); + + const { findByTestId } = render(); + + expect(await findByTestId('notFoundAlert')).toBeInTheDocument(); + }); + + it('show library data', async () => { + mockUseParams.mockReturnValue({ libraryId: libraryData.id }); + axiosMock.onGet(getContentLibraryApiUrl(libraryData.id)).reply(200, libraryData); + + const { + getByRole, getByText, queryByText, + } = render(); + + // Ensure the search endpoint is called + await waitFor(() => { expect(fetchMock).toHaveFetchedTimes(1, searchEndpoint, 'post'); }); + + expect(getByText('Content library')).toBeInTheDocument(); + expect(getByText(libraryData.title)).toBeInTheDocument(); + + expect(queryByText('You have not added any content to this library yet.')).not.toBeInTheDocument(); + + expect(getByText('Recently Modified')).toBeInTheDocument(); + expect(getByText('Collections (0)')).toBeInTheDocument(); + expect(getByText('Components (6)')).toBeInTheDocument(); + expect(getByText('There are 6 components in this library')).toBeInTheDocument(); + + // Navigate to the components tab + fireEvent.click(getByRole('tab', { name: 'Components' })); + expect(queryByText('Recently Modified')).not.toBeInTheDocument(); + expect(queryByText('Collections (0)')).not.toBeInTheDocument(); + expect(queryByText('Components (6)')).not.toBeInTheDocument(); + expect(getByText('There are 6 components in this library')).toBeInTheDocument(); + + // Navigate to the collections tab + fireEvent.click(getByRole('tab', { name: 'Collections' })); + expect(queryByText('Recently Modified')).not.toBeInTheDocument(); + expect(queryByText('Collections (0)')).not.toBeInTheDocument(); + expect(queryByText('Components (6)')).not.toBeInTheDocument(); + expect(queryByText('There are 6 components in this library')).not.toBeInTheDocument(); + expect(getByText('Coming soon!')).toBeInTheDocument(); + + // Go back to Home tab + // This step is necessary to avoid the url change leak to other tests + fireEvent.click(getByRole('tab', { name: 'Home' })); + expect(getByText('Recently Modified')).toBeInTheDocument(); + expect(getByText('Collections (0)')).toBeInTheDocument(); + expect(getByText('Components (6)')).toBeInTheDocument(); + expect(getByText('There are 6 components in this library')).toBeInTheDocument(); + }); + + it('show library without components', async () => { + mockUseParams.mockReturnValue({ libraryId: libraryData.id }); + axiosMock.onGet(getContentLibraryApiUrl(libraryData.id)).reply(200, libraryData); + fetchMock.post(searchEndpoint, returnEmptyResult, { overwriteRoutes: true }); + + const { findByText, getByText } = render(); + + expect(await findByText('Content library')).toBeInTheDocument(); + expect(await findByText(libraryData.title)).toBeInTheDocument(); + + // Ensure the search endpoint is called + await waitFor(() => { expect(fetchMock).toHaveFetchedTimes(1, searchEndpoint, 'post'); }); + + expect(getByText('You have not added any content to this library yet.')).toBeInTheDocument(); + }); + + it('show library without search results', async () => { + mockUseParams.mockReturnValue({ libraryId: libraryData.id }); + axiosMock.onGet(getContentLibraryApiUrl(libraryData.id)).reply(200, libraryData); + fetchMock.post(searchEndpoint, returnEmptyResult, { overwriteRoutes: true }); + + const { findByText, getByRole, getByText } = render(); + + expect(await findByText('Content library')).toBeInTheDocument(); + expect(await findByText(libraryData.title)).toBeInTheDocument(); + + // Ensure the search endpoint is called + await waitFor(() => { expect(fetchMock).toHaveFetchedTimes(1, searchEndpoint, 'post'); }); + + fireEvent.change(getByRole('searchbox'), { target: { value: 'noresults' } }); + + // Ensure the search endpoint is called again + await waitFor(() => { expect(fetchMock).toHaveFetchedTimes(2, searchEndpoint, 'post'); }); + + expect(getByText('No matching components found in this library.')).toBeInTheDocument(); + + // Navigate to the components tab + fireEvent.click(getByRole('tab', { name: 'Components' })); + expect(getByText('No matching components found in this library.')).toBeInTheDocument(); + + // Go back to Home tab + // This step is necessary to avoid the url change leak to other tests + fireEvent.click(getByRole('tab', { name: 'Home' })); + }); +}); diff --git a/src/library-authoring/LibraryAuthoringPage.tsx b/src/library-authoring/LibraryAuthoringPage.tsx new file mode 100644 index 0000000000..7f16432778 --- /dev/null +++ b/src/library-authoring/LibraryAuthoringPage.tsx @@ -0,0 +1,124 @@ +import React, { useState } from 'react'; +import { StudioFooter } from '@edx/frontend-component-footer'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { + Container, Icon, IconButton, SearchField, Tab, Tabs, +} from '@openedx/paragon'; +import { InfoOutline } from '@openedx/paragon/icons'; +import { + Routes, Route, useLocation, useNavigate, useParams, +} from 'react-router-dom'; + +import Loading from '../generic/Loading'; +import SubHeader from '../generic/sub-header/SubHeader'; +import Header from '../header'; +import NotFoundAlert from '../generic/NotFoundAlert'; +import LibraryComponents from './LibraryComponents'; +import LibraryCollections from './LibraryCollections'; +import LibraryHome from './LibraryHome'; +import { useContentLibrary } from './data/apiHook'; +import messages from './messages'; + +enum TabList { + home = '', + components = 'components', + collections = 'collections', +} + +const SubHeaderTitle = ({ title }: { title: string }) => { + const intl = useIntl(); + return ( + <> + {title} + + + ); +}; + +const LibraryAuthoringPage = () => { + const intl = useIntl(); + const location = useLocation(); + const navigate = useNavigate(); + const [searchKeywords, setSearchKeywords] = useState(''); + + const { libraryId } = useParams(); + + const { data: libraryData, isLoading } = useContentLibrary(libraryId); + + const currentPath = location.pathname.split('/').pop(); + const activeKey = (currentPath && currentPath in TabList) ? TabList[currentPath] : TabList.home; + + if (isLoading) { + return ; + } + + if (!libraryId || !libraryData) { + return ; + } + + const handleTabChange = (key: string) => { + // setTabKey(key); + navigate(key); + }; + + return ( + <> +
+ + } + subtitle={intl.formatMessage(messages.headingSubtitle)} + /> + setSearchKeywords(value)} + onSubmit={() => {}} + className="w-50" + /> + + + + + + + } + /> + } + /> + } + /> + } + /> + + + + + ); +}; + +export default LibraryAuthoringPage; diff --git a/src/library-authoring/LibraryCollections.tsx b/src/library-authoring/LibraryCollections.tsx new file mode 100644 index 0000000000..2f1eb8951f --- /dev/null +++ b/src/library-authoring/LibraryCollections.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; + +import messages from './messages'; + +const LibraryCollections = () => ( +
+ +
+); + +export default LibraryCollections; diff --git a/src/library-authoring/LibraryComponents.tsx b/src/library-authoring/LibraryComponents.tsx new file mode 100644 index 0000000000..fee8cb3502 --- /dev/null +++ b/src/library-authoring/LibraryComponents.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; + +import { NoComponents, NoSearchResults } from './EmptyStates'; +import { useLibraryComponentCount } from './data/apiHook'; +import messages from './messages'; + +type LibraryComponentsProps = { + libraryId: string; + filter: { + searchKeywords: string; + }; +}; + +const LibraryComponents = ({ libraryId, filter: { searchKeywords } }: LibraryComponentsProps) => { + const { componentCount } = useLibraryComponentCount(libraryId, searchKeywords); + + if (componentCount === 0) { + return searchKeywords === '' ? : ; + } + + return ( +
+ +
+ ); +}; + +export default LibraryComponents; diff --git a/src/library-authoring/LibraryHome.tsx b/src/library-authoring/LibraryHome.tsx new file mode 100644 index 0000000000..273d36fca4 --- /dev/null +++ b/src/library-authoring/LibraryHome.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { + Card, Stack, +} from '@openedx/paragon'; + +import { NoComponents, NoSearchResults } from './EmptyStates'; +import LibraryCollections from './LibraryCollections'; +import LibraryComponents from './LibraryComponents'; +import { useLibraryComponentCount } from './data/apiHook'; +import messages from './messages'; + +const Section = ({ title, children } : { title: string, children: React.ReactNode }) => ( + + + + {children} + + +); + +type LibraryHomeProps = { + libraryId: string, + filter: { + searchKeywords: string, + }, +}; + +const LibraryHome = ({ libraryId, filter } : LibraryHomeProps) => { + const intl = useIntl(); + const { searchKeywords } = filter; + const { componentCount, collectionCount } = useLibraryComponentCount(libraryId, searchKeywords); + + if (componentCount === 0) { + return searchKeywords === '' ? : ; + } + + return ( + +
+ { intl.formatMessage(messages.recentComponentsTempPlaceholder) } +
+
+ +
+
+ +
+
+ ); +}; + +export default LibraryHome; diff --git a/src/library-authoring/data/api.ts b/src/library-authoring/data/api.ts new file mode 100644 index 0000000000..95126d8269 --- /dev/null +++ b/src/library-authoring/data/api.ts @@ -0,0 +1,38 @@ +import { camelCaseObject, getConfig } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; + +const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL; +/** + * Get the URL for the content library API. + */ +export const getContentLibraryApiUrl = (libraryId: string) => `${getApiBaseUrl()}/api/libraries/v2/${libraryId}/`; + +export interface ContentLibrary { + id: string; + type: string; + org: string; + slug: string; + title: string; + description: string; + numBlocks: number; + version: number; + lastPublished: Date | null; + allowLti: boolean; + allowPublicLearning: boolean; + allowPublicRead: boolean; + hasUnpublishedChanges: boolean; + hasUnpublishedDeletes: boolean; + license: string; +} + +/** + * Fetch a content library by its ID. + */ +export async function getContentLibrary(libraryId?: string): Promise { + if (!libraryId) { + throw new Error('libraryId is required'); + } + + const { data } = await getAuthenticatedHttpClient().get(getContentLibraryApiUrl(libraryId)); + return camelCaseObject(data); +} diff --git a/src/library-authoring/data/apiHook.ts b/src/library-authoring/data/apiHook.ts new file mode 100644 index 0000000000..56a6791d26 --- /dev/null +++ b/src/library-authoring/data/apiHook.ts @@ -0,0 +1,48 @@ +import React from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { MeiliSearch } from 'meilisearch'; + +import { useContentSearchConnection, useContentSearchResults } from '../../search-modal'; +import { getContentLibrary } from './api'; + +/** + * Hook to fetch a content library by its ID. + */ +export const useContentLibrary = (libraryId?: string) => ( + useQuery({ + queryKey: ['contentLibrary', libraryId], + queryFn: () => getContentLibrary(libraryId), + }) +); + +/** + * Hook to fetch the count of components and collections in a library. + */ +export const useLibraryComponentCount = (libraryId: string, searchKeywords: string) => { + // Meilisearch code to get Collection and Component counts + const { data: connectionDetails } = useContentSearchConnection(); + + const indexName = connectionDetails?.indexName; + const client = React.useMemo(() => { + if (connectionDetails?.apiKey === undefined || connectionDetails?.url === undefined) { + return undefined; + } + return new MeiliSearch({ host: connectionDetails.url, apiKey: connectionDetails.apiKey }); + }, [connectionDetails?.apiKey, connectionDetails?.url]); + + const libFilter = `context_key = "${libraryId}"`; + + const { totalHits: componentCount } = useContentSearchResults({ + client, + indexName, + searchKeywords, + extraFilter: [libFilter], // ToDo: Add filter for components when collection is implemented + }); + + const collectionCount = 0; // ToDo: Implement collections count + + return { + componentCount, + collectionCount, + }; +}; diff --git a/src/library-authoring/index.ts b/src/library-authoring/index.ts new file mode 100644 index 0000000000..40da2db4af --- /dev/null +++ b/src/library-authoring/index.ts @@ -0,0 +1,2 @@ +export { default as LibraryAuthoringPage } from './LibraryAuthoringPage'; +export { default as CreateLibrary } from './CreateLibrary'; diff --git a/src/library-authoring/messages.ts b/src/library-authoring/messages.ts new file mode 100644 index 0000000000..c63d66b3e4 --- /dev/null +++ b/src/library-authoring/messages.ts @@ -0,0 +1,95 @@ +import { defineMessages as _defineMessages } from '@edx/frontend-platform/i18n'; +import type { defineMessages as defineMessagesType } from 'react-intl'; + +// frontend-platform currently doesn't provide types... do it ourselves. +const defineMessages = _defineMessages as typeof defineMessagesType; + +const messages = defineMessages({ + headingSubtitle: { + id: 'course-authoring.library-authoring.heading-subtitle', + defaultMessage: 'Content library', + description: 'The page heading for the library page.', + }, + headingInfoAlt: { + id: 'course-authoring.library-authoring.heading-info-alt', + defaultMessage: 'Info', + description: 'Alt text for the info icon next to the page heading.', + }, + searchPlaceholder: { + id: 'course-authoring.library-authoring.search', + defaultMessage: 'Search...', + description: 'Placeholder for search field', + }, + noSearchResults: { + id: 'course-authoring.library-authoring.no-search-results', + defaultMessage: 'No matching components found in this library.', + description: 'Message displayed when no search results are found', + }, + noComponents: { + id: 'course-authoring.library-authoring.no-components', + defaultMessage: 'You have not added any content to this library yet.', + description: 'Message displayed when the library is empty', + }, + addComponent: { + id: 'course-authoring.library-authoring.add-component', + defaultMessage: 'Add component', + description: 'Button text to add a new component', + }, + homeTab: { + id: 'course-authoring.library-authoring.home-tab', + defaultMessage: 'Home', + description: 'Tab label for the home tab', + }, + componentsTab: { + id: 'course-authoring.library-authoring.components-tab', + defaultMessage: 'Components', + description: 'Tab label for the components tab', + }, + collectionsTab: { + id: 'course-authoring.library-authoring.collections-tab', + defaultMessage: 'Collections', + description: 'Tab label for the collections tab', + }, + componentsTempPlaceholder: { + id: 'course-authoring.library-authoring.components-temp-placeholder', + defaultMessage: 'There are {componentCount} components in this library', + description: 'Temp placeholder for the component container. This will be replaced with the actual component list.', + }, + collectionsTempPlaceholder: { + id: 'course-authoring.library-authoring.collections-temp-placeholder', + defaultMessage: 'Coming soon!', + description: 'Temp placeholder for the collections container. This will be replaced with the actual collection list.', + }, + recentComponentsTempPlaceholder: { + id: 'course-authoring.library-authoring.recent-components-temp-placeholder', + defaultMessage: 'Recently modified components and collections will be displayed here.', + description: 'Temp placeholder for the recent components container. This will be replaced with the actual list.', + }, + createLibrary: { + id: 'course-authoring.library-authoring.create-library', + defaultMessage: 'Create library', + description: 'Header for the create library form', + }, + createLibraryTempPlaceholder: { + id: 'course-authoring.library-authoring.create-library-temp-placeholder', + defaultMessage: 'This is a placeholder for the create library form. This will be replaced with the actual form.', + description: 'Temp placeholder for the create library container. This will be replaced with the new library form.', + }, + recentlyModifiedTitle: { + id: 'course-authoring.library-authoring.recently-modified-title', + defaultMessage: 'Recently Modified', + description: 'Title for the recently modified components and collections container', + }, + collectionsTitle: { + id: 'course-authoring.library-authoring.collections-title', + defaultMessage: 'Collections ({collectionCount})', + description: 'Title for the collections container', + }, + componentsTitle: { + id: 'course-authoring.library-authoring.components-title', + defaultMessage: 'Components ({componentCount})', + description: 'Title for the components container', + }, +}); + +export default messages; diff --git a/src/search-modal/SearchModal.tsx b/src/search-modal/SearchModal.tsx index 9cb24dabf1..ca143df51f 100644 --- a/src/search-modal/SearchModal.tsx +++ b/src/search-modal/SearchModal.tsx @@ -5,7 +5,8 @@ import { ModalDialog } from '@openedx/paragon'; import messages from './messages'; import SearchUI from './SearchUI'; -const SearchModal: React.FC<{ courseId: string, isOpen: boolean, onClose: () => void }> = ({ courseId, ...props }) => { +// eslint-disable-next-line react/require-default-props +const SearchModal: React.FC<{ courseId?: string, isOpen: boolean, onClose: () => void }> = ({ courseId, ...props }) => { const intl = useIntl(); const title = intl.formatMessage(messages.title); diff --git a/src/search-modal/SearchResult.tsx b/src/search-modal/SearchResult.tsx index 57584cefe5..1fe86751fe 100644 --- a/src/search-modal/SearchResult.tsx +++ b/src/search-modal/SearchResult.tsx @@ -35,9 +35,9 @@ function getItemIcon(blockType: string): React.ComponentType { /** * Returns the URL Suffix for library/library component hit */ -function getLibraryHitUrl(hit: ContentHit, libraryAuthoringMfeUrl: string): string { +function getLibraryComponentUrlSuffix(hit: ContentHit) { const { contextKey } = hit; - return constructLibraryAuthoringURL(libraryAuthoringMfeUrl, `library/${contextKey}`); + return `library/${contextKey}`; } /** @@ -117,10 +117,6 @@ const SearchResult: React.FC<{ hit: ContentHit }> = ({ hit }) => { const { closeSearchModal } = useSearchContext(); const { libraryAuthoringMfeUrl, redirectToLibraryAuthoringMfe } = useSelector(getStudioHomeData); - const { usageKey } = hit; - - const noRedirectUrl = usageKey.startsWith('lb:') && !redirectToLibraryAuthoringMfe; - /** * Returns the URL for the context of the hit */ @@ -136,13 +132,19 @@ const SearchResult: React.FC<{ hit: ContentHit }> = ({ hit }) => { return `/${urlSuffix}`; } - if (usageKey.startsWith('lb:')) { - if (redirectToLibraryAuthoringMfe) { - return getLibraryHitUrl(hit, libraryAuthoringMfeUrl); + if (contextKey.startsWith('lib:')) { + const urlSuffix = getLibraryComponentUrlSuffix(hit); + if (redirectToLibraryAuthoringMfe && libraryAuthoringMfeUrl) { + return constructLibraryAuthoringURL(libraryAuthoringMfeUrl, urlSuffix); } + + if (newWindow) { + return `${getPath(getConfig().PUBLIC_PATH)}${urlSuffix}`; + } + return `/${urlSuffix}`; } - // No context URL for this hit (e.g. a library without library authoring mfe) + // istanbul ignore next - This case should never be reached return undefined; }, [libraryAuthoringMfeUrl, redirectToLibraryAuthoringMfe, hit]); @@ -189,12 +191,12 @@ const SearchResult: React.FC<{ hit: ContentHit }> = ({ hit }) => { return ( @@ -213,7 +215,6 @@ const SearchResult: React.FC<{ hit: ContentHit }> = ({ hit }) => { diff --git a/src/search-modal/SearchUI.test.tsx b/src/search-modal/SearchUI.test.tsx index 92f0f244d3..7a62193e6d 100644 --- a/src/search-modal/SearchUI.test.tsx +++ b/src/search-modal/SearchUI.test.tsx @@ -342,9 +342,10 @@ describe('', () => { window.location = location; }); - test('click lib component result doesnt navigates to the context withou libraryAuthoringMfe', async () => { + test('click lib component result navigates to course-authoring/library without libraryAuthoringMfe', async () => { const data = generateGetStudioHomeDataApiResponse(); data.redirectToLibraryAuthoringMfe = false; + data.libraryAuthoringMfeUrl = ''; axiosMock.onGet(getStudioHomeApiUrl()).reply(200, data); await executeThunk(fetchStudioHomeData(), store.dispatch); @@ -354,18 +355,21 @@ describe('', () => { const resultItem = await findByRole('button', { name: /Library Content/ }); // Clicking the "Open in new window" button should open the result in a new window: - const { open, location } = window; + const { open } = window; window.open = jest.fn(); fireEvent.click(within(resultItem).getByRole('button', { name: 'Open in new window' })); - expect(window.open).not.toHaveBeenCalled(); + + expect(window.open).toHaveBeenCalledWith( + '/library/lib:org1:libafter1', + '_blank', + ); window.open = open; - // @ts-ignore - window.location = { href: '' }; // Clicking in the result should navigate to the result's URL: fireEvent.click(resultItem); - expect(window.location.href === location.href); - window.location = location; + expect(mockNavigate).toHaveBeenCalledWith( + '/library/lib:org1:libafter1', + ); }); }); diff --git a/src/search-modal/SearchUI.tsx b/src/search-modal/SearchUI.tsx index 1ce23a8dbd..ce60d762b8 100644 --- a/src/search-modal/SearchUI.tsx +++ b/src/search-modal/SearchUI.tsx @@ -18,7 +18,7 @@ import Stats from './Stats'; import { SearchContextProvider } from './manager/SearchManager'; import messages from './messages'; -const SearchUI: React.FC<{ courseId: string, closeSearchModal?: () => void }> = (props) => { +const SearchUI: React.FC<{ courseId?: string, closeSearchModal?: () => void }> = (props) => { const hasCourseId = Boolean(props.courseId); const [searchThisCourseEnabled, setSearchThisCourse] = React.useState(hasCourseId); const switchToThisCourse = React.useCallback(() => setSearchThisCourse(true), []); diff --git a/src/search-modal/data/apiHooks.ts b/src/search-modal/data/apiHooks.ts index cfc4454d5f..fe77482285 100644 --- a/src/search-modal/data/apiHooks.ts +++ b/src/search-modal/data/apiHooks.ts @@ -35,8 +35,8 @@ export const useContentSearchResults = ({ indexName, extraFilter, searchKeywords, - blockTypesFilter, - tagsFilter, + blockTypesFilter = [], + tagsFilter = [], }: { /** The Meilisearch API client */ client?: MeiliSearch; @@ -47,9 +47,9 @@ export const useContentSearchResults = ({ /** The keywords that the user is searching for, if any */ searchKeywords: string; /** Only search for these block types (e.g. `["html", "problem"]`) */ - blockTypesFilter: string[]; + blockTypesFilter?: string[]; /** Required tags (all must match), e.g. `["Difficulty > Hard", "Subject > Math"]` */ - tagsFilter: string[]; + tagsFilter?: string[]; }) => { const query = useInfiniteQuery({ enabled: client !== undefined && indexName !== undefined, diff --git a/src/search-modal/index.ts b/src/search-modal/index.ts new file mode 100644 index 0000000000..570b22db47 --- /dev/null +++ b/src/search-modal/index.ts @@ -0,0 +1,2 @@ +export { default as SearchModal } from './SearchModal'; +export { useContentSearchConnection, useContentSearchResults } from './data/apiHooks'; diff --git a/src/studio-home/StudioHome.jsx b/src/studio-home/StudioHome.jsx index 4953c6f3ae..6c63e0b6d6 100644 --- a/src/studio-home/StudioHome.jsx +++ b/src/studio-home/StudioHome.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { Button, Container, @@ -10,7 +10,8 @@ import { import { Add as AddIcon, Error } from '@openedx/paragon/icons'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { StudioFooter } from '@edx/frontend-component-footer'; -import { getConfig, getPath } from '@edx/frontend-platform'; +import { getConfig } from '@edx/frontend-platform'; +import { useLocation, useNavigate } from 'react-router-dom'; import { constructLibraryAuthoringURL } from '../utils'; import Loading from '../generic/Loading'; @@ -19,7 +20,7 @@ import Header from '../header'; import SubHeader from '../generic/sub-header/SubHeader'; import HomeSidebar from './home-sidebar'; import TabsSection from './tabs-section'; -import { isMixedOrV2LibrariesMode } from './tabs-section/utils'; +import { isMixedOrV1LibrariesMode, isMixedOrV2LibrariesMode } from './tabs-section/utils'; import OrganizationSection from './organization-section'; import VerifyEmailLayout from './verify-email-layout'; import CreateNewCourseForm from './create-new-course-form'; @@ -28,6 +29,9 @@ import { useStudioHome } from './hooks'; import AlertMessage from '../generic/alert-message'; const StudioHome = ({ intl }) => { + const location = useLocation(); + const navigate = useNavigate(); + const isPaginationCoursesEnabled = getConfig().ENABLE_HOME_PAGE_COURSE_API_V2; const { isLoadingPage, @@ -47,6 +51,8 @@ const StudioHome = ({ intl }) => { const libMode = getConfig().LIBRARY_MODE; + const v1LibraryTab = isMixedOrV1LibrariesMode(libMode) && location?.pathname.split('/').pop() === 'libraries-v1'; + const { userIsActive, studioShortName, @@ -55,7 +61,7 @@ const StudioHome = ({ intl }) => { redirectToLibraryAuthoringMfe, } = studioHomeData; - function getHeaderButtons() { + const getHeaderButtons = useCallback(() => { const headerButtons = []; if (isFailedLoadingPage || !userIsActive) { @@ -82,15 +88,20 @@ const StudioHome = ({ intl }) => { ); } - let libraryHref = `${getConfig().STUDIO_BASE_URL}/home_library`; - if (isMixedOrV2LibrariesMode(libMode)) { - libraryHref = libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe - ? constructLibraryAuthoringURL(libraryAuthoringMfeUrl, 'create') - // Redirection to the placeholder is done in the MFE rather than - // through the backend i.e. redirection from cms, because this this will probably change, - // hence why we use the MFE's origin - : `${window.location.origin}${getPath(getConfig().PUBLIC_PATH)}library/create`; - } + const newLibraryClick = () => { + if (isMixedOrV2LibrariesMode(libMode) && !v1LibraryTab) { + if (libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe) { + // Library authoring MFE + window.open(constructLibraryAuthoringURL(libraryAuthoringMfeUrl, 'create')); + } else { + // Use course-authoring route + navigate('/library/create'); + } + } else { + // Studio home library for legacy libraries + window.open(`${getConfig().STUDIO_BASE_URL}/home_library`); + } + }; headerButtons.push(