feat!: Remove support for the (deprecated) library authoring MFE (#1327)

This commit is contained in:
Braden MacDonald
2024-09-26 08:38:16 -07:00
committed by GitHub
parent ccce44a1c8
commit 3662fadad4
14 changed files with 12 additions and 201 deletions

View File

@@ -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: '*/*' } });
});
});
});

View File

@@ -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;

View File

@@ -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

View File

@@ -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/ });

View File

@@ -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,

View File

@@ -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`);

View File

@@ -62,9 +62,7 @@ module.exports = {
},
],
librariesEnabled: true,
libraryAuthoringMfeUrl: 'http://localhost:3001',
optimizationEnabled: false,
redirectToLibraryAuthoringMfe: false,
requestCourseCreatorUrl: '/request_course_creator',
rerunCreatorStatus: true,
showNewLibraryButton: true,

View File

@@ -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

View File

@@ -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,

View File

@@ -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());

View File

@@ -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>,
);
}

View File

@@ -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">

View File

@@ -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;
};

View File

@@ -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');
});
});