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:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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} */ (() => {}),
|
||||
});
|
||||
77
src/content-tags-drawer/common/context.ts
Normal file
77
src/content-tags-drawer/common/context.ts
Normal 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,
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
@@ -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[]>
|
||||
* }
|
||||
* >}
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
81
src/content-tags-drawer/data/types.ts
Normal file
81
src/content-tags-drawer/data/types.ts
Normal 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;
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export const extractOrgFromContentId = (contentId) => contentId.split('+')[0].split(':')[1];
|
||||
export const languageExportId = 'languages-v1';
|
||||
2
src/content-tags-drawer/utils.ts
Normal file
2
src/content-tags-drawer/utils.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const extractOrgFromContentId = (contentId: string): string => contentId.split('+')[0].split(':')[1];
|
||||
export const languageExportId = 'languages-v1';
|
||||
@@ -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),
|
||||
});
|
||||
22
src/taxonomy/common/context.ts
Normal file
22
src/taxonomy/common/context.ts
Normal 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,
|
||||
});
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
60
src/taxonomy/data/types.ts
Normal file
60
src/taxonomy/data/types.ts
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
@@ -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
|
||||
*/
|
||||
@@ -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.
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user