Skip to content

Commit

Permalink
feat: enable problem bank button functionality on unit page (#1480)
Browse files Browse the repository at this point in the history
  • Loading branch information
navinkarkera authored Nov 14, 2024
1 parent 033acc4 commit cee8888
Show file tree
Hide file tree
Showing 17 changed files with 230 additions and 96 deletions.
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"
>
<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

0 comments on commit cee8888

Please sign in to comment.