Skip to content

Commit 2c76950

Browse files
committed
refactor: Import blocked state implemented
1 parent 144e2cf commit 2c76950

File tree

3 files changed

+91
-41
lines changed

3 files changed

+91
-41
lines changed

src/library-authoring/import-course/messages.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,23 @@ const messages = defineMessages({
313313
defaultMessage: 'Reason For Failed import',
314314
description: 'Label for the Reason For Failed import field in the Reasons table in the import details',
315315
},
316+
importBlockedTitle: {
317+
id: 'library-authoring.import-course.review-details.import-blocked.title',
318+
defaultMessage: 'Import Blocked',
319+
description: 'Title for the alert in review details when the import is blocked',
320+
},
321+
importBlockedBody: {
322+
id: 'library-authoring.import-course.review-details.import-blocked.body',
323+
defaultMessage: 'This import would exceed the Content Library limit of {limitNumber} items.'
324+
+ ' To prevent incomplete or lost content, the import has been blocked. For more information,'
325+
+ ' view the Content Library documentation.',
326+
description: 'Body for the alert in review details when the import is blocked',
327+
},
328+
importNotPossibleTooltip: {
329+
id: 'library-authoring.import-course.review-details.import-blocked.import-course-btn.tooltip',
330+
defaultMessage: 'Import not possible',
331+
description: 'Label for the tooltip for the import button in review details when the import is blocked',
332+
},
316333
});
317334

318335
export default messages;

src/library-authoring/import-course/stepper/ImportStepperPage.tsx

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { Helmet } from 'react-helmet';
33
import { useNavigate } from 'react-router-dom';
44
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
55
import {
6-
ActionRow, Button, Chip, Container, Layout, Stepper,
6+
ActionRow, Button, Chip, Container, Layout, OverlayTrigger, Stepper,
7+
Tooltip,
78
} from '@openedx/paragon';
89

