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: enable problem bank button functionality [FC-0062] #1480

Merged
merged 4 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
50 changes: 49 additions & 1 deletion src/course-unit/__mocks__/courseSectionVertical.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ module.exports = {
allow_unsupported_xblocks: false,
documentation_label: 'Your Platform Name Here Support Levels:',
},
beta: false,
},
{
type: 'discussion',
Expand All @@ -101,6 +102,7 @@ module.exports = {
allow_unsupported_xblocks: false,
documentation_label: 'Your Platform Name Here Support Levels:',
},
beta: false,
},
{
type: 'library',
Expand All @@ -114,12 +116,13 @@ module.exports = {
support_level: true,
},
],
display_name: 'Library Content',
display_name: 'Legacy Library Content',
support_legend: {
show_legend: false,
allow_unsupported_xblocks: false,
documentation_label: 'Your Platform Name Here Support Levels:',
},
beta: false,
},
{
type: 'html',
Expand Down Expand Up @@ -179,6 +182,7 @@ module.exports = {
allow_unsupported_xblocks: false,
documentation_label: 'Your Platform Name Here Support Levels:',
},
beta: false,
},
{
type: 'openassessment',
Expand Down Expand Up @@ -230,6 +234,7 @@ module.exports = {
allow_unsupported_xblocks: false,
documentation_label: 'Your Platform Name Here Support Levels:',
},
beta: false,
},
{
type: 'problem',
Expand All @@ -249,6 +254,7 @@ module.exports = {
allow_unsupported_xblocks: false,
documentation_label: 'Your Platform Name Here Support Levels:',
},
beta: false,
},
{
type: 'video',
Expand All @@ -268,6 +274,7 @@ module.exports = {
allow_unsupported_xblocks: false,
documentation_label: 'Your Platform Name Here Support Levels:',
},
beta: false,
},
{
type: 'drag-and-drop-v2',
Expand All @@ -287,6 +294,47 @@ module.exports = {
allow_unsupported_xblocks: false,
documentation_label: 'Your Platform Name Here Support Levels:',
},
beta: false,
},
{
type: 'library_v2',
templates: [
{
display_name: 'Library Content',
category: 'library_v2',
boilerplate_name: null,
hinted: false,
tab: 'advanced',
support_level: true,
},
],
display_name: 'Library Content',
support_legend: {
show_legend: false,
allow_unsupported_xblocks: false,
documentation_label: 'Your Platform Name Here Support Levels:',
},
beta: true,
},
{
type: 'itembank',
templates: [
{
display_name: 'Problem Bank',
category: 'itembank',
boilerplate_name: null,
hinted: false,
tab: 'advanced',
support_level: true,
},
],
display_name: 'Problem Bank',
support_legend: {
show_legend: false,
allow_unsupported_xblocks: false,
documentation_label: 'Your Platform Name Here Support Levels:',
},
beta: true,
},
],
course_sequence_ids: [
Expand Down
35 changes: 33 additions & 2 deletions 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 @@ -35,6 +47,12 @@ const AddComponent = ({ blockId, handleCreateNewCourseXBlock }) => {
case COMPONENT_TYPES.library:
handleCreateNewCourseXBlock({ type, category: 'library_content', parentLocator: blockId });
break;
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 @@ -67,7 +85,7 @@ const AddComponent = ({ blockId, handleCreateNewCourseXBlock }) => {
<h5 className="h3 mb-4 text-center">{intl.formatMessage(messages.title)}</h5>
<ul className="new-component-type list-unstyled m-0 d-flex flex-wrap justify-content-center">
{componentTemplates.map((component) => {
const { type, displayName } = component;
const { type, displayName, beta } = component;
let modalParams;

if (!component.templates.length) {
Expand Down Expand Up @@ -103,6 +121,7 @@ const AddComponent = ({ blockId, handleCreateNewCourseXBlock }) => {
onClick={() => handleCreateNewXBlock(type)}
displayName={displayName}
type={type}
beta={beta}
/>
</li>
);
Expand All @@ -118,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>
</div>
);
};
Expand Down
65 changes: 57 additions & 8 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 @@ -59,11 +64,19 @@ describe('<AddComponent />', () => {
const componentTemplates = courseSectionVerticalMock.component_templates;

expect(getByRole('heading', { name: messages.title.defaultMessage })).toBeInTheDocument();
Object.keys(componentTemplates).map((component) => (
expect(getByRole('button', {
name: new RegExp(`${messages.buttonText.defaultMessage} ${componentTemplates[component].display_name}`, 'i'),
})).toBeInTheDocument()
));
Object.keys(componentTemplates).forEach((component) => {
const btn = getByRole('button', {
name: new RegExp(
`${componentTemplates[component].type
} ${messages.buttonText.defaultMessage} ${componentTemplates[component].display_name}`,
'i',
),
});
expect(btn).toBeInTheDocument();
if (component.beta) {
expect(within(btn).queryByText('Beta')).toBeInTheDocument();
}
});
});

it('AddComponent component doesn\'t render when there aren\'t componentTemplates', async () => {
Expand Down Expand Up @@ -111,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 @@ -176,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 All @@ -187,6 +204,22 @@ describe('<AddComponent />', () => {
}, expect.any(Function));
});

it('calls handleCreateNewCourseXBlock with correct parameters when Problem bank xblock create button is clicked', () => {
const { getByRole } = renderComponent();

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

userEvent.click(problemBankBtn);
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalled();
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalledWith({
parentLocator: '123',
type: COMPONENT_TYPES.itembank,
category: 'itembank',
});
});

it('calls handleCreateNewCourseXBlock with correct parameters when Video xblock create button is clicked', () => {
const { getByRole } = renderComponent();

Expand All @@ -206,7 +239,7 @@ describe('<AddComponent />', () => {
const { getByRole } = renderComponent();

const libraryButton = getByRole('button', {
name: new RegExp(`${messages.buttonText.defaultMessage} Library Content`, 'i'),
name: new RegExp(`${messages.buttonText.defaultMessage} Legacy Library Content`, 'i'),
});

userEvent.click(libraryButton);
Expand Down Expand Up @@ -379,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
12 changes: 10 additions & 2 deletions src/course-unit/add-component/add-component-btn/index.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import PropTypes from 'prop-types';
import { Button } from '@openedx/paragon';
import { Badge, Button } from '@openedx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';

import messages from '../messages';
import AddComponentIcon from './AddComponentIcon';

const AddComponentButton = ({ type, displayName, onClick }) => {
const AddComponentButton = ({
type, displayName, onClick, beta,
}) => {
const intl = useIntl();

return (
Expand All @@ -17,14 +19,20 @@ const AddComponentButton = ({ type, displayName, onClick }) => {
<AddComponentIcon type={type} />
<span className="sr-only">{intl.formatMessage(messages.buttonText)}</span>
<span className="small mt-2">{displayName}</span>
{beta && <Badge className="pb-1 mt-1" variant="primary">Beta</Badge>}
</Button>
);
};

AddComponentButton.defaultProps = {
beta: false,
};

AddComponentButton.propTypes = {
type: PropTypes.string.isRequired,
displayName: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
beta: PropTypes.bool,
};

export default AddComponentButton;
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,11 @@ const ComponentModalView = ({
const isDisplaySupportLabel = supportLegend.showLegend && supportLabels[componentTemplate.supportLevel];

return (
<div className="d-flex justify-content-between w-100 mb-2.5 align-items-end">
<div
key={componentTemplate.displayName}
className="d-flex justify-content-between w-100 mb-2.5 align-items-end"
>
Comment on lines +61 to +64
Copy link
Contributor

Choose a reason for hiding this comment

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

Good catch!

<Form.Radio
key={componentTemplate.displayName}
className="add-component-modal-radio"
value={value}
>
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
Loading