Skip to content

Commit 2209242

Browse files
authored
feat: course outline sidebar (openedx#2731)
implements the new sidebar design for the Course Outline
1 parent 122414c commit 2209242

46 files changed

Lines changed: 1349 additions & 248 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/content-tags-drawer/ContentTagsCollapsibleHelper.jsx

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,6 @@ const getLeafTags = (tree) => {
6868
* @param {StagedTagData[]} stagedContentTags
6969
* - Array of staged tags represented as objects with value/label
7070
* @param {TaxonomyData & {contentTags: ContentTagData[]}} taxonomyAndTagsData
71-
* @returns {{
72-
* tagChangeHandler: (tagSelectableBoxValue: string, checked: boolean) => void,
73-
* removeAppliedTagHandler: (tagSelectableBoxValue: string) => void,
74-
* appliedContentTagsTree: Record<string, TagTreeEntry>,
75-
* stagedContentTagsTree: Record<string, TagTreeEntry>,
76-
* contentTagsCount: number,
77-
* checkedTags: any,
78-
* commitStagedTagsToGlobal: () => void,
79-
* updateTags: import('@tanstack/react-query').UseMutationResult<
80-
* any, unknown, { tagsData: Promise<UpdateTagsData[]>; }, unknown
81-
* >
8271
* }}
8372
*/
8473
const useContentTagsCollapsibleHelper = (

src/content-tags-drawer/ContentTagsDrawerHelper.jsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,6 @@ export const useCreateContentTagsDrawerContext = (contentId, canTagObject, fetch
407407
tags: tags.contentTags.map(t => t.value),
408408
});
409409
});
410-
// @ts-ignore
411410
updateTags.mutate({ tagsData });
412411
}, [tagsByTaxonomy]);
413412

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.tag-snippet-chip {
2+
max-width: 260px;
3+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {
2+
render, screen, waitFor, initializeMocks,
3+
} from '@src/testUtils';
4+
5+
import { mockContentTaxonomyTagsData } from './data/api.mocks';
6+
import { ContentTagsSnippet } from './ContentTagsSnippet';
7+
8+
mockContentTaxonomyTagsData.applyMock();
9+
10+
const {
11+
otherTagsId,
12+
largeTagsId,
13+
veryLongTagsId,
14+
} = mockContentTaxonomyTagsData;
15+
16+
describe('<ContentTagsSnippet />', () => {
17+
beforeEach(() => {
18+
initializeMocks();
19+
});
20+
21+
it('should render the tags correctly', async () => {
22+
render(<ContentTagsSnippet contentId={otherTagsId} />);
23+
await waitFor(() => {
24+
expect(screen.getByText('Taxonomy 1 (2)')).toBeInTheDocument();
25+
});
26+
expect(screen.getByText('Tag 1')).toBeInTheDocument();
27+
expect(screen.getByText('Tag 2')).toBeInTheDocument();
28+
expect(screen.getByText('Taxonomy 2 (2)')).toBeInTheDocument();
29+
expect(screen.getByText('Tag 3')).toBeInTheDocument();
30+
expect(screen.getByText('Tag 4')).toBeInTheDocument();
31+
});
32+
33+
it('should render the tags with lineage correctly', async () => {
34+
render(<ContentTagsSnippet contentId={largeTagsId} />);
35+
await waitFor(() => {
36+
expect(screen.getByText('Taxonomy 3 (1)')).toBeInTheDocument();
37+
});
38+
expect(screen.getByText('Tag 1 > Tag 1.1 > Tag 1.1.1')).toBeInTheDocument();
39+
});
40+
41+
it('should render the very long lineage correctly', async () => {
42+
render(<ContentTagsSnippet contentId={veryLongTagsId} />);
43+
await waitFor(() => {
44+
expect(screen.getByText('ESDC Skills and Competencies (2)')).toBeInTheDocument();
45+
});
46+
47+
// Skills > Technical Skills Sub-Category > Technical Skills
48+
// Can fit only first and last level
49+
expect(screen.getByText('Skills > .. > Technical Skills')).toBeInTheDocument();
50+
51+
// Abilities > Cognitive Abilities > Communication Abilities
52+
// can fit only last level
53+
expect(screen.getByText('.. > Communication Abilities')).toBeInTheDocument();
54+
});
55+
});
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { Chip, Stack } from '@openedx/paragon';
2+
import { Tag as TagIcon } from '@openedx/paragon/icons';
3+
4+
import { useContentTaxonomyTagsData } from './data/apiHooks';
5+
import { Tag } from './data/types';
6+
7+
interface ContentTagsSnippetProps {
8+
contentId: string;
9+
}
10+
11+
const ContentTagChip = ({ tag }: { tag: Tag }) => {
12+
let lineageStr = tag.lineage.join(' > ');
13+
const lineageLength = tag.lineage.length;
14+
const MAX_TAG_LENGTH = 30;
15+
16+
if (lineageStr.length > MAX_TAG_LENGTH && lineageLength > 1) {
17+
if (lineageLength > 2) {
18+
// NOTE: If the tag lineage is too long and have more than 2 tags, we truncate it to the first and last level
19+
// i.e "Abilities > Cognitive Abilities > Communication Abilities" becomes
20+
// "Abilities > .. > Communication Abilities"
21+
lineageStr = `${tag.lineage[0]} > .. > ${tag.lineage[lineageLength - 1]}`;
22+
}
23+
24+
if (lineageStr.length > MAX_TAG_LENGTH) {
25+
// NOTE: If the tag lineage is still too long, we truncate it only to the last level
26+
// i.e "Knowledge > .. > Administration and Management" becomes
27+
// ".. > Administration and Management"
28+
lineageStr = `.. > ${tag.lineage[lineageLength - 1]}`;
29+
}
30+
}
31+
32+
return (
33+
<Chip
34+
iconBefore={TagIcon}
35+
className="mr-1 tag-snippet-chip small"
36+
>
37+
{lineageStr}
38+
</Chip>
39+
);
40+
};
41+
42+
export const ContentTagsSnippet = ({ contentId }: ContentTagsSnippetProps) => {
43+
const {
44+
data,
45+
} = useContentTaxonomyTagsData(contentId);
46+
47+
if (!data) {
48+
return null;
49+
}
50+
51+
return (
52+
<Stack gap={2}>
53+
{data.taxonomies.map((taxonomy) => (
54+
<div key={taxonomy.taxonomyId}>
55+
<h4 className="font-weight-bold x-small text-muted">
56+
{`${taxonomy.name} (${taxonomy.tags.length})`}
57+
</h4>
58+
<div className="d-flex flex-wrap">
59+
{taxonomy.tags.map((tag) => (
60+
<ContentTagChip key={tag.value} tag={tag} />
61+
))}
62+
</div>
63+
</div>
64+
))}
65+
</Stack>
66+
);
67+
};

src/content-tags-drawer/TagOutlineIcon.tsx

Lines changed: 0 additions & 20 deletions
This file was deleted.

src/content-tags-drawer/data/api.js

Lines changed: 0 additions & 101 deletions
This file was deleted.

src/content-tags-drawer/data/api.mocks.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export async function mockContentTaxonomyTagsData(contentId: string): Promise<an
1515
case thisMock.largeTagsId: return thisMock.largeTags;
1616
case thisMock.containerTagsId: return thisMock.largeTags;
1717
case thisMock.emptyTagsId: return thisMock.emptyTags;
18+
case thisMock.veryLongTagsId: return thisMock.veryLongTags;
1819
default: throw new Error(`No mock has been set up for contentId "${contentId}"`);
1920
}
2021
}
@@ -205,6 +206,37 @@ mockContentTaxonomyTagsData.emptyTagsId = 'block-v1:EmptyTagsOrg+STC1+2023_1+typ
205206
mockContentTaxonomyTagsData.emptyTags = {
206207
taxonomies: [],
207208
};
209+
mockContentTaxonomyTagsData.veryLongTagsId = 'block-v1:VeryLongTagsOrg+STC1+2023_1+type@vertical+block@veryLongTagsId';
210+
mockContentTaxonomyTagsData.veryLongTags = {
211+
taxonomies: [
212+
{
213+
name: 'ESDC Skills and Competencies',
214+
taxonomyId: 1,
215+
canTagObject: true,
216+
tags: [
217+
{
218+
value: 'Technical Skills',
219+
lineage: [
220+
'Skills',
221+
'Technical Skills Sub-Category',
222+
'Technical Skills',
223+
],
224+
canDeleteObjecttag: true,
225+
},
226+
{
227+
value: 'Communication Abilities',
228+
lineage: [
229+
'Abilities',
230+
'Cognitive Abilities',
231+
'Communication Abilities',
232+
],
233+
canDeleteObjecttag: true,
234+
},
235+
],
236+
},
237+
],
238+
};
239+
208240
mockContentTaxonomyTagsData.containerTagsId = 'lct:StagedTagsOrg:lib:unit:container_tags';
209241
mockContentTaxonomyTagsData.applyMock = () => jest.spyOn(api, 'getContentTaxonomyTagsData').mockImplementation(mockContentTaxonomyTagsData);
210242

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// @ts-check
21
import MockAdapter from 'axios-mock-adapter';
32
import { initializeMockApp } from '@edx/frontend-platform';
43
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';

0 commit comments

Comments
 (0)