feat: library unit sidebar [FC-0083] (#1762)
Implements the placeholder for the Unit Sidebar.
This commit is contained in:
@@ -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()`
|
||||
*
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user