feat!: Remove support for the (deprecated) library authoring MFE (#1327)
This commit is contained in:
@@ -69,7 +69,7 @@ describe('cms api', () => {
|
||||
|
||||
it('should call get with normal accept header for prod', async () => {
|
||||
process.env.NODE_ENV = 'production';
|
||||
process.env.MFE_NAME = 'frontend-app-library-authoring';
|
||||
process.env.MFE_NAME = 'frontend-app-course-authoring';
|
||||
// eslint-disable-next-line no-shadow, @typescript-eslint/no-shadow
|
||||
const { apiMethods } = await import('./api');
|
||||
// eslint-disable-next-line no-shadow, @typescript-eslint/no-shadow
|
||||
@@ -90,18 +90,6 @@ describe('cms api', () => {
|
||||
apiMethods.fetchByUnitId({ blockId, studioEndpointUrl });
|
||||
expect(getSpy).toHaveBeenCalledWith(urls.blockAncestor({ studioEndpointUrl, blockId }), {});
|
||||
});
|
||||
|
||||
it('should call get with special accept header "*/*" for course-authoring', async () => {
|
||||
process.env.NODE_ENV = 'development';
|
||||
process.env.MFE_NAME = 'frontend-app-library-authoring';
|
||||
// eslint-disable-next-line no-shadow, @typescript-eslint/no-shadow
|
||||
const { apiMethods } = await import('./api');
|
||||
// eslint-disable-next-line no-shadow, @typescript-eslint/no-shadow
|
||||
const utils = await import('./utils');
|
||||
const getSpy = jest.spyOn(utils, 'get');
|
||||
apiMethods.fetchByUnitId({ blockId, studioEndpointUrl });
|
||||
expect(getSpy).toHaveBeenCalledWith(urls.blockAncestor({ studioEndpointUrl, blockId }), { headers: { Accept: '*/*' } });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -7,14 +7,6 @@ import { durationStringFromValue } from '../../../containers/VideoEditor/compone
|
||||
|
||||
const fetchByUnitIdOptions: AxiosRequestConfig = {};
|
||||
|
||||
// For some reason, the local webpack-dev-server of library-authoring does not accept the normal Accept header.
|
||||
// This is a workaround only for that specific case; the idea is to only do this locally and only for library-authoring.
|
||||
if (process.env.NODE_ENV === 'development' && process.env.MFE_NAME === 'frontend-app-library-authoring') {
|
||||
fetchByUnitIdOptions.headers = {
|
||||
Accept: '*/*',
|
||||
};
|
||||
}
|
||||
|
||||
interface Pagination {
|
||||
start: number;
|
||||
end: number;
|
||||
|
||||
@@ -7,14 +7,11 @@ import {
|
||||
Stack,
|
||||
} from '@openedx/paragon';
|
||||
import { OpenInNew } from '@openedx/paragon/icons';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { getItemIcon } from '../generic/block-type-utils';
|
||||
import { isLibraryKey } from '../generic/key-utils';
|
||||
import { useSearchContext, type ContentHit, Highlight } from '../search-manager';
|
||||
import { getStudioHomeData } from '../studio-home/data/selectors';
|
||||
import { constructLibraryAuthoringURL } from '../utils';
|
||||
import messages from './messages';
|
||||
|
||||
/**
|
||||
@@ -100,7 +97,6 @@ const SearchResult: React.FC<{ hit: ContentHit }> = ({ hit }) => {
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigate();
|
||||
const { closeSearchModal } = useSearchContext();
|
||||
const { libraryAuthoringMfeUrl, redirectToLibraryAuthoringMfe } = useSelector(getStudioHomeData);
|
||||
|
||||
/**
|
||||
* Returns the URL for the context of the hit
|
||||
@@ -119,10 +115,6 @@ const SearchResult: React.FC<{ hit: ContentHit }> = ({ hit }) => {
|
||||
|
||||
if (isLibraryKey(contextKey)) {
|
||||
const urlSuffix = getLibraryComponentUrlSuffix(hit);
|
||||
if (redirectToLibraryAuthoringMfe && libraryAuthoringMfeUrl) {
|
||||
return constructLibraryAuthoringURL(libraryAuthoringMfeUrl, urlSuffix);
|
||||
}
|
||||
|
||||
if (newWindow) {
|
||||
return `${getPath(getConfig().PUBLIC_PATH)}${urlSuffix}`;
|
||||
}
|
||||
@@ -131,7 +123,7 @@ const SearchResult: React.FC<{ hit: ContentHit }> = ({ hit }) => {
|
||||
|
||||
// istanbul ignore next - This case should never be reached
|
||||
return undefined;
|
||||
}, [libraryAuthoringMfeUrl, redirectToLibraryAuthoringMfe, hit]);
|
||||
}, [hit]);
|
||||
|
||||
/**
|
||||
* Opens the context of the hit in a new window
|
||||
|
||||
@@ -16,10 +16,6 @@ import fetchMock from 'fetch-mock-jest';
|
||||
import type { Store } from 'redux';
|
||||
|
||||
import initializeStore from '../store';
|
||||
import { executeThunk } from '../utils';
|
||||
import { getStudioHomeApiUrl } from '../studio-home/data/api';
|
||||
import { fetchStudioHomeData } from '../studio-home/data/thunks';
|
||||
import { generateGetStudioHomeDataApiResponse } from '../studio-home/factories/mockApiResponses';
|
||||
import mockResult from './__mocks__/search-result.json';
|
||||
import mockEmptyResult from './__mocks__/empty-search-result.json';
|
||||
import mockTagsFacetResult from './__mocks__/facet-search.json';
|
||||
@@ -316,43 +312,7 @@ describe('<SearchUI />', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('click lib component result navigates to the context', async () => {
|
||||
const data = generateGetStudioHomeDataApiResponse();
|
||||
data.redirectToLibraryAuthoringMfe = true;
|
||||
axiosMock.onGet(getStudioHomeApiUrl()).reply(200, data);
|
||||
|
||||
await executeThunk(fetchStudioHomeData(), store.dispatch);
|
||||
|
||||
const { findByRole } = rendered;
|
||||
|
||||
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;
|
||||
window.open = jest.fn();
|
||||
fireEvent.click(within(resultItem).getByRole('button', { name: 'Open in new window' }));
|
||||
expect(window.open).toHaveBeenCalledWith(
|
||||
'http://localhost:3001/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 = 'http://localhost:3001/library/lib:org1:libafter1');
|
||||
window.location = location;
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
test('click lib component result navigates to course-authoring/library', async () => {
|
||||
const { findByRole } = rendered;
|
||||
|
||||
const resultItem = await findByRole('button', { name: /Library Content/ });
|
||||
|
||||
@@ -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, constructLibraryAuthoringURL } from '../utils';
|
||||
import { executeThunk } from '../utils';
|
||||
import { studioHomeMock } from './__mocks__';
|
||||
import { getStudioHomeApiUrl } from './data/api';
|
||||
import { fetchStudioHomeData } from './data/thunks';
|
||||
@@ -193,27 +193,6 @@ describe('<StudioHome />', () => {
|
||||
window.open = open;
|
||||
});
|
||||
|
||||
it('should navigate to the library authoring mfe', () => {
|
||||
useSelector.mockReturnValue({
|
||||
...studioHomeMock,
|
||||
courseCreatorStatus: COURSE_CREATOR_STATES.granted,
|
||||
splitStudioHome: true,
|
||||
redirectToLibraryAuthoringMfe: true,
|
||||
});
|
||||
const libraryAuthoringMfeUrl = 'http://localhost:3001';
|
||||
|
||||
const { getByTestId } = render(<RootWrapper />);
|
||||
const createNewLibraryButton = getByTestId('new-library-button');
|
||||
|
||||
const { open } = window;
|
||||
window.open = jest.fn();
|
||||
fireEvent.click(createNewLibraryButton);
|
||||
expect(window.open).toHaveBeenCalledWith(
|
||||
`${constructLibraryAuthoringURL(libraryAuthoringMfeUrl, 'create')}`,
|
||||
);
|
||||
window.open = open;
|
||||
});
|
||||
|
||||
it('should navigate to the library authoring page in course authoring', () => {
|
||||
useSelector.mockReturnValue({
|
||||
...studioHomeMock,
|
||||
|
||||
@@ -13,7 +13,6 @@ import { StudioFooter } from '@edx/frontend-component-footer';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { constructLibraryAuthoringURL } from '../utils';
|
||||
import Loading from '../generic/Loading';
|
||||
import InternetConnectionAlert from '../generic/internet-connection-alert';
|
||||
import Header from '../header';
|
||||
@@ -58,8 +57,6 @@ const StudioHome = () => {
|
||||
userIsActive,
|
||||
studioShortName,
|
||||
studioRequestEmail,
|
||||
libraryAuthoringMfeUrl,
|
||||
redirectToLibraryAuthoringMfe,
|
||||
showNewLibraryButton,
|
||||
} = studioHomeData;
|
||||
|
||||
@@ -93,13 +90,7 @@ const StudioHome = () => {
|
||||
if (showNewLibraryButton || showV2LibraryURL) {
|
||||
const newLibraryClick = () => {
|
||||
if (showV2LibraryURL) {
|
||||
if (libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe) {
|
||||
// Library authoring MFE
|
||||
window.open(constructLibraryAuthoringURL(libraryAuthoringMfeUrl, 'create'));
|
||||
} else {
|
||||
// Use course-authoring route
|
||||
navigate('/library/create');
|
||||
}
|
||||
navigate('/library/create');
|
||||
} else {
|
||||
// Studio home library for legacy libraries
|
||||
window.open(`${getConfig().STUDIO_BASE_URL}/home_library`);
|
||||
|
||||
@@ -62,9 +62,7 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
librariesEnabled: true,
|
||||
libraryAuthoringMfeUrl: 'http://localhost:3001',
|
||||
optimizationEnabled: false,
|
||||
redirectToLibraryAuthoringMfe: false,
|
||||
requestCourseCreatorUrl: '/request_course_creator',
|
||||
rerunCreatorStatus: true,
|
||||
showNewLibraryButton: true,
|
||||
|
||||
@@ -62,7 +62,7 @@ const CardItem: React.FC<Props> = ({
|
||||
} = useSelector(getStudioHomeData);
|
||||
const destinationUrl: string = path ?? new URL(url, getConfig().STUDIO_BASE_URL).toString();
|
||||
const subtitle = isLibraries ? `${org} / ${number}` : `${org} / ${number} / ${run}`;
|
||||
const readOnlyItem = !(lmsLink || rerunLink || url);
|
||||
const readOnlyItem = !(lmsLink || rerunLink || url || path);
|
||||
const showActions = !(readOnlyItem || isLibraries);
|
||||
const isShowRerunLink = allowCourseReruns
|
||||
&& rerunCreatorStatus
|
||||
|
||||
@@ -32,9 +32,7 @@ export const generateGetStudioHomeDataApiResponse = () => ({
|
||||
inProcessCourseActions: [],
|
||||
libraries: [],
|
||||
librariesEnabled: true,
|
||||
libraryAuthoringMfeUrl: 'http://localhost:3001/',
|
||||
optimizationEnabled: false,
|
||||
redirectToLibraryAuthoringMfe: false,
|
||||
requestCourseCreatorUrl: '/request_course_creator',
|
||||
rerunCreatorStatus: true,
|
||||
showNewLibraryButton: true,
|
||||
|
||||
@@ -428,22 +428,6 @@ describe('<TabsSection />', () => {
|
||||
expect(screen.queryByText(tabMessages.legacyLibrariesTabTitle.defaultMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('should redirect to library authoring mfe', async () => {
|
||||
const data = generateGetStudioHomeDataApiResponse();
|
||||
data.redirectToLibraryAuthoringMfe = true;
|
||||
|
||||
render();
|
||||
axiosMock.onGet(getStudioHomeApiUrl()).reply(200, data);
|
||||
await executeThunk(fetchStudioHomeData(), store.dispatch);
|
||||
|
||||
const librariesTab = screen.getByText(tabMessages.legacyLibrariesTabTitle.defaultMessage);
|
||||
fireEvent.click(librariesTab);
|
||||
|
||||
waitFor(() => {
|
||||
expect(window.location.href).toBe(data.libraryAuthoringMfeUrl);
|
||||
});
|
||||
});
|
||||
|
||||
it('should render libraries fetch failure alert', async () => {
|
||||
render();
|
||||
axiosMock.onGet(getStudioHomeApiUrl()).reply(200, generateGetStudioHomeDataApiResponse());
|
||||
|
||||
@@ -63,8 +63,6 @@ const TabsSection = ({
|
||||
}, [pathname]);
|
||||
|
||||
const {
|
||||
libraryAuthoringMfeUrl,
|
||||
redirectToLibraryAuthoringMfe,
|
||||
courses, librariesEnabled, libraries, archivedCourses,
|
||||
numPages, coursesCount,
|
||||
} = useSelector(getStudioHomeData);
|
||||
@@ -125,10 +123,7 @@ const TabsSection = ({
|
||||
eventKey={TABS_LIST.libraries}
|
||||
title={intl.formatMessage(messages.librariesTabTitle)}
|
||||
>
|
||||
<LibrariesV2Tab
|
||||
libraryAuthoringMfeUrl={libraryAuthoringMfeUrl}
|
||||
redirectToLibraryAuthoringMfe={redirectToLibraryAuthoringMfe}
|
||||
/>
|
||||
<LibrariesV2Tab />
|
||||
</Tab>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,24 +7,18 @@ import {
|
||||
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 { useContentLibraryV2List } from '../../../library-authoring';
|
||||
import { constructLibraryAuthoringURL } from '../../../utils';
|
||||
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: React.FC<{
|
||||
libraryAuthoringMfeUrl: string,
|
||||
redirectToLibraryAuthoringMfe: boolean
|
||||
}> = ({
|
||||
libraryAuthoringMfeUrl,
|
||||
redirectToLibraryAuthoringMfe,
|
||||
}) => {
|
||||
type Props = Record<never, never>;
|
||||
|
||||
const LibrariesV2Tab: React.FC<Props> = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
@@ -55,15 +49,6 @@ const LibrariesV2Tab: React.FC<{
|
||||
);
|
||||
}
|
||||
|
||||
const libURL = (id: string) => (
|
||||
libraryAuthoringMfeUrl && redirectToLibraryAuthoringMfe
|
||||
? constructLibraryAuthoringURL(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,
|
||||
// hence why we use the MFE's origin
|
||||
: `${window.location.origin}${getPath(getConfig().PUBLIC_PATH)}library/${id}`
|
||||
);
|
||||
|
||||
const hasV2Libraries = !isLoading && ((data!.results.length || 0) > 0);
|
||||
|
||||
return (
|
||||
@@ -109,7 +94,7 @@ const LibrariesV2Tab: React.FC<{
|
||||
displayName={title}
|
||||
org={org}
|
||||
number={slug}
|
||||
url={libURL(id)}
|
||||
path={`/library/${id}`}
|
||||
/>
|
||||
)) : isFiltered && !isLoading && (
|
||||
<Alert className="mt-4">
|
||||
|
||||
24
src/utils.js
24
src/utils.js
@@ -301,27 +301,3 @@ 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;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getConfig, getPath } from '@edx/frontend-platform';
|
||||
|
||||
import { getFileSizeToClosestByte, createCorrectInternalRoute, constructLibraryAuthoringURL } from './utils';
|
||||
import { getFileSizeToClosestByte, createCorrectInternalRoute } from './utils';
|
||||
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
getConfig: jest.fn(),
|
||||
@@ -78,30 +78,3 @@ 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');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user