feat: Libraries v2: Advanced Component Info & OLX Editor (#1346)

This commit is contained in:
Braden MacDonald
2024-10-08 09:41:21 -07:00
committed by GitHub
parent 85b5730114
commit 75f937e11a
12 changed files with 515 additions and 54 deletions

View File

@@ -1,5 +1,6 @@
/* istanbul ignore file */
import { mockContentTaxonomyTagsData } from '../../content-tags-drawer/data/api.mocks';
import { getBlockType } from '../../generic/key-utils';
import { createAxiosError } from '../../testUtils';
import * as api from './api';
@@ -283,3 +284,53 @@ mockGetCollectionMetadata.collectionData = {
mockGetCollectionMetadata.applyMock = () => {
jest.spyOn(api, 'getCollectionMetadata').mockImplementation(mockGetCollectionMetadata);
};
/**
* Mock for `getXBlockOLX()`
*
* This mock returns different data/responses depending on the ID of the block
* that you request. Use `mockXBlockOLX.applyMock()` to apply it to the whole
* test suite.
*/
export async function mockXBlockOLX(usageKey: string): Promise<string> {
const thisMock = mockXBlockOLX;
switch (usageKey) {
case thisMock.usageKeyHtml: return thisMock.olxHtml;
default: {
const blockType = getBlockType(usageKey);
return `<${blockType}>This is mock OLX for usageKey "${usageKey}"</${blockType}>`;
}
}
}
// Mock of a "regular" HTML (Text) block:
mockXBlockOLX.usageKeyHtml = mockXBlockFields.usageKeyHtml;
mockXBlockOLX.olxHtml = `
<html display_name="${mockXBlockFields.dataHtml.displayName}">
${mockXBlockFields.dataHtml.data}
</html>
`;
/** Apply this mock. Returns a spy object that can tell you if it's been called. */
mockXBlockOLX.applyMock = () => jest.spyOn(api, 'getXBlockOLX').mockImplementation(mockXBlockOLX);
/**
* Mock for `setXBlockOLX()`
*/
export async function mockSetXBlockOLX(_usageKey: string, newOLX: string): Promise<string> {
return newOLX;
}
/** Apply this mock. Returns a spy object that can tell you if it's been called. */
mockSetXBlockOLX.applyMock = () => jest.spyOn(api, 'setXBlockOLX').mockImplementation(mockSetXBlockOLX);
/**
* Mock for `getXBlockAssets()`
*
* Use `getXBlockAssets.applyMock()` to apply it to the whole test suite.
*/
export async function mockXBlockAssets(): ReturnType<typeof api['getXBlockAssets']> {
return [
{ path: 'static/image1.png', url: 'https://cdn.test.none/image1.png', size: 12_345_000 },
{ path: 'static/data.csv', url: 'https://cdn.test.none/data.csv', size: 8_000 },
];
}
/** Apply this mock. Returns a spy object that can tell you if it's been called. */
mockXBlockAssets.applyMock = () => jest.spyOn(api, 'getXBlockAssets').mockImplementation(mockXBlockAssets);

View File

@@ -41,6 +41,10 @@ export const getXBlockFieldsApiUrl = (usageKey: string) => `${getApiBaseUrl()}/a
* Get the URL for the xblock OLX API
*/
export const getXBlockOLXApiUrl = (usageKey: string) => `${getApiBaseUrl()}/api/libraries/v2/blocks/${usageKey}/olx/`;
/**
* Get the URL for the xblock Assets List API
*/
export const getXBlockAssetsApiUrl = (usageKey: string) => `${getApiBaseUrl()}/api/libraries/v2/blocks/${usageKey}/assets/`;
/**
* Get the URL for the Library Collections API.
*/
@@ -300,11 +304,31 @@ export async function createCollection(libraryId: string, collectionData: Create
/**
* Fetch the OLX for the given XBlock.
*/
// istanbul ignore next
export async function getXBlockOLX(usageKey: string): Promise<string> {
const { data } = await getAuthenticatedHttpClient().get(getXBlockOLXApiUrl(usageKey));
return data.olx;
}
/**
* Set the OLX for the given XBlock.
* Returns the OLX as it was actually saved.
*/
// istanbul ignore next
export async function setXBlockOLX(usageKey: string, newOLX: string): Promise<string> {
const { data } = await getAuthenticatedHttpClient().post(getXBlockOLXApiUrl(usageKey), { olx: newOLX });
return data.olx;
}
/**
* Fetch the asset (static file) list for the given XBlock.
*/
// istanbul ignore next
export async function getXBlockAssets(usageKey: string): Promise<{ path: string; url: string; size: number }[]> {
const { data } = await getAuthenticatedHttpClient().get(getXBlockAssetsApiUrl(usageKey));
return data.files;
}
/**
* Get the collection metadata.
*/

View File

@@ -30,6 +30,8 @@ import {
updateCollectionComponents,
type CreateLibraryCollectionDataRequest,
getCollectionMetadata,
setXBlockOLX,
getXBlockAssets,
} from './api';
export const libraryQueryPredicate = (query: Query, libraryId: string): boolean => {
@@ -75,6 +77,8 @@ export const xblockQueryKeys = {
xblockFields: (usageKey: string) => [...xblockQueryKeys.xblock(usageKey), 'fields'],
/** 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'],
};
@@ -255,7 +259,7 @@ export const useCreateLibraryCollection = (libraryId: string) => {
});
};
/* istanbul ignore next */ // This is only used in developer builds, and the associated UI doesn't work in test or prod
/** Get the OLX source of a library component */
export const useXBlockOLX = (usageKey: string) => (
useQuery({
queryKey: xblockQueryKeys.xblockOLX(usageKey),
@@ -264,6 +268,34 @@ export const useXBlockOLX = (usageKey: string) => (
})
);
/**
* 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) => setXBlockOLX(usageKey, newOLX),
onSuccess: (olxFromServer) => {
queryClient.setQueryData(xblockQueryKeys.xblockOLX(usageKey), olxFromServer);
// Reload the other data for this component:
invalidateComponentData(queryClient, contentLibraryId, usageKey);
// And the description and display name etc. may have changed, so refresh everything in the library too:
queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.contentLibrary(contentLibraryId) });
queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, contentLibraryId) });
},
});
};
/** Get the list of assets (static files) attached to a library component */
export const useXBlockAssets = (usageKey: string) => (
useQuery({
queryKey: xblockQueryKeys.xblockAssets(usageKey),
queryFn: () => getXBlockAssets(usageKey),
enabled: !!usageKey,
})
);
/**
* Get the metadata for a collection in a library
*/