feat: add tags to collections [FC-0062] (#1379)
* feat: Add ContentTagsDrawer to collection * test: Add test to show ContentTagsDrawer on CollectionInfo
This commit is contained in:
@@ -333,7 +333,7 @@ const useContentTagsDrawerContext = (contentId, canTagObject) => {
|
||||
const closeToast = React.useCallback(() => setToastMessage(undefined), [setToastMessage]);
|
||||
|
||||
let contentName = '';
|
||||
if (isContentDataLoaded) {
|
||||
if (isContentDataLoaded && contentData) {
|
||||
if ('displayName' in contentData) {
|
||||
contentName = contentData.displayName;
|
||||
} else {
|
||||
|
||||
@@ -72,10 +72,16 @@ export async function getContentTaxonomyTagsCount(contentId) {
|
||||
/**
|
||||
* Fetch meta data (eg: display_name) about the content object (unit/compoenent)
|
||||
* @param {string} contentId The id of the content object (unit/component)
|
||||
* @returns {Promise<import("./types.mjs").ContentData>}
|
||||
* @returns {Promise<import("./types.mjs").ContentData | null>}
|
||||
*/
|
||||
export async function getContentData(contentId) {
|
||||
let url;
|
||||
if (contentId.startsWith('lib-collection:')) {
|
||||
// This type of usage_key is not used to obtain collections
|
||||
// is only used in tagging.
|
||||
return null;
|
||||
}
|
||||
|
||||
if (contentId.startsWith('lb:')) {
|
||||
url = getLibraryContentDataApiUrl(contentId);
|
||||
} else if (contentId.startsWith('course-v1:')) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
getContentTaxonomyTagsCount,
|
||||
} from './api';
|
||||
import { libraryQueryPredicate, xblockQueryKeys } from '../../library-authoring/data/apiHooks';
|
||||
import { getLibraryId } from '../../generic/key-utils';
|
||||
|
||||
/** @typedef {import("../../taxonomy/tag-list/data/types.mjs").TagListData} TagListData */
|
||||
/** @typedef {import("../../taxonomy/tag-list/data/types.mjs").TagData} TagData */
|
||||
@@ -147,9 +148,9 @@ export const useContentTaxonomyTagsUpdater = (contentId) => {
|
||||
contentPattern = contentId.replace(/\+type@.*$/, '*');
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: ['contentTagsCount', contentPattern] });
|
||||
if (contentId.includes('lb:')) {
|
||||
if (contentId.startsWith('lb:') || contentId.startsWith('lib-collection:')) {
|
||||
// Obtain library id from contentId
|
||||
const libraryId = ['lib', ...contentId.split(':').slice(1, 3)].join(':');
|
||||
const libraryId = getLibraryId(contentId);
|
||||
// Invalidate component metadata to update tags count
|
||||
queryClient.invalidateQueries(xblockQueryKeys.componentMetadata(contentId));
|
||||
// Invalidate content search to update tags count
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
buildCollectionUsageKey,
|
||||
getBlockType,
|
||||
getLibraryId,
|
||||
isLibraryKey,
|
||||
@@ -29,6 +30,8 @@ describe('component utils', () => {
|
||||
['lb:org:lib:html:id', 'lib:org:lib'],
|
||||
['lb:OpenCraftX:ALPHA:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd', 'lib:OpenCraftX:ALPHA'],
|
||||
['lb:Axim:beta:problem:571fe018-f3ce-45c9-8f53-5dafcb422fdd', 'lib:Axim:beta'],
|
||||
['lib-collection:org:lib:coll', 'lib:org:lib'],
|
||||
['lib-collection:OpenCraftX:ALPHA:coll', 'lib:OpenCraftX:ALPHA'],
|
||||
]) {
|
||||
it(`returns '${expected}' for usage key '${input}'`, () => {
|
||||
expect(getLibraryId(input)).toStrictEqual(expected);
|
||||
@@ -75,4 +78,21 @@ describe('component utils', () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('buildCollectionUsageKey', () => {
|
||||
for (const [libraryKey, collectionId, expected] of [
|
||||
['lib:org:lib', 'coll', 'lib-collection:org:lib:coll'],
|
||||
['lib:OpenCraftX:ALPHA', 'coll', 'lib-collection:OpenCraftX:ALPHA:coll'],
|
||||
['lb:org:lib:html:id', 'coll', ''],
|
||||
['lb:OpenCraftX:ALPHA:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd', 'coll', ''],
|
||||
['library-v1:AximX+L1', 'coll', ''],
|
||||
['course-v1:AximX+TS100+23', 'coll', ''],
|
||||
['', 'coll', ''],
|
||||
['', 'coll', ''],
|
||||
] as const) {
|
||||
it(`returns '${expected}' for learning context key '${libraryKey}' and collection Id '${collectionId}'`, () => {
|
||||
expect(buildCollectionUsageKey(libraryKey, collectionId)).toStrictEqual(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ export function getBlockType(usageKey: string): string {
|
||||
* @returns The library key, e.g. `lib:org:lib`
|
||||
*/
|
||||
export function getLibraryId(usageKey: string): string {
|
||||
if (usageKey && usageKey.startsWith('lb:')) {
|
||||
if (usageKey && (usageKey.startsWith('lb:') || usageKey.startsWith('lib-collection:'))) {
|
||||
const org = usageKey.split(':')[1];
|
||||
const lib = usageKey.split(':')[2];
|
||||
if (org && lib) {
|
||||
@@ -38,3 +38,16 @@ export function isLibraryKey(learningContextKey: string | undefined): learningCo
|
||||
export function isLibraryV1Key(learningContextKey: string | undefined): learningContextKey is string {
|
||||
return typeof learningContextKey === 'string' && learningContextKey.startsWith('library-v1:');
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a collection usage key from library V2 context key and collection Id.
|
||||
* This Collection Usage Key is only used on tagging.
|
||||
*/
|
||||
export const buildCollectionUsageKey = (learningContextKey: string, collectionId: string) => {
|
||||
if (!isLibraryKey(learningContextKey)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const orgLib = learningContextKey.replace('lib:', '');
|
||||
return `lib-collection:${orgLib}:${collectionId}`;
|
||||
};
|
||||
|
||||
@@ -11,6 +11,8 @@ import { useNavigate, useMatch } from 'react-router-dom';
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import CollectionDetails from './CollectionDetails';
|
||||
import messages from './messages';
|
||||
import { ContentTagsDrawer } from '../../content-tags-drawer';
|
||||
import { buildCollectionUsageKey } from '../../generic/key-utils';
|
||||
|
||||
const CollectionInfo = () => {
|
||||
const intl = useIntl();
|
||||
@@ -34,6 +36,8 @@ const CollectionInfo = () => {
|
||||
throw new Error('sidebarCollectionId is required');
|
||||
}
|
||||
|
||||
const collectionUsageKey = buildCollectionUsageKey(libraryId, sidebarCollectionId);
|
||||
|
||||
const handleOpenCollection = useCallback(() => {
|
||||
if (!componentPickerMode) {
|
||||
navigate(url);
|
||||
@@ -61,7 +65,10 @@ const CollectionInfo = () => {
|
||||
defaultActiveKey="manage"
|
||||
>
|
||||
<Tab eventKey="manage" title={intl.formatMessage(messages.manageTabTitle)}>
|
||||
Manage tab placeholder
|
||||
<ContentTagsDrawer
|
||||
id={collectionUsageKey}
|
||||
variant="component"
|
||||
/>
|
||||
</Tab>
|
||||
<Tab eventKey="details" title={intl.formatMessage(messages.detailsTabTitle)}>
|
||||
<CollectionDetails />
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import { mockContentSearchConfig, mockGetBlockTypes } from '../../search-manager/data/api.mock';
|
||||
import { mockBroadcastChannel, mockClipboardEmpty } from '../../generic/data/api.mock';
|
||||
import { LibraryLayout } from '..';
|
||||
import { ContentTagsDrawer } from '../../content-tags-drawer';
|
||||
import { getLibraryCollectionComponentApiUrl } from '../data/api';
|
||||
|
||||
let axiosMock: MockAdapter;
|
||||
@@ -43,6 +44,7 @@ const mockCollection = {
|
||||
};
|
||||
|
||||
const { title } = mockGetCollectionMetadata.collectionData;
|
||||
jest.mock('../../content-tags-drawer/ContentTagsDrawer', () => jest.fn(() => <div>Mocked ContentTagsDrawer</div>));
|
||||
|
||||
describe('<LibraryCollectionPage />', () => {
|
||||
beforeEach(() => {
|
||||
@@ -200,6 +202,8 @@ describe('<LibraryCollectionPage />', () => {
|
||||
});
|
||||
|
||||
it('should open collection Info by default', async () => {
|
||||
const expectedCollectionUsageKey = 'lib-collection:Axim:TEST:my-first-collection';
|
||||
|
||||
await renderLibraryCollectionPage();
|
||||
|
||||
expect(await screen.findByText('All Collections')).toBeInTheDocument();
|
||||
@@ -209,9 +213,18 @@ describe('<LibraryCollectionPage />', () => {
|
||||
|
||||
expect(screen.getByText('Manage')).toBeInTheDocument();
|
||||
expect(screen.getByText('Details')).toBeInTheDocument();
|
||||
expect(screen.getByText('Mocked ContentTagsDrawer')).toBeInTheDocument();
|
||||
expect(ContentTagsDrawer).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: expectedCollectionUsageKey,
|
||||
}),
|
||||
{},
|
||||
);
|
||||
});
|
||||
|
||||
it('should close and open Collection Info', async () => {
|
||||
const expectedCollectionUsageKey = 'lib-collection:Axim:TEST:my-first-collection';
|
||||
|
||||
await renderLibraryCollectionPage();
|
||||
|
||||
expect(await screen.findByText('All Collections')).toBeInTheDocument();
|
||||
@@ -230,6 +243,13 @@ describe('<LibraryCollectionPage />', () => {
|
||||
fireEvent.click(collectionInfoBtn);
|
||||
expect(screen.getByText('Manage')).toBeInTheDocument();
|
||||
expect(screen.getByText('Details')).toBeInTheDocument();
|
||||
expect(screen.getByText('Mocked ContentTagsDrawer')).toBeInTheDocument();
|
||||
expect(ContentTagsDrawer).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: expectedCollectionUsageKey,
|
||||
}),
|
||||
{},
|
||||
);
|
||||
});
|
||||
|
||||
it('sorts collection components', async () => {
|
||||
|
||||
Reference in New Issue
Block a user