Skip to content

Commit e80930e

Browse files
authored
fix: add components button in item bank children page (#2491)
`Add components` button in item bank children page was not working.
1 parent 66dad5f commit e80930e

File tree

10 files changed

+91
-70
lines changed

10 files changed

+91
-70
lines changed

src/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ export const COURSE_BLOCK_NAMES = ({
6262
libraryContent: { id: 'library_content', name: 'Library content' },
6363
splitTest: { id: 'split_test', name: 'Split Test' },
6464
component: { id: 'component', name: 'Component' },
65+
itembank: { id: 'itembank', name: 'Problem Bank' },
66+
legacyLibraryContent: { id: 'library_content', name: 'Randomized Content Block' },
6567
});
6668

6769
export const STUDIO_CLIPBOARD_CHANNEL = 'studio_clipboard_channel';

src/course-unit/CourseUnit.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const CourseUnit = ({ courseId }) => {
5050
isUnitVerticalType,
5151
isUnitLibraryType,
5252
isSplitTestType,
53+
isProblemBankType,
5354
staticFileNotices,
5455
currentlyVisibleToStudents,
5556
unitXBlockActions,
@@ -219,6 +220,7 @@ const CourseUnit = ({ courseId }) => {
219220
parentLocator={blockId}
220221
isSplitTestType={isSplitTestType}
221222
isUnitVerticalType={isUnitVerticalType}
223+
isProblemBankType={isProblemBankType}
222224
handleCreateNewCourseXBlock={handleCreateNewCourseXBlock}
223225
addComponentTemplateData={addComponentTemplateData}
224226
/>

src/course-unit/add-component/AddComponent.test.jsx renamed to src/course-unit/add-component/AddComponent.test.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { fetchCourseSectionVerticalData } from '../data/thunk';
1414
import { getCourseSectionVerticalApiUrl } from '../data/api';
1515
import { courseSectionVerticalMock } from '../__mocks__';
1616
import { COMPONENT_TYPES } from '../../generic/block-type-utils/constants';
17-
import AddComponent from './AddComponent';
17+
import AddComponent, { AddComponentProps } from './AddComponent';
1818
import messages from './messages';
1919
import { IframeProvider } from '../../generic/hooks/context/iFrameContext';
2020
import { messageTypes } from '../constants';
@@ -56,13 +56,11 @@ jest.mock('../../generic/hooks/context/hooks', () => ({
5656
}),
5757
}));
5858

59-
const renderComponent = (props) => render(
59+
const renderComponent = (props?: AddComponentProps) => render(
6060
<IframeProvider>
6161
<AddComponent
62-
blockId={blockId}
6362
isUnitVerticalType
6463
parentLocator={blockId}
65-
addComponentTemplateData={{}}
6664
handleCreateNewCourseXBlock={handleCreateNewCourseXBlockMock}
6765
{...props}
6866
/>
@@ -94,7 +92,7 @@ describe('<AddComponent />', () => {
9492
),
9593
});
9694
expect(btn).toBeInTheDocument();
97-
if (component.beta) {
95+
if (componentTemplates[component].beta) {
9896
expect(within(btn).queryByText('Beta')).toBeInTheDocument();
9997
}
10098
});

src/course-unit/add-component/AddComponent.jsx renamed to src/course-unit/add-component/AddComponent.tsx

Lines changed: 61 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,86 @@
11
import { useCallback, useState } from 'react';
2-
import PropTypes from 'prop-types';
32
import { useDispatch, useSelector } from 'react-redux';
43
import { getConfig } from '@edx/frontend-platform';
54
import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
65
import {
76
ActionRow, Button, StandardModal, useToggle,
87
} from '@openedx/paragon';
98

10-
import { getCourseSectionVertical, getCourseUnitData } from '../data/selectors';
11-
import { useWaffleFlags } from '../../data/apiHooks';
12-
import { COMPONENT_TYPES } from '../../generic/block-type-utils/constants';
13-
import ComponentModalView from './add-component-modals/ComponentModalView';
14-
import AddComponentButton from './add-component-btn';
15-
import messages from './messages';
16-
import { ComponentPicker } from '../../library-authoring/component-picker';
17-
import { ContentType } from '../../library-authoring/routes';
18-
import { messageTypes } from '../constants';
19-
import { useIframe } from '../../generic/hooks/context/hooks';
20-
import { useEventListener } from '../../generic/hooks';
21-
import VideoSelectorPage from '../../editors/VideoSelectorPage';
22-
import EditorPage from '../../editors/EditorPage';
9+
import { useWaffleFlags } from '@src/data/apiHooks';
10+
import { COMPONENT_TYPES } from '@src/generic/block-type-utils/constants';
11+
import { ComponentPicker } from '@src/library-authoring/component-picker';
12+
import { ContentType } from '@src/library-authoring/routes';
13+
import { useIframe } from '@src/generic/hooks/context/hooks';
14+
import { useEventListener } from '@src/generic/hooks';
15+
import VideoSelectorPage from '@src/editors/VideoSelectorPage';
16+
import EditorPage from '@src/editors/EditorPage';
17+
import { SelectedComponent } from '@src/library-authoring';
2318
import { fetchCourseSectionVerticalData } from '../data/thunk';
19+
import { messageTypes } from '../constants';
20+
import messages from './messages';
21+
import AddComponentButton from './add-component-btn';
22+
import ComponentModalView from './add-component-modals/ComponentModalView';
23+
import { getCourseSectionVertical, getCourseUnitData } from '../data/selectors';
24+
25+
type ComponentTemplateData = {
26+
displayName: string,
27+
category?: string,
28+
type: string,
29+
beta?: boolean,
30+
templates: Array<{
31+
boilerplateName?: string,
32+
category?: string,
33+
displayName: string,
34+
supportLevel?: string | boolean,
35+
}>,
36+
supportLegend: {
37+
allowUnsupportedXblocks?: boolean,
38+
documentationLabel?: string,
39+
showLegend?: boolean,
40+
},
41+
};
42+
43+
export interface AddComponentProps {
44+
isSplitTestType?: boolean,
45+
isUnitVerticalType?: boolean,
46+
parentLocator: string,
47+
handleCreateNewCourseXBlock: (
48+
args: object,
49+
callback?: (args: { courseKey: string, locator: string }) => void
50+
) => void,
51+
isProblemBankType?: boolean,
52+
addComponentTemplateData?: {
53+
blockId: string,
54+
parentLocator?: string,
55+
model: ComponentTemplateData,
56+
},
57+
}
2458

2559
const AddComponent = ({
2660
parentLocator,
2761
isSplitTestType,
2862
isUnitVerticalType,
63+
isProblemBankType,
2964
addComponentTemplateData,
3065
handleCreateNewCourseXBlock,
31-
}) => {
66+
}: AddComponentProps) => {
3267
const intl = useIntl();
3368
const dispatch = useDispatch();
3469

3570
const [isOpenAdvanced, openAdvanced, closeAdvanced] = useToggle(false);
3671
const [isOpenHtml, openHtml, closeHtml] = useToggle(false);
3772
const [isOpenOpenAssessment, openOpenAssessment, closeOpenAssessment] = useToggle(false);
3873
const { componentTemplates = {} } = useSelector(getCourseSectionVertical);
39-
const blockId = addComponentTemplateData.parentLocator || parentLocator;
74+
const blockId = addComponentTemplateData?.parentLocator || parentLocator;
4075
const [isAddLibraryContentModalOpen, showAddLibraryContentModal, closeAddLibraryContentModal] = useToggle();
4176
const [isVideoSelectorModalOpen, showVideoSelectorModal, closeVideoSelectorModal] = useToggle();
4277
const [isXBlockEditorModalOpen, showXBlockEditorModal, closeXBlockEditorModal] = useToggle();
4378

44-
const [blockType, setBlockType] = useState(null);
45-
const [courseId, setCourseId] = useState(null);
46-
const [newBlockId, setNewBlockId] = useState(null);
79+
const [blockType, setBlockType] = useState<string | null>(null);
80+
const [courseId, setCourseId] = useState<string | null>(null);
81+
const [newBlockId, setNewBlockId] = useState<string | null>(null);
4782
const [isSelectLibraryContentModalOpen, showSelectLibraryContentModal, closeSelectLibraryContentModal] = useToggle();
48-
const [selectedComponents, setSelectedComponents] = useState([]);
83+
const [selectedComponents, setSelectedComponents] = useState<SelectedComponent[]>([]);
4984
const [usageId, setUsageId] = useState(null);
5085
const { sendMessageToIframe } = useIframe();
5186
const { useVideoGalleryFlow } = useWaffleFlags(courseId ?? undefined);
@@ -84,7 +119,7 @@ const AddComponent = ({
84119
dispatch(fetchCourseSectionVerticalData(blockId, sequenceId));
85120
}, [closeXBlockEditorModal, closeVideoSelectorModal, sendMessageToIframe, blockId, sequenceId]);
86121

87-
const handleLibraryV2Selection = useCallback((selection) => {
122+
const handleLibraryV2Selection = useCallback((selection: SelectedComponent) => {
88123
handleCreateNewCourseXBlock({
89124
type: COMPONENT_TYPES.libraryV2,
90125
category: selection.blockType,
@@ -94,7 +129,7 @@ const AddComponent = ({
94129
closeAddLibraryContentModal();
95130
}, [usageId]);
96131

97-
const handleCreateNewXBlock = (type, moduleName) => {
132+
const handleCreateNewXBlock = (type: string, moduleName?: string) => {
98133
switch (type) {
99134
case COMPONENT_TYPES.discussion:
100135
case COMPONENT_TYPES.dragAndDrop:
@@ -156,16 +191,16 @@ const AddComponent = ({
156191
}
157192
};
158193

159-
if (isUnitVerticalType || isSplitTestType) {
194+
if (isUnitVerticalType || isSplitTestType || isProblemBankType) {
160195
return (
161196
<div className="py-4">
162197
{Object.keys(componentTemplates).length && isUnitVerticalType ? (
163198
<>
164199
<h5 className="h3 mb-4 text-center">{intl.formatMessage(messages.title)}</h5>
165200
<ul className="new-component-type list-unstyled m-0 d-flex flex-wrap justify-content-center">
166-
{componentTemplates.map((component) => {
201+
{componentTemplates.map((component: ComponentTemplateData) => {
167202
const { type, displayName, beta } = component;
168-
let modalParams;
203+
let modalParams: { open: () => void, close: () => void, isOpen: boolean };
169204

170205
if (!component.templates.length) {
171206
return null;
@@ -268,7 +303,7 @@ const AddComponent = ({
268303
/>
269304
</div>
270305
</StandardModal>
271-
{isXBlockEditorModalOpen && (
306+
{isXBlockEditorModalOpen && courseId && blockType && newBlockId && (
272307
<div className="editor-page">
273308
<EditorPage
274309
courseId={courseId}
@@ -288,32 +323,4 @@ const AddComponent = ({
288323
return null;
289324
};
290325

291-
AddComponent.propTypes = {
292-
isSplitTestType: PropTypes.bool.isRequired,
293-
isUnitVerticalType: PropTypes.bool.isRequired,
294-
parentLocator: PropTypes.string.isRequired,
295-
handleCreateNewCourseXBlock: PropTypes.func.isRequired,
296-
addComponentTemplateData: {
297-
blockId: PropTypes.string.isRequired,
298-
model: PropTypes.shape({
299-
displayName: PropTypes.string.isRequired,
300-
category: PropTypes.string,
301-
type: PropTypes.string.isRequired,
302-
templates: PropTypes.arrayOf(
303-
PropTypes.shape({
304-
boilerplateName: PropTypes.string,
305-
category: PropTypes.string,
306-
displayName: PropTypes.string.isRequired,
307-
supportLevel: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
308-
}),
309-
),
310-
supportLegend: PropTypes.shape({
311-
allowUnsupportedXblocks: PropTypes.bool,
312-
documentationLabel: PropTypes.string,
313-
showLegend: PropTypes.bool,
314-
}),
315-
}),
316-
},
317-
};
318-
319326
export default AddComponent;

src/course-unit/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export const getXBlockSupportMessages = (intl) => ({
4141

4242
export const messageTypes = {
4343
refreshXBlock: 'refreshXBlock',
44+
refreshIframe: 'refreshIframe',
4445
showMoveXBlockModal: 'showMoveXBlockModal',
4546
completeXBlockMoving: 'completeXBlockMoving',
4647
rollbackMovedXBlock: 'rollbackMovedXBlock',

src/course-unit/hooks.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ export const useCourseUnit = ({ courseId, blockId }) => {
7272
const isUnitVerticalType = unitCategory === COURSE_BLOCK_NAMES.vertical.id;
7373
const isUnitLibraryType = unitCategory === COURSE_BLOCK_NAMES.libraryContent.id;
7474
const isSplitTestType = unitCategory === COURSE_BLOCK_NAMES.splitTest.id;
75+
const isProblemBankType = [
76+
COURSE_BLOCK_NAMES.legacyLibraryContent.id,
77+
COURSE_BLOCK_NAMES.itembank.id,
78+
].includes(unitCategory);
7579

7680
const headerNavigationsActions = {
7781
handleViewLive: () => {
@@ -254,6 +258,7 @@ export const useCourseUnit = ({ courseId, blockId }) => {
254258
isUnitVerticalType,
255259
isUnitLibraryType,
256260
isSplitTestType,
261+
isProblemBankType,
257262
sharedClipboardData,
258263
showPasteXBlock,
259264
showPasteUnit,

src/course-unit/xblock-container-iframe/hooks/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export type UseMessageHandlersTypes = {
1515
handleOpenManageTagsModal: (id: string) => void;
1616
handleShowProcessingNotification: (variant: string) => void;
1717
handleHideProcessingNotification: () => void;
18+
handleRefreshIframe: () => void;
1819
};
1920

2021
export type MessageHandlersTypes = Record<string, (payload: any) => void>;

src/course-unit/xblock-container-iframe/hooks/useMessageHandlers.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const useMessageHandlers = ({
3131
handleShowProcessingNotification,
3232
handleHideProcessingNotification,
3333
handleEditXBlock,
34+
handleRefreshIframe,
3435
}: UseMessageHandlersTypes): MessageHandlersTypes => {
3536
const { copyToClipboard } = useClipboard();
3637

@@ -50,6 +51,7 @@ export const useMessageHandlers = ({
5051
[messageTypes.saveEditedXBlockData]: handleSaveEditedXBlockData,
5152
[messageTypes.studioAjaxError]: ({ error }) => handleResponseErrors(error, dispatch, updateSavingStatus),
5253
[messageTypes.refreshPositions]: handleFinishXBlockDragging,
54+
[messageTypes.refreshIframe]: handleRefreshIframe,
5355
[messageTypes.openManageTags]: (payload) => handleOpenManageTagsModal(payload.contentId),
5456
[messageTypes.addNewComponent]: () => handleShowProcessingNotification(NOTIFICATION_MESSAGES.adding),
5557
[messageTypes.pasteNewComponent]: () => handleShowProcessingNotification(NOTIFICATION_MESSAGES.pasting),

src/course-unit/xblock-container-iframe/index.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
4646
const intl = useIntl();
4747
const dispatch = useDispatch();
4848

49+
// Useful to reload iframe
50+
const [iframeKey, setIframeKey] = useState(0);
4951
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useToggle(false);
5052
const [isUnlinkModalOpen, openUnlinkModal, closeUnlinkModal] = useToggle(false);
5153
const [isConfigureModalOpen, openConfigureModal, closeConfigureModal] = useToggle(false);
@@ -182,6 +184,12 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
182184
dispatch(hideProcessingNotification());
183185
};
184186

187+
const handleRefreshIframe = () => {
188+
// Updating iframeKey forces the iframe to re-render.
189+
/* istanbul ignore next */
190+
setIframeKey((prev) => prev + 1);
191+
};
192+
185193
const messageHandlers = useMessageHandlers({
186194
courseId,
187195
dispatch,
@@ -199,6 +207,7 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
199207
handleShowProcessingNotification,
200208
handleHideProcessingNotification,
201209
handleEditXBlock,
210+
handleRefreshIframe,
202211
});
203212

204213
useIframeMessages(messageHandlers);
@@ -268,6 +277,7 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
268277
/>
269278
) : null}
270279
<iframe
280+
key={iframeKey}
271281
ref={iframeRef}
272282
title={intl.formatMessage(messages.xblockIframeTitle)}
273283
name="xblock-iframe"

src/library-authoring/component-picker/ComponentPicker.tsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,10 @@ type ComponentPickerProps = {
5353
showOnlyPublished?: boolean,
5454
extraFilter?: string[],
5555
visibleTabs?: ContentType[],
56-
} & (
57-
{
58-
componentPickerMode?: 'single',
59-
onComponentSelected?: ComponentSelectedEvent,
60-
onChangeComponentSelection?: never,
61-
} | {
62-
componentPickerMode: 'multiple'
63-
onComponentSelected?: never,
64-
onChangeComponentSelection?: ComponentSelectionChangedEvent,
65-
}
66-
);
56+
componentPickerMode?: 'single' | 'multiple',
57+
onComponentSelected?: ComponentSelectedEvent,
58+
onChangeComponentSelection?: ComponentSelectionChangedEvent,
59+
};
6760

6861
export const ComponentPicker: React.FC<ComponentPickerProps> = ({
6962
/** Restrict the component picker to a specific library */

0 commit comments

Comments
 (0)