910
import { CoursesList, MigrationStatusProps } from '@src/studio-home/tabs-section/courses-tab';
@@ -72,6 +73,7 @@ export const ImportStepperPage = () => {
7273
const [currentStep, setCurrentStep] = useState<MigrationStep>('select-course');
7374
const [selectedCourseId, setSelectedCourseId] = useState<string>();
7475
const [analysisCompleted, setAnalysisCompleted] = useState<boolean>(false);
76+
const [importIsBlocked, setImportIsBlocked] = useState<boolean>(false);
7577
const { data: courseData } = useCourseDetails(selectedCourseId);
7678
const { libraryId, libraryData, readOnly } = useLibraryContext();
7779
const { showToast } = useContext(ToastContext);
@@ -152,6 +154,7 @@ export const ImportStepperPage = () => {
152154
>
153155
<ReviewImportDetails
154156
markAnalysisComplete={setAnalysisCompleted}
157+
setImportIsBlocked={setImportIsBlocked}
155158
courseId={selectedCourseId}
156159
/>
157160
</Stepper.Step>
@@ -175,12 +178,27 @@ export const ImportStepperPage = () => {
175178
<Button onClick={() => setCurrentStep('select-course')} variant="tertiary">
176179
<FormattedMessage {...messages.importCourseBack} />
177180
</Button>
178-
<LoadingButton
179-
onClick={handleImportCourse}
180-
label={intl.formatMessage(messages.importCourseButton)}
181-
variant="primary"
182-
disabled={!analysisCompleted}
183-
/>
181+
{importIsBlocked ? (
182+
<OverlayTrigger
183+
placement="top"
184+
overlay={(
185+
<Tooltip id="tooltip-import-course-button">
186+
<FormattedMessage {...messages.importNotPossibleTooltip} />
187+
</Tooltip>
188+
)}
189+
>
190+
<Button variant="primary" disabled>
191+
<FormattedMessage {...messages.importCourseButton} />
192+
</Button>
193+
</OverlayTrigger>
194+
) : (
195+
<LoadingButton
196+
onClick={handleImportCourse}
197+
label={intl.formatMessage(messages.importCourseButton)}
198+
variant="primary"
199+
disabled={!analysisCompleted}
200+
/>
201+
)}
184202
</ActionRow>
185203
)}
186204
</div>

src/library-authoring/import-course/stepper/ReviewImportDetails.tsx

Lines changed: 49 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { LoadingSpinner } from '@src/generic/Loading';
55
import { useCourseDetails } from '@src/course-outline/data/apiHooks';
66

77
import { useEffect, useMemo } from 'react';
8-
import { CheckCircle, Warning } from '@openedx/paragon/icons';
8+
import { CheckCircle, Info, Warning } from '@openedx/paragon/icons';
99
import { useLibraryContext } from '@src/library-authoring/common/context/LibraryContext';
1010
import { useLibraryBlockLimits, useMigrationInfo } from '@src/library-authoring/data/apiHooks';
1111
import { useGetBlockTypes, useGetContentHits } from '@src/search-manager';
@@ -15,6 +15,7 @@ import messages from '../messages';
1515
interface Props {
1616
courseId?: string;
1717
markAnalysisComplete: (analysisCompleted: boolean) => void;
18+
setImportIsBlocked: (importIsBlocked: boolean) => void;
1819
}
1920

2021
interface BannerProps {
@@ -114,14 +115,21 @@ const Banner = ({ courseId, isBlockDataPending, unsupportedBlockPercentage }: Ba
114115
);
115116
};
116117

117-
export const ReviewImportDetails = ({ courseId, markAnalysisComplete }: Props) => {
118+
export const ReviewImportDetails = ({
119+
courseId,
120+
markAnalysisComplete,
121+
setImportIsBlocked,
122+
}: Props) => {
118123
const { data: blockTypes, isPending: isBlockDataPending } = useGetBlockTypes([
119124
`context_key = "${courseId}"`,
120125
]);
121126
const {
122127
data: libraryBlockLimits,
123128
isPending: isPendinglibraryBlockLimits,
124129
} = useLibraryBlockLimits();
130+
const {
131+
libraryData,
132+
} = useLibraryContext();
125133

126134
useEffect(() => {
127135
// Mark complete to inform parent component of analysis completion.
@@ -176,21 +184,25 @@ export const ReviewImportDetails = ({ courseId, markAnalysisComplete }: Props) =
176184

177185
/** Finally calculate the final number of unsupported blocks by adding parent unsupported and children
178186
unsupported blocks. */
179-
let finalUnsupportedBlocks = useMemo(
187+
const finalUnsupportedBlocks = useMemo(
180188
() => totalUnsupportedBlocks + totalUnsupportedBlockChildren,
181189
[totalUnsupportedBlocks, totalUnsupportedBlockChildren],
182190
);
183191

192+
/** Calculate total supported blocks by subtracting final unsupported blocks from the total number of blocks */
193+
const totalBlocks = useMemo(() => {
194+
if (!blockTypes) {
195+
return undefined;
196+
}
197+
return Object.values(blockTypes).reduce((total, block) => total + block, 0) - finalUnsupportedBlocks;
198+
}, [blockTypes, finalUnsupportedBlocks]);
199+
184200
/** Calculate total components by excluding those that are chapters, sequential, or vertical. */
185-
/** Also, calculate if the total components exceed the limits */
186-
const { totalComponents, unsupportedByLimit } = useMemo(() => {
201+
const totalComponents = useMemo(() => {
187202
if (!blockTypes) {
188-
return {
189-
totalComponents: undefined,
190-
unsupportedByLimit: 0,
191-
};
203+
return undefined;
192204
}
193-
let resultTotalComponents = Object.entries(blockTypes).reduce(
205+
return Object.entries(blockTypes).reduce(
194206
(total, [blockType, count]) => {
195207
const isComponent = !['chapter', 'sequential', 'vertical'].includes(blockType);
196208
if (isComponent) {
@@ -200,32 +212,8 @@ export const ReviewImportDetails = ({ courseId, markAnalysisComplete }: Props) =
200212
},
201213
0,
202214
) - finalUnsupportedBlocks;
203-
204-
let resultUnsupportedByLimit = 0;
205-
if (libraryBlockLimits && resultTotalComponents > libraryBlockLimits.maxBlocksPerContentLibrary) {
206-
resultUnsupportedByLimit = resultTotalComponents - libraryBlockLimits.maxBlocksPerContentLibrary;
207-
resultTotalComponents -= resultUnsupportedByLimit;
208-
}
209-
210-
return {
211-
totalComponents: resultTotalComponents,
212-
unsupportedByLimit: resultUnsupportedByLimit,
213-
};
214215
}, [blockTypes, finalUnsupportedBlocks, libraryBlockLimits]);
215216

216-
// Adds the components exceed the limit to the final unsupported count
217-
if (unsupportedByLimit) {
218-
finalUnsupportedBlocks += unsupportedByLimit;
219-
}
220-
221-
/** Calculate total supported blocks by subtracting final unsupported blocks from the total number of blocks */
222-
const totalBlocks = useMemo(() => {
223-
if (!blockTypes) {
224-
return undefined;
225-
}
226-
return Object.values(blockTypes).reduce((total, block) => total + block, 0) - finalUnsupportedBlocks;
227-
}, [blockTypes, finalUnsupportedBlocks]);
228-
229217
/** Calculate the unsupported block percentage based on the final total blocks and unsupported blocks. */
230218
const unsupportedBlockPercentage = useMemo(() => {
231219
if (!blockTypes || !totalBlocks) {
@@ -234,6 +222,33 @@ export const ReviewImportDetails = ({ courseId, markAnalysisComplete }: Props) =
234222
return (finalUnsupportedBlocks / (totalBlocks + finalUnsupportedBlocks)) * 100;
235223
}, [blockTypes, finalUnsupportedBlocks]);
236224

225+
const limitIsExceeded = useMemo(() => (
226+
libraryData?.numBlocks || 0) + (totalBlocks || 0) > (libraryBlockLimits?.maxBlocksPerContentLibrary || 0
227+
), [libraryData?.numBlocks, totalBlocks, libraryBlockLimits?.maxBlocksPerContentLibrary]);
228+
229+
useEffect(() => {
230+
setImportIsBlocked(limitIsExceeded);
231+
}, [limitIsExceeded, setImportIsBlocked]);
232+
233+
// If the total blocks exceeds the permitted limit, render the page to block import
234+
if (limitIsExceeded) {
235+
return (
236+
<Stack gap={4}>
237+
<Alert variant="danger" icon={Info}>
238+
<Alert.Heading>
239+
<FormattedMessage {...messages.importBlockedTitle} />
240+
</Alert.Heading>
241+
</Alert>
242+
<FormattedMessage
243+
{...messages.importBlockedBody}
244+
values={{
245+
limitNumber: libraryBlockLimits?.maxBlocksPerContentLibrary || 0,
246+
}}
247+
/>
248+
</Stack>
249+
);
250+
}
251+
237252
return (
238253
<Stack gap={4}>
239254
<Banner

0 commit comments

Comments
 (0)