Skip to content

Commit

Permalink
[FC-0049] feat: Other Tags section added on tags drawer (#987)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ChrisChV authored May 9, 2024
1 parent 55adcfe commit e0fb41d
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 3 deletions.
24 changes: 24 additions & 0 deletions src/content-tags-drawer/ContentTagsDrawer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const ContentTagsDrawer = ({ id, onClose }) => {
toastMessage,
closeToast,
setCollapsibleToInitalState,
otherTaxonomies,
} = context;

let onCloseDrawer = onClose;
Expand Down Expand Up @@ -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>

Expand Down
4 changes: 4 additions & 0 deletions src/content-tags-drawer/ContentTagsDrawer.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
142 changes: 142 additions & 0 deletions src/content-tags-drawer/ContentTagsDrawer.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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();
});
});
53 changes: 50 additions & 3 deletions src/content-tags-drawer/ContentTagsDrawerHelper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { ContentTagsDrawerSheetContext } from './common/context';
* showToastAfterSave: () => void,
* closeToast: () => void,
* setCollapsibleToInitalState: () => void,
* otherTaxonomies: TagsInTaxonomy[],
* }}
*/
const useContentTagsDrawerContext = (contentId) => {
Expand All @@ -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.
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]);

Expand All @@ -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]);

Expand Down Expand Up @@ -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
Expand All @@ -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),
);
}
});

Expand All @@ -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 = () => {
Expand Down Expand Up @@ -364,6 +403,7 @@ const useContentTagsDrawerContext = (contentId) => {
}
}, [
fechedTaxonomies,
fechedOtherTaxonomies,
globalStagedContentTags,
globalStagedRemovedContentTags,
]);
Expand All @@ -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]);
Expand Down Expand Up @@ -408,6 +454,7 @@ const useContentTagsDrawerContext = (contentId) => {
showToastAfterSave,
closeToast,
setCollapsibleToInitalState,
otherTaxonomies,
};
};

Expand Down
1 change: 1 addition & 0 deletions src/content-tags-drawer/common/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/content-tags-drawer/data/types.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* @property {number} taxonomyId
* @property {boolean} canTagObject
* @property {Tag[]} tags
* @property {string} exportId
*/

/**
Expand Down
10 changes: 10 additions & 0 deletions src/content-tags-drawer/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

0 comments on commit e0fb41d

Please sign in to comment.