feat: library unit sidebar [FC-0083] (#1762)

Implements the placeholder for the Unit Sidebar.
This commit is contained in:
Rômulo Penido
2025-04-07 13:51:10 -03:00
committed by GitHub
parent 2a31434a55
commit 68d62cd62f
19 changed files with 679 additions and 38 deletions

View File

@@ -457,6 +457,47 @@ mockGetCollectionMetadata.applyMock = () => {
jest.spyOn(api, 'getCollectionMetadata').mockImplementation(mockGetCollectionMetadata);
};
/**
* Mock for `getContainerMetadata()`
*
* This mock returns a fixed response for the container ID *container_1*.
*/
export async function mockGetContainerMetadata(containerId: string): Promise<api.Container> {
switch (containerId) {
case mockGetCollectionMetadata.collectionIdError:
throw createAxiosError({
code: 404,
message: 'Not found.',
path: api.getLibraryContainerApiUrl(containerId),
});
case mockGetContainerMetadata.containerIdLoading:
return new Promise(() => { });
default:
return Promise.resolve(mockGetContainerMetadata.containerData);
}
}
mockGetContainerMetadata.containerId = 'lct:org:lib:unit:test-unit-9a207';
mockGetContainerMetadata.containerIdError = 'lct:org:lib:unit:container_error';
mockGetContainerMetadata.containerIdLoading = 'lct:org:lib:unit:container_loading';
mockGetContainerMetadata.containerData = {
containerKey: 'lct:org:lib:unit:test-unit-9a2072',
containerType: 'unit',
displayName: 'Test Unit',
created: '2024-09-19T10:00:00Z',
createdBy: 'test_author',
lastPublished: '2024-09-20T10:00:00Z',
publishedBy: 'test_publisher',
lastDraftCreated: '2024-09-20T10:00:00Z',
lastDraftCreatedBy: 'test_author',
modified: '2024-09-20T11:00:00Z',
hasUnpublishedChanges: true,
collections: [],
} satisfies api.Container;
/** Apply this mock. Returns a spy object that can tell you if it's been called. */
mockGetContainerMetadata.applyMock = () => {
jest.spyOn(api, 'getContainerMetadata').mockImplementation(mockGetContainerMetadata);
};
/**
* Mock for `getXBlockOLX()`
*

View File

@@ -107,6 +107,10 @@ export const getContentStoreApiUrl = () => `${getApiBaseUrl()}/api/contentstore/
* Get the URL for the library container api.
*/
export const getLibraryContainersApiUrl = (libraryId: string) => `${getApiBaseUrl()}/api/libraries/v2/${libraryId}/containers/`;
/**
* Get the URL for the container detail api.
*/
export const getLibraryContainerApiUrl = (containerId: string) => `${getApiBaseUrl()}/api/libraries/v2/containers/${containerId}/`;
export interface ContentLibrary {
id: string;
@@ -574,3 +578,41 @@ export async function createLibraryContainer(libraryId: string, containerData: C
const client = getAuthenticatedHttpClient();
await client.post(getLibraryContainersApiUrl(libraryId), snakeCaseObject(containerData));
}
export interface Container {
containerKey: string;
containerType: 'unit';
displayName: string;
lastPublished: string | null;
publishedBy: string | null;
createdBy: string | null;
lastDraftCreated: string | null;
lastDraftCreatedBy: string | null,
hasUnpublishedChanges: boolean;
created: string;
modified: string;
collections: CollectionMetadata[];
}
/**
* Get the container metadata.
*/
export async function getContainerMetadata(containerId: string): Promise<Container> {
const { data } = await getAuthenticatedHttpClient().get(getLibraryContainerApiUrl(containerId));
return camelCaseObject(data);
}
export interface UpdateContainerDataRequest {
displayName: string;
}
/**
* Update container metadata.
*/
export async function updateContainerMetadata(
containerId: string,
containerData: UpdateContainerDataRequest,
) {
const client = getAuthenticatedHttpClient();
await client.patch(getLibraryContainerApiUrl(containerId), snakeCaseObject(containerData));
}

View File

@@ -12,6 +12,7 @@ import {
getLibraryCollectionsApiUrl,
getLibraryCollectionApiUrl,
getBlockTypesMetaDataUrl,
getLibraryContainerApiUrl,
} from './api';
import {
useCommitLibraryChanges,
@@ -21,6 +22,7 @@ import {
useAddComponentsToCollection,
useCollection,
useBlockTypesMetadata,
useContainer,
} from './apiHooks';
let axiosMock;
@@ -137,4 +139,17 @@ describe('library api hooks', () => {
expect(result.current.data).toEqual({ testData: 'test-value' });
expect(axiosMock.history.get[0].url).toEqual(url);
});
it('should get container metadata', async () => {
const containerId = 'lct:lib:org:unit:unit1';
const url = getLibraryContainerApiUrl(containerId);
axiosMock.onGet(url).reply(200, { 'test-data': 'test-value' });
const { result } = renderHook(() => useContainer(containerId), { wrapper });
await waitFor(() => {
expect(result.current.isLoading).toBeFalsy();
});
expect(result.current.data).toEqual({ testData: 'test-value' });
expect(axiosMock.history.get[0].url).toEqual(url);
});
});

View File

@@ -48,6 +48,9 @@ import {
getBlockTypes,
createLibraryContainer,
type CreateLibraryContainerDataRequest,
getContainerMetadata,
updateContainerMetadata,
type UpdateContainerDataRequest,
} from './api';
import { VersionSpec } from '../LibraryBlock';
@@ -110,6 +113,14 @@ export const xblockQueryKeys = {
componentDownstreamLinks: (usageKey: string) => [...xblockQueryKeys.xblock(usageKey), 'downstreamLinks'],
};
export const containerQueryKeys = {
all: ['container'],
/**
* Base key for data specific to a container
*/
container: (usageKey?: string) => [...containerQueryKeys.all, usageKey],
};
/**
* Tell react-query to refresh its cache of any data related to the given
* component (XBlock).
@@ -575,3 +586,30 @@ export const useCreateLibraryContainer = (libraryId: string) => {
},
});
};
/**
* Get the metadata for a container in a library
*/
export const useContainer = (containerId: string) => (
useQuery({
queryKey: containerQueryKeys.container(containerId),
queryFn: containerId ? () => getContainerMetadata(containerId) : undefined,
})
);
/**
* 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();
return useMutation({
mutationFn: (data: UpdateContainerDataRequest) => updateContainerMetadata(containerId, data),
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: containerQueryKeys.container(containerId) });
},
});
};