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:
Rômulo Penido
2024-09-27 23:24:12 -03:00
committed by GitHub
parent c80483c053
commit 4d67e8bda9
29 changed files with 1155 additions and 139 deletions

View 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": {}
}
]
}

View File

@@ -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({});

View File

@@ -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;

View 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);
});
});

View File

@@ -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;