Skip to content

Commit 2b0c3a4

Browse files
committed
fix: include children component while analyzing course for imports
Currently, components with children are not supported by libraries v2. The analysis step before importing a course considers the parent block while counting unsupported blocks but does not include children in the unsupported count. We fetch usage_keys of all unsupported blocks and fetch the children blocks that contain these usage_keys in their breadcrumb field i.e., they are part of the unsupported blocks.
1 parent 2215fc5 commit 2b0c3a4

5 files changed

Lines changed: 89 additions & 22 deletions

File tree

.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ INVITE_STUDENTS_EMAIL_TO=''
4545
ENABLE_CHECKLIST_QUALITY=''
4646
ENABLE_GRADING_METHOD_IN_PROBLEMS=false
4747
# "Multi-level" blocks are unsupported in libraries
48-
LIBRARY_UNSUPPORTED_BLOCKS="conditional,step-builder,problem-builder"
48+
LIBRARY_UNSUPPORTED_BLOCKS="conditional,step-builder,problem-builder,library_content"
4949
# Fallback in local style files
5050
PARAGON_THEME_URLS={}
5151
COURSE_TEAM_SUPPORT_EMAIL=''

.env.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,6 @@ INVITE_STUDENTS_EMAIL_TO="[email protected]"
4040
ENABLE_CHECKLIST_QUALITY=true
4141
ENABLE_GRADING_METHOD_IN_PROBLEMS=false
4242
# "Multi-level" blocks are unsupported in libraries
43-
LIBRARY_UNSUPPORTED_BLOCKS="conditional,step-builder,problem-builder"
43+
LIBRARY_UNSUPPORTED_BLOCKS="conditional,step-builder,problem-builder,library_content"
4444
PARAGON_THEME_URLS=
4545
COURSE_TEAM_SUPPORT_EMAIL='[email protected]'

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

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { useMigrationInfo } from '@src/library-authoring/data/apiHooks';
1111
import { useGetBlockTypes } from '@src/search-manager';
1212
import { SummaryCard } from './SummaryCard';
1313
import messages from '../messages';
14+
import { useGetContentHits } from '../../../search-manager/data/apiHooks';
1415

1516
interface Props {
1617
courseId?: string;
@@ -123,26 +124,55 @@ export const ReviewImportDetails = ({ courseId, markAnalysisComplete }: Props) =
123124
markAnalysisComplete(!isBlockDataPending);
124125
}, [isBlockDataPending]);
125126

126-
const totalUnsupportedBlocks = useMemo(() => {
127+
const unsupportedBlockTypes = useMemo(() => {
127128
if (!blockTypes) {
129+
return undefined;
130+
}
131+
return Object.entries(blockTypes).filter(([blockType, ]) => (
132+
getConfig().LIBRARY_UNSUPPORTED_BLOCKS.includes(blockType)
133+
));
134+
}, [blockTypes]);
135+
136+
const totalUnsupportedBlocks = useMemo(() => {
137+
if (!unsupportedBlockTypes) {
128138
return 0;
129139
}
130-
const unsupportedBlocks = Object.entries(blockTypes).reduce((total, [blockType, count]) => {
131-
const isUnsupportedBlock = getConfig().LIBRARY_UNSUPPORTED_BLOCKS.includes(blockType);
132-
if (isUnsupportedBlock) {
133-
return total + count;
134-
}
135-
return total;
140+
const unsupportedBlocks = unsupportedBlockTypes.reduce((total, [, count]) => {
141+
return total + count;
136142
}, 0);
137143
return unsupportedBlocks;
138-
}, [blockTypes]);
144+
}, [unsupportedBlockTypes]);
145+
146+
const { data: unsupportedBlocksData } = useGetContentHits([
147+
`context_key = "${courseId}"`,
148+
`block_type IN [${unsupportedBlockTypes?.flatMap(([value]) => `"${value}"`).join(',')}]`,
149+
], totalUnsupportedBlocks > 0, totalUnsupportedBlocks, 'always')
150+
151+
const { data: unsupportedBlocksChildren } = useGetBlockTypes([
152+
`context_key = "${courseId}"`,
153+
`breadcrumbs.usage_key IN [${unsupportedBlocksData?.hits.map((value) => `"${value.usage_key}"`).join(',')}]`,
154+
], (unsupportedBlocksData?.estimatedTotalHits || 0) > 0);
155+
156+
const totalUnsupportedBlockChildren = useMemo(() => {
157+
if (!unsupportedBlocksChildren) {
158+
return 0;
159+
}
160+
const unsupportedBlocks = Object.values(unsupportedBlocksChildren).reduce((total, count) => {
161+
return total + count;
162+
}, 0);
163+
return unsupportedBlocks;
164+
}, [unsupportedBlocksChildren]);
165+
166+
const finalUnssupportedBlocks = useMemo(() => {
167+
return totalUnsupportedBlocks + totalUnsupportedBlockChildren;
168+
}, [totalUnsupportedBlocks, totalUnsupportedBlockChildren]);
139169

140170
const totalBlocks = useMemo(() => {
141171
if (!blockTypes) {
142172
return undefined;
143173
}
144-
return Object.values(blockTypes).reduce((total, block) => total + block, 0) - totalUnsupportedBlocks;
145-
}, [blockTypes]);
174+
return Object.values(blockTypes).reduce((total, block) => total + block, 0) - finalUnssupportedBlocks;
175+
}, [blockTypes, finalUnssupportedBlocks]);
146176

