feat: improve collection sidebar (#1320)
* feat: improve collection sidebar * feat: add comments to splice blockTypesArray code Co-authored-by: Jillian <jill@opencraft.com> --------- Co-authored-by: Jillian <jill@opencraft.com> Co-authored-by: Chris Chávez <xnpiochv@gmail.com>
This commit is contained in:
24
src/search-manager/data/__mocks__/block-types.json
Normal file
24
src/search-manager/data/__mocks__/block-types.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"comment": "This is a mock of the response from Meilisearch, based on an actual search in Studio.",
|
||||
"results": [
|
||||
{
|
||||
"indexUid": "studio",
|
||||
"hits": [],
|
||||
"query": "",
|
||||
"processingTimeMs": 1,
|
||||
"limit": 0,
|
||||
"offset": 0,
|
||||
"estimatedTotalHits": 0,
|
||||
"facetDistribution": {
|
||||
"block_type": {
|
||||
"chapter": 1,
|
||||
"html": 2,
|
||||
"problem": 16,
|
||||
"vertical": 2,
|
||||
"video": 1
|
||||
}
|
||||
},
|
||||
"facetStats": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -40,3 +40,27 @@ export function mockSearchResult(mockResponse: MultiSearchResponse) {
|
||||
return newMockResponse;
|
||||
}, { overwriteRoutes: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock the block types returned by the API.
|
||||
*/
|
||||
export async function mockGetBlockTypes(
|
||||
mockResponse: 'noBlocks' | 'someBlocks' | 'moreBlocks',
|
||||
) {
|
||||
const mockResponseMap = {
|
||||
noBlocks: {},
|
||||
someBlocks: { problem: 1, html: 2 },
|
||||
moreBlocks: {
|
||||
advanced: 1,
|
||||
discussion: 2,
|
||||
library: 3,
|
||||
drag_and_drop_v2: 4,
|
||||
openassessment: 5,
|
||||
html: 6,
|
||||
problem: 7,
|
||||
video: 8,
|
||||
},
|
||||
};
|
||||
jest.spyOn(api, 'fetchBlockTypes').mockResolvedValue(mockResponseMap[mockResponse]);
|
||||
}
|
||||
mockGetBlockTypes.applyMock = () => jest.spyOn(api, 'fetchBlockTypes').mockResolvedValue({});
|
||||
|
||||
@@ -101,6 +101,8 @@ interface BaseContentHit {
|
||||
id: string;
|
||||
type: 'course_block' | 'library_block' | 'collection';
|
||||
displayName: string;
|
||||
usageKey: string;
|
||||
blockId: string;
|
||||
/** The course or library ID */
|
||||
contextKey: string;
|
||||
org: string;
|
||||
@@ -117,8 +119,6 @@ interface BaseContentHit {
|
||||
* Defined in edx-platform/openedx/core/djangoapps/content/search/documents.py
|
||||
*/
|
||||
export interface ContentHit extends BaseContentHit {
|
||||
usageKey: string;
|
||||
blockId: string;
|
||||
/** The block_type part of the usage key. What type of XBlock this is. */
|
||||
blockType: string;
|
||||
/**
|
||||
@@ -144,7 +144,7 @@ export interface CollectionHit extends BaseContentHit {
|
||||
* Convert search hits to camelCase
|
||||
* @param hit A search result directly from Meilisearch
|
||||
*/
|
||||
function formatSearchHit(hit: Record<string, any>): ContentHit | CollectionHit {
|
||||
export function formatSearchHit(hit: Record<string, any>): ContentHit | CollectionHit {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { _formatted, ...newHit } = hit;
|
||||
newHit.formatted = {
|
||||
@@ -303,6 +303,29 @@ export async function fetchSearchResults({
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the block types facet distribution for the search results.
|
||||
*/
|
||||
export const fetchBlockTypes = async (
|
||||
client: MeiliSearch,
|
||||
indexName: string,
|
||||
extraFilter?: Filter,
|
||||
): Promise<Record<string, number>> => {
|
||||
// Convert 'extraFilter' into an array
|
||||
const extraFilterFormatted = forceArray(extraFilter);
|
||||
|
||||
const { results } = await client.multiSearch({
|
||||
queries: [{
|
||||
indexUid: indexName,
|
||||
facets: ['block_type'],
|
||||
filter: extraFilterFormatted,
|
||||
limit: 0, // We don't need any "hits" for this - just the facetDistribution
|
||||
}],
|
||||
});
|
||||
|
||||
return results[0].facetDistribution?.block_type ?? {};
|
||||
};
|
||||
|
||||
/** Information about a single tag in the tag tree, as returned by fetchAvailableTagOptions() */
|
||||
export interface TagEntry {
|
||||
tagName: string;
|
||||
|
||||
57
src/search-manager/data/apiHooks.test.tsx
Normal file
57
src/search-manager/data/apiHooks.test.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import fetchMock from 'fetch-mock-jest';
|
||||
|
||||
import mockResult from './__mocks__/block-types.json';
|
||||
import { mockContentSearchConfig } from './api.mock';
|
||||
import {
|
||||
useGetBlockTypes,
|
||||
} from './apiHooks';
|
||||
|
||||
mockContentSearchConfig.applyMock();
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper = ({ children }) => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
const fetchMockResponse = () => {
|
||||
fetchMock.post(
|
||||
mockContentSearchConfig.searchEndpointUrl,
|
||||
() => mockResult,
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
};
|
||||
|
||||
describe('search manager api hooks', () => {
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
});
|
||||
|
||||
it('it should return block types facet', async () => {
|
||||
fetchMockResponse();
|
||||
const { result } = renderHook(() => useGetBlockTypes('filter'), { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
const expectedData = {
|
||||
chapter: 1,
|
||||
html: 2,
|
||||
problem: 16,
|
||||
vertical: 2,
|
||||
video: 1,
|
||||
};
|
||||
expect(result.current.data).toEqual(expectedData);
|
||||
expect(fetchMock.calls().length).toEqual(1);
|
||||
});
|
||||
});
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
fetchTagsThatMatchKeyword,
|
||||
getContentSearchConfig,
|
||||
fetchDocumentById,
|
||||
fetchBlockTypes,
|
||||
OverrideQueries,
|
||||
} from './api';
|
||||
|
||||
@@ -243,6 +244,22 @@ export const useTagFilterOptions = (args: {
|
||||
return { ...mainQuery, data };
|
||||
};
|
||||
|
||||
export const useGetBlockTypes = (extraFilters: Filter) => {
|
||||
const { client, indexName } = useContentSearchConnection();
|
||||
return useQuery({
|
||||
enabled: client !== undefined && indexName !== undefined,
|
||||
queryKey: [
|
||||
'content_search',
|
||||
client?.config.apiKey,
|
||||
client?.config.host,
|
||||
indexName,
|
||||
extraFilters,
|
||||
'block_types',
|
||||
],
|
||||
queryFn: () => fetchBlockTypes(client!, indexName!, extraFilters),
|
||||
});
|
||||
};
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const useGetSingleDocument = ({ client, indexName, id }: {
|
||||
client?: MeiliSearch;
|
||||
|
||||
Reference in New Issue
Block a user