diff --git a/src/index.jsx b/src/index.jsx
index b0247846bd..3b50076d34 100755
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -28,7 +28,6 @@ import messages from './i18n';
import {
LibraryAndComponentPicker,
CreateLibrary,
- CreateLegacyLibrary,
LibraryLayout,
PreviewChangesEmbed,
} from './library-authoring';
@@ -77,7 +76,6 @@ const App = () => {
} />
} />
} />
- } />
} />
} />
({
- ...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('', () => {
- 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();
-
- 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();
-
- 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();
-
- 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();
-
- 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();
-
- await user.click(await screen.findByRole('button', { name: /cancel/i }));
- await waitFor(() => {
- expect(mockNavigate).toHaveBeenCalledWith('/libraries-v1');
- });
- });
-});
diff --git a/src/library-authoring/create-legacy-library/CreateLegacyLibrary.tsx b/src/library-authoring/create-legacy-library/CreateLegacyLibrary.tsx
deleted file mode 100644
index 373c38f90a..0000000000
--- a/src/library-authoring/create-legacy-library/CreateLegacyLibrary.tsx
+++ /dev/null
@@ -1,219 +0,0 @@
-import { StudioFooterSlot } from '@edx/frontend-component-footer';
-import { getConfig } from '@edx/frontend-platform';
-import { useIntl } from '@edx/frontend-platform/i18n';
-import {
- Alert,
- Container,
- Form,
- Button,
- StatefulButton,
- ActionRow,
-} from '@openedx/paragon';
-import { Warning } from '@openedx/paragon/icons';
-import { Formik } from 'formik';
-import { useNavigate, Link } 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 && }
-
- {!showInModal && (
-
- )}
-
- {intl.formatMessage(legacyMessages.warningTitle)}
- {intl.formatMessage(legacyMessages.warningBody, {
- libraryLink: (
-
- {intl.formatMessage(legacyMessages.warningLibraryFeature)}
-
- ),
- })}
-
- mutate(values)}
- >
- {(formikProps) => (
-
- )}
-
- {isError && }
-
- {!showInModal && }
- >
- );
-};
diff --git a/src/library-authoring/create-legacy-library/data/api.ts b/src/library-authoring/create-legacy-library/data/api.ts
deleted file mode 100644
index 2ba9425fdb..0000000000
--- a/src/library-authoring/create-legacy-library/data/api.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-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 {
- const client = getAuthenticatedHttpClient();
- const url = getContentLibraryV1CreateApiUrl();
-
- const { data: newLibrary } = await client.post(url, { ...snakeCaseObject(data) });
-
- return camelCaseObject(newLibrary);
-}
diff --git a/src/library-authoring/create-legacy-library/data/apiHooks.ts b/src/library-authoring/create-legacy-library/data/apiHooks.ts
deleted file mode 100644
index 1d10760b6f..0000000000
--- a/src/library-authoring/create-legacy-library/data/apiHooks.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-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() });
- },
- });
-};
diff --git a/src/library-authoring/create-legacy-library/index.ts b/src/library-authoring/create-legacy-library/index.ts
deleted file mode 100644
index b31a5c4243..0000000000
--- a/src/library-authoring/create-legacy-library/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { CreateLegacyLibrary } from './CreateLegacyLibrary';
diff --git a/src/library-authoring/create-legacy-library/messages.ts b/src/library-authoring/create-legacy-library/messages.ts
deleted file mode 100644
index c6e7549766..0000000000
--- a/src/library-authoring/create-legacy-library/messages.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-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.',
- },
- warningTitle: {
- id: 'course-authoring.library-authoring.create-legacy-library.warning.title',
- defaultMessage: 'You are creating content in a deprecated format',
- description: 'Warning to discourage users from creating a new Legacy Library',
- },
- warningBody: {
- id: 'course-authoring.library-authoring.create-legacy-library.warning.body',
- defaultMessage: 'Legacy libraries will be unsupported in Willow. Any content you create in a legacy library will soon need to be migrated. Consider using the {libraryLink} instead.',
- description: 'Warning to discourage users from creating a new Legacy Library',
- },
- warningLibraryFeature: {
- id: 'course-authoring.library-authoring.create-legacy-library.warning.library-feature',
- defaultMessage: 'Library feature',
- description: 'Link to the Libraries feature page',
- },
- 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;
diff --git a/src/library-authoring/index.tsx b/src/library-authoring/index.tsx
index 6c9ecd97bd..e40fdf27fe 100644
--- a/src/library-authoring/index.tsx
+++ b/src/library-authoring/index.tsx
@@ -1,6 +1,5 @@
export { type SelectedComponent } from './common/context/ComponentPickerContext';
export { LibraryAndComponentPicker, ComponentPicker } from './component-picker';
-export { CreateLegacyLibrary } from './create-legacy-library';
export { CreateLibrary, CreateLibraryModal } from './create-library';
export { libraryAuthoringQueryKeys, useContentLibraryV2List } from './data/apiHooks';
export { default as PreviewChangesEmbed } from './legacy-integration/PreviewChangesEmbed';
diff --git a/src/studio-home/StudioHome.test.tsx b/src/studio-home/StudioHome.test.tsx
index ec3d195e76..57eac57f82 100644
--- a/src/studio-home/StudioHome.test.tsx
+++ b/src/studio-home/StudioHome.test.tsx
@@ -5,7 +5,6 @@ import {
fireEvent,
render,
screen,
- waitFor,
initializeMocks,
within,
} from '@src/testUtils';
@@ -22,10 +21,8 @@ const {
const mockUseSelector = jest.fn();
jest.spyOn(reactRedux, 'useSelector').mockImplementation(mockUseSelector);
-const mockNavigate = jest.fn();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
- useNavigate: () => mockNavigate,
}));
/** Helper function to get the Studio header in the rendered HTML */
@@ -126,53 +123,37 @@ describe('', () => {
});
describe('render new library button', () => {
- it('should navigate to legacy library creation when libraries-v2 disabled', async () => {
+ it('should not render new library button when libraries-v2 disabled', () => {
mockUseSelector.mockReturnValue({
...studioHomeMock,
courseCreatorStatus: COURSE_CREATOR_STATES.granted,
librariesV2Enabled: false,
});
- render(, { path: '/home' });
- await waitFor(() => {
- const createNewLibraryButton = screen.getByRole('button', { name: 'New library' });
- fireEvent.click(createNewLibraryButton);
- expect(mockNavigate).toHaveBeenCalledWith('/libraries-v1/create');
- });
+ render(, { path: '/home' });
+ expect(screen.queryByRole('button', { name: 'New library' })).not.toBeInTheDocument();
});
- it('should navigate to the library authoring page in course authoring', async () => {
+ it('should render a link to the library authoring page when libraries-v2 enabled', () => {
mockUseSelector.mockReturnValue({
...studioHomeMock,
librariesV1Enabled: false,
});
render(, { path: '/home' });
- const createNewLibraryButton = screen.getByRole('button', { name: 'New library' });
- fireEvent.click(createNewLibraryButton);
- expect(mockNavigate).toHaveBeenCalledWith('/library/create');
+ const createNewLibraryButton = screen.getByRole('link', { name: 'New library' });
+ expect(createNewLibraryButton).toHaveAttribute('href', '/library/create');
});
});
- it('does not render new library button for "v1 only" mode if showNewLibraryButton is False', () => {
+ it('does not render new library button when showNewLibraryV2Button is False', () => {
mockUseSelector.mockReturnValue({
...studioHomeMock,
- showNewLibraryButton: false,
- librariesV2Enabled: false,
+ showNewLibraryV2Button: false,
});
render(, { path: '/home' });
expect(screen.queryByRole('button', { name: 'New library' })).not.toBeInTheDocument();
});
- it('render new library button for "v2 only" mode even if showNewLibraryButton is False', () => {
- mockUseSelector.mockReturnValue({
- ...studioHomeMock,
- showNewLibraryButton: false,
- librariesV1Enabled: false,
- });
- render(, { path: '/home' });
- expect(screen.queryByRole('button', { name: 'New library' })).toBeInTheDocument();
- });
-
it('should render "create new course" container', async () => {
mockUseSelector.mockReturnValue({
...studioHomeMock,
diff --git a/src/studio-home/StudioHome.tsx b/src/studio-home/StudioHome.tsx
index a2be1984da..6de0c96393 100644
--- a/src/studio-home/StudioHome.tsx
+++ b/src/studio-home/StudioHome.tsx
@@ -8,10 +8,10 @@ import {
Row,
} from '@openedx/paragon';
import { Add as AddIcon, Error, ManageAccounts } from '@openedx/paragon/icons';
-import { useIntl } from '@edx/frontend-platform/i18n';
+import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
import { StudioFooterSlot } from '@edx/frontend-component-footer';
-import { useLocation, useNavigate } from 'react-router-dom';
+import { Link, useLocation } from 'react-router-dom';
import Loading from '../generic/Loading';
import InternetConnectionAlert from '../generic/internet-connection-alert';
@@ -29,8 +29,6 @@ import AlertMessage from '../generic/alert-message';
const StudioHome = () => {
const intl = useIntl();
const location = useLocation();
- const navigate = useNavigate();
-
const {
isLoadingPage,
isFailedLoadingPage,
@@ -44,21 +42,17 @@ const StudioHome = () => {
hasAbilityToCreateNewCourse,
isFiltered,
setShowNewCourseContainer,
+ canCreateNewLibrary,
librariesV1Enabled,
librariesV2Enabled,
} = useStudioHome();
const adminConsoleUrl = `${getConfig().ADMIN_CONSOLE_URL}/authz`;
- const v1LibraryTab = librariesV1Enabled && location?.pathname.split('/').pop() === 'libraries-v1';
- const showV2LibraryURL = librariesV2Enabled && !v1LibraryTab;
-
const {
userIsActive,
studioShortName,
studioRequestEmail,
- showNewLibraryButton,
- showNewLibraryV2Button,
} = studioHomeData;
const getHeaderButtons = useCallback(() => {
@@ -103,23 +97,16 @@ const StudioHome = () => {
);
}
- if ((showNewLibraryButton && !showV2LibraryURL) || (showV2LibraryURL && showNewLibraryV2Button)) {
- const newLibraryClick = () => {
- if (showV2LibraryURL) {
- navigate('/library/create');
- } else {
- navigate('/libraries-v1/create');
- }
- };
-
+ if (canCreateNewLibrary) {
headerButtons.push(
,
);
}
diff --git a/src/studio-home/__mocks__/studioHomeMock.ts b/src/studio-home/__mocks__/studioHomeMock.ts
index 5b8ff21f5c..6495efa831 100644
--- a/src/studio-home/__mocks__/studioHomeMock.ts
+++ b/src/studio-home/__mocks__/studioHomeMock.ts
@@ -69,7 +69,6 @@ export default {
optimizationEnabled: false,
requestCourseCreatorUrl: '/request_course_creator',
rerunCreatorStatus: true,
- showNewLibraryButton: true,
showNewLibraryV2Button: true,
studioName: 'Studio',
studioShortName: 'Studio',
diff --git a/src/studio-home/data/slice.ts b/src/studio-home/data/slice.ts
index 6c4b3b35f7..a16cedfba0 100644
--- a/src/studio-home/data/slice.ts
+++ b/src/studio-home/data/slice.ts
@@ -58,7 +58,6 @@ const slice = createSlice({
platformName?: string;
rerunCreatorStatus?: boolean;
requestCourseCreatorUrl?: string;
- showNewLibraryButton?: boolean;
showNewLibraryV2Button?: boolean;
studioRequestEmail?: string;
studioName?: string;
diff --git a/src/studio-home/factories/mockApiResponses.tsx b/src/studio-home/factories/mockApiResponses.tsx
index 055f9cf68d..c44c79f52f 100644
--- a/src/studio-home/factories/mockApiResponses.tsx
+++ b/src/studio-home/factories/mockApiResponses.tsx
@@ -36,7 +36,6 @@ export const generateGetStudioHomeDataApiResponse = (): DeprecatedReduxState['st
optimizationEnabled: false,
requestCourseCreatorUrl: '/request_course_creator',
rerunCreatorStatus: true,
- showNewLibraryButton: true,
showNewLibraryV2Button: true,
studioName: 'Studio',
studioShortName: 'Studio',
diff --git a/src/studio-home/hooks.tsx b/src/studio-home/hooks.tsx
index bc5dc249fe..2d9badf567 100644
--- a/src/studio-home/hooks.tsx
+++ b/src/studio-home/hooks.tsx
@@ -87,12 +87,14 @@ const useStudioHome = () => {
courseCreatorStatus,
librariesV1Enabled,
librariesV2Enabled,
+ showNewLibraryV2Button,
} = studioHomeData;
const isShowOrganizationDropdown = optimizationEnabled && courseCreatorStatus === COURSE_CREATOR_STATES.granted;
const isShowEmailStaff = courseCreatorStatus === COURSE_CREATOR_STATES.disallowedForThisSite && !!studioRequestEmail;
const isShowProcessing = allowCourseReruns && rerunCreatorStatus && inProcessCourseActions?.length > 0;
const hasAbilityToCreateNewCourse = courseCreatorStatus === COURSE_CREATOR_STATES.granted;
+ const canCreateNewLibrary = !!librariesV2Enabled && !!showNewLibraryV2Button;
const anyQueryIsPending = [deleteNotificationSavingStatus, courseCreatorSavingStatus, savingCreateRerunStatus]
.includes(RequestStatus.PENDING);
const anyQueryIsFailed = [deleteNotificationSavingStatus, courseCreatorSavingStatus, savingCreateRerunStatus]
@@ -111,6 +113,7 @@ const useStudioHome = () => {
courseCreatorSavingStatus,
isShowOrganizationDropdown,
hasAbilityToCreateNewCourse,
+ canCreateNewLibrary,
isFiltered,
setShowNewCourseContainer,
librariesV1Enabled,