refactor: course import analysis and details page in libraries (#2774)

* Updates analysis details body text
* Updates partial banner text
* Rounds percentage of supported blocks
* Removes unsupported children blocks from total counts and percentages.
* Updates spacing in analysis page.
This commit is contained in:
Navin Karkera
2025-12-31 21:06:44 +05:30
committed by GitHub
parent 0f20267cc4
commit 38dfb68286
5 changed files with 32 additions and 34 deletions

View File

@@ -109,6 +109,7 @@ describe('<ImportDetailsPage />', () => {
});
it('should render Partial Succeeded state', async () => {
const user = userEvent.setup();
mockGetModulestoreMigratedBlocksInfo.applyMockPartial();
(useGetContentHits as jest.Mock).mockReturnValue({
isPending: false,
@@ -143,12 +144,12 @@ describe('<ImportDetailsPage />', () => {
expect(await screen.findByText(/partial import successful/i)).toBeInTheDocument();
expect(await screen.findByText(/Total Blocks/i)).toBeInTheDocument();
expect(await screen.findByText('2/5')).toBeInTheDocument();
expect(await screen.findByText('2/3')).toBeInTheDocument();
expect(await screen.findByText(/Components/i)).toBeInTheDocument();
expect(await screen.findByText('1/4')).toBeInTheDocument();
expect(await screen.findByText('1/2')).toBeInTheDocument();
expect(await screen.findByText(
/40% of course test course has been imported successfully/i,
/66% of course test course has been imported successfully/i,
)).toBeInTheDocument();
expect(await screen.findByRole('cell', {
@@ -165,7 +166,7 @@ describe('<ImportDetailsPage />', () => {
name: /view imported content/i,
});
await viewImportedContentBtn.click();
await user.click(viewImportedContentBtn);
await waitFor(() => expect(mockNavigate).toHaveBeenCalledWith('/library/lib:Axim:TEST/collection/coll'));
});
});

View File

@@ -100,12 +100,6 @@ const ImportDetailsContent = () => {
// The migrations of this block is failed
counts.unsupported += 1;
resultUnsupportedIds.push(block.sourceKey);
if (block.unsupportedReason) {
// Verify if the unsupported block has children
const match = block.unsupportedReason.match(/It has (\d+) children/);
counts.unsupported += match ? Number(match[1]) : 0;
}
} else {
counts.totalBlocks += 1;
const blockType = getBlockType(block.sourceKey);

View File

@@ -181,12 +181,12 @@ const messages = defineMessages({
},
importCourseAnalysisCompleteSomeContentBody: {
id: 'library-authoring.import-course.review-details.analysis-complete.100.body',
defaultMessage: '{unsupportedBlockPercentage}% of content cannot be imported. For details see below.',
defaultMessage: '{supportedBlockPercentage}% of course content will be imported into a collection in your library called {courseName}. Some content will not be imported. For details see below.',
description: 'Body of the info card when course import analysis is complete and some data can be imported.',
},
importCourseAnalysisDetailsUnsupportedBlocksBody: {
id: 'library-authoring.import-course.review-details.analysis-details.unsupportedBlocks.body',
defaultMessage: 'The following block types cannot be imported into your library because they\'re are not yet supported. These block types will be replaced with a placeholder block in the library. For more information, reference the Help & Support sidebar.',
defaultMessage: 'Some block types cannot be imported into your library because theyre not yet supported. These blocks will be replaced with a placeholder block in the library. For more information, reference the Help & Support sidebar.',
description: 'Body of analysis details when some unsupported blocks are present',
},
importCourseComponentsUnsupportedInfo: {

View File

@@ -120,7 +120,7 @@ describe('ReviewImportDetails', () => {
expect(await screen.findByRole('alert')).toBeInTheDocument();
expect(await screen.findByText(/Import Analysis Complete/i)).toBeInTheDocument();
expect(await screen.findByText(
/12.50% of content cannot be imported. For details see below./i,
/88% of course content will be imported into a collection in your library called Test Course. Some content will not be imported. For details see below./i,
)).toBeInTheDocument();
expect(await screen.findByText(/Total Blocks/i)).toBeInTheDocument();
expect(await screen.findByText('7/8')).toBeInTheDocument();
@@ -135,7 +135,7 @@ describe('ReviewImportDetails', () => {
expect(markAnalysisComplete).toHaveBeenCalledWith(true);
});
it('considers children blocks of unsupportedBlocks', async () => {
it('skips children blocks from total counts', async () => {
(useCourseDetails as jest.Mock).mockReturnValue({ isPending: false, data: { title: 'Test Course' } });
(useMigrationInfo as jest.Mock).mockReturnValue({
isPending: false,
@@ -161,7 +161,7 @@ describe('ReviewImportDetails', () => {
}).mockReturnValueOnce({
isPending: false,
data: {
problem: 2,
problem: 2, // should be ignored from total count.
},
});
@@ -170,10 +170,10 @@ describe('ReviewImportDetails', () => {
expect(await screen.findByRole('alert')).toBeInTheDocument();
expect(await screen.findByText(/Import Analysis Complete/i)).toBeInTheDocument();
expect(await screen.findByText(
/25.00% of content cannot be imported. For details see below./i,
/90% of course content will be imported into a collection in your library called Test Course. Some content will not be imported. For details see below./i,
)).toBeInTheDocument();
expect(await screen.findByText(/Total Blocks/i)).toBeInTheDocument();
expect(await screen.findByText('9/12')).toBeInTheDocument();
expect(await screen.findByText('9/10')).toBeInTheDocument();
expect(await screen.findByText('Sections')).toBeInTheDocument();
expect(await screen.findByText('1')).toBeInTheDocument();
expect(await screen.findByText('Subsections')).toBeInTheDocument();
@@ -181,7 +181,7 @@ describe('ReviewImportDetails', () => {
expect(await screen.findByText('Units')).toBeInTheDocument();
expect(await screen.findByText('3')).toBeInTheDocument();
expect(await screen.findByText('Components')).toBeInTheDocument();
expect(await screen.findByText('3/6')).toBeInTheDocument();
expect(await screen.findByText('3/4')).toBeInTheDocument();
expect(markAnalysisComplete).toHaveBeenCalledWith(true);
});

View File

@@ -91,7 +91,8 @@ const Banner = ({ courseId, isBlockDataPending, unsupportedBlockPercentage }: Ba
<FormattedMessage
{...messages.importCourseAnalysisCompleteSomeContentBody}
values={{
unsupportedBlockPercentage: unsupportedBlockPercentage.toFixed(2),
supportedBlockPercentage: (100 - unsupportedBlockPercentage).toFixed(0),
courseName: data?.title || '',
}}
/>
</p>
@@ -207,8 +208,8 @@ export const ReviewImportDetails = ({ courseId, markAnalysisComplete }: Props) =
if (!blockTypes || !totalBlocks) {
return 0;
}
return (finalUnssupportedBlocks / (totalBlocks + finalUnssupportedBlocks)) * 100;
}, [blockTypes, finalUnssupportedBlocks]);
return (totalUnsupportedBlocks / (totalBlocks + totalUnsupportedBlocks)) * 100;
}, [blockTypes, totalUnsupportedBlocks]);
return (
<Stack gap={4}>
@@ -217,24 +218,26 @@ export const ReviewImportDetails = ({ courseId, markAnalysisComplete }: Props) =
isBlockDataPending={isBlockDataPending}
unsupportedBlockPercentage={unsupportedBlockPercentage}
/>
<h4><FormattedMessage {...messages.importCourseAnalysisSummary} /></h4>
<SummaryCard
totalBlocks={totalBlocks}
totalComponents={totalComponents}
sections={blockTypes?.chapter}
subsections={blockTypes?.sequential}
units={blockTypes?.vertical}
unsupportedBlocks={finalUnssupportedBlocks}
isPending={isBlockDataPending}
/>
{!isBlockDataPending && finalUnssupportedBlocks > 0
<Stack gap={2}>
<h4><FormattedMessage {...messages.importCourseAnalysisSummary} /></h4>
<SummaryCard
totalBlocks={totalBlocks}
totalComponents={totalComponents}
sections={blockTypes?.chapter}
subsections={blockTypes?.sequential}
units={blockTypes?.vertical}
unsupportedBlocks={totalUnsupportedBlocks}
isPending={isBlockDataPending}
/>
</Stack>
{!isBlockDataPending && totalUnsupportedBlocks > 0
&& (
<>
<Stack gap={2}>
<h4><FormattedMessage {...messages.importCourseAnalysisDetails} /></h4>
<Stack className="align-items-center" gap={3}>
<FormattedMessage {...messages.importCourseAnalysisDetailsUnsupportedBlocksBody} />
</Stack>
</>
</Stack>
)}
</Stack>
);