Skip to content

Commit 24e4695

Browse files
authored
perf: use Library search results to populate container card preview (#1820)
* fix: use Library search results to populate container card preview * feat: show published children when showing only published Unit content * fix: nits
1 parent 0fdc460 commit 24e4695

File tree

3 files changed

+57
-25
lines changed

3 files changed

+57
-25
lines changed

src/library-authoring/components/ContainerCard.test.tsx

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
fireEvent,
77
} from '../../testUtils';
88
import { LibraryProvider } from '../common/context/LibraryContext';
9-
import { mockContentLibrary, mockGetContainerChildren } from '../data/api.mocks';
9+
import { mockContentLibrary } from '../data/api.mocks';
1010
import { type ContainerHit, PublishStatus } from '../../search-manager';
1111
import ContainerCard from './ContainerCard';
1212
import { getLibraryContainerApiUrl, getLibraryContainerRestoreApiUrl } from '../data/api';
@@ -40,7 +40,6 @@ let axiosMock: MockAdapter;
4040
let mockShowToast;
4141

4242
mockContentLibrary.applyMock();
43-
mockGetContainerChildren.applyMock();
4443

4544
const render = (ui: React.ReactElement, showOnlyPublished: boolean = false) => baseRender(ui, {
4645
extraWrapper: ({ children }) => (
@@ -155,29 +154,54 @@ describe('<ContainerCard />', () => {
155154
it('should render no child blocks in card preview', async () => {
156155
render(<ContainerCard hit={containerHitSample} />);
157156

158-
expect(screen.queryByTitle('text block')).not.toBeInTheDocument();
157+
expect(screen.queryByTitle('lb:org1:Demo_course:html:text-0')).not.toBeInTheDocument();
159158
expect(screen.queryByText('+0')).not.toBeInTheDocument();
160159
});
161160

162161
it('should render <=5 child blocks in card preview', async () => {
163162
const containerWith5Children = {
164163
...containerHitSample,
165-
usageKey: mockGetContainerChildren.fiveChildren,
166-
};
164+
content: {
165+
childUsageKeys: Array(5).fill('').map((_child, idx) => `lb:org1:Demo_course:html:text-${idx}`),
166+
},
167+
} satisfies ContainerHit;
167168
render(<ContainerCard hit={containerWith5Children} />);
168169

169-
expect((await screen.findAllByTitle(/text block */)).length).toBe(5);
170+
expect((await screen.findAllByTitle(/lb:org1:Demo_course:html:text-*/)).length).toBe(5);
170171
expect(screen.queryByText('+0')).not.toBeInTheDocument();
171172
});
172173

173174
it('should render >5 child blocks with +N in card preview', async () => {
174175
const containerWith6Children = {
175176
...containerHitSample,
176-
usageKey: mockGetContainerChildren.sixChildren,
177-
};
177+
content: {
178+
childUsageKeys: Array(6).fill('').map((_child, idx) => `lb:org1:Demo_course:html:text-${idx}`),
179+
},
180+
} satisfies ContainerHit;
178181
render(<ContainerCard hit={containerWith6Children} />);
179182

180-
expect((await screen.findAllByTitle(/text block */)).length).toBe(4);
183+
expect((await screen.findAllByTitle(/lb:org1:Demo_course:html:text-*/)).length).toBe(4);
181184
expect(screen.queryByText('+2')).toBeInTheDocument();
182185
});
186+
187+
it('should render published child blocks when rendering a published card preview', async () => {
188+
const containerWithPublishedChildren = {
189+
...containerHitSample,
190+
content: {
191+
childUsageKeys: Array(6).fill('').map((_child, idx) => `lb:org1:Demo_course:html:text-${idx}`),
192+
},
193+
published: {
194+
content: {
195+
childUsageKeys: Array(2).fill('').map((_child, idx) => `lb:org1:Demo_course:html:text-${idx}`),
196+
},
197+
},
198+
} satisfies ContainerHit;
199+
render(
200+
<ContainerCard hit={containerWithPublishedChildren} />,
201+
true,
202+
);
203+
204+
expect((await screen.findAllByTitle(/lb:org1:Demo_course:html:text-*/)).length).toBe(2);
205+
expect(screen.queryByText('+2')).not.toBeInTheDocument();
206+
});
183207
});

src/library-authoring/components/ContainerCard.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ import { MoreVert } from '@openedx/paragon/icons';
1212
import { Link } from 'react-router-dom';
1313

1414
import { getItemIcon, getComponentStyleColor } from '../../generic/block-type-utils';
15+
import { getBlockType } from '../../generic/key-utils';
1516
import { ToastContext } from '../../generic/toast-context';
1617
import { type ContainerHit, PublishStatus } from '../../search-manager';
1718
import { useComponentPickerContext } from '../common/context/ComponentPickerContext';
1819
import { useLibraryContext } from '../common/context/LibraryContext';
1920
import { SidebarActions, useSidebarContext } from '../common/context/SidebarContext';
20-
import { useContainerChildren, useRemoveItemsFromCollection } from '../data/apiHooks';
21+
import { useRemoveItemsFromCollection } from '../data/apiHooks';
2122
import { useLibraryRoutes } from '../routes';
2223
import AddComponentWidget from './AddComponentWidget';
2324
import BaseCard from './BaseCard';
@@ -107,21 +108,17 @@ const ContainerMenu = ({ hit } : ContainerMenuProps) => {
107108
};
108109

109110
type ContainerCardPreviewProps = {
110-
containerId: string;
111+
childUsageKeys: Array<string>;
111112
showMaxChildren?: number;
112113
};
113114

114-
const ContainerCardPreview = ({ containerId, showMaxChildren = 5 }: ContainerCardPreviewProps) => {
115-
const { data, isLoading, isError } = useContainerChildren(containerId);
116-
if (isLoading || isError) {
117-
return null;
118-
}
119-
120-
const hiddenChildren = data.length - showMaxChildren;
115+
const ContainerCardPreview = ({ childUsageKeys, showMaxChildren = 5 }: ContainerCardPreviewProps) => {
116+
const hiddenChildren = childUsageKeys.length - showMaxChildren;
121117
return (
122118
<Stack direction="horizontal" gap={2}>
123119
{
124-
data.slice(0, showMaxChildren).map(({ id, blockType, displayName }, idx) => {
120+
childUsageKeys.slice(0, showMaxChildren).map((usageKey, idx) => {
121+
const blockType = getBlockType(usageKey);
125122
let blockPreview: ReactNode;
126123
let classNames;
127124

@@ -133,7 +130,7 @@ const ContainerCardPreview = ({ containerId, showMaxChildren = 5 }: ContainerCar
133130
<Icon
134131
src={getItemIcon(blockType)}
135132
screenReaderText={blockType}
136-
title={displayName}
133+
title={usageKey}
137134
/>
138135
);
139136
} else {
@@ -149,7 +146,7 @@ const ContainerCardPreview = ({ containerId, showMaxChildren = 5 }: ContainerCar
149146
<div
150147
// A container can have multiple instances of the same block
151148
// eslint-disable-next-line react/no-array-index-key
152-
key={`${id}-${idx}`}
149+
key={`${usageKey}-${idx}`}
153150
className={classNames}
154151
>
155152
{blockPreview}
@@ -178,6 +175,7 @@ const ContainerCard = ({ hit } : ContainerCardProps) => {
178175
published,
179176
publishStatus,
180177
usageKey: unitId,
178+
content,
181179
} = hit;
182180

183181
const numChildrenCount = showOnlyPublished ? (
@@ -188,6 +186,10 @@ const ContainerCard = ({ hit } : ContainerCardProps) => {
188186
showOnlyPublished ? formatted.published?.displayName : formatted.displayName
189187
) ?? '';
190188

189+
const childUsageKeys: Array<string> = (
190+
showOnlyPublished ? published?.content?.childUsageKeys : content?.childUsageKeys
191+
) ?? [];
192+
191193
const { navigateTo } = useLibraryRoutes();
192194

193195
const openContainer = useCallback(() => {
@@ -202,7 +204,7 @@ const ContainerCard = ({ hit } : ContainerCardProps) => {
202204
<BaseCard
203205
itemType={itemType}
204206
displayName={displayName}
205-
preview={<ContainerCardPreview containerId={unitId} />}
207+
preview={<ContainerCardPreview childUsageKeys={childUsageKeys} />}
206208
tags={tags}
207209
numChildren={numChildrenCount}
208210
actions={(

src/search-manager/data/api.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export const getContentSearchConfig = async (): Promise<{ url: string, indexName
5050
export interface ContentDetails {
5151
htmlContent?: string;
5252
capaContent?: string;
53+
childUsageKeys?: Array<string>;
5354
[k: string]: any;
5455
}
5556

@@ -151,9 +152,10 @@ export interface ContentHit extends BaseContentHit {
151152
* Defined in edx-platform/openedx/core/djangoapps/content/search/documents.py
152153
*/
153154
export interface ContentPublishedData {
154-
description?: string,
155-
displayName?: string,
156-
numChildren?: number,
155+
description?: string;
156+
displayName?: string;
157+
numChildren?: number;
158+
content?: ContentDetails;
157159
}
158160

159161
/**
@@ -171,13 +173,17 @@ export interface CollectionHit extends BaseContentHit {
171173
* Information about a single container returned in the search results
172174
* Defined in edx-platform/openedx/core/djangoapps/content/search/documents.py
173175
*/
176+
interface ContainerHitContent {
177+
childUsageKeys?: string[],
178+
}
174179
export interface ContainerHit extends BaseContentHit {
175180
type: 'library_container';
176181
blockType: 'unit'; // This should be expanded to include other container types
177182
numChildren?: number;
178183
published?: ContentPublishedData;
179184
publishStatus: PublishStatus;
180185
formatted: BaseContentHit['formatted'] & { published?: ContentPublishedData, };
186+
content?: ContainerHitContent;
181187
}
182188

183189
export type HitType = ContentHit | CollectionHit | ContainerHit;

0 commit comments

Comments
 (0)