import { camelCaseObject } from '@edx/frontend-platform'; import { useQuery, useMutation, useQueryClient, type Query, type QueryClient, replaceEqualDeep, } from '@tanstack/react-query'; import { useCallback } from 'react'; import { getLibraryId } from '../../generic/key-utils'; import * as api from './api'; import { VersionSpec } from '../LibraryBlock'; import { useContentSearchConnection, useContentSearchResults } from '../../search-manager'; export const libraryQueryPredicate = (query: Query, libraryId: string): boolean => { // Invalidate all content queries related to this library. // If we allow searching "all courses and libraries" in the future, // then we'd have to invalidate all `["content_search", "results"]` // queries, and not just the ones for this library, because items from // this library could be included in an "all courses and libraries" // search. For now we only allow searching individual libraries. const extraFilter = query.queryKey[5]; // extraFilter contains library id if (!(Array.isArray(extraFilter) || typeof extraFilter === 'string')) { return false; } return query.queryKey[0] === 'content_search' && extraFilter?.includes(`context_key = "${libraryId}"`); }; export const libraryAuthoringQueryKeys = { all: ['contentLibrary'], /** * Base key for data specific to a contentLibrary */ contentLibrary: (contentLibraryId?: string) => [...libraryAuthoringQueryKeys.all, contentLibraryId], /** All keys for content within the library should be below this key */ contentLibraryContent: (contentLibraryId?: string) => [ ...libraryAuthoringQueryKeys.contentLibrary(contentLibraryId), 'content', ], /** Keys for the list of all libraries */ contentLibraryList: (customParams?: api.GetLibrariesV2CustomParams) => [ ...libraryAuthoringQueryKeys.all, 'list', ...(customParams ? [customParams] : []), ], libraryTeam: (libraryId?: string) => [ ...libraryAuthoringQueryKeys.all, 'list', libraryId, ], collection: (libraryId?: string, collectionId?: string) => [ ...libraryAuthoringQueryKeys.contentLibraryContent(libraryId), 'collection', collectionId, ], blockTypes: (libraryId?: string) => [ ...libraryAuthoringQueryKeys.all, 'blockTypes', libraryId, ], allContainers: (libraryId?: string) => [ ...libraryAuthoringQueryKeys.contentLibraryContent(libraryId), 'container', ], container: (containerId?: string) => { const baseKey = containerId ? libraryAuthoringQueryKeys.allContainers(getLibraryId(containerId)) : [...libraryAuthoringQueryKeys.all, 'container']; return [ ...baseKey, containerId, ]; }, containerChildren: (containerId: string) => [ ...libraryAuthoringQueryKeys.container(containerId), 'children', ], }; export const xblockQueryKeys = { all: ['xblock'], /** * Base key for data specific to a xblock */ xblock: (usageKey?: string) => [...xblockQueryKeys.all, usageKey], /** Fields (i.e. the content, display name, etc.) of an XBlock */ xblockFields: (usageKey: string, version: VersionSpec = 'draft') => [...xblockQueryKeys.xblock(usageKey), 'fields', version], /** OLX (XML representation of the fields/content) */ xblockOLX: (usageKey: string) => [...xblockQueryKeys.xblock(usageKey), 'OLX'], /** assets (static files) */ xblockAssets: (usageKey: string) => [...xblockQueryKeys.xblock(usageKey), 'assets'], componentMetadata: (usageKey: string) => [...xblockQueryKeys.xblock(usageKey), 'componentMetadata'], componentDownstreamLinks: (usageKey: string) => [...xblockQueryKeys.xblock(usageKey), 'downstreamLinks'], /** * Predicate used to invalidate all metadata only (not OLX, fields, assets, etc.). * Affects all libraries; we could do a more complex version that affects only one library, but it would require * introspecting the usage keys. */ allComponentMetadata: (query: Query) => query.queryKey[0] === 'xblock' && query.queryKey[2] === 'componentMetadata', }; /** * Tell react-query to refresh its cache of any data related to the given * component (XBlock). * * Note that technically it's possible to derive the library key from the * usageKey, so we could refactor this to only require the usageKey. * * @param queryClient The query client - get it via useQueryClient() * @param contentLibraryId The ID of library that holds the XBlock ("lib:...") * @param usageKey The usage ID of the XBlock ("lb:...") */ export function invalidateComponentData(queryClient: QueryClient, contentLibraryId: string, usageKey: string) { queryClient.invalidateQueries({ queryKey: xblockQueryKeys.xblockFields(usageKey) }); queryClient.invalidateQueries({ queryKey: xblockQueryKeys.componentMetadata(usageKey) }); // The description and display name etc. may have changed, so refresh everything in the library too: // This might fail in case this helper is called after deleting the block. queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.contentLibrary(contentLibraryId) }); queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, contentLibraryId) }); } /** * Hook to fetch a content library by its ID. */ export const useContentLibrary = (libraryId: string | undefined) => ( useQuery({ queryKey: libraryAuthoringQueryKeys.contentLibrary(libraryId), queryFn: () => api.getContentLibrary(libraryId!), enabled: libraryId !== undefined, }) ); /** * Use this mutation to create a block in a library */ export const useCreateLibraryBlock = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: api.createLibraryBlock, onSettled: (_data, _error, variables) => { queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.contentLibrary(variables.libraryId) }); queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, variables.libraryId) }); }, }); }; /** * Use this mutation to delete a block in a library */ export const useDeleteLibraryBlock = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: api.deleteLibraryBlock, onSettled: (_data, _error, variables) => { const libraryId = getLibraryId(variables.usageKey); invalidateComponentData(queryClient, libraryId, variables.usageKey); }, }); }; /** * Use this mutation to restore a deleted block in a library */ export const useRestoreLibraryBlock = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: api.restoreLibraryBlock, onSettled: (_data, _error, variables) => { const libraryId = getLibraryId(variables.usageKey); invalidateComponentData(queryClient, libraryId, variables.usageKey); }, }); }; export const useUpdateLibraryMetadata = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: api.updateLibraryMetadata, onMutate: async (data) => { const queryKey = libraryAuthoringQueryKeys.contentLibrary(data.id); const previousLibraryData = queryClient.getQueriesData(queryKey)[0][1] as api.ContentLibrary; const newLibraryData = { ...previousLibraryData, ...camelCaseObject(data), }; queryClient.setQueryData(queryKey, newLibraryData); return { previousLibraryData, newLibraryData }; }, onError: (_err, data, context) => { queryClient.setQueryData( libraryAuthoringQueryKeys.contentLibrary(data.id), context?.previousLibraryData, ); }, onSettled: (_data, _error, variables) => { queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.contentLibrary(variables.id) }); }, }); }; /** * Builds the query to fetch list of V2 Libraries */ export const useContentLibraryV2List = (customParams: api.GetLibrariesV2CustomParams) => ( useQuery({ queryKey: libraryAuthoringQueryKeys.contentLibraryList(customParams), queryFn: () => api.getContentLibraryV2List(customParams), keepPreviousData: true, }) ); /** Publish all changes in the library. */ export const useCommitLibraryChanges = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: api.commitLibraryChanges, onSettled: (_data, _error, libraryId) => { // Invalidate all content-related metadata and search results for the whole library. queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.contentLibrary(libraryId) }); queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); // For XBlocks, the only thing we need to invalidate is the metadata which includes "has unpublished changes" queryClient.invalidateQueries({ predicate: xblockQueryKeys.allComponentMetadata }); }, }); }; /** Discard all un-published changes in the library */ export const useRevertLibraryChanges = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: api.revertLibraryChanges, onSettled: (_data, _error, libraryId) => { // Invalidate all content-related metadata and search results for the whole library. queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.contentLibrary(libraryId) }); queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); // For XBlocks, the only thing we need to invalidate is the metadata which includes "has unpublished changes" queryClient.invalidateQueries({ predicate: xblockQueryKeys.allComponentMetadata }); }, }); }; /** * Hook to fetch a content library's team members */ export const useLibraryTeam = (libraryId?: string) => ( useQuery({ queryKey: libraryAuthoringQueryKeys.libraryTeam(libraryId), queryFn: () => api.getLibraryTeam(libraryId!), enabled: libraryId !== undefined, }) ); /** * Hook to fetch the list of XBlock types that can be added to this library. */ export const useBlockTypesMetadata = (libraryId?: string) => ( useQuery({ queryKey: libraryAuthoringQueryKeys.blockTypes(libraryId), queryFn: () => api.getBlockTypes(libraryId!), enabled: libraryId !== undefined, }) ); /** * Hook to add a new member to a content library's team */ export const useAddLibraryTeamMember = (libraryId: string | undefined) => { const queryClient = useQueryClient(); const queryKey = libraryAuthoringQueryKeys.libraryTeam(libraryId); return useMutation({ mutationFn: api.addLibraryTeamMember, onSettled: () => { queryClient.invalidateQueries({ queryKey }); }, }); }; /** * Hook to delete an existing member from a content library's team */ export const useDeleteLibraryTeamMember = (libraryId: string | undefined) => { const queryClient = useQueryClient(); const queryKey = libraryAuthoringQueryKeys.libraryTeam(libraryId); return useMutation({ mutationFn: api.deleteLibraryTeamMember, onSuccess: () => { queryClient.invalidateQueries({ queryKey }); }, }); }; /** * Hook to update an existing member's access in a content library's team */ export const useUpdateLibraryTeamMember = (libraryId: string | undefined) => { const queryClient = useQueryClient(); const queryKey = libraryAuthoringQueryKeys.libraryTeam(libraryId); return useMutation({ mutationFn: api.updateLibraryTeamMember, onSuccess: () => { queryClient.invalidateQueries({ queryKey }); }, }); }; export const useLibraryPasteClipboard = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: api.libraryPasteClipboard, onSettled: (_data, _error, variables) => { queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.contentLibrary(variables.libraryId) }); queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, variables.libraryId) }); }, }); }; export const useLibraryBlockMetadata = (usageId: string | undefined) => ( useQuery({ queryKey: xblockQueryKeys.componentMetadata(usageId!), queryFn: () => api.getLibraryBlockMetadata(usageId!), enabled: !!usageId, }) ); export const useXBlockFields = (usageKey: string, version: VersionSpec = 'draft') => ( useQuery({ queryKey: xblockQueryKeys.xblockFields(usageKey, version), queryFn: () => api.getXBlockFields(usageKey, version), enabled: !!usageKey, }) ); export const useUpdateXBlockFields = (usageKey: string) => { const contentLibraryId = getLibraryId(usageKey); const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: api.UpdateXBlockFieldsRequest) => api.updateXBlockFields(usageKey, data), onMutate: async (data) => { const queryKey = xblockQueryKeys.xblockFields(usageKey); const previousBlockData = queryClient.getQueriesData(queryKey)?.[0]?.[1] as api.XBlockFields | undefined; const formatedData = camelCaseObject(data); if (!previousBlockData) { return { previousBlockData }; } const newBlockData = { ...previousBlockData, ...(formatedData.metadata?.displayName && { displayName: formatedData.metadata.displayName }), metadata: { ...previousBlockData.metadata, ...formatedData.metadata, }, }; queryClient.setQueryData(queryKey, newBlockData); return { previousBlockData }; }, onError: (_err, _data, context) => { queryClient.setQueryData( xblockQueryKeys.xblockFields(usageKey), context?.previousBlockData, ); }, onSettled: () => { invalidateComponentData(queryClient, contentLibraryId, usageKey); }, }); }; /** * Use this mutation to create a library collection */ export const useCreateLibraryCollection = (libraryId: string) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: api.CreateLibraryCollectionDataRequest) => api.createCollection(libraryId, data), onSettled: () => { queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); }, }); }; /** Get the OLX source of a library component */ export const useXBlockOLX = (usageKey: string, version: VersionSpec) => ( useQuery({ queryKey: xblockQueryKeys.xblockOLX(usageKey), queryFn: () => api.getXBlockOLX(usageKey, version), enabled: !!usageKey, }) ); /** * Update the OLX of a library component (advanced feature) */ export const useUpdateXBlockOLX = (usageKey: string) => { const contentLibraryId = getLibraryId(usageKey); const queryClient = useQueryClient(); return useMutation({ mutationFn: (newOLX: string) => api.setXBlockOLX(usageKey, newOLX), onSuccess: (olxFromServer) => { queryClient.setQueryData(xblockQueryKeys.xblockOLX(usageKey), olxFromServer); invalidateComponentData(queryClient, contentLibraryId, usageKey); }, }); }; /** * Publish changes to a library component */ export const usePublishComponent = (usageKey: string) => { const queryClient = useQueryClient(); const contentLibraryId = getLibraryId(usageKey); return useMutation({ mutationFn: () => api.publishXBlock(usageKey), onSettled: () => { invalidateComponentData(queryClient, contentLibraryId, usageKey); }, }); }; /** Get the list of assets (static files) attached to a library component */ export const useXBlockAssets = (usageKey: string) => ( useQuery({ queryKey: xblockQueryKeys.xblockAssets(usageKey), queryFn: () => api.getXBlockAssets(usageKey), enabled: !!usageKey, }) ); /** Refresh the list of assets (static files) attached to a library component */ export const useInvalidateXBlockAssets = (usageKey: string) => { const client = useQueryClient(); return useCallback(() => { client.invalidateQueries({ queryKey: xblockQueryKeys.xblockAssets(usageKey) }); }, [usageKey]); }; /** * Use this mutation to delete an asset file from a library */ export const useDeleteXBlockAsset = (usageKey: string) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (path: string) => api.deleteXBlockAsset(usageKey, path), onSettled: () => { queryClient.invalidateQueries({ queryKey: xblockQueryKeys.xblockAssets(usageKey) }); }, }); }; /** * Get the metadata for a collection in a library */ export const useCollection = (libraryId: string, collectionId?: string) => ( useQuery({ enabled: !!libraryId && !!collectionId, queryKey: libraryAuthoringQueryKeys.collection(libraryId, collectionId), queryFn: () => api.getCollectionMetadata(libraryId!, collectionId!), }) ); /** * Use this mutation to update the fields of a collection in a library */ export const useUpdateCollection = (libraryId: string, collectionId: string) => { const queryClient = useQueryClient(); const collectionQueryKey = libraryAuthoringQueryKeys.collection(libraryId, collectionId); return useMutation({ mutationFn: (data: api.UpdateCollectionComponentsRequest) => ( api.updateCollectionMetadata(libraryId, collectionId, data) ), onMutate: (data) => { const previousData = queryClient.getQueryData(collectionQueryKey) as api.CollectionMetadata; queryClient.setQueryData(collectionQueryKey, { ...previousData, ...data, }); return { previousData }; }, onError: (_err, _data, context) => { queryClient.setQueryData(collectionQueryKey, context?.previousData); }, onSettled: () => { // NOTE: We invalidate the library query here because we need to update the library's // collection list. queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); queryClient.invalidateQueries({ queryKey: collectionQueryKey }); }, }); }; /** * Use this mutation to add items to a collection in a library */ export const useAddItemsToCollection = (libraryId?: string, collectionId?: string) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (usageKeys: string[]) => { if (libraryId !== undefined && collectionId !== undefined) { return api.addItemsToCollection(libraryId, collectionId, usageKeys); } return undefined; }, onSettled: () => { if (libraryId !== undefined && collectionId !== undefined) { queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); } }, }); }; /** * Use this mutation to remove items from a collection in a library */ export const useRemoveItemsFromCollection = (libraryId?: string, collectionId?: string) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (usageKeys: string[]) => { if (libraryId !== undefined && collectionId !== undefined) { return api.removeItemsFromCollection(libraryId, collectionId, usageKeys); } return undefined; }, onSettled: () => { if (libraryId !== undefined && collectionId !== undefined) { queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); } }, }); }; /** * Use this mutation to soft delete collections in a library */ export const useDeleteCollection = (libraryId: string, collectionId: string) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async () => api.deleteCollection(libraryId, collectionId), onSettled: () => { queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.contentLibrary(libraryId) }); queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); }, }); }; /** * Use this mutation to restore soft deleted collections in a library */ export const useRestoreCollection = (libraryId: string, collectionId: string) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async () => api.restoreCollection(libraryId, collectionId), onSettled: () => { queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.contentLibrary(libraryId) }); queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); }, }); }; /** * Use this mutation to update collections related a component in a library */ export const useUpdateComponentCollections = (usageKey: string) => { const queryClient = useQueryClient(); const libraryId = getLibraryId(usageKey); return useMutation({ mutationFn: async (collectionKeys: string[]) => api.updateComponentCollections(usageKey, collectionKeys), onSettled: () => { queryClient.invalidateQueries({ queryKey: xblockQueryKeys.componentMetadata(usageKey) }); queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); }, }); }; /** * Use this mutation to create a library container */ export const useCreateLibraryContainer = (libraryId: string) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: api.CreateLibraryContainerDataRequest) => api.createLibraryContainer(libraryId, data), onSettled: () => { queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); }, }); }; /** * Get the metadata for a container in a library */ export const useContainer = (containerId?: string) => ( useQuery({ enabled: !!containerId, queryKey: libraryAuthoringQueryKeys.container(containerId!), queryFn: () => api.getContainerMetadata(containerId!), }) ); /** * Use this mutation to update the fields of a container in a library */ export const useUpdateContainer = (containerId: string) => { const libraryId = getLibraryId(containerId); const queryClient = useQueryClient(); const containerQueryKey = libraryAuthoringQueryKeys.container(containerId); return useMutation({ mutationFn: (data: api.UpdateContainerDataRequest) => api.updateContainerMetadata(containerId, data), onMutate: (data) => { const previousData = queryClient.getQueryData(containerQueryKey) as api.Container; queryClient.setQueryData(containerQueryKey, { ...previousData, ...data, }); return { previousData }; }, onError: (_err, _data, context) => { queryClient.setQueryData(containerQueryKey, context?.previousData); }, onSettled: () => { // NOTE: We invalidate the library query here because we need to update the library's // container list. queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); queryClient.invalidateQueries({ queryKey: containerQueryKey }); // NOTE: We invalidate all container query to update names in children list of containers queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.allContainers(libraryId) }); }, }); }; /** * Use this mutation to soft delete containers in a library */ export const useDeleteContainer = (containerId: string) => { const libraryId = getLibraryId(containerId); const queryClient = useQueryClient(); return useMutation({ mutationFn: async () => api.deleteContainer(containerId), onSettled: () => { queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.contentLibrary(libraryId) }); queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); }, }); }; /** * Use this mutation to restore a container */ export const useRestoreContainer = (containerId: string) => { const libraryId = getLibraryId(containerId); const queryClient = useQueryClient(); return useMutation({ mutationFn: async () => api.restoreContainer(containerId), onSettled: () => { queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); }, }); }; /** * Get the metadata and children for a container in a library */ export const useContainerChildren = (containerId?: string, published: boolean = false) => ( useQuery({ enabled: !!containerId, queryKey: libraryAuthoringQueryKeys.containerChildren(containerId!), queryFn: () => api.getLibraryContainerChildren(containerId!, published), structuralSharing: (oldData: api.LibraryBlockMetadata[], newData: api.LibraryBlockMetadata[]) => { // This just sets `isNew` flag to new children components if (oldData) { const oldDataIds = oldData.map((obj) => obj.id); // eslint-disable-next-line no-param-reassign newData = newData.map((newObj) => { if (!oldDataIds.includes(newObj.id)) { // Set isNew = true if we have new child on refetch // eslint-disable-next-line no-param-reassign newObj.isNew = true; } return newObj; }); } return replaceEqualDeep(oldData, newData); }, }) ); /** * Use this mutation to add items to a container */ export const useAddItemsToContainer = (containerId?: string) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (itemIds: string[]) => { // istanbul ignore if: this should never happen if (!containerId) { return undefined; } return api.addComponentsToContainer(containerId, itemIds); }, onSettled: () => { // istanbul ignore if: this should never happen if (!containerId) { return; } // NOTE: We invalidate the library query here because we need to update the library's // container list. const libraryId = getLibraryId(containerId); queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.containerChildren(containerId) }); queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); }, }); }; /** * Use this mutation to update collections related a container in a library */ export const useUpdateContainerCollections = (containerId: string) => { const queryClient = useQueryClient(); const libraryId = getLibraryId(containerId); return useMutation({ mutationFn: async (collectionKeys: string[]) => api.updateContainerCollections(containerId, collectionKeys), onSettled: () => { queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.container(containerId) }); queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); }, }); }; /** * Update container children */ export const useUpdateContainerChildren = (containerId?: string) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (usageKeys: string[]) => { if (!containerId) { return undefined; } return api.updateLibraryContainerChildren(containerId, usageKeys); }, onSettled: () => { if (!containerId) { return; } // NOTE: We invalidate the library query here because we need to update the library's // container list. const libraryId = getLibraryId(containerId); queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.container(containerId) }); }, }); }; /** * Remove components from container */ export const useRemoveContainerChildren = (containerId?: string) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (usageKeys: string[]) => { if (!containerId) { return undefined; } return api.removeLibraryContainerChildren(containerId, usageKeys); }, onSettled: () => { if (!containerId) { return; } // NOTE: We invalidate the library query here because we need to update the container // count in the library const libraryId = getLibraryId(containerId); queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.container(containerId) }); }, }); }; /** * Use this mutation to publish changes to a container and any children within it */ export const usePublishContainer = (containerId: string) => { const queryClient = useQueryClient(); const libraryId = getLibraryId(containerId); return useMutation({ mutationFn: () => api.publishContainer(containerId), onSettled: () => { // Invalidate all content-related metadata and search results for the whole library. // The child components/xblocks could and even the container itself could appear in many different collections // or other containers, so it's best to just invalidate everything. queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.contentLibraryContent(libraryId) }); queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) }); // For XBlocks, the only thing we need to invalidate is the metadata which includes "has unpublished changes" queryClient.invalidateQueries({ predicate: xblockQueryKeys.allComponentMetadata }); }, }); }; /** * Use this mutations to get a list of objects from the search index */ export const useContentFromSearchIndex = (contentIds: string[]) => { const { client, indexName } = useContentSearchConnection(); return useContentSearchResults({ client, indexName, searchKeywords: '', extraFilter: [`usage_key IN ["${contentIds.join('","')}"]`], limit: contentIds.length, enabled: !!contentIds.length, skipBlockTypeFetch: true, }); };