diff --git a/src/content-tags-drawer/ContentTagsCollapsible.jsx b/src/content-tags-drawer/ContentTagsCollapsible.jsx index 574e1617fb..470b38f9ce 100644 --- a/src/content-tags-drawer/ContentTagsCollapsible.jsx +++ b/src/content-tags-drawer/ContentTagsCollapsible.jsx @@ -99,11 +99,10 @@ import useContentTagsCollapsibleHelper from './ContentTagsCollapsibleHelper'; * @param {Object} props - The component props. * @param {string} props.contentId - Id of the content object * @param {TaxonomyData & {contentTags: ContentTagData[]}} props.taxonomyAndTagsData - Taxonomy metadata & applied tags - * @param {boolean} props.editable - Whether the tags can be edited */ -const ContentTagsCollapsible = ({ contentId, taxonomyAndTagsData, editable }) => { +const ContentTagsCollapsible = ({ contentId, taxonomyAndTagsData }) => { const intl = useIntl(); - const { id, name } = taxonomyAndTagsData; + const { id, name, canTagObject } = taxonomyAndTagsData; const { tagChangeHandler, tagsTree, contentTagsCount, checkedTags, @@ -141,12 +140,12 @@ const ContentTagsCollapsible = ({ contentId, taxonomyAndTagsData, editable }) =>
- +
- {editable && ( + {canTagObject && (
@@ -73,10 +72,10 @@ ContentTagsTree.propTypes = { PropTypes.shape({ explicit: PropTypes.bool.isRequired, children: PropTypes.shape({}).isRequired, + canDelete: PropTypes.bool.isRequired, }).isRequired, ).isRequired, removeTagHandler: PropTypes.func.isRequired, - editable: PropTypes.bool.isRequired, }; export default ContentTagsTree; diff --git a/src/content-tags-drawer/ContentTagsTree.test.jsx b/src/content-tags-drawer/ContentTagsTree.test.jsx index ac41f3c1f1..b42d08f482 100644 --- a/src/content-tags-drawer/ContentTagsTree.test.jsx +++ b/src/content-tags-drawer/ContentTagsTree.test.jsx @@ -15,8 +15,10 @@ const data = { 'DNA Sequencing': { explicit: true, children: {}, + canDelete: true, }, }, + canDelete: false, }, 'Molecular, Cellular, and Microbiology': { explicit: false, @@ -24,16 +26,18 @@ const data = { Virology: { explicit: true, children: {}, + canDelete: true, }, }, + canDelete: false, }, }, }, }; -const ContentTagsTreeComponent = ({ tagsTree, removeTagHandler, editable }) => ( +const ContentTagsTreeComponent = ({ tagsTree, removeTagHandler }) => ( - + ); @@ -42,16 +46,16 @@ ContentTagsTreeComponent.propTypes = { PropTypes.shape({ explicit: PropTypes.bool.isRequired, children: PropTypes.shape({}).isRequired, + canDelete: PropTypes.bool.isRequired, }).isRequired, ).isRequired, removeTagHandler: PropTypes.func.isRequired, - editable: PropTypes.bool.isRequired, }; describe('', () => { it('should render taxonomy tags data along content tags number badge', async () => { await act(async () => { - const { getByText } = render( {}} editable />); + const { getByText } = render( {}} />); expect(getByText('Science and Research')).toBeInTheDocument(); expect(getByText('Genetics Subcategory')).toBeInTheDocument(); expect(getByText('Molecular, Cellular, and Microbiology')).toBeInTheDocument(); diff --git a/src/content-tags-drawer/TagBubble.jsx b/src/content-tags-drawer/TagBubble.jsx index 2287e48ab3..2eb1583444 100644 --- a/src/content-tags-drawer/TagBubble.jsx +++ b/src/content-tags-drawer/TagBubble.jsx @@ -8,15 +8,15 @@ import PropTypes from 'prop-types'; import TagOutlineIcon from './TagOutlineIcon'; const TagBubble = ({ - value, implicit, level, lineage, removeTagHandler, editable, + value, implicit, level, lineage, removeTagHandler, canDelete, }) => { const className = `tag-bubble mb-2 border-light-300 ${implicit ? 'implicit' : ''}`; const handleClick = React.useCallback(() => { - if (!implicit && editable) { + if (!implicit && canDelete) { removeTagHandler(lineage.join(','), false); } - }, [implicit, lineage, editable, removeTagHandler]); + }, [implicit, lineage, canDelete, removeTagHandler]); return (
@@ -24,7 +24,7 @@ const TagBubble = ({ className={className} variant="light" iconBefore={!implicit ? Tag : TagOutlineIcon} - iconAfter={!implicit && editable ? Close : null} + iconAfter={!implicit && canDelete ? Close : null} onIconAfterClick={handleClick} > {value} @@ -44,7 +44,7 @@ TagBubble.propTypes = { level: PropTypes.number, lineage: PropTypes.arrayOf(PropTypes.string).isRequired, removeTagHandler: PropTypes.func.isRequired, - editable: PropTypes.bool.isRequired, + canDelete: PropTypes.bool.isRequired, }; export default TagBubble; diff --git a/src/content-tags-drawer/TagBubble.test.jsx b/src/content-tags-drawer/TagBubble.test.jsx index e03fe18726..e15853e842 100644 --- a/src/content-tags-drawer/TagBubble.test.jsx +++ b/src/content-tags-drawer/TagBubble.test.jsx @@ -12,7 +12,7 @@ const data = { }; const TagBubbleComponent = ({ - value, implicit, level, lineage, removeTagHandler, editable, + value, implicit, level, lineage, removeTagHandler, canDelete, }) => ( ); @@ -37,7 +37,7 @@ TagBubbleComponent.propTypes = { level: PropTypes.number, lineage: PropTypes.arrayOf(PropTypes.string).isRequired, removeTagHandler: PropTypes.func.isRequired, - editable: PropTypes.bool.isRequired, + canDelete: PropTypes.bool.isRequired, }; describe('', () => { @@ -45,7 +45,7 @@ describe('', () => { const { container, getByText } = render( , @@ -63,7 +63,7 @@ describe('', () => { const { container, getByText } = render( ', () => { const { container } = render( getConfig().STUDIO_BASE_URL; * @returns {string} the URL */ export const getTaxonomyTagsApiUrl = (taxonomyId, options = {}) => { - const url = new URL(`api/content_tagging/v1/taxonomies/${taxonomyId}/tags/`, getApiBaseUrl()); + const url = new URL(`api/content_tagging/v1/taxonomies/${taxonomyId}/tags/?include_perms`, getApiBaseUrl()); if (options.parentTag) { url.searchParams.append('parent_tag', options.parentTag); } @@ -28,7 +28,7 @@ export const getTaxonomyTagsApiUrl = (taxonomyId, options = {}) => { return url.href; }; -export const getContentTaxonomyTagsApiUrl = (contentId) => new URL(`api/content_tagging/v1/object_tags/${contentId}/`, getApiBaseUrl()).href; +export const getContentTaxonomyTagsApiUrl = (contentId) => new URL(`api/content_tagging/v1/object_tags/${contentId}/?include_perms`, getApiBaseUrl()).href; export const getXBlockContentDataApiURL = (contentId) => new URL(`/xblock/outline/${contentId}`, getApiBaseUrl()).href; export const getLibraryContentDataApiUrl = (contentId) => new URL(`/api/libraries/v2/blocks/${contentId}/`, getApiBaseUrl()).href; @@ -76,7 +76,7 @@ export async function getContentData(contentId) { */ export async function updateContentTaxonomyTags(contentId, taxonomyId, tags) { let url = getContentTaxonomyTagsApiUrl(contentId); - url = `${url}?taxonomy=${taxonomyId}`; + url = `${url}&taxonomy=${taxonomyId}`; const { data } = await getAuthenticatedHttpClient().put(url, { tags }); return camelCaseObject(data[contentId]); } diff --git a/src/content-tags-drawer/data/api.test.js b/src/content-tags-drawer/data/api.test.js index d183f59ea2..229e6daee1 100644 --- a/src/content-tags-drawer/data/api.test.js +++ b/src/content-tags-drawer/data/api.test.js @@ -110,10 +110,10 @@ describe('content tags drawer api calls', () => { const contentId = 'block-v1:SampleTaxonomyOrg1+STC1+2023_1+type@vertical+block@aaf8b8eb86b54281aeeab12499d2cb0b'; const taxonomyId = 3; const tags = ['flat taxonomy tag 100', 'flat taxonomy tag 3856']; - axiosMock.onPut(`${getContentTaxonomyTagsApiUrl(contentId)}?taxonomy=${taxonomyId}`).reply(200, updateContentTaxonomyTagsMock); + axiosMock.onPut(`${getContentTaxonomyTagsApiUrl(contentId)}&taxonomy=${taxonomyId}`).reply(200, updateContentTaxonomyTagsMock); const result = await updateContentTaxonomyTags(contentId, taxonomyId, tags); - expect(axiosMock.history.put[0].url).toEqual(`${getContentTaxonomyTagsApiUrl(contentId)}?taxonomy=${taxonomyId}`); + expect(axiosMock.history.put[0].url).toEqual(`${getContentTaxonomyTagsApiUrl(contentId)}&taxonomy=${taxonomyId}`); expect(result).toEqual(updateContentTaxonomyTagsMock[contentId]); }); }); diff --git a/src/content-tags-drawer/data/types.mjs b/src/content-tags-drawer/data/types.mjs index 2145f41b95..9e249f7ade 100644 --- a/src/content-tags-drawer/data/types.mjs +++ b/src/content-tags-drawer/data/types.mjs @@ -10,7 +10,7 @@ * @typedef {Object} ContentTaxonomyTagData A list of the tags from one taxonomy that are applied to a content object. * @property {string} name * @property {number} taxonomyId - * @property {boolean} editable + * @property {boolean} canTagObject * @property {Tag[]} tags */ diff --git a/src/taxonomy/data/api.js b/src/taxonomy/data/api.js index c4a3a7d653..aa282dcb09 100644 --- a/src/taxonomy/data/api.js +++ b/src/taxonomy/data/api.js @@ -5,7 +5,7 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL; export const getTaxonomyListApiUrl = (org) => { - const url = new URL('api/content_tagging/v1/taxonomies/', getApiBaseUrl()); + const url = new URL('api/content_tagging/v1/taxonomies/?include_perms', getApiBaseUrl()); url.searchParams.append('enabled', 'true'); if (org !== undefined) { if (org === 'Unassigned') { @@ -32,7 +32,7 @@ export const getTaxonomyTemplateApiUrl = (format) => new URL( * @param {number} pk * @returns {string} */ -export const getTaxonomyApiUrl = (pk) => new URL(`api/content_tagging/v1/taxonomies/${pk}/`, getApiBaseUrl()).href; +export const getTaxonomyApiUrl = (pk) => new URL(`api/content_tagging/v1/taxonomies/${pk}/?include_perms`, getApiBaseUrl()).href; /** * Get list of taxonomies. diff --git a/src/taxonomy/data/types.mjs b/src/taxonomy/data/types.mjs index 6268b882e0..2e606712a1 100644 --- a/src/taxonomy/data/types.mjs +++ b/src/taxonomy/data/types.mjs @@ -15,6 +15,7 @@ * @property {boolean} allOrgs * @property {boolean} canChange * @property {boolean} canDelete + * @property {boolean} canTagObject */ /**