[FC-0049] feat: Other Tags section added on tags drawer (#987)
Adds the new "Other tags" Section to tags drawer that contains the taxonomies/tags that the user doesn't have permission to see/edit. It allow to delete those tags.
This commit is contained in:
@@ -51,6 +51,7 @@ const ContentTagsDrawer = ({ id, onClose }) => {
|
||||
toastMessage,
|
||||
closeToast,
|
||||
setCollapsibleToInitalState,
|
||||
otherTaxonomies,
|
||||
} = context;
|
||||
|
||||
let onCloseDrawer = onClose;
|
||||
@@ -122,6 +123,29 @@ const ContentTagsDrawer = ({ id, onClose }) => {
|
||||
</div>
|
||||
))
|
||||
: <Loading />}
|
||||
{otherTaxonomies.length !== 0 && (
|
||||
<div>
|
||||
<p className="h4 text-gray-500 font-weight-bold">
|
||||
{intl.formatMessage(messages.otherTagsHeader)}
|
||||
</p>
|
||||
<p className="other-description text-gray-500">
|
||||
{intl.formatMessage(messages.otherTagsDescription)}
|
||||
</p>
|
||||
{ isTaxonomyListLoaded && isContentTaxonomyTagsLoaded && (
|
||||
otherTaxonomies.map((data) => (
|
||||
<div key={`taxonomy-tags-collapsible-${data.id}`}>
|
||||
<ContentTagsCollapsible
|
||||
contentId={contentId}
|
||||
taxonomyAndTagsData={data}
|
||||
stagedContentTags={stagedContentTags[data.id] || []}
|
||||
collapsibleState={collapsibleStates[data.id] || false}
|
||||
/>
|
||||
<hr />
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Container>
|
||||
</Container>
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
background-color: transparent;
|
||||
color: $gray-300 !important;
|
||||
}
|
||||
|
||||
.other-description {
|
||||
font-size: .9rem;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply styles to sheet only if it has a child with a .tags-drawer class
|
||||
|
||||
@@ -162,6 +162,100 @@ describe('<ContentTagsDrawer />', () => {
|
||||
});
|
||||
};
|
||||
|
||||
const setupMockDataWithOtherTagsTestings = () => {
|
||||
useContentTaxonomyTagsData.mockReturnValue({
|
||||
isSuccess: true,
|
||||
data: {
|
||||
taxonomies: [
|
||||
{
|
||||
name: 'Taxonomy 1',
|
||||
taxonomyId: 123,
|
||||
canTagObject: true,
|
||||
tags: [
|
||||
{
|
||||
value: 'Tag 1',
|
||||
lineage: ['Tag 1'],
|
||||
canDeleteObjecttag: true,
|
||||
},
|
||||
{
|
||||
value: 'Tag 2',
|
||||
lineage: ['Tag 2'],
|
||||
canDeleteObjecttag: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Taxonomy 2',
|
||||
taxonomyId: 1234,
|
||||
canTagObject: false,
|
||||
tags: [
|
||||
{
|
||||
value: 'Tag 3',
|
||||
lineage: ['Tag 3'],
|
||||
canDeleteObjecttag: true,
|
||||
},
|
||||
{
|
||||
value: 'Tag 4',
|
||||
lineage: ['Tag 4'],
|
||||
canDeleteObjecttag: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
getTaxonomyListData.mockResolvedValue({
|
||||
results: [
|
||||
{
|
||||
id: 123,
|
||||
name: 'Taxonomy 1',
|
||||
description: 'This is a description 1',
|
||||
canTagObject: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
useTaxonomyTagsData.mockReturnValue({
|
||||
hasMorePages: false,
|
||||
canAddTag: false,
|
||||
tagPages: {
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
data: [{
|
||||
value: 'Tag 1',
|
||||
externalId: null,
|
||||
childCount: 0,
|
||||
depth: 0,
|
||||
parentValue: null,
|
||||
id: 12345,
|
||||
subTagsUrl: null,
|
||||
canChangeTag: false,
|
||||
canDeleteTag: false,
|
||||
}, {
|
||||
value: 'Tag 2',
|
||||
externalId: null,
|
||||
childCount: 0,
|
||||
depth: 0,
|
||||
parentValue: null,
|
||||
id: 12346,
|
||||
subTagsUrl: null,
|
||||
canChangeTag: false,
|
||||
canDeleteTag: false,
|
||||
}, {
|
||||
value: 'Tag 3',
|
||||
externalId: null,
|
||||
childCount: 0,
|
||||
depth: 0,
|
||||
parentValue: null,
|
||||
id: 12347,
|
||||
subTagsUrl: null,
|
||||
canChangeTag: false,
|
||||
canDeleteTag: false,
|
||||
}],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const setupLargeMockDataForStagedTagsTesting = () => {
|
||||
useContentTaxonomyTagsData.mockReturnValue({
|
||||
isSuccess: true,
|
||||
@@ -915,4 +1009,52 @@ describe('<ContentTagsDrawer />', () => {
|
||||
expect(taxonomies[i].textContent).toBe(expectedOrder[i]);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not show "Other tags" section', async () => {
|
||||
setupMockDataForStagedTagsTesting();
|
||||
|
||||
render(<RootWrapper />);
|
||||
expect(await screen.findByText('Taxonomy 1')).toBeInTheDocument();
|
||||
|
||||
expect(screen.queryByText('Other tags')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show "Other tags" section', async () => {
|
||||
setupMockDataWithOtherTagsTestings();
|
||||
|
||||
render(<RootWrapper />);
|
||||
expect(await screen.findByText('Taxonomy 1')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText('Other tags')).toBeInTheDocument();
|
||||
expect(screen.getByText('Taxonomy 2')).toBeInTheDocument();
|
||||
expect(screen.getByText('Tag 3')).toBeInTheDocument();
|
||||
expect(screen.getByText('Tag 4')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should test delete "Other tags" and cancel', async () => {
|
||||
setupMockDataWithOtherTagsTestings();
|
||||
render(<RootWrapper />);
|
||||
expect(await screen.findByText('Taxonomy 2')).toBeInTheDocument();
|
||||
|
||||
// To edit mode
|
||||
const editTagsButton = screen.getByRole('button', {
|
||||
name: /edit tags/i,
|
||||
});
|
||||
fireEvent.click(editTagsButton);
|
||||
|
||||
// Delete the tag
|
||||
const tag = screen.getByText(/tag 3/i);
|
||||
const deleteButton = within(tag).getByRole('button', {
|
||||
name: /delete/i,
|
||||
});
|
||||
fireEvent.click(deleteButton);
|
||||
|
||||
expect(tag).not.toBeInTheDocument();
|
||||
|
||||
// Click "Cancel"
|
||||
const cancelButton = screen.getByRole('button', { name: /cancel/i });
|
||||
fireEvent.click(cancelButton);
|
||||
|
||||
expect(screen.getByText(/tag 3/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,6 +43,7 @@ import { ContentTagsDrawerSheetContext } from './common/context';
|
||||
* showToastAfterSave: () => void,
|
||||
* closeToast: () => void,
|
||||
* setCollapsibleToInitalState: () => void,
|
||||
* otherTaxonomies: TagsInTaxonomy[],
|
||||
* }}
|
||||
*/
|
||||
const useContentTagsDrawerContext = (contentId) => {
|
||||
@@ -61,6 +62,8 @@ const useContentTagsDrawerContext = (contentId) => {
|
||||
const [globalStagedRemovedContentTags, setGlobalStagedRemovedContentTags] = React.useState({});
|
||||
// Merges feched tags, global staged tags and global removed staged tags
|
||||
const [tagsByTaxonomy, setTagsByTaxonomy] = React.useState(/** @type TagsInTaxonomy[] */ ([]));
|
||||
// Other taxonomies that the user doesn't have permissions
|
||||
const [otherTaxonomies, setOtherTaxonomies] = React.useState(/** @type TagsInTaxonomy[] */ ([]));
|
||||
// This stores taxonomy collapsible states (open/close).
|
||||
const [collapsibleStates, setColapsibleStates] = React.useState({});
|
||||
// Message to show a toast in the content drawer.
|
||||
@@ -77,7 +80,7 @@ const useContentTagsDrawerContext = (contentId) => {
|
||||
const { data: taxonomyListData, isSuccess: isTaxonomyListLoaded } = useTaxonomyList(org);
|
||||
|
||||
// Tags feched from database
|
||||
const fechedTaxonomies = React.useMemo(() => {
|
||||
const { fechedTaxonomies, fechedOtherTaxonomies } = React.useMemo(() => {
|
||||
const sortTaxonomies = (taxonomiesList) => {
|
||||
const taxonomiesWithData = taxonomiesList.filter(
|
||||
(t) => t.contentTags.length !== 0,
|
||||
@@ -117,17 +120,37 @@ const useContentTagsDrawerContext = (contentId) => {
|
||||
|
||||
const contentTaxonomies = contentTaxonomyTagsData.taxonomies;
|
||||
|
||||
const otherTaxonomiesList = [];
|
||||
|
||||
// eslint-disable-next-line array-callback-return
|
||||
contentTaxonomies.map((contentTaxonomyTags) => {
|
||||
const contentTaxonomy = taxonomiesList.find((taxonomy) => taxonomy.id === contentTaxonomyTags.taxonomyId);
|
||||
if (contentTaxonomy) {
|
||||
contentTaxonomy.contentTags = contentTaxonomyTags.tags;
|
||||
} else {
|
||||
otherTaxonomiesList.push({
|
||||
canChangeTaxonomy: false,
|
||||
canDeleteTaxonomy: false,
|
||||
canTagObject: false,
|
||||
contentTags: contentTaxonomyTags.tags,
|
||||
enabled: true,
|
||||
exportId: contentTaxonomyTags.exportId,
|
||||
id: contentTaxonomyTags.taxonomyId,
|
||||
name: contentTaxonomyTags.name,
|
||||
visibleToAuthors: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return sortTaxonomies(taxonomiesList);
|
||||
return {
|
||||
fechedTaxonomies: sortTaxonomies(taxonomiesList),
|
||||
fechedOtherTaxonomies: otherTaxonomiesList,
|
||||
};
|
||||
}
|
||||
return [];
|
||||
return {
|
||||
fechedTaxonomies: [],
|
||||
fechedOtherTaxonomies: [],
|
||||
};
|
||||
}, [taxonomyListData, contentTaxonomyTagsData]);
|
||||
|
||||
// Add a content tags to the staged tags for a taxonomy
|
||||
@@ -204,6 +227,9 @@ const useContentTagsDrawerContext = (contentId) => {
|
||||
fechedTaxonomies.forEach((taxonomy) => {
|
||||
updatedState[taxonomy.id] = true;
|
||||
});
|
||||
fechedOtherTaxonomies.forEach((taxonomy) => {
|
||||
updatedState[taxonomy.id] = true;
|
||||
});
|
||||
setColapsibleStates(updatedState);
|
||||
}, [fechedTaxonomies, setColapsibleStates]);
|
||||
|
||||
@@ -214,6 +240,10 @@ const useContentTagsDrawerContext = (contentId) => {
|
||||
// Taxonomy with content tags must be open
|
||||
updatedState[taxonomy.id] = taxonomy.contentTags.length !== 0;
|
||||
});
|
||||
fechedOtherTaxonomies.forEach((taxonomy) => {
|
||||
// Taxonomy with content tags must be open
|
||||
updatedState[taxonomy.id] = taxonomy.contentTags.length !== 0;
|
||||
});
|
||||
setColapsibleStates(updatedState);
|
||||
}, [fechedTaxonomies, setColapsibleStates]);
|
||||
|
||||
@@ -310,6 +340,10 @@ const useContentTagsDrawerContext = (contentId) => {
|
||||
{ ...acc, [obj.id]: obj }
|
||||
), {});
|
||||
|
||||
const mergedOtherTaxonomies = cloneDeep(fechedOtherTaxonomies).reduce((acc, obj) => (
|
||||
{ ...acc, [obj.id]: obj }
|
||||
), {});
|
||||
|
||||
Object.keys(globalStagedContentTags).forEach((taxonomyId) => {
|
||||
if (mergedTags[taxonomyId]) {
|
||||
// TODO test this
|
||||
@@ -329,6 +363,10 @@ const useContentTagsDrawerContext = (contentId) => {
|
||||
mergedTags[taxonomyId].contentTags = mergedTags[taxonomyId].contentTags.filter(
|
||||
(t) => !globalStagedRemovedContentTags[taxonomyId].includes(t.value),
|
||||
);
|
||||
} else if (mergedOtherTaxonomies[taxonomyId]) {
|
||||
mergedOtherTaxonomies[taxonomyId].contentTags = mergedOtherTaxonomies[taxonomyId].contentTags.filter(
|
||||
(t) => !globalStagedRemovedContentTags[taxonomyId].includes(t.value),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -337,6 +375,7 @@ const useContentTagsDrawerContext = (contentId) => {
|
||||
const mergedTagsArray = fechedTaxonomies.map(obj => mergedTags[obj.id]);
|
||||
|
||||
setTagsByTaxonomy(mergedTagsArray);
|
||||
setOtherTaxonomies(Object.values(mergedOtherTaxonomies));
|
||||
|
||||
if (setBlockingSheet) {
|
||||
const areChangesInTags = () => {
|
||||
@@ -364,6 +403,7 @@ const useContentTagsDrawerContext = (contentId) => {
|
||||
}
|
||||
}, [
|
||||
fechedTaxonomies,
|
||||
fechedOtherTaxonomies,
|
||||
globalStagedContentTags,
|
||||
globalStagedRemovedContentTags,
|
||||
]);
|
||||
@@ -376,6 +416,12 @@ const useContentTagsDrawerContext = (contentId) => {
|
||||
tags: tags.contentTags.map(t => t.value),
|
||||
});
|
||||
});
|
||||
otherTaxonomies.forEach((tags) => {
|
||||
tagsData.push({
|
||||
taxonomy: tags.id,
|
||||
tags: tags.contentTags.map(t => t.value),
|
||||
});
|
||||
});
|
||||
// @ts-ignore
|
||||
updateTags.mutate({ tagsData });
|
||||
}, [tagsByTaxonomy]);
|
||||
@@ -408,6 +454,7 @@ const useContentTagsDrawerContext = (contentId) => {
|
||||
showToastAfterSave,
|
||||
closeToast,
|
||||
setCollapsibleToInitalState,
|
||||
otherTaxonomies,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ export const ContentTagsDrawerContext = React.createContext({
|
||||
showToastAfterSave: /** @type{() => void} */ (() => {}),
|
||||
closeToast: /** @type{() => void} */ (() => {}),
|
||||
setCollapsibleToInitalState: /** @type{() => void} */ (() => {}),
|
||||
otherTaxonomies: /** @type{TagsInTaxonomy[]} */ ([]),
|
||||
});
|
||||
|
||||
// This context has not been added to ContentTagsDrawerContext because it has been
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* @property {number} taxonomyId
|
||||
* @property {boolean} canTagObject
|
||||
* @property {Tag[]} tags
|
||||
* @property {string} exportId
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -114,6 +114,16 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Delete',
|
||||
description: 'Alt label for Delete tag button.',
|
||||
},
|
||||
otherTagsHeader: {
|
||||
id: 'course-authoring.content-tags-drawer.other-tags.header',
|
||||
defaultMessage: 'Other tags',
|
||||
description: 'Header of "Other tags" subsection in tags drawer',
|
||||
},
|
||||
otherTagsDescription: {
|
||||
id: 'course-authoring.content-tags-drawer.other-tags.description',
|
||||
defaultMessage: 'These tags are already applied, but you can\'t add new ones as you don\'t have access to their taxonomies.',
|
||||
description: 'Description of "Other tags" subsection in tags drawer',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
Reference in New Issue
Block a user