Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add library v2 component in course #75

Merged
merged 4 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/course-unit/CourseUnit.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ describe('<CourseUnit />', () => {

await waitFor(() => {
const problemButton = getByRole('button', {
name: new RegExp(`${addComponentMessages.buttonText.defaultMessage} Problem`, 'i'),
name: new RegExp(`problem ${addComponentMessages.buttonText.defaultMessage} Problem`, 'i'),
});

userEvent.click(problemButton);
Expand Down
29 changes: 28 additions & 1 deletion src/course-unit/add-component/AddComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { useIntl } from '@edx/frontend-platform/i18n';
import { useToggle } from '@openedx/paragon';
import { StandardModal, useToggle } from '@openedx/paragon';

import { getCourseSectionVertical } from '../data/selectors';
import { COMPONENT_TYPES } from '../../generic/block-type-utils/constants';
import ComponentModalView from './add-component-modals/ComponentModalView';
import AddComponentButton from './add-component-btn';
import messages from './messages';
import { ComponentPicker } from '../../library-authoring/component-picker';

const AddComponent = ({ blockId, handleCreateNewCourseXBlock }) => {
const navigate = useNavigate();
Expand All @@ -17,6 +18,17 @@ const AddComponent = ({ blockId, handleCreateNewCourseXBlock }) => {
const [isOpenHtml, openHtml, closeHtml] = useToggle(false);
const [isOpenOpenAssessment, openOpenAssessment, closeOpenAssessment] = useToggle(false);
const { componentTemplates } = useSelector(getCourseSectionVertical);
const [isAddLibraryContentModalOpen, showAddLibraryContentModal, closeAddLibraryContentModal] = useToggle();

const handleLibraryV2Selection = (selection) => {
handleCreateNewCourseXBlock({
type: COMPONENT_TYPES.libraryV2,
category: selection.blockType,
parentLocator: blockId,
libraryContentKey: selection.usageKey,
});
closeAddLibraryContentModal();
};

const handleCreateNewXBlock = (type, moduleName) => {
switch (type) {
Expand All @@ -38,6 +50,9 @@ const AddComponent = ({ blockId, handleCreateNewCourseXBlock }) => {
case COMPONENT_TYPES.itembank:
handleCreateNewCourseXBlock({ type, category: 'itembank', parentLocator: blockId });
break;
case COMPONENT_TYPES.libraryV2:
showAddLibraryContentModal();
break;
case COMPONENT_TYPES.advanced:
handleCreateNewCourseXBlock({
type: moduleName, category: moduleName, parentLocator: blockId,
Expand Down Expand Up @@ -122,6 +137,18 @@ const AddComponent = ({ blockId, handleCreateNewCourseXBlock }) => {
);
})}
</ul>
<StandardModal
title="Select component"
isOpen={isAddLibraryContentModalOpen}
onClose={closeAddLibraryContentModal}
isOverflowVisible={false}
size="xl"
>
<ComponentPicker
showOnlyPublished
onComponentSelected={handleLibraryV2Selection}
/>
</StandardModal>
Comment on lines +140 to +151
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have the ComponentPickerModal, but I'm fine as you did here.
Maybe we shouldn't need the modal component after all.

Copy link
Member Author

@navinkarkera navinkarkera Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rpenido Yes, we can use it but then have to modify it to have the same props as the ComponentPicker component in addition to StandardModal props.

Maybe we shouldn't need the modal component after all.

Agreed, it doesn't seem to be doing much except for wrapping component picker in a modal component.

Update: Removed: f32ae3e

</div>
);
};
Expand Down
35 changes: 32 additions & 3 deletions src/course-unit/add-component/AddComponent.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ let axiosMock;
const blockId = '123';
const handleCreateNewCourseXBlockMock = jest.fn();

// Mock ComponentPicker to call onComponentSelected on load
jest.mock('../../library-authoring/component-picker', () => ({
ComponentPicker: (props) => props.onComponentSelected({ usageKey: 'test-usage-key', blockType: 'html' }),
}));

const renderComponent = (props) => render(
<AppProvider store={store}>
<IntlProvider locale="en">
Expand Down Expand Up @@ -61,7 +66,11 @@ describe('<AddComponent />', () => {
expect(getByRole('heading', { name: messages.title.defaultMessage })).toBeInTheDocument();
Object.keys(componentTemplates).forEach((component) => {
const btn = getByRole('button', {
name: new RegExp(`${messages.buttonText.defaultMessage} ${componentTemplates[component].display_name}`, 'i'),
name: new RegExp(
`${componentTemplates[component].type
} ${messages.buttonText.defaultMessage} ${componentTemplates[component].display_name}`,
'i',
),
});
expect(btn).toBeInTheDocument();
if (component.beta) {
Expand Down Expand Up @@ -115,7 +124,11 @@ describe('<AddComponent />', () => {
}

return expect(getByRole('button', {
name: new RegExp(`${messages.buttonText.defaultMessage} ${componentTemplates[component].display_name}`, 'i'),
name: new RegExp(
`${componentTemplates[component].type
} ${messages.buttonText.defaultMessage} ${componentTemplates[component].display_name}`,
'i',
),
})).toBeInTheDocument();
});
});
Expand Down Expand Up @@ -180,7 +193,7 @@ describe('<AddComponent />', () => {
const { getByRole } = renderComponent();

const discussionButton = getByRole('button', {
name: new RegExp(`${messages.buttonText.defaultMessage} Problem`, 'i'),
name: new RegExp(`problem ${messages.buttonText.defaultMessage} Problem`, 'i'),
});

userEvent.click(discussionButton);
Expand Down Expand Up @@ -399,6 +412,22 @@ describe('<AddComponent />', () => {
});
});

it('shows library picker on clicking v2 library content btn', async () => {
const { findByRole } = renderComponent();
const libBtn = await findByRole('button', {
name: new RegExp(`${messages.buttonText.defaultMessage} Library content`, 'i'),
});

userEvent.click(libBtn);
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalled();
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalledWith({
type: COMPONENT_TYPES.libraryV2,
parentLocator: '123',
category: 'html',
libraryContentKey: 'test-usage-key',
});
});

describe('component support label', () => {
it('component support label is hidden if component support legend is disabled', async () => {
const supportLevels = ['fs', 'ps'];
Expand Down
10 changes: 9 additions & 1 deletion src/course-unit/data/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,16 @@ export async function getCourseSectionVerticalData(unitId) {
* @param {string} [options.displayName] - The display name.
* @param {string} [options.boilerplate] - The boilerplate.
* @param {string} [options.stagedContent] - The staged content.
* @param {string} [options.libraryContentKey] - component key from library if being imported.
*/
export async function createCourseXblock({
type, category, parentLocator, displayName, boilerplate, stagedContent,
type,
category,
parentLocator,
displayName,
boilerplate,
stagedContent,
libraryContentKey,
}) {
const body = {
type,
Expand All @@ -74,6 +81,7 @@ export async function createCourseXblock({
parent_locator: parentLocator,
display_name: displayName,
staged_content: stagedContent,
library_content_key: libraryContentKey,
};

const { data } = await getAuthenticatedHttpClient()
Expand Down
5 changes: 3 additions & 2 deletions src/library-authoring/component-picker/ComponentPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const defaultSelectionChangedCallback: ComponentSelectionChangedEvent = (selecti
window.parent.postMessage({ type: 'pickerSelectionChanged', selections }, '*');
};

type ComponentPickerProps = { libraryId?: string } & (
type ComponentPickerProps = { libraryId?: string, showOnlyPublished?: boolean } & (
{
componentPickerMode?: 'single',
onComponentSelected?: ComponentSelectedEvent,
Expand All @@ -51,6 +51,7 @@ type ComponentPickerProps = { libraryId?: string } & (
export const ComponentPicker: React.FC<ComponentPickerProps> = ({
/** Restrict the component picker to a specific library */
libraryId,
showOnlyPublished,
componentPickerMode = 'single',
/** This default callback is used to send the selected component back to the parent window,
* when the component picker is used in an iframe.
Expand Down Expand Up @@ -99,7 +100,7 @@ export const ComponentPicker: React.FC<ComponentPickerProps> = ({
<Stepper.Step eventKey="pick-components" title="Pick some components">
<LibraryProvider
libraryId={selectedLibrary}
showOnlyPublished={variant === 'published'}
showOnlyPublished={variant === 'published' || showOnlyPublished}
{...libraryProviderProps}
>
<InnerComponentPicker returnToLibrarySelection={returnToLibrarySelection} />
Expand Down