Skip to content

Commit 6b32842

Browse files
committed
feat: wip
1 parent e95758c commit 6b32842

File tree

28 files changed

+524
-172
lines changed

28 files changed

+524
-172
lines changed
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: 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"
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.
Lines changed: 4 additions & 5 deletions
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';
@@ -44,7 +43,7 @@ describe('content tags drawer api calls', () => {
4443
});
4544

4645
it('should get taxonomy tags data', async () => {
47-
const taxonomyId = 123;
46+
const taxonomyId = '123';
4847
axiosMock.onGet().reply(200, taxonomyTagsMock);
4948
const result = await getTaxonomyTagsData(taxonomyId);
5049

@@ -53,7 +52,7 @@ describe('content tags drawer api calls', () => {
5352
});
5453

5554
it('should get taxonomy tags data with parentTag', async () => {
56-
const taxonomyId = 123;
55+
const taxonomyId = '123';
5756
const options = { parentTag: 'Sample Tag' };
5857
axiosMock.onGet().reply(200, taxonomyTagsMock);
5958
const result = await getTaxonomyTagsData(taxonomyId, options);
@@ -63,7 +62,7 @@ describe('content tags drawer api calls', () => {
6362
});
6463

6564
it('should get taxonomy tags data with page', async () => {
66-
const taxonomyId = 123;
65+
const taxonomyId = '123';
6766
const options = { page: 2 };
6867
axiosMock.onGet().reply(200, taxonomyTagsMock);
6968
const result = await getTaxonomyTagsData(taxonomyId, options);
@@ -73,7 +72,7 @@ describe('content tags drawer api calls', () => {
7372
});
7473

7574
it('should get taxonomy tags data with searchTerm', async () => {
76-
const taxonomyId = 123;
75+
const taxonomyId = '123';
7776
const options = { searchTerm: 'memo' };
7877
axiosMock.onGet().reply(200, taxonomyTagsMock);
7978
const result = await getTaxonomyTagsData(taxonomyId, options);
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
2+
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
3+
4+
import type { TagListData } from '@src/taxonomy/data/types';
5+
6+
import type { ContentData, ContentTaxonomyTagsData, UpdateTagsData } from './types';
7+
8+
const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL;
9+
10+
interface GetTaxonomyTagsApiUrlOptions {
11+
parentTag?: string;
12+
page?: number;
13+
searchTerm?: string;
14+
}
15+
16+
/**
17+
* Get the URL used to fetch tags data from the "taxonomy tags" REST API
18+
*/
19+
export const getTaxonomyTagsApiUrl = (taxonomyId: string, options: GetTaxonomyTagsApiUrlOptions = {}): string => {
20+
const url = new URL(`api/content_tagging/v1/taxonomies/${taxonomyId}/tags/`, getApiBaseUrl());
21+
if (options.parentTag) {
22+
url.searchParams.append('parent_tag', options.parentTag);
23+
}
24+
if (options.page) {
25+
url.searchParams.append('page', String(options.page));
26+
}
27+
if (options.searchTerm) {
28+
url.searchParams.append('search_term', options.searchTerm);
29+
}
30+
31+
// Load in the full tree if children at once, if we can:
32+
// Note: do not combine this with page_size (we currently aren't using page_size)
33+
url.searchParams.append('full_depth_threshold', '1000');
34+
35+
return url.href;
36+
};
37+
38+
export const getContentTaxonomyTagsApiUrl = (contentId: string) => new URL(`api/content_tagging/v1/object_tags/${contentId}/`, getApiBaseUrl()).href;
39+
export const getXBlockContentDataApiURL = (contentId: string) => new URL(`/xblock/outline/${contentId}`, getApiBaseUrl()).href;
40+
export const getCourseContentDataApiURL = (contentId: string) => new URL(`/api/contentstore/v1/course_settings/${contentId}`, getApiBaseUrl()).href;
41+
export const getLibraryContentDataApiUrl = (contentId: string) => new URL(`/api/libraries/v2/blocks/${contentId}/`, getApiBaseUrl()).href;
42+
export const getContentTaxonomyTagsCountApiUrl = (contentId: string) => new URL(`api/content_tagging/v1/object_tag_counts/${contentId}/?count_implicit`, getApiBaseUrl()).href;
43+
44+
/**
45+
* Get all tags that belong to taxonomy.
46+
*/
47+
export async function getTaxonomyTagsData(
48+
taxonomyId: string,
49+
options: GetTaxonomyTagsApiUrlOptions = {},
50+
): Promise<TagListData> {
51+
const url = getTaxonomyTagsApiUrl(taxonomyId, options);
52+
const { data } = await getAuthenticatedHttpClient().get(url);
53+
return camelCaseObject(data);
54+
}
55+
56+
/**
57+
* Get the tags that are applied to the content object
58+
* @param contentId The id of the content object to fetch the applied tags for
59+
*/
60+
export async function getContentTaxonomyTagsData(contentId: string): Promise<ContentTaxonomyTagsData> {
61+
const url = getContentTaxonomyTagsApiUrl(contentId);
62+
const { data } = await getAuthenticatedHttpClient().get(url);
63+
return camelCaseObject(data[contentId]);
64+
}
65+
66+
/**
67+
* Get the count of tags that are applied to the content object
68+
* @param contentId The id of the content object to fetch the count of the applied tags for
69+
*/
70+
export async function getContentTaxonomyTagsCount(contentId: string): Promise<number> {
71+
const url = getContentTaxonomyTagsCountApiUrl(contentId);
72+
const { data } = await getAuthenticatedHttpClient().get(url);
73+
if (contentId in data) {
74+
return camelCaseObject(data[contentId]);
75+
}
76+
return 0;
77+
}
78+
79+
/**
80+
* Fetch meta data (eg: display_name) about the content object (unit/component)
81+
* @param contentId The id of the content object (unit/component)
82+
*/
83+
export async function getContentData(contentId: string): Promise<ContentData> {
84+
let url: string;
85+
86+
if (contentId.startsWith('lb:')) {
87+
url = getLibraryContentDataApiUrl(contentId);
88+
} else if (contentId.startsWith('course-v1:')) {
89+
url = getCourseContentDataApiURL(contentId);
90+
} else {
91+
url = getXBlockContentDataApiURL(contentId);
92+
}
93+
const { data } = await getAuthenticatedHttpClient().get(url);
94+
return camelCaseObject(data);
95+
}
96+
97+
/**
98+
* Update content object's applied tags
99+
* @param contentId The id of the content object (unit/component)
100+
* @param tagsData The list of tags (values) to set on content object
101+
*/
102+
export async function updateContentTaxonomyTags(
103+
contentId: string,
104+
tagsData: UpdateTagsData,
105+
): Promise<ContentTaxonomyTagsData> {
106+
const url = getContentTaxonomyTagsApiUrl(contentId);
107+
const { data } = await getAuthenticatedHttpClient().put(url, { tagsData });
108+
return camelCaseObject(data[contentId]);
109+
}

src/content-tags-drawer/data/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { TaxonomyData } from '../../taxonomy/data/types';
1+
import type { TaxonomyData } from '@src/taxonomy/data/types';
22

33
/** A tag that has been applied to some content. */
44
export interface Tag {

src/content-tags-drawer/index.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
@import "content-tags-drawer/TagsTree";
22
@import "content-tags-drawer/tags-sidebar-controls/TagsSidebarControls";
33
@import "content-tags-drawer/ContentTagsDrawer";
4+
@import "content-tags-drawer/ContentTagsSnippet";

src/content-tags-drawer/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { default as ContentTagsDrawer } from './ContentTagsDrawer';
22
export { default as ContentTagsDrawerSheet } from './ContentTagsDrawerSheet';
33
export { useContentTaxonomyTagsData } from './data/apiHooks';
4+
export { ContentTagsSnippet } from './ContentTagsSnippet';

src/course-outline/CourseOutline.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
@import "./publish-modal/PublishModal";
99
@import "./xblock-status/XBlockStatus";
1010
@import "./drag-helper/SortableItem";
11+
@import "./outline-sidebar";

0 commit comments

Comments
 (0)