diff --git a/src/messages.js b/src/messages.js index 8a5aafd036..1620914b5b 100644 --- a/src/messages.js +++ b/src/messages.js @@ -10,14 +10,6 @@ const messages = defineMessages({ id: 'authoring.alert.support.text', defaultMessage: 'Support Page', }, - noResultsFoundMessage: { - id: 'authoring.table.noResultsFound.message', - defaultMessage: 'No results found', - }, - actionsButtonLabel: { - id: 'authoring.action.button.label', - defaultMessage: 'Actions', - }, }); export default messages; diff --git a/src/taxonomy/api/hooks/api.js b/src/taxonomy/api/hooks/api.js index 0b6d56ad69..b524bd197f 100644 --- a/src/taxonomy/api/hooks/api.js +++ b/src/taxonomy/api/hooks/api.js @@ -5,18 +5,10 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL; const getTaxonomyListApiUrl = () => new URL('api/content_tagging/v1/taxonomies/?enabled=true', getApiBaseUrl()).href; -const getExportTaxonomyApiUrl = (pk, format) => new URL( +export const getExportTaxonomyApiUrl = (pk, format) => new URL( `api/content_tagging/v1/taxonomies/${pk}/export/?output_format=${format}&download=1`, getApiBaseUrl(), ).href; -const getTaxonomyDetailApiUrl = (taxonomyId) => new URL( - `api/content_tagging/v1/taxonomies/${taxonomyId}/`, - getApiBaseUrl(), -).href; -const getTagListApiUrl = (taxonomyId, page) => new URL( - `api/content_tagging/v1/taxonomies/${taxonomyId}/tags/?page=${page + 1}`, - getApiBaseUrl(), -).href; /** * @returns {import("../types.mjs").UseQueryResult} @@ -32,31 +24,3 @@ export const useTaxonomyListData = () => ( export const exportTaxonomy = (pk, format) => { window.location.href = getExportTaxonomyApiUrl(pk, format); }; - -/** - * @param {number} taxonomyId - * @returns {import('@tanstack/react-query').UseQueryResult} - */ -export const useTaxonomyDetailData = (taxonomyId) => ( - useQuery({ - queryKey: ['taxonomyDetail', taxonomyId], - queryFn: () => getAuthenticatedHttpClient().get(getTaxonomyDetailApiUrl(taxonomyId)) - .then(camelCaseObject) - .then((response) => response.data), - }) -); - -/** - * @param {number} taxonomyId - * @param {import('../types.mjs').QueryOptions} options - * @returns {import('@tanstack/react-query').UseQueryResult} - */ -export const useTagListData = (taxonomyId, options) => { - const { pageIndex } = options; - return useQuery({ - queryKey: ['tagList', taxonomyId, pageIndex], - queryFn: () => getAuthenticatedHttpClient().get(getTagListApiUrl(taxonomyId, pageIndex)) - .then(camelCaseObject) - .then((response) => response.data), - }); -}; diff --git a/src/taxonomy/api/hooks/selectors.js b/src/taxonomy/api/hooks/selectors.js index 7522dfaed8..b2c678be78 100644 --- a/src/taxonomy/api/hooks/selectors.js +++ b/src/taxonomy/api/hooks/selectors.js @@ -1,8 +1,6 @@ // @ts-check import { - useTaxonomyDetailData, useTaxonomyListData, - useTagListData, exportTaxonomy, } from './api'; @@ -27,72 +25,3 @@ export const useIsTaxonomyListDataLoaded = () => ( export const callExportTaxonomy = (pk, format) => ( exportTaxonomy(pk, format) ); - -/** - * @param {number} taxonomyId - * @returns {Pick} - */ -export const useTaxonomyDetailDataStatus = (taxonomyId) => { - const { - isError, - error, - isFetched, - isSuccess, - } = useTaxonomyDetailData(taxonomyId); - return { - isError, - error, - isFetched, - isSuccess, - }; -}; - -/** - * @param {number} taxonomyId - * @returns {import("../types.mjs").TaxonomyData | undefined} - */ -export const useTaxonomyDetailDataResponse = (taxonomyId) => { - const { isSuccess, data } = useTaxonomyDetailData(taxonomyId); - if (isSuccess) { - return data; - } - - return undefined; -}; - -/* eslint-disable max-len */ -/** - * @param {number} taxonomyId - * @param {import("../types.mjs").QueryOptions} options - * @returns {Pick} - */ /* eslint-enable max-len */ -export const useTagListDataStatus = (taxonomyId, options) => { - const { - error, - isError, - isFetched, - isLoading, - isSuccess, - } = useTagListData(taxonomyId, options); - return { - error, - isError, - isFetched, - isLoading, - isSuccess, - }; -}; - -/** - * @param {number} taxonomyId - * @param {import("../types.mjs").QueryOptions} options - * @returns {import("../types.mjs").TaxonomyData | undefined} - */ -export const useTagListDataResponse = (taxonomyId, options) => { - const { isSuccess, data } = useTagListData(taxonomyId, options); - if (isSuccess) { - return data; - } - - return undefined; -}; diff --git a/src/taxonomy/api/hooks/selectors.test.js b/src/taxonomy/api/hooks/selectors.test.js index 3ae04a742b..12b3716097 100644 --- a/src/taxonomy/api/hooks/selectors.test.js +++ b/src/taxonomy/api/hooks/selectors.test.js @@ -1,17 +1,11 @@ import { useTaxonomyListDataResponse, useIsTaxonomyListDataLoaded, - useTaxonomyDetailDataStatus, - useTaxonomyDetailDataResponse, - useTagListDataStatus, - useTagListDataResponse, callExportTaxonomy, } from './selectors'; import { useTaxonomyListData, exportTaxonomy, - useTaxonomyDetailData, - useTagListData, } from './api'; jest.mock('./api', () => ({ @@ -65,74 +59,3 @@ describe('callExportTaxonomy', () => { expect(exportTaxonomy).toHaveBeenCalled(); }); }); - -describe('useTaxonomyDetailDataStatus', () => { - it('should return status values', () => { - const status = { - isError: false, - error: undefined, - isFetched: true, - isSuccess: true, - }; - - useTaxonomyDetailData.mockReturnValueOnce(status); - - const result = useTaxonomyDetailDataStatus(0); - - expect(result).toEqual(status); - }); -}); - -describe('useTaxonomyDetailDataResponse', () => { - it('should return data when status is success', () => { - useTaxonomyDetailData.mockReturnValueOnce({ isSuccess: true, data: 'data' }); - - const result = useTaxonomyDetailDataResponse(); - - expect(result).toEqual('data'); - }); - - it('should return undefined when status is not success', () => { - useTaxonomyDetailData.mockReturnValueOnce({ isSuccess: false }); - - const result = useTaxonomyDetailDataResponse(); - - expect(result).toBeUndefined(); - }); -}); - -describe('useTagListDataStatus', () => { - it('should return status values', () => { - const status = { - error: undefined, - isError: false, - isFetched: true, - isLoading: true, - isSuccess: true, - }; - - useTagListData.mockReturnValueOnce(status); - - const result = useTagListDataStatus(0, {}); - - expect(result).toEqual(status); - }); -}); - -describe('useTagListDataResponse', () => { - it('should return data when status is success', () => { - useTagListData.mockReturnValueOnce({ isSuccess: true, data: 'data' }); - - const result = useTagListDataResponse(0, {}); - - expect(result).toEqual('data'); - }); - - it('should return undefined when status is not success', () => { - useTagListData.mockReturnValueOnce({ isSuccess: false }); - - const result = useTagListDataResponse(0, {}); - - expect(result).toBeUndefined(); - }); -}); diff --git a/src/taxonomy/api/types.mjs b/src/taxonomy/api/types.mjs index e059c48ced..be2d86d1c3 100644 --- a/src/taxonomy/api/types.mjs +++ b/src/taxonomy/api/types.mjs @@ -39,8 +39,3 @@ * @property {Object} data * @property {string} status */ - -/** - * @typedef {Object} QueryOptions - * @property {number} pageIndex - */ diff --git a/src/taxonomy/messages.js b/src/taxonomy/messages.js index 8b3f2a9838..3c29b8a3ab 100644 --- a/src/taxonomy/messages.js +++ b/src/taxonomy/messages.js @@ -69,18 +69,6 @@ const messages = defineMessages({ id: 'course-authoring.taxonomy-list.modal.cancel', defaultMessage: 'Cancel', }, - taxonomyDetailsHeader: { - id: 'course-authoring.taxonomy-detail.side-card.header', - defaultMessage: 'Taxonomy details', - }, - taxonomyDetailsName: { - id: 'course-authoring.taxonomy-detail.side-card.name', - defaultMessage: 'Title', - }, - taxonomyDetailsDescription: { - id: 'course-authoring.taxonomy-detail.side-card.description', - defaultMessage: 'Description', - }, tagListColumnValueHeader: { id: 'course-authoring.tag-list.column.value.header', defaultMessage: 'Value', diff --git a/src/taxonomy/tag-list/data/api.js b/src/taxonomy/tag-list/data/api.js new file mode 100644 index 0000000000..3bbca572ac --- /dev/null +++ b/src/taxonomy/tag-list/data/api.js @@ -0,0 +1,26 @@ +// @ts-check +import { useQuery } from '@tanstack/react-query'; +import { camelCaseObject, getConfig } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; + +const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL; +const getTagListApiUrl = (taxonomyId, page) => new URL( + `api/content_tagging/v1/taxonomies/${taxonomyId}/tags/?page=${page + 1}`, + getApiBaseUrl(), +).href; + +// ToDo: fix types +/** + * @param {number} taxonomyId + * @param {import('../types.mjs').QueryOptions} options + * @returns {import('@tanstack/react-query').UseQueryResult} + */ // eslint-disable-next-line import/prefer-default-export +export const useTagListData = (taxonomyId, options) => { + const { pageIndex } = options; + return useQuery({ + queryKey: ['tagList', taxonomyId, pageIndex], + queryFn: () => getAuthenticatedHttpClient().get(getTagListApiUrl(taxonomyId, pageIndex)) + .then(camelCaseObject) + .then((response) => response.data), + }); +}; diff --git a/src/taxonomy/tag-list/data/api.test.js b/src/taxonomy/tag-list/data/api.test.js new file mode 100644 index 0000000000..de9e06080f --- /dev/null +++ b/src/taxonomy/tag-list/data/api.test.js @@ -0,0 +1,27 @@ +import { useQuery } from '@tanstack/react-query'; +import { + useTagListData, +} from './api'; + +const mockHttpClient = { + get: jest.fn(), +}; + +jest.mock('@tanstack/react-query', () => ({ + useQuery: jest.fn(), +})); + +jest.mock('@edx/frontend-platform/auth', () => ({ + getAuthenticatedHttpClient: jest.fn(() => mockHttpClient), +})); + +describe('useTagListData', () => { + it('should call useQuery with the correct parameters', () => { + useTagListData('1', { pageIndex: 3 }); + + expect(useQuery).toHaveBeenCalledWith({ + queryKey: ['tagList', '1', 3], + queryFn: expect.any(Function), + }); + }); +}); diff --git a/src/taxonomy/tag-list/data/selectors.js b/src/taxonomy/tag-list/data/selectors.js new file mode 100644 index 0000000000..f94bfedbba --- /dev/null +++ b/src/taxonomy/tag-list/data/selectors.js @@ -0,0 +1,42 @@ +// @ts-check +import { + useTagListData, +} from './api'; + +/* eslint-disable max-len */ +/** + * @param {number} taxonomyId + * @param {import("../types.mjs").QueryOptions} options + * @returns {Pick} + */ /* eslint-enable max-len */ +export const useTagListDataStatus = (taxonomyId, options) => { + const { + error, + isError, + isFetched, + isLoading, + isSuccess, + } = useTagListData(taxonomyId, options); + return { + error, + isError, + isFetched, + isLoading, + isSuccess, + }; +}; + +// ToDo: fix types +/** + * @param {number} taxonomyId + * @param {import("../types.mjs").QueryOptions} options + * @returns {import("../types.mjs").TaxonomyData | undefined} + */ +export const useTagListDataResponse = (taxonomyId, options) => { + const { isSuccess, data } = useTagListData(taxonomyId, options); + if (isSuccess) { + return data; + } + + return undefined; +}; diff --git a/src/taxonomy/tag-list/data/selectors.test.js b/src/taxonomy/tag-list/data/selectors.test.js new file mode 100644 index 0000000000..31a2450303 --- /dev/null +++ b/src/taxonomy/tag-list/data/selectors.test.js @@ -0,0 +1,47 @@ +import { + useTagListDataStatus, + useTagListDataResponse, +} from './selectors'; +import { + useTagListData, +} from './api'; + +jest.mock('./api', () => ({ + useTagListData: jest.fn(), +})); + +describe('useTagListDataStatus', () => { + it('should return status values', () => { + const status = { + error: undefined, + isError: false, + isFetched: true, + isLoading: true, + isSuccess: true, + }; + + useTagListData.mockReturnValueOnce(status); + + const result = useTagListDataStatus(0, {}); + + expect(result).toEqual(status); + }); +}); + +describe('useTagListDataResponse', () => { + it('should return data when status is success', () => { + useTagListData.mockReturnValueOnce({ isSuccess: true, data: 'data' }); + + const result = useTagListDataResponse(0, {}); + + expect(result).toEqual('data'); + }); + + it('should return undefined when status is not success', () => { + useTagListData.mockReturnValueOnce({ isSuccess: false }); + + const result = useTagListDataResponse(0, {}); + + expect(result).toBeUndefined(); + }); +}); diff --git a/src/taxonomy/tag-list/messages.js b/src/taxonomy/tag-list/messages.js new file mode 100644 index 0000000000..b2e215c4db --- /dev/null +++ b/src/taxonomy/tag-list/messages.js @@ -0,0 +1,10 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + noResultsFoundMessage: { + id: 'course-authoring.tag-list.no-results-found.message', + defaultMessage: 'No results found', + }, +}); + +export default messages; diff --git a/src/taxonomy/tag-list/types.mjs b/src/taxonomy/tag-list/types.mjs new file mode 100644 index 0000000000..3141228c7d --- /dev/null +++ b/src/taxonomy/tag-list/types.mjs @@ -0,0 +1,6 @@ +// @ts-check + +/** + * @typedef {Object} QueryOptions + * @property {number} pageIndex + */ diff --git a/src/taxonomy/taxonomy-detail/TaxonomyDetailMenu.jsx b/src/taxonomy/taxonomy-detail/TaxonomyDetailMenu.jsx index dcf1d15629..6fd7613e12 100644 --- a/src/taxonomy/taxonomy-detail/TaxonomyDetailMenu.jsx +++ b/src/taxonomy/taxonomy-detail/TaxonomyDetailMenu.jsx @@ -1,3 +1,4 @@ +// ts-check import { useIntl } from '@edx/frontend-platform/i18n'; import { Dropdown, @@ -5,7 +6,7 @@ import { } from '@edx/paragon'; import PropTypes from 'prop-types'; -import messages from '../../messages'; +import messages from './messages'; import taxonomyMessages from '../messages'; const TaxonomyDetailMenu = ({ diff --git a/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.jsx b/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.jsx index 29f83b79cc..23ddf046f0 100644 --- a/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.jsx +++ b/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.jsx @@ -10,12 +10,12 @@ import { Link, useParams } from 'react-router-dom'; import ConnectionErrorAlert from '../../generic/ConnectionErrorAlert'; import Loading from '../../generic/Loading'; import SubHeader from '../../generic/sub-header/SubHeader'; -import messages from '../messages'; +import messages from './messages'; import TaxonomyDetailMenu from './TaxonomyDetailMenu'; import TaxonomyDetailSideCard from './TaxonomyDetailSideCard'; import TagListTable from './TagListTable'; import ExportModal from '../modals/ExportModal'; -import { useTaxonomyDetailDataResponse, useTaxonomyDetailDataStatus } from '../api/hooks/selectors'; +import { useTaxonomyDetailDataResponse, useTaxonomyDetailDataStatus } from './data/selectors'; const TaxonomyDetailPage = () => { const intl = useIntl(); diff --git a/src/taxonomy/taxonomy-detail/TaxonomyDetailSideCard.jsx b/src/taxonomy/taxonomy-detail/TaxonomyDetailSideCard.jsx index 9706f972ce..3a808530b2 100644 --- a/src/taxonomy/taxonomy-detail/TaxonomyDetailSideCard.jsx +++ b/src/taxonomy/taxonomy-detail/TaxonomyDetailSideCard.jsx @@ -4,7 +4,7 @@ import { } from '@edx/paragon'; import Proptypes from 'prop-types'; -import messages from '../messages'; +import messages from './messages'; const TaxonomyDetailSideCard = ({ taxonomy }) => { const intl = useIntl(); diff --git a/src/taxonomy/taxonomy-detail/data/api.js b/src/taxonomy/taxonomy-detail/data/api.js new file mode 100644 index 0000000000..9148310926 --- /dev/null +++ b/src/taxonomy/taxonomy-detail/data/api.js @@ -0,0 +1,23 @@ +// @ts-check +import { camelCaseObject, getConfig } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { useQuery } from '@tanstack/react-query'; + +const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL; +const getTaxonomyDetailApiUrl = (taxonomyId) => new URL( + `api/content_tagging/v1/taxonomies/${taxonomyId}/`, + getApiBaseUrl(), +).href; + +/** + * @param {number} taxonomyId + * @returns {import('@tanstack/react-query').UseQueryResult} + */ // eslint-disable-next-line import/prefer-default-export +export const useTaxonomyDetailData = (taxonomyId) => ( + useQuery({ + queryKey: ['taxonomyDetail', taxonomyId], + queryFn: () => getAuthenticatedHttpClient().get(getTaxonomyDetailApiUrl(taxonomyId)) + .then(camelCaseObject) + .then((response) => response.data), + }) +); diff --git a/src/taxonomy/taxonomy-detail/data/api.test.js b/src/taxonomy/taxonomy-detail/data/api.test.js new file mode 100644 index 0000000000..257421680c --- /dev/null +++ b/src/taxonomy/taxonomy-detail/data/api.test.js @@ -0,0 +1,27 @@ +import { useQuery } from '@tanstack/react-query'; +import { + useTaxonomyDetailData, +} from './api'; + +const mockHttpClient = { + get: jest.fn(), +}; + +jest.mock('@tanstack/react-query', () => ({ + useQuery: jest.fn(), +})); + +jest.mock('@edx/frontend-platform/auth', () => ({ + getAuthenticatedHttpClient: jest.fn(() => mockHttpClient), +})); + +describe('useTaxonomyDetailData', () => { + it('should call useQuery with the correct parameters', () => { + useTaxonomyDetailData('1'); + + expect(useQuery).toHaveBeenCalledWith({ + queryKey: ['taxonomyDetail', '1'], + queryFn: expect.any(Function), + }); + }); +}); diff --git a/src/taxonomy/taxonomy-detail/data/selectors.js b/src/taxonomy/taxonomy-detail/data/selectors.js new file mode 100644 index 0000000000..4805b2c94b --- /dev/null +++ b/src/taxonomy/taxonomy-detail/data/selectors.js @@ -0,0 +1,36 @@ +// @ts-check +import { + useTaxonomyDetailData, +} from './api'; + +/** + * @param {number} taxonomyId + * @returns {Pick} + */ +export const useTaxonomyDetailDataStatus = (taxonomyId) => { + const { + isError, + error, + isFetched, + isSuccess, + } = useTaxonomyDetailData(taxonomyId); + return { + isError, + error, + isFetched, + isSuccess, + }; +}; + +/** + * @param {number} taxonomyId + * @returns {import("../types.mjs").TaxonomyData | undefined} + */ +export const useTaxonomyDetailDataResponse = (taxonomyId) => { + const { isSuccess, data } = useTaxonomyDetailData(taxonomyId); + if (isSuccess) { + return data; + } + + return undefined; +}; diff --git a/src/taxonomy/taxonomy-detail/data/selectors.test.js b/src/taxonomy/taxonomy-detail/data/selectors.test.js new file mode 100644 index 0000000000..210afc40bf --- /dev/null +++ b/src/taxonomy/taxonomy-detail/data/selectors.test.js @@ -0,0 +1,47 @@ +import { + useTaxonomyDetailData, +} from './api'; +import { + useTaxonomyDetailDataStatus, + useTaxonomyDetailDataResponse, +} from './selectors'; + +jest.mock('./api', () => ({ + __esModule: true, + useTaxonomyDetailData: jest.fn(), +})); + +describe('useTaxonomyDetailDataStatus', () => { + it('should return status values', () => { + const status = { + isError: false, + error: undefined, + isFetched: true, + isSuccess: true, + }; + + useTaxonomyDetailData.mockReturnValueOnce(status); + + const result = useTaxonomyDetailDataStatus(0); + + expect(result).toEqual(status); + }); +}); + +describe('useTaxonomyDetailDataResponse', () => { + it('should return data when status is success', () => { + useTaxonomyDetailData.mockReturnValueOnce({ isSuccess: true, data: 'data' }); + + const result = useTaxonomyDetailDataResponse(); + + expect(result).toEqual('data'); + }); + + it('should return undefined when status is not success', () => { + useTaxonomyDetailData.mockReturnValueOnce({ isSuccess: false }); + + const result = useTaxonomyDetailDataResponse(); + + expect(result).toBeUndefined(); + }); +}); diff --git a/src/taxonomy/taxonomy-detail/messages.js b/src/taxonomy/taxonomy-detail/messages.js new file mode 100644 index 0000000000..a1d9c15404 --- /dev/null +++ b/src/taxonomy/taxonomy-detail/messages.js @@ -0,0 +1,22 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + taxonomyDetailsHeader: { + id: 'course-authoring.taxonomy-detail.side-card.header', + defaultMessage: 'Taxonomy details', + }, + taxonomyDetailsName: { + id: 'course-authoring.taxonomy-detail.side-card.name', + defaultMessage: 'Title', + }, + taxonomyDetailsDescription: { + id: 'course-authoring.taxonomy-detail.side-card.description', + defaultMessage: 'Description', + }, + actionsButtonLabel: { + id: 'course-authoring.taxonomy-detail.action.button.label', + defaultMessage: 'Actions', + }, +}); + +export default messages;