From abe68ac599dc314399785ff2cd991acfc9723d80 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Wed, 27 Nov 2024 12:31:55 -0800 Subject: [PATCH] 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. --- .../ContentTagsCollapsible.jsx | 6 +- .../ContentTagsCollapsibleHelper.jsx | 8 +- src/content-tags-drawer/ContentTagsDrawer.tsx | 4 +- .../ContentTagsDrawerHelper.jsx | 47 ++------ ...{TagOutlineIcon.jsx => TagOutlineIcon.tsx} | 0 src/content-tags-drawer/common/context.js | 48 --------- src/content-tags-drawer/common/context.ts | 77 +++++++++++++ src/content-tags-drawer/data/api.js | 10 +- src/content-tags-drawer/data/apiHooks.jsx | 6 +- src/content-tags-drawer/data/types.mjs | 101 ------------------ src/content-tags-drawer/data/types.ts | 81 ++++++++++++++ .../{messages.js => messages.ts} | 0 src/content-tags-drawer/utils.js | 2 - src/content-tags-drawer/utils.ts | 2 + src/taxonomy/common/context.js | 15 --- src/taxonomy/common/context.ts | 22 ++++ src/taxonomy/data/api.js | 4 +- src/taxonomy/data/apiHooks.js | 36 ++++++- src/taxonomy/data/types.mjs | 32 ------ src/taxonomy/data/types.ts | 60 +++++++++++ src/taxonomy/tag-list/TagListTable.jsx | 2 +- src/taxonomy/tag-list/data/apiHooks.js | 41 ------- src/taxonomy/tag-list/data/types.mjs | 36 ------- src/taxonomy/tag-list/{index.js => index.ts} | 0 .../tag-list/{messages.js => messages.ts} | 0 src/taxonomy/taxonomy-menu/TaxonomyMenu.jsx | 2 +- 26 files changed, 307 insertions(+), 335 deletions(-) rename src/content-tags-drawer/{TagOutlineIcon.jsx => TagOutlineIcon.tsx} (100%) delete mode 100644 src/content-tags-drawer/common/context.js create mode 100644 src/content-tags-drawer/common/context.ts delete mode 100644 src/content-tags-drawer/data/types.mjs create mode 100644 src/content-tags-drawer/data/types.ts rename src/content-tags-drawer/{messages.js => messages.ts} (100%) delete mode 100644 src/content-tags-drawer/utils.js create mode 100644 src/content-tags-drawer/utils.ts delete mode 100644 src/taxonomy/common/context.js create mode 100644 src/taxonomy/common/context.ts delete mode 100644 src/taxonomy/data/types.mjs create mode 100644 src/taxonomy/data/types.ts delete mode 100644 src/taxonomy/tag-list/data/apiHooks.js delete mode 100644 src/taxonomy/tag-list/data/types.mjs rename src/taxonomy/tag-list/{index.js => index.ts} (100%) rename src/taxonomy/tag-list/{messages.js => messages.ts} (100%) diff --git a/src/content-tags-drawer/ContentTagsCollapsible.jsx b/src/content-tags-drawer/ContentTagsCollapsible.jsx index 8a7374fcc5..bbc9879c67 100644 --- a/src/content-tags-drawer/ContentTagsCollapsible.jsx +++ b/src/content-tags-drawer/ContentTagsCollapsible.jsx @@ -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 diff --git a/src/content-tags-drawer/ContentTagsCollapsibleHelper.jsx b/src/content-tags-drawer/ContentTagsCollapsibleHelper.jsx index dcc016ab8a..26e27a1188 100644 --- a/src/content-tags-drawer/ContentTagsCollapsibleHelper.jsx +++ b/src/content-tags-drawer/ContentTagsCollapsibleHelper.jsx @@ -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. diff --git a/src/content-tags-drawer/ContentTagsDrawer.tsx b/src/content-tags-drawer/ContentTagsDrawer.tsx index 4f225666fd..da025f59f0 100644 --- a/src/content-tags-drawer/ContentTagsDrawer.tsx +++ b/src/content-tags-drawer/ContentTagsDrawer.tsx @@ -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 { diff --git a/src/content-tags-drawer/ContentTagsDrawerHelper.jsx b/src/content-tags-drawer/ContentTagsDrawerHelper.jsx index 2c9bec15bb..49b4d28532 100644 --- a/src/content-tags-drawer/ContentTagsDrawerHelper.jsx +++ b/src/content-tags-drawer/ContentTagsDrawerHelper.jsx @@ -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, - * 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, - * globalStagedRemovedContentTags: Record, - * setGlobalStagedContentTags: Function, - * commitGlobalStagedTags: () => void, - * commitGlobalStagedTagsStatus: string, - * isContentDataLoaded: boolean, - * isContentTaxonomyTagsLoaded: boolean, - * isTaxonomyListLoaded: boolean, - * contentName: string, - * tagsByTaxonomy: TagsInTaxonomy[], - * isEditMode: boolean, - * toEditMode: () => void, - * toReadMode: () => void, - * collapsibleStates: Record, - * 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; diff --git a/src/content-tags-drawer/TagOutlineIcon.jsx b/src/content-tags-drawer/TagOutlineIcon.tsx similarity index 100% rename from src/content-tags-drawer/TagOutlineIcon.jsx rename to src/content-tags-drawer/TagOutlineIcon.tsx diff --git a/src/content-tags-drawer/common/context.js b/src/content-tags-drawer/common/context.js deleted file mode 100644 index a1379fff3e..0000000000 --- a/src/content-tags-drawer/common/context.js +++ /dev/null @@ -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} */ ({}), - globalStagedContentTags: /** @type{Record} */ ({}), - globalStagedRemovedContentTags: /** @type{Record} */ ({}), - 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} */ ({}), - 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} */ (() => {}), -}); diff --git a/src/content-tags-drawer/common/context.ts b/src/content-tags-drawer/common/context.ts new file mode 100644 index 0000000000..6f4d76b76a --- /dev/null +++ b/src/content-tags-drawer/common/context.ts @@ -0,0 +1,77 @@ +import React from 'react'; + +import type { TagsInTaxonomy, StagedTagData } from '../data/types'; + +export interface ContentTagsDrawerContextData { + stagedContentTags: Record; + globalStagedContentTags: Record; + globalStagedRemovedContentTags: Record; + 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; + 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({ + 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, +}); diff --git a/src/content-tags-drawer/data/api.js b/src/content-tags-drawer/data/api.js index 94841f7c9b..e2534e9b89 100644 --- a/src/content-tags-drawer/data/api.js +++ b/src/content-tags-drawer/data/api.js @@ -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} + * @returns {Promise} */ 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} + * @returns {Promise} */ 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} + * @returns {Promise} */ 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} tagsData The list of tags (values) to set on content object - * @returns {Promise} + * @param {Promise} tagsData The list of tags (values) to set on content object + * @returns {Promise} */ export async function updateContentTaxonomyTags(contentId, tagsData) { const url = getContentTaxonomyTagsApiUrl(contentId); diff --git a/src/content-tags-drawer/data/apiHooks.jsx b/src/content-tags-drawer/data/apiHooks.jsx index 5b90a6da85..f7e5dd2c13 100644 --- a/src/content-tags-drawer/data/apiHooks.jsx +++ b/src/content-tags-drawer/data/apiHooks.jsx @@ -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 + * tagsData: Promise * } * >} */ diff --git a/src/content-tags-drawer/data/types.mjs b/src/content-tags-drawer/data/types.mjs deleted file mode 100644 index 42de8e3a23..0000000000 --- a/src/content-tags-drawer/data/types.mjs +++ /dev/null @@ -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 - */ \ No newline at end of file diff --git a/src/content-tags-drawer/data/types.ts b/src/content-tags-drawer/data/types.ts new file mode 100644 index 0000000000..127be3dc9b --- /dev/null +++ b/src/content-tags-drawer/data/types.ts @@ -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; +} diff --git a/src/content-tags-drawer/messages.js b/src/content-tags-drawer/messages.ts similarity index 100% rename from src/content-tags-drawer/messages.js rename to src/content-tags-drawer/messages.ts diff --git a/src/content-tags-drawer/utils.js b/src/content-tags-drawer/utils.js deleted file mode 100644 index 81849805b9..0000000000 --- a/src/content-tags-drawer/utils.js +++ /dev/null @@ -1,2 +0,0 @@ -export const extractOrgFromContentId = (contentId) => contentId.split('+')[0].split(':')[1]; -export const languageExportId = 'languages-v1'; diff --git a/src/content-tags-drawer/utils.ts b/src/content-tags-drawer/utils.ts new file mode 100644 index 0000000000..ff1d71321a --- /dev/null +++ b/src/content-tags-drawer/utils.ts @@ -0,0 +1,2 @@ +export const extractOrgFromContentId = (contentId: string): string => contentId.split('+')[0].split(':')[1]; +export const languageExportId = 'languages-v1'; diff --git a/src/taxonomy/common/context.js b/src/taxonomy/common/context.js deleted file mode 100644 index a1179efb29..0000000000 --- a/src/taxonomy/common/context.js +++ /dev/null @@ -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>} */ (null), - alertProps: /** @type{null|AlertProps} */ (null), - setAlertProps: /** @type{null|React.Dispatch>} */ (null), -}); diff --git a/src/taxonomy/common/context.ts b/src/taxonomy/common/context.ts new file mode 100644 index 0000000000..502d3e070d --- /dev/null +++ b/src/taxonomy/common/context.ts @@ -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>; + alertProps: null | AlertProps; + setAlertProps: null | React.Dispatch>; +} + +export const TaxonomyContext = React.createContext({ + toastMessage: null, + setToastMessage: null, + alertProps: null, + setAlertProps: null, +}); diff --git a/src/taxonomy/data/api.js b/src/taxonomy/data/api.js index 65c7bd1779..a36cf6d513 100644 --- a/src/taxonomy/data/api.js +++ b/src/taxonomy/data/api.js @@ -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} + * @returns {Promise} */ 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} + * @returns {Promise} */ export async function getTaxonomy(taxonomyId) { const { data } = await getAuthenticatedHttpClient().get(apiUrls.taxonomy(taxonomyId)); diff --git a/src/taxonomy/data/apiHooks.js b/src/taxonomy/data/apiHooks.js index c734ad022d..8b2037d390 100644 --- a/src/taxonomy/data/apiHooks.js +++ b/src/taxonomy/data/apiHooks.js @@ -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} + */ +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} + */ +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); + }, +}); diff --git a/src/taxonomy/data/types.mjs b/src/taxonomy/data/types.mjs deleted file mode 100644 index 4d649404de..0000000000 --- a/src/taxonomy/data/types.mjs +++ /dev/null @@ -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 - */ diff --git a/src/taxonomy/data/types.ts b/src/taxonomy/data/types.ts new file mode 100644 index 0000000000..dbc7186031 --- /dev/null +++ b/src/taxonomy/data/types.ts @@ -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; +} diff --git a/src/taxonomy/tag-list/TagListTable.jsx b/src/taxonomy/tag-list/TagListTable.jsx index 486543aebc..013fcb6530 100644 --- a/src/taxonomy/tag-list/TagListTable.jsx +++ b/src/taxonomy/tag-list/TagListTable.jsx @@ -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); diff --git a/src/taxonomy/tag-list/data/apiHooks.js b/src/taxonomy/tag-list/data/apiHooks.js deleted file mode 100644 index 60043f4e8b..0000000000 --- a/src/taxonomy/tag-list/data/apiHooks.js +++ /dev/null @@ -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} - */ -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} - */ -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); - }, -}); diff --git a/src/taxonomy/tag-list/data/types.mjs b/src/taxonomy/tag-list/data/types.mjs deleted file mode 100644 index 8998e609ef..0000000000 --- a/src/taxonomy/tag-list/data/types.mjs +++ /dev/null @@ -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 - */ diff --git a/src/taxonomy/tag-list/index.js b/src/taxonomy/tag-list/index.ts similarity index 100% rename from src/taxonomy/tag-list/index.js rename to src/taxonomy/tag-list/index.ts diff --git a/src/taxonomy/tag-list/messages.js b/src/taxonomy/tag-list/messages.ts similarity index 100% rename from src/taxonomy/tag-list/messages.js rename to src/taxonomy/tag-list/messages.ts diff --git a/src/taxonomy/taxonomy-menu/TaxonomyMenu.jsx b/src/taxonomy/taxonomy-menu/TaxonomyMenu.jsx index 576cf5d8ff..fce069a95d 100644 --- a/src/taxonomy/taxonomy-menu/TaxonomyMenu.jsx +++ b/src/taxonomy/taxonomy-menu/TaxonomyMenu.jsx @@ -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. /**