fix: Refresh section list on subsection page (#2103)

Invalidates the query in the subsection page used to get the list of sections that contains the subsection
This commit is contained in:
Chris Chávez
2025-06-11 19:57:25 -05:00
committed by GitHub
parent 569a981a85
commit 4adf2ff087
4 changed files with 104 additions and 10 deletions

View File

@@ -311,6 +311,26 @@ describe('library api hooks', () => {
});
});
it('should invalidate subsection when added to section', async () => {
const spy = jest.spyOn(queryClient, 'invalidateQueries');
const subsectionId1 = 'lct:org:lib:subsection:1';
const subsectionId2 = 'lct:org:lib:subsection:2';
const sectionId = 'lct:org:lib:section:1';
const url = getLibraryContainerChildrenApiUrl(sectionId);
axiosMock.onPost(url).reply(200);
const { result } = renderHook(() => useAddItemsToContainer(sectionId), { wrapper });
await result.current.mutateAsync([subsectionId1, subsectionId2]);
expect(axiosMock.history.post[0].url).toEqual(url);
// Two call for `containerChildren` and library predicate
// and two more calls to invalidate the subsections.
expect(spy).toHaveBeenCalledTimes(4);
});
describe('publishContainer', () => {
it('should publish a container', async () => {
const containerId = 'lct:org:lib:unit:1';

View File

@@ -8,11 +8,12 @@ import {
replaceEqualDeep,
} from '@tanstack/react-query';
import { useCallback } from 'react';
import { type MeiliSearch } from 'meilisearch';
import { getLibraryId } from '../../generic/key-utils';
import { getBlockType, getLibraryId } from '../../generic/key-utils';
import * as api from './api';
import { VersionSpec } from '../LibraryBlock';
import { useContentSearchConnection, useContentSearchResults } from '../../search-manager';
import { useContentSearchConnection, useContentSearchResults, buildSearchQueryKey } from '../../search-manager';
export const libraryQueryPredicate = (query: Query, libraryId: string): boolean => {
// Invalidate all content queries related to this library.
@@ -697,11 +698,34 @@ export const useContainerChildren = (containerId?: string, published: boolean =
})
);
/**
* If you work with `useContentFromSearchIndex`, you can use this
* function to get the query key, usually to invalidate the query.
*/
const getSearchQueryKeyFromContent = (
contentIds: string[],
client?: MeiliSearch,
indexName?: string,
) => (
buildSearchQueryKey({
client,
indexName,
extraFilter: [`usage_key IN ["${contentIds.join('","')}"]`],
searchKeywords: '',
blockTypesFilter: [],
problemTypesFilter: [],
publishStatusFilter: [],
tagsFilter: [],
sort: [],
})
);
/**
* Use this mutation to add items to a container
*/
export const useAddItemsToContainer = (containerId?: string) => {
const queryClient = useQueryClient();
const { client, indexName } = useContentSearchConnection();
return useMutation({
mutationFn: async (itemIds: string[]) => {
// istanbul ignore if: this should never happen
@@ -710,7 +734,7 @@ export const useAddItemsToContainer = (containerId?: string) => {
}
return api.addComponentsToContainer(containerId, itemIds);
},
onSettled: () => {
onSettled: (_data, _error, variables) => {
// istanbul ignore if: this should never happen
if (!containerId) {
return;
@@ -720,6 +744,17 @@ export const useAddItemsToContainer = (containerId?: string) => {
const libraryId = getLibraryId(containerId);
queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.containerChildren(containerId) });
queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) });
const containerType = getBlockType(containerId);
if (containerType === 'section') {
// We invalidate the search query of the each itemId if the container is a section.
// This because the subsection page calls this query individually.
variables.forEach((itemId) => {
queryClient.invalidateQueries({
queryKey: getSearchQueryKeyFromContent([itemId], client, indexName),
});
});
}
},
});
};

View File

@@ -44,6 +44,43 @@ export const useContentSearchConnection = (): {
return { client, indexName, hasConnectionError };
};
export const buildSearchQueryKey = ({
client,
indexName,
extraFilter,
searchKeywords,
blockTypesFilter,
problemTypesFilter,
publishStatusFilter,
tagsFilter,
sort,
}: {
client?: MeiliSearch;
indexName?: string;
extraFilter?: Filter;
searchKeywords: string;
blockTypesFilter: string[];
problemTypesFilter: string[];
publishStatusFilter: PublishStatus[];
tagsFilter: string[];
sort: SearchSortOption[];
}) => (
[
'content_search',
'results',
client?.config.apiKey,
client?.config.host,
indexName,
extraFilter,
searchKeywords,
blockTypesFilter,
problemTypesFilter,
publishStatusFilter,
tagsFilter,
sort,
]
);
/**
* Get the results of a search
*/
@@ -87,11 +124,8 @@ export const useContentSearchResults = ({
}) => {
const query = useInfiniteQuery({
enabled: enabled && client !== undefined && indexName !== undefined,
queryKey: [
'content_search',
'results',
client?.config.apiKey,
client?.config.host,
queryKey: buildSearchQueryKey({
client,
indexName,
extraFilter,
searchKeywords,
@@ -100,7 +134,7 @@ export const useContentSearchResults = ({
publishStatusFilter,
tagsFilter,
sort,
],
}),
queryFn: ({ pageParam = 0 }) => {
// istanbul ignore if: this should never happen
if (client === undefined || indexName === undefined) {

View File

@@ -9,7 +9,12 @@ export { default as SearchKeywordsField } from './SearchKeywordsField';
export { default as SearchSortWidget } from './SearchSortWidget';
export { default as Stats } from './Stats';
export { HIGHLIGHT_PRE_TAG, HIGHLIGHT_POST_TAG, PublishStatus } from './data/api';
export { useContentSearchConnection, useContentSearchResults, useGetBlockTypes } from './data/apiHooks';
export {
useContentSearchConnection,
useContentSearchResults,
useGetBlockTypes,
buildSearchQueryKey,
} from './data/apiHooks';
export { TypesFilterData } from './hooks';
export type {