feat: Add ability to create Legacy Libraries (#2551)
This adds a CreateLegacyLibrary component. It functions the same as CreateLibrary, but it calls the V1 (legacy) creation REST API rather the V2 (new/beta) REST API. This reinstates, in the MFE, something that was possible using the legacy frontend until it was prematurely removed by https://github.com/openedx/edx-platform/pull/37454. We plan to re-remove this ability between Ulmo and Verawood as part of: https://github.com/openedx/edx-platform/issues/32457. So, we have intentionally avoided factoring out common logic between CreateLibrary and CreateLegacyLibrary, ensuring that the latter remains easy to remove and clean up.
This commit is contained in:
@@ -19,6 +19,7 @@ import messages from './i18n';
|
||||
import {
|
||||
ComponentPicker,
|
||||
CreateLibrary,
|
||||
CreateLegacyLibrary,
|
||||
LibraryLayout,
|
||||
PreviewChangesEmbed,
|
||||
} from './library-authoring';
|
||||
@@ -67,6 +68,7 @@ const App = () => {
|
||||
<Route path="/libraries" element={<StudioHome />} />
|
||||
<Route path="/libraries-v1" element={<StudioHome />} />
|
||||
<Route path="/libraries-v1/migrate" element={<LegacyLibMigrationPage />} />
|
||||
<Route path="/libraries-v1/create" element={<CreateLegacyLibrary />} />
|
||||
<Route path="/library/create" element={<CreateLibrary />} />
|
||||
<Route path="/library/:libraryId/*" element={<LibraryLayout />} />
|
||||
<Route
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
import React from 'react';
|
||||
import type MockAdapter from 'axios-mock-adapter';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import {
|
||||
initializeMocks,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@src/testUtils';
|
||||
import studioHomeMock from '@src/studio-home/__mocks__/studioHomeMock';
|
||||
import { getStudioHomeApiUrl } from '@src/studio-home/data/api';
|
||||
import { getApiWaffleFlagsUrl } from '@src/data/api';
|
||||
import { CreateLegacyLibrary } from '.';
|
||||
import { getContentLibraryV1CreateApiUrl } from './data/api';
|
||||
|
||||
const mockNavigate = jest.fn();
|
||||
const realWindowLocationAssign = window.location.assign;
|
||||
let axiosMock: MockAdapter;
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useNavigate: () => mockNavigate,
|
||||
}));
|
||||
|
||||
jest.mock('@src/generic/data/apiHooks', () => ({
|
||||
...jest.requireActual('@src/generic/data/apiHooks'),
|
||||
useOrganizationListData: () => ({
|
||||
data: ['org1', 'org2', 'org3', 'org4', 'org5'],
|
||||
isLoading: false,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('<CreateLegacyLibrary />', () => {
|
||||
beforeEach(() => {
|
||||
axiosMock = initializeMocks().axiosMock;
|
||||
axiosMock
|
||||
.onGet(getApiWaffleFlagsUrl(undefined))
|
||||
.reply(200, {});
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: { assign: jest.fn() },
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
axiosMock.restore();
|
||||
window.location.assign = realWindowLocationAssign;
|
||||
});
|
||||
|
||||
test('call api data with correct data', async () => {
|
||||
const user = userEvent.setup();
|
||||
axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock);
|
||||
axiosMock.onPost(getContentLibraryV1CreateApiUrl()).reply(200, {
|
||||
id: 'library-id',
|
||||
url: '/library/library-id',
|
||||
});
|
||||
|
||||
render(<CreateLegacyLibrary />);
|
||||
|
||||
const titleInput = await screen.findByRole('textbox', { name: /library name/i });
|
||||
await user.click(titleInput);
|
||||
await user.type(titleInput, 'Test Library Name');
|
||||
|
||||
const orgInput = await screen.findByRole('combobox', { name: /organization/i });
|
||||
await user.click(orgInput);
|
||||
await user.type(orgInput, 'org1');
|
||||
await user.tab();
|
||||
|
||||
const slugInput = await screen.findByRole('textbox', { name: /library id/i });
|
||||
await user.click(slugInput);
|
||||
await user.type(slugInput, 'test_library_slug');
|
||||
|
||||
await user.click(await screen.findByRole('button', { name: /create/i }));
|
||||
await waitFor(() => {
|
||||
expect(axiosMock.history.post.length).toBe(1);
|
||||
expect(axiosMock.history.post[0].data).toBe(
|
||||
'{"display_name":"Test Library Name","org":"org1","number":"test_library_slug"}',
|
||||
);
|
||||
expect(window.location.assign).toHaveBeenCalledWith('http://localhost:18010/library/library-id');
|
||||
});
|
||||
});
|
||||
|
||||
test('cannot create new org unless allowed', async () => {
|
||||
const user = userEvent.setup();
|
||||
axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock);
|
||||
axiosMock.onPost(getContentLibraryV1CreateApiUrl()).reply(200, {
|
||||
id: 'library-id',
|
||||
url: '/library/library-id',
|
||||
});
|
||||
|
||||
render(<CreateLegacyLibrary />);
|
||||
|
||||
const titleInput = await screen.findByRole('textbox', { name: /library name/i });
|
||||
await user.click(titleInput);
|
||||
await user.type(titleInput, 'Test Library Name');
|
||||
|
||||
// We cannot create a new org, and so we're restricted to the allowed list
|
||||
const orgOptions = screen.getByTestId('autosuggest-iconbutton');
|
||||
await user.click(orgOptions);
|
||||
expect(screen.getByText('org1')).toBeInTheDocument();
|
||||
['org2', 'org3', 'org4', 'org5'].forEach((org) => expect(screen.queryByText(org)).not.toBeInTheDocument());
|
||||
|
||||
const orgInput = await screen.findByRole('combobox', { name: /organization/i });
|
||||
await user.click(orgInput);
|
||||
await user.type(orgInput, 'NewOrg');
|
||||
await user.tab();
|
||||
|
||||
const slugInput = await screen.findByRole('textbox', { name: /library id/i });
|
||||
await user.click(slugInput);
|
||||
await user.type(slugInput, 'test_library_slug');
|
||||
|
||||
await user.click(await screen.findByRole('button', { name: /create/i }));
|
||||
await waitFor(() => {
|
||||
expect(axiosMock.history.post.length).toBe(0);
|
||||
});
|
||||
expect(await screen.findByText('Required field.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('can create new org if allowed', async () => {
|
||||
const user = userEvent.setup();
|
||||
axiosMock.onGet(getStudioHomeApiUrl()).reply(200, {
|
||||
...studioHomeMock,
|
||||
allow_to_create_new_org: true,
|
||||
});
|
||||
axiosMock.onPost(getContentLibraryV1CreateApiUrl()).reply(200, {
|
||||
id: 'library-id',
|
||||
url: '/library/library-id',
|
||||
});
|
||||
|
||||
render(<CreateLegacyLibrary />);
|
||||
|
||||
const titleInput = await screen.findByRole('textbox', { name: /library name/i });
|
||||
await user.click(titleInput);
|
||||
await user.type(titleInput, 'Test Library Name');
|
||||
|
||||
// We can create a new org, so we're also allowed to use any existing org
|
||||
const orgOptions = screen.getByTestId('autosuggest-iconbutton');
|
||||
await user.click(orgOptions);
|
||||
['org1', 'org2', 'org3', 'org4', 'org5'].forEach((org) => expect(screen.queryByText(org)).toBeInTheDocument());
|
||||
|
||||
const orgInput = await screen.findByRole('combobox', { name: /organization/i });
|
||||
await user.click(orgInput);
|
||||
await user.type(orgInput, 'NewOrg');
|
||||
await user.tab();
|
||||
|
||||
const slugInput = await screen.findByRole('textbox', { name: /library id/i });
|
||||
await user.click(slugInput);
|
||||
await user.type(slugInput, 'test_library_slug');
|
||||
|
||||
await user.click(await screen.findByRole('button', { name: /create/i }));
|
||||
await waitFor(() => {
|
||||
expect(axiosMock.history.post.length).toBe(1);
|
||||
expect(axiosMock.history.post[0].data).toBe(
|
||||
'{"display_name":"Test Library Name","org":"NewOrg","number":"test_library_slug"}',
|
||||
);
|
||||
expect(window.location.assign).toHaveBeenCalledWith('http://localhost:18010/library/library-id');
|
||||
});
|
||||
});
|
||||
|
||||
test('show api error', async () => {
|
||||
const user = userEvent.setup();
|
||||
axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock);
|
||||
axiosMock.onPost(getContentLibraryV1CreateApiUrl()).reply(400, {
|
||||
field: 'Error message',
|
||||
});
|
||||
render(<CreateLegacyLibrary />);
|
||||
|
||||
const titleInput = await screen.findByRole('textbox', { name: /library name/i });
|
||||
await user.click(titleInput);
|
||||
await user.type(titleInput, 'Test Library Name');
|
||||
|
||||
const orgInput = await screen.findByRole('combobox', { name: /organization/i });
|
||||
await user.click(orgInput);
|
||||
await user.type(orgInput, 'org1');
|
||||
await user.tab();
|
||||
|
||||
const slugInput = await screen.findByRole('textbox', { name: /library id/i });
|
||||
await user.click(slugInput);
|
||||
await user.type(slugInput, 'test_library_slug');
|
||||
|
||||
await user.click(await screen.findByRole('button', { name: /create/i }));
|
||||
await waitFor(async () => {
|
||||
expect(axiosMock.history.post.length).toBe(1);
|
||||
expect(axiosMock.history.post[0].data).toBe(
|
||||
'{"display_name":"Test Library Name","org":"org1","number":"test_library_slug"}',
|
||||
);
|
||||
expect(mockNavigate).not.toHaveBeenCalled();
|
||||
});
|
||||
await screen.findByText('Request failed with status code 400');
|
||||
});
|
||||
|
||||
test('cancel creating library navigates to libraries page', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<CreateLegacyLibrary />);
|
||||
|
||||
await user.click(await screen.findByRole('button', { name: /cancel/i }));
|
||||
await waitFor(() => {
|
||||
expect(mockNavigate).toHaveBeenCalledWith('/libraries-v1');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,206 @@
|
||||
import { StudioFooterSlot } from '@edx/frontend-component-footer';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Container,
|
||||
Form,
|
||||
Button,
|
||||
StatefulButton,
|
||||
ActionRow,
|
||||
} from '@openedx/paragon';
|
||||
import { Formik } from 'formik';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import * as Yup from 'yup';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { REGEX_RULES } from '@src/constants';
|
||||
import { useOrganizationListData } from '@src/generic/data/apiHooks';
|
||||
import { useStudioHome } from '@src/studio-home/hooks';
|
||||
import Header from '@src/header';
|
||||
import SubHeader from '@src/generic/sub-header/SubHeader';
|
||||
import FormikControl from '@src/generic/FormikControl';
|
||||
import FormikErrorFeedback from '@src/generic/FormikErrorFeedback';
|
||||
import AlertError from '@src/generic/alert-error';
|
||||
|
||||
import messages from '@src/library-authoring/create-library/messages';
|
||||
import type { LibraryV1Data } from '@src/studio-home/data/api';
|
||||
import legacyMessages from './messages';
|
||||
import { useCreateLibraryV1 } from './data/apiHooks';
|
||||
|
||||
/**
|
||||
* Renders the form and logic to create a new library.
|
||||
*
|
||||
* Use `showInModal` to render this component in a way that can be
|
||||
* used in a modal. Currently this component is used in a modal in the
|
||||
* legacy libraries migration flow.
|
||||
*/
|
||||
export const CreateLegacyLibrary = ({
|
||||
showInModal = false,
|
||||
handleCancel,
|
||||
handlePostCreate,
|
||||
}: {
|
||||
showInModal?: boolean,
|
||||
handleCancel?: () => void,
|
||||
handlePostCreate?: (library: LibraryV1Data) => void,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { noSpaceRule, specialCharsRule } = REGEX_RULES;
|
||||
const validSlugIdRegex = /^[a-zA-Z\d]+(?:[\w-]*[a-zA-Z\d]+)*$/;
|
||||
|
||||
const {
|
||||
mutate,
|
||||
data,
|
||||
isPending,
|
||||
isError,
|
||||
error,
|
||||
} = useCreateLibraryV1();
|
||||
|
||||
const {
|
||||
data: allOrganizations,
|
||||
isLoading: isOrganizationListLoading,
|
||||
} = useOrganizationListData();
|
||||
|
||||
const {
|
||||
studioHomeData: {
|
||||
allowedOrganizationsForLibraries,
|
||||
allowToCreateNewOrg,
|
||||
},
|
||||
} = useStudioHome();
|
||||
|
||||
const organizations = (
|
||||
allowToCreateNewOrg
|
||||
? allOrganizations
|
||||
: allowedOrganizationsForLibraries
|
||||
) || [];
|
||||
|
||||
const handleOnClickCancel = () => {
|
||||
if (handleCancel) {
|
||||
handleCancel();
|
||||
} else {
|
||||
navigate('/libraries-v1');
|
||||
}
|
||||
};
|
||||
|
||||
if (data) {
|
||||
if (handlePostCreate) {
|
||||
handlePostCreate(data);
|
||||
} else {
|
||||
window.location.assign(`${getConfig().STUDIO_BASE_URL}${data.url}`);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{!showInModal && (<Header isHiddenMainMenu />)}
|
||||
<Container size="xl" className="p-4 mt-3">
|
||||
{!showInModal && (
|
||||
<SubHeader
|
||||
title={intl.formatMessage(legacyMessages.createLibrary)}
|
||||
/>
|
||||
)}
|
||||
<Formik
|
||||
initialValues={{
|
||||
displayName: '',
|
||||
org: '',
|
||||
number: '',
|
||||
}}
|
||||
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)),
|
||||
number: Yup.string()
|
||||
.required(intl.formatMessage(messages.requiredFieldError))
|
||||
.matches(
|
||||
validSlugIdRegex,
|
||||
intl.formatMessage(messages.invalidSlugError),
|
||||
),
|
||||
})
|
||||
}
|
||||
onSubmit={(values) => mutate(values)}
|
||||
>
|
||||
{(formikProps) => (
|
||||
<Form onSubmit={formikProps.handleSubmit}>
|
||||
<FormikControl
|
||||
name="displayName"
|
||||
label={<Form.Label>{intl.formatMessage(legacyMessages.titleLabel)}</Form.Label>}
|
||||
value={formikProps.values.displayName}
|
||||
placeholder={intl.formatMessage(messages.titlePlaceholder)}
|
||||
help={intl.formatMessage(messages.titleHelp)}
|
||||
className=""
|
||||
controlClasses="pb-2"
|
||||
/>
|
||||
<Form.Group>
|
||||
<Form.Label>{intl.formatMessage(messages.orgLabel)}</Form.Label>
|
||||
<Form.Autosuggest
|
||||
name="org"
|
||||
isLoading={isOrganizationListLoading}
|
||||
onChange={(event) => formikProps.setFieldValue(
|
||||
'org',
|
||||
allowToCreateNewOrg
|
||||
? (event.selectionId || event.userProvidedText)
|
||||
: event.selectionId,
|
||||
)}
|
||||
placeholder={intl.formatMessage(messages.orgPlaceholder)}
|
||||
>
|
||||
{organizations.map((org) => (
|
||||
<Form.AutosuggestOption key={org} id={org}>{org}</Form.AutosuggestOption>
|
||||
))}
|
||||
</Form.Autosuggest>
|
||||
<FormikErrorFeedback name="org">
|
||||
<Form.Text>{intl.formatMessage(messages.orgHelp)}</Form.Text>
|
||||
</FormikErrorFeedback>
|
||||
</Form.Group>
|
||||
<FormikControl
|
||||
name="number"
|
||||
label={<Form.Label>{intl.formatMessage(messages.slugLabel)}</Form.Label>}
|
||||
value={formikProps.values.number}
|
||||
placeholder={intl.formatMessage(messages.slugPlaceholder)}
|
||||
help={intl.formatMessage(messages.slugHelp)}
|
||||
className=""
|
||||
controlClasses="pb-2"
|
||||
/>
|
||||
<ActionRow className={
|
||||
classNames(
|
||||
{
|
||||
'justify-content-start': !showInModal,
|
||||
'justify-content-end': showInModal,
|
||||
},
|
||||
)
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
onClick={handleOnClickCancel}
|
||||
>
|
||||
{intl.formatMessage(messages.cancelCreateLibraryButton)}
|
||||
</Button>
|
||||
<StatefulButton
|
||||
type="submit"
|
||||
variant="primary"
|
||||
className="action btn-primary"
|
||||
state={isPending ? 'disabled' : 'enabled'}
|
||||
disabledStates={['disabled']}
|
||||
labels={{
|
||||
enabled: intl.formatMessage(legacyMessages.createLibraryButton),
|
||||
disabled: intl.formatMessage(legacyMessages.createLibraryButtonPending),
|
||||
}}
|
||||
/>
|
||||
</ActionRow>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
{isError && (<AlertError error={error} />)}
|
||||
</Container>
|
||||
{!showInModal && (<StudioFooterSlot />)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
27
src/library-authoring/create-legacy-library/data/api.ts
Normal file
27
src/library-authoring/create-legacy-library/data/api.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { camelCaseObject, snakeCaseObject, getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
import type { LibraryV1Data } from '@src/studio-home/data/api';
|
||||
|
||||
/**
|
||||
* Get the URL for creating a new library.
|
||||
*/
|
||||
export const getContentLibraryV1CreateApiUrl = () => `${getConfig().STUDIO_BASE_URL}/library/`;
|
||||
|
||||
export interface CreateContentLibraryV1Args {
|
||||
displayName: string,
|
||||
org: string,
|
||||
number: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new library
|
||||
*/
|
||||
export async function createLibraryV1(data: CreateContentLibraryV1Args): Promise<LibraryV1Data> {
|
||||
const client = getAuthenticatedHttpClient();
|
||||
const url = getContentLibraryV1CreateApiUrl();
|
||||
|
||||
const { data: newLibrary } = await client.post(url, { ...snakeCaseObject(data) });
|
||||
|
||||
return camelCaseObject(newLibrary);
|
||||
}
|
||||
21
src/library-authoring/create-legacy-library/data/apiHooks.ts
Normal file
21
src/library-authoring/create-legacy-library/data/apiHooks.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import {
|
||||
useMutation,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query';
|
||||
|
||||
import { studioHomeQueryKeys } from '@src/studio-home/data/apiHooks';
|
||||
import { createLibraryV1 } from './api';
|
||||
|
||||
/**
|
||||
* Hook that provides a "mutation" that can be used to create a new content library.
|
||||
*/
|
||||
export const useCreateLibraryV1 = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: createLibraryV1,
|
||||
onSettled: () => {
|
||||
queryClient.invalidateQueries({ queryKey: studioHomeQueryKeys.librariesV1() });
|
||||
},
|
||||
});
|
||||
};
|
||||
1
src/library-authoring/create-legacy-library/index.ts
Normal file
1
src/library-authoring/create-legacy-library/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { CreateLegacyLibrary } from './CreateLegacyLibrary';
|
||||
26
src/library-authoring/create-legacy-library/messages.ts
Normal file
26
src/library-authoring/create-legacy-library/messages.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
createLibrary: {
|
||||
id: 'course-authoring.library-authoring.create-legacy-library',
|
||||
defaultMessage: 'Create new legacy library',
|
||||
description: 'Header for the create legacy library form',
|
||||
},
|
||||
titleLabel: {
|
||||
id: 'course-authoring.library-authoring.create-legacy-library.form.title.label',
|
||||
defaultMessage: 'Legacy library name',
|
||||
description: 'Label for the title field when creating a legacy library.',
|
||||
},
|
||||
createLibraryButton: {
|
||||
id: 'course-authoring.library-authoring.create-legacy-library.form.create-library.button',
|
||||
defaultMessage: 'Create legacy library',
|
||||
description: 'Button text for creating a new legacy library.',
|
||||
},
|
||||
createLibraryButtonPending: {
|
||||
id: 'course-authoring.library-authoring.create-legacy-library.form.create-library.button.pending',
|
||||
defaultMessage: 'Creating legacy library..',
|
||||
description: 'Button text while the legacy library is being created.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -2,5 +2,6 @@ export { default as LibraryLayout } from './LibraryLayout';
|
||||
export { ComponentPicker } from './component-picker';
|
||||
export { type SelectedComponent } from './common/context/ComponentPickerContext';
|
||||
export { CreateLibrary, CreateLibraryModal } from './create-library';
|
||||
export { CreateLegacyLibrary } from './create-legacy-library';
|
||||
export { libraryAuthoringQueryKeys, useContentLibraryV2List } from './data/apiHooks';
|
||||
export { default as PreviewChangesEmbed } from './legacy-integration/PreviewChangesEmbed';
|
||||
|
||||
@@ -113,22 +113,18 @@ describe('<StudioHome />', () => {
|
||||
});
|
||||
|
||||
describe('render new library button', () => {
|
||||
it('should navigate to home_library when libraries-v2 disabled', async () => {
|
||||
it('should navigate to legacy library creation when libraries-v2 disabled', async () => {
|
||||
mockUseSelector.mockReturnValue({
|
||||
...studioHomeMock,
|
||||
courseCreatorStatus: COURSE_CREATOR_STATES.granted,
|
||||
librariesV2Enabled: false,
|
||||
});
|
||||
const studioBaseUrl = 'http://localhost:18010';
|
||||
|
||||
render(<StudioHome />, { path: '/home' });
|
||||
await waitFor(() => {
|
||||
const createNewLibraryButton = screen.getByRole('button', { name: 'New library' });
|
||||
|
||||
const mockWindowOpen = jest.spyOn(window, 'open');
|
||||
fireEvent.click(createNewLibraryButton);
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(`${studioBaseUrl}/home_library`);
|
||||
mockWindowOpen.mockRestore();
|
||||
expect(mockNavigate).toHaveBeenCalledWith('/libraries-v1/create');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import { Add as AddIcon, Error } from '@openedx/paragon/icons';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { StudioFooterSlot } from '@edx/frontend-component-footer';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import Loading from '../generic/Loading';
|
||||
@@ -91,8 +90,7 @@ const StudioHome = () => {
|
||||
if (showV2LibraryURL) {
|
||||
navigate('/library/create');
|
||||
} else {
|
||||
// Studio home library for legacy libraries
|
||||
window.open(`${getConfig().STUDIO_BASE_URL}/home_library`);
|
||||
navigate('/libraries-v1/create');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user