diff --git a/README.rst b/README.rst index e62b777d10..5b55d2b923 100644 --- a/README.rst +++ b/README.rst @@ -253,15 +253,7 @@ Requirements * ``edx-platform`` Waffle flags: - * ``new_studio_mfe.use_tagging_taxonomy_list_page``: this feature flag must be enabled. - -Configuration -------------- - -In additional to the standard settings, the following local configuration items are required: - -* ``ENABLE_TAGGING_TAXONOMY_PAGES``: must be enabled in order to actually present the new Tagging/Taxonomy pages. - + * ``content_tagging.disable_tagging_feature``: this feature flag must NOT be checked. Developing ********** diff --git a/src/CourseAuthoringPage.test.jsx b/src/CourseAuthoringPage.test.jsx index c7eeeb9be8..59aba8024d 100644 --- a/src/CourseAuthoringPage.test.jsx +++ b/src/CourseAuthoringPage.test.jsx @@ -7,6 +7,7 @@ import MockAdapter from 'axios-mock-adapter'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { AppProvider } from '@edx/frontend-platform/react'; import { IntlProvider } from '@edx/frontend-platform/i18n'; +import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import initializeStore from './store'; import CourseAuthoringPage from './CourseAuthoringPage'; import PagesAndResources from './pages-and-resources/PagesAndResources'; @@ -38,6 +39,8 @@ beforeEach(() => { axiosMock = new MockAdapter(getAuthenticatedHttpClient()); }); +const queryClient = new QueryClient(); + describe('Editor Pages Load no header', () => { const mockStoreSuccess = async () => { const apiBaseUrl = getConfig().STUDIO_BASE_URL; @@ -53,9 +56,11 @@ describe('Editor Pages Load no header', () => { const wrapper = render( - - - + + + + + , @@ -68,9 +73,11 @@ describe('Editor Pages Load no header', () => { const wrapper = render( - - - + + + + + , @@ -103,7 +110,9 @@ describe('Course authoring page', () => { const wrapper = render( - + + + , @@ -120,9 +129,11 @@ describe('Course authoring page', () => { const wrapper = render( - -
- + + +
+ + , diff --git a/src/CourseAuthoringRoutes.test.jsx b/src/CourseAuthoringRoutes.test.jsx index 3a38fe7c24..953174a8ab 100644 --- a/src/CourseAuthoringRoutes.test.jsx +++ b/src/CourseAuthoringRoutes.test.jsx @@ -1,6 +1,7 @@ import React from 'react'; import { AppProvider } from '@edx/frontend-platform/react'; import { initializeMockApp } from '@edx/frontend-platform'; +import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import { render, screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import CourseAuthoringRoutes from './CourseAuthoringRoutes'; @@ -48,6 +49,8 @@ jest.mock('./custom-pages/CustomPages', () => (props) => { return customPagesMockText; }); +const queryClient = new QueryClient(); + describe('', () => { beforeEach(() => { initializeMockApp({ @@ -65,7 +68,9 @@ describe('', () => { render( - + + + , ); @@ -82,7 +87,9 @@ describe('', () => { render( - + + + , ); @@ -100,7 +107,9 @@ describe('', () => { render( - + + + , ); diff --git a/src/accessibility-page/AccessibilityPage.test.jsx b/src/accessibility-page/AccessibilityPage.test.jsx index f686daf4d5..d974a5989a 100644 --- a/src/accessibility-page/AccessibilityPage.test.jsx +++ b/src/accessibility-page/AccessibilityPage.test.jsx @@ -5,6 +5,7 @@ import { import { AppProvider } from '@edx/frontend-platform/react'; import { IntlProvider } from '@edx/frontend-platform/i18n'; import { initializeMockApp } from '@edx/frontend-platform'; +import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import initializeStore from '../store'; import AccessibilityPage from './index'; @@ -15,11 +16,15 @@ const initialState = { }; let store; +const queryClient = new QueryClient(); + const renderComponent = () => { render( - + + + , ); diff --git a/src/content-tags-drawer/__mocks__/contentDataMock.js b/src/content-tags-drawer/__mocks__/contentDataMock.js index 292efc38d0..b88b0bef88 100644 --- a/src/content-tags-drawer/__mocks__/contentDataMock.js +++ b/src/content-tags-drawer/__mocks__/contentDataMock.js @@ -53,7 +53,7 @@ module.exports = { taxonomyTagsWidgetUrl: 'http://localhost:2001/tagging/components/widget/', staffOnlyMessage: false, enableCopyPasteUnits: true, - useTaggingTaxonomyListPage: true, + isTaggingFeatureDisabled: false, hasPartitionGroupComponents: false, userPartitionInfo: { selectablePartitions: [], diff --git a/src/course-outline/card-header/CardHeader.jsx b/src/course-outline/card-header/CardHeader.jsx index 3634e42e70..00694ffb83 100644 --- a/src/course-outline/card-header/CardHeader.jsx +++ b/src/course-outline/card-header/CardHeader.jsx @@ -18,7 +18,7 @@ import { EditOutline as EditIcon, } from '@openedx/paragon/icons'; -import { useContentTagsCount } from '../../generic/data/apiHooks'; +import { useContentTagsCount, useTaggingFeaturesEnabled } from '../../generic/data/apiHooks'; import { ContentTagsDrawer } from '../../content-tags-drawer'; import TagCount from '../../generic/tag-count'; import { useEscapeClick } from '../../hooks'; @@ -71,6 +71,7 @@ const CardHeader = ({ || status === ITEM_BADGE_STATUS.publishedNotLive) && !hasChanges; const { data: contentTagCount } = useContentTagsCount(cardId); + const taxonomiesEnabled = useTaggingFeaturesEnabled(); useEffect(() => { const locatorId = searchParams.get('show'); @@ -143,7 +144,7 @@ const CardHeader = ({ {(isVertical || isSequential) && ( )} - { getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true' && contentTagCount > 0 && ( + { taxonomiesEnabled && contentTagCount > 0 && ( )} @@ -181,7 +182,7 @@ const CardHeader = ({ > {intl.formatMessage(messages.menuConfigure)} - {getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true' && ( + {taxonomiesEnabled && ( ({ ...jest.requireActual('../../generic/data/api'), getTagsCount: () => mockGetTagsCount(), })); +jest.mock('../../generic/data/apiHooks', () => ({ + ...jest.requireActual('../../generic/data/apiHooks'), + useTaggingFeaturesEnabled: () => mockTaggingFeaturesEnabled(), +})); const cardHeaderProps = { title: 'Some title', @@ -85,6 +90,13 @@ const renderComponent = (props, entry = '/') => { }; describe('', () => { + beforeEach(() => { + jest.clearAllMocks(); + queryClient.clear(); + mockGetTagsCount.mockReturnValue({}); + mockTaggingFeaturesEnabled.mockReturnValue(true); + }); + it('render CardHeader component correctly', async () => { const { findByText, findByTestId, queryByTestId } = renderComponent(); @@ -181,11 +193,16 @@ describe('', () => { expect(onClickPublishMock).toHaveBeenCalled(); }); - it('only shows Manage tags menu if the waffle flag is enabled', async () => { - setConfig({ - ...getConfig(), - ENABLE_TAGGING_TAXONOMY_PAGES: 'false', - }); + it('shows the "Manage tags" menu item by default', async () => { + renderComponent(); + const menuButton = await screen.findByTestId('subsection-card-header__menu-button'); + fireEvent.click(menuButton); + + expect(screen.queryByText(messages.menuManageTags.defaultMessage)).toBeInTheDocument(); + }); + + it('hides the "Manage tags" menu item if the tagging functionality is disabled', async () => { + mockTaggingFeaturesEnabled.mockReturnValue(false); renderComponent(); const menuButton = await screen.findByTestId('subsection-card-header__menu-button'); fireEvent.click(menuButton); @@ -194,10 +211,6 @@ describe('', () => { }); it('shows ContentTagsDrawer when the menu is clicked', async () => { - setConfig({ - ...getConfig(), - ENABLE_TAGGING_TAXONOMY_PAGES: 'true', - }); renderComponent(); const menuButton = await screen.findByTestId('subsection-card-header__menu-button'); fireEvent.click(menuButton); @@ -310,31 +323,20 @@ describe('', () => { expect(queryByText(messages.discussionEnabledBadgeText.defaultMessage)).toBeInTheDocument(); }); - it('should render tag count if is not zero and the waffle flag is enabled', async () => { - setConfig({ - ...getConfig(), - ENABLE_TAGGING_TAXONOMY_PAGES: 'true', - }); + it('should render tag count if is not zero', async () => { mockGetTagsCount.mockResolvedValue({ 12345: 17 }); renderComponent(); expect(await screen.findByText('17')).toBeInTheDocument(); }); - it('shouldn render tag count if the waffle flag is disabled', async () => { - setConfig({ - ...getConfig(), - ENABLE_TAGGING_TAXONOMY_PAGES: 'false', - }); + it('should not render tag count if the waffle flag is disabled', async () => { + mockTaggingFeaturesEnabled.mockReturnValue(false); mockGetTagsCount.mockResolvedValue({ 12345: 17 }); renderComponent(); expect(screen.queryByText('17')).not.toBeInTheDocument(); }); it('should not render tag count if is zero', () => { - setConfig({ - ...getConfig(), - ENABLE_TAGGING_TAXONOMY_PAGES: 'true', - }); mockGetTagsCount.mockResolvedValue({ 12345: 0 }); renderComponent(); expect(screen.queryByText('0')).not.toBeInTheDocument(); diff --git a/src/course-outline/status-bar/StatusBar.jsx b/src/course-outline/status-bar/StatusBar.jsx index 97c4b93538..b53c2b4c6e 100644 --- a/src/course-outline/status-bar/StatusBar.jsx +++ b/src/course-outline/status-bar/StatusBar.jsx @@ -2,7 +2,6 @@ import React, { useContext } from 'react'; import moment from 'moment/moment'; import PropTypes from 'prop-types'; import { FormattedDate, useIntl } from '@edx/frontend-platform/i18n'; -import { getConfig } from '@edx/frontend-platform/config'; import { Button, Hyperlink, Form, Sheet, Stack, useToggle, } from '@openedx/paragon'; @@ -10,9 +9,9 @@ import { AppContext } from '@edx/frontend-platform/react'; import { ContentTagsDrawer } from '../../content-tags-drawer'; import TagCount from '../../generic/tag-count'; +import { useContentTagsCount, useTaggingFeaturesEnabled } from '../../generic/data/apiHooks'; import { useHelpUrls } from '../../help-urls/hooks'; import { VIDEO_SHARING_OPTIONS } from '../constants'; -import { useContentTagsCount } from '../../generic/data/apiHooks'; import messages from './messages'; import { getVideoSharingOptionText } from '../utils'; @@ -71,6 +70,7 @@ const StatusBar = ({ } = useHelpUrls(['contentHighlights', 'socialSharing']); const { data: courseTagCount } = useContentTagsCount(courseId); + const taxonomiesEnabled = useTaggingFeaturesEnabled(); const [isManageTagsDrawerOpen, openManageTagsDrawer, closeManageTagsDrawer] = useToggle(false); @@ -136,7 +136,7 @@ const StatusBar = ({
- {getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true' && ( + {taxonomiesEnabled && (
diff --git a/src/course-outline/status-bar/StatusBar.test.jsx b/src/course-outline/status-bar/StatusBar.test.jsx index c57613ae04..a7ba97cbb5 100644 --- a/src/course-outline/status-bar/StatusBar.test.jsx +++ b/src/course-outline/status-bar/StatusBar.test.jsx @@ -3,7 +3,6 @@ import { render, fireEvent } from '@testing-library/react'; import { IntlProvider } from '@edx/frontend-platform/i18n'; import { AppProvider } from '@edx/frontend-platform/react'; import { initializeMockApp } from '@edx/frontend-platform'; -import { getConfig, setConfig } from '@edx/frontend-platform/config'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import StatusBar from './StatusBar'; @@ -17,6 +16,7 @@ const courseId = 'course-v1:123'; const isLoading = false; const openEnableHighlightsModalMock = jest.fn(); const handleVideoSharingOptionChange = jest.fn(); +const mockTaggingFeaturesEnabled = jest.fn(); jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), @@ -27,7 +27,12 @@ jest.mock('react-router-dom', () => ({ jest.mock('../../generic/data/api', () => ({ ...jest.requireActual('../../generic/data/api'), - getTagsCount: jest.fn().mockResolvedValue({ 'course-v1:123': 17 }), + getTagsCount: () => ({ 'course-v1:123': 17 }), +})); + +jest.mock('../../generic/data/apiHooks', () => ({ + ...jest.requireActual('../../generic/data/apiHooks'), + useTaggingFeaturesEnabled: () => mockTaggingFeaturesEnabled(), })); jest.mock('../../help-urls/hooks', () => ({ @@ -82,6 +87,8 @@ describe('', () => { }, }); store = initializeStore(); + queryClient.clear(); + mockTaggingFeaturesEnabled.mockReturnValue(true); }); it('renders StatusBar component correctly', () => { @@ -145,20 +152,14 @@ describe('', () => { expect(queryByTestId('video-sharing-wrapper')).not.toBeInTheDocument(); }); - it('renders the tag count if the waffle flag is enabled', async () => { - setConfig({ - ...getConfig(), - ENABLE_TAGGING_TAXONOMY_PAGES: 'true', - }); + it('renders the tag count', async () => { const { findByText } = renderComponent(); expect(await findByText('17')).toBeInTheDocument(); }); - it('doesnt renders the tag count if the waffle flag is disabled', () => { - setConfig({ - ...getConfig(), - ENABLE_TAGGING_TAXONOMY_PAGES: 'false', - }); + + it('does not render the tag count if the waffle flag is disabled', () => { + mockTaggingFeaturesEnabled.mockReturnValue(false); const { queryByText } = renderComponent(); expect(queryByText('17')).not.toBeInTheDocument(); diff --git a/src/course-rerun/CourseRerun.test.jsx b/src/course-rerun/CourseRerun.test.jsx index 9c403b368b..85d9073044 100644 --- a/src/course-rerun/CourseRerun.test.jsx +++ b/src/course-rerun/CourseRerun.test.jsx @@ -3,6 +3,7 @@ import { useSelector } from 'react-redux'; import { MemoryRouter } from 'react-router-dom'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { initializeMockApp } from '@edx/frontend-platform'; +import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; import { AppProvider } from '@edx/frontend-platform/react'; import { @@ -33,12 +34,16 @@ jest.mock('react-router-dom', () => ({ }), })); +const queryClient = new QueryClient(); + const RootWrapper = () => ( - - - + + + + + ); diff --git a/src/course-unit/CourseUnit.jsx b/src/course-unit/CourseUnit.jsx index f82d80dd98..4eeca5ecd4 100644 --- a/src/course-unit/CourseUnit.jsx +++ b/src/course-unit/CourseUnit.jsx @@ -31,6 +31,7 @@ import PublishControls from './sidebar/PublishControls'; import LocationInfo from './sidebar/LocationInfo'; import TagsSidebarControls from '../content-tags-drawer/tags-sidebar-controls'; import { PasteNotificationAlert } from './clipboard'; +import { useTaggingFeaturesEnabled } from '../generic/data/apiHooks'; const CourseUnit = ({ courseId }) => { const { blockId } = useParams(); @@ -65,6 +66,8 @@ const CourseUnit = ({ courseId }) => { const initialXBlocksData = useMemo(() => courseVerticalChildren.children ?? [], [courseVerticalChildren.children]); const [unitXBlocks, setUnitXBlocks] = useState(initialXBlocksData); + const taxonomiesEnabled = useTaggingFeaturesEnabled(); + useEffect(() => { document.title = getPageHeadTitle('', unitTitle); }, [unitTitle]); @@ -200,9 +203,11 @@ const CourseUnit = ({ courseId }) => { - - - + {taxonomiesEnabled && ( + + + + )} diff --git a/src/generic/data/api.js b/src/generic/data/api.js index 83fd561ff3..77b23d0631 100644 --- a/src/generic/data/api.js +++ b/src/generic/data/api.js @@ -10,6 +10,7 @@ export const getCourseRerunUrl = (courseId) => new URL(`/api/contentstore/v1/cou export const getOrganizationsUrl = () => new URL('organizations', getApiBaseUrl()).href; export const getClipboardUrl = () => `${getApiBaseUrl()}/api/content-staging/v1/clipboard/`; export const getTagsCountApiUrl = (contentPattern) => new URL(`api/content_tagging/v1/object_tag_counts/${contentPattern}/?count_implicit`, getApiBaseUrl()).href; +export const getStudioHomeApiUrl = () => new URL('api/contentstore/v1/home', getApiBaseUrl()).href; /** * Get's organizations data. Returns list of organization names. @@ -83,3 +84,12 @@ export async function getTagsCount(contentPattern) { } return null; } + +/** + * Get's studio home data. + * @returns {Promise} + */ +export async function getStudioHomeData() { + const { data } = await getAuthenticatedHttpClient().get(getStudioHomeApiUrl()); + return camelCaseObject(data); +} diff --git a/src/generic/data/apiHooks.js b/src/generic/data/apiHooks.js index e15ebc12ad..e601d70b46 100644 --- a/src/generic/data/apiHooks.js +++ b/src/generic/data/apiHooks.js @@ -1,6 +1,6 @@ // @ts-check import { useQuery } from '@tanstack/react-query'; -import { getOrganizations, getTagsCount } from './api'; +import { getOrganizations, getStudioHomeData, getTagsCount } from './api'; /** * Builds the query to get a list of available organizations @@ -32,3 +32,30 @@ export const useContentTagsCount = (contentId) => { select: (data) => data[contentId] || 0, // Return the tags count of the specific contentId }); }; + +export const useStudioHomeData = () => useQuery({ + queryKey: ['studioHomeData'], + queryFn: getStudioHomeData, + // Currently this is only used for 'useTaggingFeaturesEnabled' so we don't need to refetch it so often. + cacheTime: 10 * 60_000, // Even if we're not actively using this, keep it in memory up to ten minutes + staleTime: 10 * 60_000, // If cache is up to ten minutes old, no need to re-fetch + refetchInterval: 10 * 60_000, + refetchOnWindowFocus: false, // This doesn't need to be refreshed when the user switches back to this tab. + refetchOnMount: false, +}); + +/** + * Are the tagging/taxonomy features enabled? + * + * TODO: This is temporary, for the Open edX Redwood release. Sometime post-Redwood pre-Sumac (and after being enabled + * for a while on edx.org), this should be removed and the tagging/taxonomy functionality always enabled. + * + * @returns {boolean} Whether they are enabled (default true) + */ +export const useTaggingFeaturesEnabled = () => { + const { data: studioHomeData } = useStudioHomeData(); + // Default is true unless it's explicitly disabled. This _may_ cause a "flash" of features that then disappear + // on some instances with tagging disabled, but we are treating "tagging disabled" as an unusual exception, and + // having tagging enabled as the default expected for 99% of users. + return studioHomeData?.taxonomiesEnabled ?? true; +}; diff --git a/src/header/Header.jsx b/src/header/Header.jsx index 7cc1adcb08..1c8d6620c1 100644 --- a/src/header/Header.jsx +++ b/src/header/Header.jsx @@ -6,6 +6,7 @@ import { useIntl } from '@edx/frontend-platform/i18n'; import { StudioHeader } from '@edx/frontend-component-header'; import { useToggle } from '@openedx/paragon'; +import { useTaggingFeaturesEnabled } from '../generic/data/apiHooks'; import SearchModal from '../search-modal/SearchModal'; import { getContentMenuItems, getSettingMenuItems, getToolsMenuItems } from './utils'; import messages from './messages'; @@ -20,6 +21,7 @@ const Header = ({ const intl = useIntl(); const [isShowSearchModalOpen, openSearchModal, closeSearchModal] = useToggle(false); + const taxonomiesEnabled = useTaggingFeaturesEnabled(); const studioBaseUrl = getConfig().STUDIO_BASE_URL; const meiliSearchEnabled = [true, 'true'].includes(getConfig().MEILISEARCH_ENABLED); @@ -37,7 +39,9 @@ const Header = ({ { 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, intl, taxonomiesEnabled, + }), }, ]; const outlineLink = `${studioBaseUrl}/course/${courseId}`; diff --git a/src/header/utils.js b/src/header/utils.js index c1de7e0923..6fc1104731 100644 --- a/src/header/utils.js +++ b/src/header/utils.js @@ -58,7 +58,9 @@ export const getSettingMenuItems = ({ studioBaseUrl, courseId, intl }) => ([ }, ]); -export const getToolsMenuItems = ({ studioBaseUrl, courseId, intl }) => ([ +export const getToolsMenuItems = ({ + studioBaseUrl, courseId, intl, taxonomiesEnabled, +}) => ([ { href: `${studioBaseUrl}/import/${courseId}`, title: intl.formatMessage(messages['header.links.import']), @@ -67,7 +69,7 @@ export const getToolsMenuItems = ({ studioBaseUrl, courseId, intl }) => ([ href: `${studioBaseUrl}/export/${courseId}`, title: intl.formatMessage(messages['header.links.exportCourse']), }, - ...(getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true' + ...(taxonomiesEnabled ? [{ href: `${studioBaseUrl}/api/content_tagging/v1/object_tags/${courseId}/export/`, title: intl.formatMessage(messages['header.links.exportTags']), diff --git a/src/header/utils.test.js b/src/header/utils.test.js index afcb5da24d..028bd0c89e 100644 --- a/src/header/utils.test.js +++ b/src/header/utils.test.js @@ -31,10 +31,7 @@ describe('header utils', () => { describe('getToolsMenuItems', () => { it('should include export tags option', () => { - setConfig({ - ...getConfig(), - ENABLE_TAGGING_TAXONOMY_PAGES: 'true', - }); + props.taxonomiesEnabled = true; const actualItemsTitle = getToolsMenuItems(props).map((item) => item.title); expect(actualItemsTitle).toEqual([ 'Import', @@ -44,10 +41,7 @@ describe('header utils', () => { ]); }); it('should not include export tags option', () => { - setConfig({ - ...getConfig(), - ENABLE_TAGGING_TAXONOMY_PAGES: 'false', - }); + props.taxonomiesEnabled = false; const actualItemsTitle = getToolsMenuItems(props).map((item) => item.title); expect(actualItemsTitle).toEqual([ 'Import', diff --git a/src/index.jsx b/src/index.jsx index 889063acd7..6195d9582d 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -57,20 +57,16 @@ const App = () => { {getConfig().ENABLE_ACCESSIBILITY_PAGE === 'true' && ( } /> )} - {getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true' && ( - <> - }> - } /> - - }> - } /> - - } - /> - - )} + }> + } /> + + }> + } /> + + } + /> , ), { @@ -121,7 +117,6 @@ initialize({ ENABLE_UNIT_PAGE: process.env.ENABLE_UNIT_PAGE || 'false', ENABLE_ASSETS_PAGE: process.env.ENABLE_ASSETS_PAGE || 'false', ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN: process.env.ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN || 'false', - ENABLE_TAGGING_TAXONOMY_PAGES: process.env.ENABLE_TAGGING_TAXONOMY_PAGES || 'false', ENABLE_HOME_PAGE_COURSE_API_V2: process.env.ENABLE_HOME_PAGE_COURSE_API_V2 === 'true', ENABLE_CHECKLIST_QUALITY: process.env.ENABLE_CHECKLIST_QUALITY || 'true', ENABLE_GRADING_METHOD_IN_PROBLEMS: process.env.ENABLE_GRADING_METHOD_IN_PROBLEMS === 'true', diff --git a/src/studio-home/StudioHome.test.jsx b/src/studio-home/StudioHome.test.jsx index 7286acda0f..5b103263b4 100644 --- a/src/studio-home/StudioHome.test.jsx +++ b/src/studio-home/StudioHome.test.jsx @@ -3,6 +3,7 @@ import { useSelector } from 'react-redux'; import { initializeMockApp } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; +import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import { AppProvider } from '@edx/frontend-platform/react'; import { act, fireEvent, render, waitFor, @@ -41,11 +42,15 @@ jest.mock('react-router-dom', () => ({ }), })); +const queryClient = new QueryClient(); + const RootWrapper = () => ( - - - + + + + + ); diff --git a/src/studio-home/data/api.js b/src/studio-home/data/api.js index 1fefe2981a..9e8e5aa598 100644 --- a/src/studio-home/data/api.js +++ b/src/studio-home/data/api.js @@ -8,7 +8,6 @@ export const getCourseNotificationUrl = (url) => new URL(url, getApiBaseUrl()).h /** * Get's studio home data. - * @param {string} search * @returns {Promise} */ export async function getStudioHomeData() { diff --git a/src/taxonomy/TaxonomyListPage.jsx b/src/taxonomy/TaxonomyListPage.jsx index 4ad78fadb1..8d85dadf75 100644 --- a/src/taxonomy/TaxonomyListPage.jsx +++ b/src/taxonomy/TaxonomyListPage.jsx @@ -18,10 +18,10 @@ import { Check, } from '@openedx/paragon/icons'; import { useIntl } from '@edx/frontend-platform/i18n'; - +import { Navigate } from 'react-router'; import { Helmet } from 'react-helmet'; -import { useOrganizationListData } from '../generic/data/apiHooks'; +import { useOrganizationListData, useTaggingFeaturesEnabled } from '../generic/data/apiHooks'; import SubHeader from '../generic/sub-header/SubHeader'; import getPageHeadTitle from '../generic/utils'; import { ALL_TAXONOMIES, apiUrls, UNASSIGNED } from './data/api'; @@ -168,6 +168,11 @@ const TaxonomyListPage = () => { /> ); + const taxonomiesEnabled = useTaggingFeaturesEnabled(); + if (!taxonomiesEnabled) { + return ; + } + return ( <> diff --git a/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.jsx b/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.jsx index bb9fd89c46..ea6b6f2796 100644 --- a/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.jsx +++ b/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.jsx @@ -7,12 +7,13 @@ import { Layout, } from '@openedx/paragon'; import { Helmet } from 'react-helmet'; -import { Link, useParams } from 'react-router-dom'; +import { Link, Navigate, useParams } from 'react-router-dom'; import ConnectionErrorAlert from '../../generic/ConnectionErrorAlert'; import Loading from '../../generic/Loading'; import getPageHeadTitle from '../../generic/utils'; import SubHeader from '../../generic/sub-header/SubHeader'; +import { useTaggingFeaturesEnabled } from '../../generic/data/apiHooks'; import taxonomyMessages from '../messages'; import { TagListTable } from '../tag-list'; import { TaxonomyMenu } from '../taxonomy-menu'; @@ -31,6 +32,11 @@ const TaxonomyDetailPage = () => { isFetched, } = useTaxonomyDetails(taxonomyId); + const taxonomiesEnabled = useTaggingFeaturesEnabled(); + if (!taxonomiesEnabled) { + return ; + } + if (!isFetched) { return ( diff --git a/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.test.jsx b/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.test.jsx index 49df3d8a7e..cf43316483 100644 --- a/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.test.jsx +++ b/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.test.jsx @@ -13,6 +13,7 @@ import TaxonomyDetailPage from './TaxonomyDetailPage'; let store; const mockNavigate = jest.fn(); const mockMutate = jest.fn(); +const mockTaggingFeaturesEnabled = jest.fn(); let axiosMock; jest.mock('react-router-dom', () => ({ @@ -21,11 +22,16 @@ jest.mock('react-router-dom', () => ({ taxonomyId: '1', }), useNavigate: () => mockNavigate, + Navigate: jest.fn(({ to }) => `[Navigate: Redirected to ${to}]`), })); jest.mock('../data/apiHooks', () => ({ ...jest.requireActual('../data/apiHooks'), useDeleteTaxonomy: () => mockMutate, })); +jest.mock('../../generic/data/apiHooks', () => ({ + ...jest.requireActual('../../generic/data/apiHooks'), + useTaggingFeaturesEnabled: () => mockTaggingFeaturesEnabled(), +})); jest.mock('./TaxonomyDetailSideCard', () => jest.fn(() => <>Mock TaxonomyDetailSideCard)); jest.mock('../tag-list/TagListTable', () => jest.fn(() => <>Mock TagListTable)); @@ -54,6 +60,7 @@ describe('', () => { }); store = initializeStore(); axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + mockTaggingFeaturesEnabled.mockReturnValue(true); }); afterEach(() => { @@ -62,6 +69,12 @@ describe('', () => { queryClient.clear(); }); + it('redirects to Studio home if the tagging feature is disabled', () => { + mockTaggingFeaturesEnabled.mockReturnValue(false); + const doc = render(); + expect(doc.asFragment().textContent).toEqual('[Navigate: Redirected to /home]'); + }); + it('shows the spinner before the query is complete', () => { // Use unresolved promise to keep the Loading visible axiosMock.onGet(apiUrls.taxonomy(1)).reply(() => new Promise());