147177
const totalComponents = useMemo(() => {
148178
if (!blockTypes) {
@@ -157,15 +187,15 @@ export const ReviewImportDetails = ({ courseId, markAnalysisComplete }: Props) =
157187
return total;
158188
},
159189
0,
160-
) - totalUnsupportedBlocks;
161-
}, [blockTypes]);
190+
) - finalUnssupportedBlocks;
191+
}, [blockTypes, finalUnssupportedBlocks]);
162192

163193
const unsupportedBlockPercentage = useMemo(() => {
164194
if (!blockTypes || !totalBlocks) {
165195
return 0;
166196
}
167-
return (totalUnsupportedBlocks / (totalBlocks + totalUnsupportedBlocks)) * 100;
168-
}, [blockTypes]);
197+
return (finalUnssupportedBlocks / (totalBlocks + finalUnssupportedBlocks)) * 100;
198+
}, [blockTypes, finalUnssupportedBlocks]);
169199

170200
return (
171201
<Stack gap={4}>
@@ -181,10 +211,10 @@ export const ReviewImportDetails = ({ courseId, markAnalysisComplete }: Props) =
181211
sections={blockTypes?.chapter}
182212
subsections={blockTypes?.sequential}
183213
units={blockTypes?.vertical}
184-
unsupportedBlocks={totalUnsupportedBlocks}
214+
unsupportedBlocks={finalUnssupportedBlocks}
185215
isPending={isBlockDataPending}
186216
/>
187-
{!isBlockDataPending && totalUnsupportedBlocks > 0
217+
{!isBlockDataPending && finalUnssupportedBlocks > 0
188218
&& (
189219
<>
190220
<h4><FormattedMessage {...messages.importCourseAnalysisDetails} /></h4>

src/search-manager/data/api.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
22
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
33
import type {
4-
Filter, MeiliSearch, MultiSearchQuery,
4+
Filter, MeiliSearch, MultiSearchQuery, SearchResponse,
55
} from 'meilisearch';
66
import { ContainerType } from '../../generic/key-utils';
77

@@ -552,3 +552,23 @@ export async function fetchTagsThatMatchKeyword({
552552

553553
return { matches: Array.from(matches).map((tagPath) => ({ tagPath })), mayBeMissingResults: hits.length === limit };
554554
}
555+
556+
/**
557+
* Fetch the blocks that match query
558+
*/
559+
export const fetchContentHits = async (
560+
client: MeiliSearch,
561+
indexName: string,
562+
extraFilter?: Filter,
563+
limit?: number,
564+
): Promise<SearchResponse<Record<string, any>>> => {
565+
// Convert 'extraFilter' into an array
566+
const extraFilterFormatted = forceArray(extraFilter);
567+
568+
const results = await client.index(indexName).search("", {
569+
filter: extraFilterFormatted,
570+
limit,
571+
});
572+
573+
return results;
574+
};

src/search-manager/data/apiHooks.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { keepPreviousData, useInfiniteQuery, useQuery } from '@tanstack/react-query';
2+
import { keepPreviousData, skipToken, useInfiniteQuery, useQuery } from '@tanstack/react-query';
33
import { type Filter, MeiliSearch } from 'meilisearch';
44

55
import {
@@ -11,6 +11,7 @@ import {
1111
getContentSearchConfig,
1212
fetchBlockTypes,
1313
type PublishStatus,
14+
fetchContentHits,
1415
} from './api';
1516

1617
/**
@@ -286,7 +287,7 @@ export const useTagFilterOptions = (args: {
286287
return { ...mainQuery, data };
287288
};
288289

289-
export const useGetBlockTypes = (extraFilters: Filter) => {
290+
export const useGetBlockTypes = (extraFilters: Filter, enabled: boolean = true) => {
290291
const { client, indexName } = useContentSearchConnection();
291292
return useQuery({
292293
enabled: client !== undefined && indexName !== undefined,
@@ -298,7 +299,23 @@ export const useGetBlockTypes = (extraFilters: Filter) => {
298299
extraFilters,
299300
'block_types',
300301
],
301-
queryFn: () => fetchBlockTypes(client!, indexName!, extraFilters),
302+
queryFn: enabled ? () => fetchBlockTypes(client!, indexName!, extraFilters): skipToken,
302303
refetchOnMount: 'always',
303304
});
304305
};
306+
307+
export const useGetContentHits = (extraFilters: Filter, enabled: boolean = true, limit?: number, refetchOnMount?: boolean | 'always') => {
308+
const { client, indexName } = useContentSearchConnection();
309+
return useQuery({
310+
enabled: client !== undefined && indexName !== undefined,
311+
queryKey: [
312+
'content_search',
313+
client?.config.apiKey,
314+
client?.config.host,
315+
indexName,
316+
extraFilters,
317+
],
318+
queryFn: enabled ? () => fetchContentHits(client!, indexName!, extraFilters, limit): skipToken,
319+
refetchOnMount,
320+
});
321+
};

0 commit comments

Comments
 (0)