refactor: Convert more Taxonomy code to TypeScript (#1532)

* Converts some files from .js or .mjs to .ts
* Moves the API code from src/taxonomy/tag-list/data into src/taxonomy/data
* Cleans up and improves some type definitions
* No user-visible changes / functionality changes.
This commit is contained in:
Braden MacDonald
2024-11-27 12:31:55 -08:00
committed by GitHub
parent f86c609ff1
commit abe68ac599
26 changed files with 307 additions and 335 deletions

View File

@@ -25,9 +25,9 @@ import TagsTree from './TagsTree';
import { ContentTagsDrawerContext } from './common/context';
/** @typedef {import("./ContentTagsCollapsible").TaxonomySelectProps} TaxonomySelectProps */
/** @typedef {import("../taxonomy/data/types.mjs").TaxonomyData} TaxonomyData */
/** @typedef {import("./data/types.mjs").Tag} ContentTagData */
/** @typedef {import("./data/types.mjs").StagedTagData} StagedTagData */
/** @typedef {import("../taxonomy/data/types.js").TaxonomyData} TaxonomyData */
/** @typedef {import("./data/types.js").Tag} ContentTagData */
/** @typedef {import("./data/types.js").StagedTagData} StagedTagData */
/**
* Custom Menu component for our Select box

View File

@@ -6,11 +6,11 @@ import { cloneDeep } from 'lodash';
import { useContentTaxonomyTagsUpdater } from './data/apiHooks';
import { ContentTagsDrawerContext } from './common/context';
/** @typedef {import("../taxonomy/data/types.mjs").TaxonomyData} TaxonomyData */
/** @typedef {import("./data/types.mjs").Tag} ContentTagData */
/** @typedef {import("../taxonomy/data/types.js").TaxonomyData} TaxonomyData */
/** @typedef {import("./data/types.js").Tag} ContentTagData */
/** @typedef {import("./ContentTagsCollapsible").TagTreeEntry} TagTreeEntry */
/** @typedef {import("./data/types.mjs").StagedTagData} StagedTagData */
/** @typedef {import("./data/types.mjs").UpdateTagsData} UpdateTagsData */
/** @typedef {import("./data/types.js").StagedTagData} StagedTagData */
/** @typedef {import("./data/types.js").UpdateTagsData} UpdateTagsData */
/**
* Util function that sorts the keys of a tree in alphabetical order.

View File

@@ -12,7 +12,7 @@ import classNames from 'classnames';
import messages from './messages';
import ContentTagsCollapsible from './ContentTagsCollapsible';
import Loading from '../generic/Loading';
import useContentTagsDrawerContext from './ContentTagsDrawerHelper';
import { useCreateContentTagsDrawerContext } from './ContentTagsDrawerHelper';
import { ContentTagsDrawerContext, ContentTagsDrawerSheetContext } from './common/context';
interface TaxonomyListProps {
@@ -246,7 +246,7 @@ const ContentTagsDrawer = ({
throw new Error('Error: contentId cannot be null.');
}
const context = useContentTagsDrawerContext(contentId, !readOnly);
const context = useCreateContentTagsDrawerContext(contentId, !readOnly);
const { blockingSheet } = useContext(ContentTagsDrawerSheetContext);
const {

View File

@@ -8,46 +8,21 @@ import { extractOrgFromContentId, languageExportId } from './utils';
import messages from './messages';
import { ContentTagsDrawerSheetContext } from './common/context';
/** @typedef {import("./data/types.mjs").Tag} ContentTagData */
/** @typedef {import("./data/types.mjs").StagedTagData} StagedTagData */
/** @typedef {import("./data/types.mjs").TagsInTaxonomy} TagsInTaxonomy */
/** @typedef {import("./data/types.js").Tag} ContentTagData */
/** @typedef {import("./data/types.js").StagedTagData} StagedTagData */
/** @typedef {import("./data/types.js").TagsInTaxonomy} TagsInTaxonomy */
/** @typedef {import("./common/context").ContentTagsDrawerContextData} ContentTagsDrawerContextData */
/**
* Handles the context and all the underlying logic for the ContentTagsDrawer component
* Helper hook for *creating* a `ContentTagsDrawerContext`.
* Handles the context and all the underlying logic for the ContentTagsDrawer component.
*
* To *use* the context, just use `useContext(ContentTagsDrawerContext)`
* @param {string} contentId
* @param {boolean} canTagObject
* @returns {{
* stagedContentTags: Record<number, StagedTagData[]>,
* addStagedContentTag: (taxonomyId: number, addedTag: StagedTagData) => void,
* removeStagedContentTag: (taxonomyId: number, tagValue: string) => void,
* removeGlobalStagedContentTag: (taxonomyId: number, tagValue: string) => void,
* addRemovedContentTag: (taxonomyId: number, tagValue: string) => void,
* deleteRemovedContentTag: (taxonomyId: number, tagValue: string) => void,
* setStagedTags: (taxonomyId: number, tagsList: StagedTagData[]) => void,
* globalStagedContentTags: Record<number, StagedTagData[]>,
* globalStagedRemovedContentTags: Record<number, string>,
* setGlobalStagedContentTags: Function,
* commitGlobalStagedTags: () => void,
* commitGlobalStagedTagsStatus: string,
* isContentDataLoaded: boolean,
* isContentTaxonomyTagsLoaded: boolean,
* isTaxonomyListLoaded: boolean,
* contentName: string,
* tagsByTaxonomy: TagsInTaxonomy[],
* isEditMode: boolean,
* toEditMode: () => void,
* toReadMode: () => void,
* collapsibleStates: Record<number, boolean>,
* openCollapsible: (taxonomyId: number) => void,
* closeCollapsible: (taxonomyId: number) => void,
* toastMessage: string | undefined,
* showToastAfterSave: () => void,
* closeToast: () => void,
* setCollapsibleToInitalState: () => void,
* otherTaxonomies: TagsInTaxonomy[],
* }}
* @returns {ContentTagsDrawerContextData}
*/
const useContentTagsDrawerContext = (contentId, canTagObject) => {
export const useCreateContentTagsDrawerContext = (contentId, canTagObject) => {
const intl = useIntl();
const org = extractOrgFromContentId(contentId);
@@ -465,5 +440,3 @@ const useContentTagsDrawerContext = (contentId, canTagObject) => {
otherTaxonomies,
};
};
export default useContentTagsDrawerContext;

View File

@@ -1,48 +0,0 @@
// @ts-check
import React from 'react';
/** @typedef {import("../data/types.mjs").TagsInTaxonomy} TagsInTaxonomy */
/** @typedef {import("../data/types.mjs").StagedTagData} StagedTagData */
/* istanbul ignore next */
export const ContentTagsDrawerContext = React.createContext({
stagedContentTags: /** @type{Record<number, StagedTagData[]>} */ ({}),
globalStagedContentTags: /** @type{Record<number, StagedTagData[]>} */ ({}),
globalStagedRemovedContentTags: /** @type{Record<number, string>} */ ({}),
addStagedContentTag: /** @type{(taxonomyId: number, addedTag: StagedTagData) => void} */ (() => {}),
removeStagedContentTag: /** @type{(taxonomyId: number, tagValue: string) => void} */ (() => {}),
removeGlobalStagedContentTag: /** @type{(taxonomyId: number, tagValue: string) => void} */ (() => {}),
addRemovedContentTag: /** @type{(taxonomyId: number, tagValue: string) => void} */ (() => {}),
deleteRemovedContentTag: /** @type{(taxonomyId: number, tagValue: string) => void} */ (() => {}),
setStagedTags: /** @type{(taxonomyId: number, tagsList: StagedTagData[]) => void} */ (() => {}),
setGlobalStagedContentTags: /** @type{Function} */ (() => {}),
commitGlobalStagedTags: /** @type{() => void} */ (() => {}),
commitGlobalStagedTagsStatus: /** @type{null|string} */ (null),
isContentDataLoaded: /** @type{boolean} */ (false),
isContentTaxonomyTagsLoaded: /** @type{boolean} */ (false),
isTaxonomyListLoaded: /** @type{boolean} */ (false),
contentName: /** @type{string} */ (''),
tagsByTaxonomy: /** @type{TagsInTaxonomy[]} */ ([]),
isEditMode: /** @type{boolean} */ (false),
toEditMode: /** @type{() => void} */ (() => {}),
toReadMode: /** @type{() => void} */ (() => {}),
collapsibleStates: /** @type{Record<number, boolean>} */ ({}),
openCollapsible: /** @type{(taxonomyId: number) => void} */ (() => {}),
closeCollapsible: /** @type{(taxonomyId: number) => void} */ (() => {}),
toastMessage: /** @type{string|undefined} */ (undefined),
showToastAfterSave: /** @type{() => void} */ (() => {}),
closeToast: /** @type{() => void} */ (() => {}),
setCollapsibleToInitalState: /** @type{() => void} */ (() => {}),
otherTaxonomies: /** @type{TagsInTaxonomy[]} */ ([]),
});
// This context has not been added to ContentTagsDrawerContext because it has been
// created one level higher to control the behavior of the Sheet that contatins the Drawer.
// This logic is not used in legacy edx-platform screens. But it can be separated if we keep
// the contexts separate.
// TODO We can join both contexts when the Drawer is no longer used on edx-platform
/* istanbul ignore next */
export const ContentTagsDrawerSheetContext = React.createContext({
blockingSheet: /** @type{boolean} */ (false),
setBlockingSheet: /** @type{Function} */ (() => {}),
});

View File

@@ -0,0 +1,77 @@
import React from 'react';
import type { TagsInTaxonomy, StagedTagData } from '../data/types';
export interface ContentTagsDrawerContextData {
stagedContentTags: Record<number, StagedTagData[]>;
globalStagedContentTags: Record<number, StagedTagData[]>;
globalStagedRemovedContentTags: Record<number, string>;
addStagedContentTag: (taxonomyId: number, addedTag: StagedTagData) => void;
removeStagedContentTag: (taxonomyId: number, tagValue: string) => void;
removeGlobalStagedContentTag: (taxonomyId: number, tagValue: string) => void;
addRemovedContentTag: (taxonomyId: number, tagValue: string) => void;
deleteRemovedContentTag: (taxonomyId: number, tagValue: string) => void;
setStagedTags: (taxonomyId: number, tagsList: StagedTagData[]) => void;
setGlobalStagedContentTags: Function;
commitGlobalStagedTags: () => void;
commitGlobalStagedTagsStatus: null | string;
isContentDataLoaded: boolean;
isContentTaxonomyTagsLoaded: boolean;
isTaxonomyListLoaded: boolean;
contentName: string;
tagsByTaxonomy: TagsInTaxonomy[];
isEditMode: boolean;
toEditMode: () => void;
toReadMode: () => void;
collapsibleStates: Record<number, boolean>;
openCollapsible: (taxonomyId: number) => void;
closeCollapsible: (taxonomyId: number) => void;
toastMessage: string | undefined;
showToastAfterSave: () => void;
closeToast: () => void;
setCollapsibleToInitalState: () => void;
otherTaxonomies: TagsInTaxonomy[];
}
/* istanbul ignore next */
export const ContentTagsDrawerContext = React.createContext<ContentTagsDrawerContextData>({
stagedContentTags: {},
globalStagedContentTags: {},
globalStagedRemovedContentTags: {},
addStagedContentTag: () => {},
removeStagedContentTag: () => {},
removeGlobalStagedContentTag: () => {},
addRemovedContentTag: () => {},
deleteRemovedContentTag: () => {},
setStagedTags: () => {},
setGlobalStagedContentTags: () => {},
commitGlobalStagedTags: () => {},
commitGlobalStagedTagsStatus: null,
isContentDataLoaded: false,
isContentTaxonomyTagsLoaded: false,
isTaxonomyListLoaded: false,
contentName: '',
tagsByTaxonomy: [],
isEditMode: false,
toEditMode: () => {},
toReadMode: () => {},
collapsibleStates: {},
openCollapsible: () => {},
closeCollapsible: () => {},
toastMessage: undefined,
showToastAfterSave: () => {},
closeToast: () => {},
setCollapsibleToInitalState: () => {},
otherTaxonomies: [],
});
// This context has not been added to ContentTagsDrawerContext because it has been
// created one level higher to control the behavior of the Sheet that contatins the Drawer.
// This logic is not used in legacy edx-platform screens. But it can be separated if we keep
// the contexts separate.
// TODO We can join both contexts when the Drawer is no longer used on edx-platform
/* istanbul ignore next */
export const ContentTagsDrawerSheetContext = React.createContext({
blockingSheet: false,
setBlockingSheet: (() => {}) as (blockingSheet: boolean) => void,
});

View File

@@ -38,7 +38,7 @@ export const getContentTaxonomyTagsCountApiUrl = (contentId) => new URL(`api/con
* Get all tags that belong to taxonomy.
* @param {number} taxonomyId The id of the taxonomy to fetch tags for
* @param {{page?: number, searchTerm?: string, parentTag?: string}} options
* @returns {Promise<import("../../taxonomy/tag-list/data/types.mjs").TagListData>}
* @returns {Promise<import("../../taxonomy/data/types.js").TagListData>}
*/
export async function getTaxonomyTagsData(taxonomyId, options = {}) {
const url = getTaxonomyTagsApiUrl(taxonomyId, options);
@@ -49,7 +49,7 @@ export async function getTaxonomyTagsData(taxonomyId, options = {}) {
/**
* Get the tags that are applied to the content object
* @param {string} contentId The id of the content object to fetch the applied tags for
* @returns {Promise<import("./types.mjs").ContentTaxonomyTagsData>}
* @returns {Promise<import("./types.js").ContentTaxonomyTagsData>}
*/
export async function getContentTaxonomyTagsData(contentId) {
const { data } = await getAuthenticatedHttpClient().get(getContentTaxonomyTagsApiUrl(contentId));
@@ -72,7 +72,7 @@ export async function getContentTaxonomyTagsCount(contentId) {
/**
* Fetch meta data (eg: display_name) about the content object (unit/compoenent)
* @param {string} contentId The id of the content object (unit/component)
* @returns {Promise<import("./types.mjs").ContentData | null>}
* @returns {Promise<import("./types.js").ContentData | null>}
*/
export async function getContentData(contentId) {
let url;
@@ -96,8 +96,8 @@ export async function getContentData(contentId) {
/**
* Update content object's applied tags
* @param {string} contentId The id of the content object (unit/component)
* @param {Promise<import("./types.mjs").UpdateTagsData[]>} tagsData The list of tags (values) to set on content object
* @returns {Promise<import("./types.mjs").ContentTaxonomyTagsData>}
* @param {Promise<import("./types.js").UpdateTagsData[]>} tagsData The list of tags (values) to set on content object
* @returns {Promise<import("./types.js").ContentTaxonomyTagsData>}
*/
export async function updateContentTaxonomyTags(contentId, tagsData) {
const url = getContentTaxonomyTagsApiUrl(contentId);

View File

@@ -17,8 +17,8 @@ import {
import { libraryQueryPredicate, xblockQueryKeys } from '../../library-authoring/data/apiHooks';
import { getLibraryId } from '../../generic/key-utils';
/** @typedef {import("../../taxonomy/tag-list/data/types.mjs").TagListData} TagListData */
/** @typedef {import("../../taxonomy/tag-list/data/types.mjs").TagData} TagData */
/** @typedef {import("../../taxonomy/data/types.js").TagListData} TagListData */
/** @typedef {import("../../taxonomy/data/types.js").TagData} TagData */
/**
* Builds the query to get the taxonomy tags
@@ -133,7 +133,7 @@ export const useContentTaxonomyTagsUpdater = (contentId) => {
* any,
* any,
* {
* tagsData: Promise<import("./types.mjs").UpdateTagsData[]>
* tagsData: Promise<import("./types.js").UpdateTagsData[]>
* }
* >}
*/

View File

@@ -1,101 +0,0 @@
// @ts-check
/**
* @typedef {Object} Tag A tag that has been applied to some content.
* @property {string} value The value of the tag, also its ID. e.g. "Biology"
* @property {string[]} lineage The values of the tag and its parent(s) in the hierarchy
* @property {boolean} canChangeObjecttag
* @property {boolean} canDeleteObjecttag
*/
/**
* @typedef {Object} ContentTaxonomyTagData A list of the tags from one taxonomy that are applied to a content object.
* @property {string} name
* @property {number} taxonomyId
* @property {boolean} canTagObject
* @property {Tag[]} tags
* @property {string} exportId
*/
/**
* @typedef {Object} ContentTaxonomyTagsData A list of all the tags applied to some content object, grouped by taxonomy.
* @property {ContentTaxonomyTagData[]} taxonomies
*/
/**
* @typedef {Object} ContentActions
* @property {boolean} deleteable
* @property {boolean} draggable
* @property {boolean} childAddable
* @property {boolean} duplicable
*/
/**
* @typedef {Object} XBlockData
* @property {string} id
* @property {string} displayName
* @property {string} category
* @property {boolean} hasChildren
* @property {string} editedOn
* @property {boolean} published
* @property {string} publishedOn
* @property {string} studioUrl
* @property {boolean} releasedToStudents
* @property {string|null} releaseDate
* @property {string} visibilityState
* @property {boolean} hasExplicitStaffLock
* @property {string} start
* @property {boolean} graded
* @property {string} dueDate
* @property {string} due
* @property {string|null} relativeWeeksDue
* @property {string|null} format
* @property {boolean} hasChanges
* @property {ContentActions} actions
* @property {string} explanatoryMessage
* @property {string} showCorrectness
* @property {boolean} discussionEnabled
* @property {boolean} ancestorHasStaffLock
* @property {boolean} staffOnlyMessage
* @property {boolean} hasPartitionGroupComponents
*/
/**
* @typedef {Object} TagsInTaxonomy
* @property {boolean} allOrgs
* @property {boolean} allowFreeText
* @property {boolean} allowMultiple
* @property {boolean} canChangeTaxonomy
* @property {boolean} canDeleteTaxonomy
* @property {boolean} canTagObject
* @property {Tag[]} contentTags
* @property {string} description
* @property {boolean} enabled
* @property {string} exportId
* @property {number} id
* @property {string} name
* @property {boolean} systemDefined
* @property {number} tagsCount
* @property {boolean} visibleToAuthors
*/
/**
* @typedef {Object} CourseData
* @property {string} courseDisplayNameWithDefault
*/
/**
* @typedef {XBlockData | CourseData} ContentData
*/
/**
* @typedef {Object} UpdateTagsData
* @property {number} taxonomy
* @property {string[]} tags
*/
/**
* @typedef {Object} StagedTagData
* @property {string} value
* @property {string} label
*/

View File

@@ -0,0 +1,81 @@
import type { TaxonomyData } from '../../taxonomy/data/types';
/** A tag that has been applied to some content. */
export interface Tag {
/** The value of the tag, also its ID. e.g. "Biology" */
value: string;
/** The values of the tag and its parent(s) in the hierarchy */
lineage: string[];
canChangeObjecttag: boolean;
canDeleteObjecttag: boolean;
}
/** A list of the tags from one taxonomy that are applied to a content object. */
export interface ContentTaxonomyTagData {
name: string;
taxonomyId: number;
canTagObject: boolean;
tags: Tag[];
exportId: string;
}
/** A list of all the tags applied to some content object, grouped by taxonomy. */
export interface ContentTaxonomyTagsData {
taxonomies: ContentTaxonomyTagData[];
}
export interface ContentActions {
deleteable: boolean;
draggable: boolean;
childAddable: boolean;
duplicable: boolean;
}
export interface XBlockData {
id: string;
displayName: string;
category: string;
hasChildren: boolean;
editedOn: string;
published: boolean;
publishedOn: string;
studioUrl: string;
releasedToStudents: boolean;
releaseDate: string | null;
visibilityState: string;
hasExplicitStaffLock: boolean;
start: string;
graded: boolean;
dueDate: string;
due: string;
relativeWeeksDue: string | null;
format: string | null;
hasChanges: boolean;
actions: ContentActions;
explanatoryMessage: string;
showCorrectness: string;
discussionEnabled: boolean;
ancestorHasStaffLock: boolean;
staffOnlyMessage: boolean;
hasPartitionGroupComponents: boolean;
}
export interface TagsInTaxonomy extends TaxonomyData {
contentTags: Tag[];
}
export interface CourseData {
courseDisplayNameWithDefault: string;
}
export type ContentData = XBlockData | CourseData;
export interface UpdateTagsData {
taxonomy: number;
tags: string[];
}
export interface StagedTagData {
value: string;
label: string;
}

View File

@@ -1,2 +0,0 @@
export const extractOrgFromContentId = (contentId) => contentId.split('+')[0].split(':')[1];
export const languageExportId = 'languages-v1';

View File

@@ -0,0 +1,2 @@
export const extractOrgFromContentId = (contentId: string): string => contentId.split('+')[0].split(':')[1];
export const languageExportId = 'languages-v1';

View File

@@ -1,15 +0,0 @@
// @ts-check
import React from 'react';
/**
* @typedef AlertProps
* @type {Object}
* @property {React.ReactNode} title - title of the alert.
* @property {React.ReactNode} description - description of the alert.
*/
export const TaxonomyContext = React.createContext({
toastMessage: /** @type{null|string} */ (null),
setToastMessage: /** @type{null|React.Dispatch<React.SetStateAction<null|string>>} */ (null),
alertProps: /** @type{null|AlertProps} */ (null),
setAlertProps: /** @type{null|React.Dispatch<React.SetStateAction<null|AlertProps>>} */ (null),
});

View File

@@ -0,0 +1,22 @@
import React from 'react';
export interface AlertProps {
/** title of the alert */
title: React.ReactNode;
/** description of the alert */
description: React.ReactNode;
}
export interface TaxonomyContextData {
toastMessage: null | string;
setToastMessage: null | React.Dispatch<React.SetStateAction<null | string>>;
alertProps: null | AlertProps;
setAlertProps: null | React.Dispatch<React.SetStateAction<null | AlertProps>>;
}
export const TaxonomyContext = React.createContext<TaxonomyContextData>({
toastMessage: null,
setToastMessage: null,
alertProps: null,
setAlertProps: null,
});

View File

@@ -88,7 +88,7 @@ export const apiUrls = {
/**
* Get list of taxonomies.
* @param {string} [org] Filter the list to only show taxonomies assigned to this org
* @returns {Promise<import("./types.mjs").TaxonomyListData>}
* @returns {Promise<import("./types.js").TaxonomyListData>}
*/
export async function getTaxonomyListData(org) {
const { data } = await getAuthenticatedHttpClient().get(apiUrls.taxonomyList(org));
@@ -107,7 +107,7 @@ export async function deleteTaxonomy(taxonomyId) {
/**
* Get metadata about a Taxonomy
* @param {number} taxonomyId The ID of the taxonomy to get
* @returns {Promise<import("./types.mjs").TaxonomyData>}
* @returns {Promise<import("./types.js").TaxonomyData>}
*/
export async function getTaxonomy(taxonomyId) {
const { data } = await getAuthenticatedHttpClient().get(apiUrls.taxonomy(taxonomyId));

View File

@@ -109,7 +109,7 @@ export const useImportNewTaxonomy = () => {
return useMutation({
/**
* @type {import("@tanstack/react-query").MutateFunction<
* import("./types.mjs").TaxonomyData,
* import("./types.js").TaxonomyData,
* any,
* {
* name: string,
@@ -147,7 +147,7 @@ export const useImportTags = () => {
return useMutation({
/**
* @type {import("@tanstack/react-query").MutateFunction<
* import("./types.mjs").TaxonomyData,
* import("./types.js").TaxonomyData,
* any,
* {
* taxonomyId: number,
@@ -202,3 +202,35 @@ export const useImportPlan = (taxonomyId, file) => useQuery({
},
retry: false, // If there's an error, it's probably a real problem with the file. Don't try again several times!
});
/**
* @param {number} taxonomyId
* @param {import('./types.js').QueryOptions} options
* @returns {import('@tanstack/react-query').UseQueryResult<import('./types.js').TagListData>}
*/
export const useTagListData = (taxonomyId, options) => {
const { pageIndex, pageSize } = options;
return useQuery({
queryKey: taxonomyQueryKeys.taxonomyTagListPage(taxonomyId, pageIndex, pageSize),
queryFn: async () => {
const { data } = await getAuthenticatedHttpClient().get(apiUrls.tagList(taxonomyId, pageIndex, pageSize));
return camelCaseObject(data);
},
});
};
/**
* Temporary hook to load *all* the subtags of a given tag in a taxonomy.
* Doesn't handle pagination or anything. This is meant to be replaced by
* something more sophisticated later, as we improve the "taxonomy details" page.
* @param {number} taxonomyId
* @param {string} parentTagValue
* @returns {import('@tanstack/react-query').UseQueryResult<import('./types.js').TagListData>}
*/
export const useSubTags = (taxonomyId, parentTagValue) => useQuery({
queryKey: taxonomyQueryKeys.taxonomyTagSubtagsList(taxonomyId, parentTagValue),
queryFn: async () => {
const response = await getAuthenticatedHttpClient().get(apiUrls.allSubtagsOf(taxonomyId, parentTagValue));
return camelCaseObject(response.data);
},
});

View File

@@ -1,32 +0,0 @@
// @ts-check
/**
* @typedef {Object} TaxonomyData Metadata about a taxonomy
* @property {number} id
* @property {string} name
* @property {string} description
* @property {string} exportId
* @property {boolean} enabled
* @property {boolean} allowMultiple
* @property {boolean} allowFreeText
* @property {boolean} systemDefined
* @property {boolean} visibleToAuthors
* @property {number} tagsCount
* @property {string[]} orgs
* @property {boolean} allOrgs
* @property {boolean} canChangeTaxonomy
* @property {boolean} canDeleteTaxonomy
* @property {boolean} canTagObject
*/
/**
* @typedef {Object} TaxonomyListData The list of taxonomies
* @property {string} next
* @property {string} previous
* @property {number} count
* @property {number} numPages
* @property {number} currentPage
* @property {number} start
* @property {boolean} canAddTaxonomy
* @property {TaxonomyData[]} results
*/

View File

@@ -0,0 +1,60 @@
/** Metadata about a taxonomy */
export interface TaxonomyData {
id: number;
name: string;
description: string;
exportId: string;
enabled: boolean;
allowMultiple: boolean;
allowFreeText: boolean;
systemDefined: boolean;
visibleToAuthors: boolean;
tagsCount: number;
orgs: string[];
allOrgs: boolean;
canChangeTaxonomy: boolean;
canDeleteTaxonomy: boolean;
canTagObject: boolean;
}
/** The list of taxonomies */
export interface TaxonomyListData {
next: string;
previous: string;
count: number;
numPages: number;
currentPage: number;
start: number;
canAddTaxonomy: boolean;
results: TaxonomyData[];
}
export interface QueryOptions {
pageIndex: number;
pageSize: number;
}
export interface TagData {
childCount: number;
descendantCount: number;
depth: number;
externalId: string;
id: number;
parentValue: string | null;
subTagsUrl: string | null;
/** Unique ID for this tag, also its display text */
value: string;
usageCount?: number;
/** Database ID. Don't rely on this, as it is not present for free-text tags. */
_id?: string;
}
export interface TagListData {
count: number;
currentPage: number;
next: string;
numPages: number;
previous: string;
results: TagData[];
start: number;
}

View File

@@ -7,7 +7,7 @@ import Proptypes from 'prop-types';
import { LoadingSpinner } from '../../generic/Loading';
import messages from './messages';
import { useTagListData, useSubTags } from './data/apiHooks';
import { useTagListData, useSubTags } from '../data/apiHooks';
const SubTagsExpanded = ({ taxonomyId, parentTagValue }) => {
const subTagsData = useSubTags(taxonomyId, parentTagValue);

View File

@@ -1,41 +0,0 @@
// @ts-check
// TODO: this file needs to be merged into src/taxonomy/data/apiHooks.js
import { useQuery } from '@tanstack/react-query';
import { camelCaseObject } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { apiUrls } from '../../data/api';
import { taxonomyQueryKeys } from '../../data/apiHooks';
/**
* @param {number} taxonomyId
* @param {import('./types.mjs').QueryOptions} options
* @returns {import('@tanstack/react-query').UseQueryResult<import('./types.mjs').TagListData>}
*/
export const useTagListData = (taxonomyId, options) => {
const { pageIndex, pageSize } = options;
return useQuery({
queryKey: taxonomyQueryKeys.taxonomyTagListPage(taxonomyId, pageIndex, pageSize),
queryFn: async () => {
const { data } = await getAuthenticatedHttpClient().get(apiUrls.tagList(taxonomyId, pageIndex, pageSize));
return camelCaseObject(data);
},
});
};
/**
* Temporary hook to load *all* the subtags of a given tag in a taxonomy.
* Doesn't handle pagination or anything. This is meant to be replaced by
* something more sophisticated later, as we improve the "taxonomy details" page.
* @param {number} taxonomyId
* @param {string} parentTagValue
* @returns {import('@tanstack/react-query').UseQueryResult<import('./types.mjs').TagListData>}
*/
export const useSubTags = (taxonomyId, parentTagValue) => useQuery({
queryKey: taxonomyQueryKeys.taxonomyTagSubtagsList(taxonomyId, parentTagValue),
queryFn: async () => {
const response = await getAuthenticatedHttpClient().get(apiUrls.allSubtagsOf(taxonomyId, parentTagValue));
return camelCaseObject(response.data);
},
});

View File

@@ -1,36 +0,0 @@
// @ts-check
// TODO: this file needs to be merged into src/taxonomy/data/types.mjs
// We are creating a mess with so many different /data/[api|types].js files in subfolders.
// There is only one tagging/taxonomy API, and it should be implemented via a single types.mjs and api.js file.
/**
* @typedef {Object} QueryOptions
* @property {number} pageIndex
* @property {number} pageSize
*/
/**
* @typedef {Object} TagData
* @property {number} childCount
* @property {number} descendantCount
* @property {number} depth
* @property {string} externalId
* @property {number} id
* @property {string | null} parentValue
* @property {string | null} subTagsUrl
* @property {string} value Unique ID for this tag, also its display text
* @property {number?} usageCount
* @property {string?} _id Database ID. Don't rely on this, as it is not present for free-text tags.
*/
/**
* @typedef {Object} TagListData
* @property {number} count
* @property {number} currentPage
* @property {string} next
* @property {number} numPages
* @property {string} previous
* @property {TagData[]} results
* @property {number} start
*/

View File

@@ -21,7 +21,7 @@ import { ImportTagsWizard } from '../import-tags';
import { ManageOrgsModal } from '../manage-orgs';
import messages from './messages';
/** @typedef {import('../data/types.mjs').TaxonomyData} TaxonomyData */
/** @typedef {import('../data/types.js').TaxonomyData} TaxonomyData */
// Note: to make mocking easier for tests, the types below only specify the subset of TaxonomyData that we actually use.
/**