Skip to content

Commit b9d3ab5

Browse files
committed
feat: wip
1 parent 3eeca24 commit b9d3ab5

37 files changed

Lines changed: 1127 additions & 328 deletions

package-lock.json

Lines changed: 360 additions & 109 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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 = (
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: 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';
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: number, 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: number,
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/apiHooks.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,17 @@ import { UpdateTagsData } from './types';
2121

2222
/**
2323
* Builds the query to get the taxonomy tags
24-
* @param taxonomyId The id of the taxonomy to fetch tags for
25-
* @param parentTag The tag whose children we're loading, if any
26-
* @param searchTerm The term passed in to perform search on tags
27-
* @param numPages How many pages of tags to load at this level
2824
*/
29-
export const useTaxonomyTagsData = (taxonomyId: number, parentTag: string | null = null, numPages = 1, searchTerm = '') => {
25+
export const useTaxonomyTagsData = (
26+
/** The id of the taxonomy to fetch tags for */
27+
taxonomyId: number,
28+
/** The tag whose children we're loading, if any */
29+
parentTag: string | null = null,
30+
/** How many pages of tags to load at this level */
31+
numPages = 1,
32+
/** The term passed in to perform search on tags */
33+
searchTerm = '',
34+
) => {
3035
const queryClient = useQueryClient();
3136

3237
const queryFn = async ({ queryKey }) => {
@@ -128,7 +133,7 @@ export const useContentTaxonomyTagsUpdater = (contentId: string) => {
128133
const { containerId } = useParams();
129134

130135
return useMutation({
131-
mutationFn: ({ tagsData }: { tagsData: Promise<UpdateTagsData[]> }) => (
136+
mutationFn: ({ tagsData }: { tagsData: UpdateTagsData }) => (
132137
updateContentTaxonomyTags(contentId, tagsData)
133138
),
134139
onSettled: () => {

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 {

0 commit comments

Comments
 (0)