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

@@ -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.
/**