Skip to content

Commit

Permalink
feat: add taxonomy detail page
Browse files Browse the repository at this point in the history
  • Loading branch information
rpenido committed Oct 24, 2023
1 parent 0be804c commit 3d45969
Show file tree
Hide file tree
Showing 14 changed files with 416 additions and 9 deletions.
22 changes: 16 additions & 6 deletions src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import CourseAuthoringRoutes from './CourseAuthoringRoutes';
import Head from './head/Head';
import { StudioHome } from './studio-home';
import CourseRerun from './course-rerun';
import { TaxonomyListPage } from './taxonomy';
import { TaxonomyDetailPage, TaxonomyListPage } from './taxonomy';

import 'react-datepicker/dist/react-datepicker.css';
import './index.scss';
Expand Down Expand Up @@ -71,12 +71,22 @@ const App = () => {
}}
/>
{process.env.ENABLE_TAGGING_TAXONOMY_PAGES === 'true' && (
<Route
path="/taxonomy-list"
>
<TaxonomyListPage />
</Route>
<>
<Route exact path="/taxonomy-list/" component={TaxonomyListPage} />
<Route
path="/taxonomy-list/:taxonomyId"
render={({ match }) => {
const { params: { taxonomyId } } = match;
return (
<TaxonomyDetailPage taxonomyId={Number(taxonomyId)} />
);
}}
/>
</>
)}
<Route>
No match (404)
</Route>
</Switch>
</QueryClientProvider>
</AppProvider>
Expand Down
36 changes: 35 additions & 1 deletion src/taxonomy/api/hooks/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@ 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;
export const getExportTaxonomyApiUrl = (pk, format) => new URL(
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) => new URL(
`api/content_tagging/v1/taxonomies/${taxonomyId}/tags/?page_size=300`,
getApiBaseUrl(),
).href;

/**
* @returns {import("../types.mjs").UseQueryResult}
Expand All @@ -24,3 +32,29 @@ export const useTaxonomyListData = () => (
export const exportTaxonomy = (pk, format) => {
window.location.href = getExportTaxonomyApiUrl(pk, format);
};

/**
* @param {number} taxonomyId
* @returns {import('@tanstack/react-query').UseQueryResult<import('../types.mjs').TaxonomyData>}
*/
export const useTaxonomyDetailData = (taxonomyId) => (
useQuery({
queryKey: ['taxonomyList', taxonomyId],
queryFn: () => getAuthenticatedHttpClient().get(getTaxonomyDetailApiUrl(taxonomyId))
.then(camelCaseObject)
.then((response) => response.data),
})
);

/**
* @param {number} taxonomyId
* @returns {import('@tanstack/react-query').UseQueryResult<import('../types.mjs').TaxonomyData>}
*/
export const useTagListData = (taxonomyId) => (
useQuery({
queryKey: ['tagList', taxonomyId],
queryFn: () => getAuthenticatedHttpClient().get(getTagListApiUrl(taxonomyId))
.then(camelCaseObject)
.then((response) => response.data),
})
);
5 changes: 5 additions & 0 deletions src/taxonomy/api/hooks/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ const mockHttpClient = {

jest.mock('@tanstack/react-query', () => ({
useQuery: jest.fn(),
useMutation: jest.fn(),
}));

jest.mock('@edx/frontend-platform/auth', () => ({
getAuthenticatedHttpClient: jest.fn(() => mockHttpClient),
}));

jest.mock('../../../utils', () => ({
downloadDataAsFile: jest.fn(),
}));

describe('useTaxonomyListData', () => {
afterEach(() => {
jest.clearAllMocks();
Expand Down
66 changes: 66 additions & 0 deletions src/taxonomy/api/hooks/selectors.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// @ts-check
import {
useTaxonomyDetailData,
useTaxonomyListData,
useTagListData,
exportTaxonomy,
} from './api';

Expand All @@ -25,3 +27,67 @@ export const useIsTaxonomyListDataLoaded = () => (
export const callExportTaxonomy = (pk, format) => (
exportTaxonomy(pk, format)
);

/**
* @param {number} taxonomyId
* @returns {Pick<import('@tanstack/react-query').UseQueryResult, "error" | "isError" | "isFetched" | "isSuccess">}
*/
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;
};

/**
* @param {number} taxonomyId
* @returns {Pick<import('@tanstack/react-query').UseQueryResult, "error" | "isError" | "isFetched" | "isSuccess">}
*/
export const useTagListDataStatus = (taxonomyId) => {
const {
isError,
error,
isFetched,
isSuccess,
} = useTagListData(taxonomyId);
return {
isError,
error,
isFetched,
isSuccess,
};
};

/**
* @param {number} taxonomyId
* @returns {import("../types.mjs").TaxonomyData | undefined}
*/
export const useTagListDataResponse = (taxonomyId) => {
const { isSuccess, data } = useTagListData(taxonomyId);
if (isSuccess) {
return data;
}

return undefined;
};
7 changes: 7 additions & 0 deletions src/taxonomy/api/types.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@
* @property {TaxonomyListData} data
*/

/**
* @typedef {Object} ExportRequestParams
* @property {number} pk
* @property {string} format
* @property {string} name
*/

/**
* @typedef {Object} UseQueryResult
* @property {Object} data
Expand Down
2 changes: 1 addition & 1 deletion src/taxonomy/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// eslint-disable-next-line import/prefer-default-export
export { default as TaxonomyListPage } from './TaxonomyListPage';
export { default as TaxonomyDetailPage } from './taxonomy-detail/TaxonomyDetailPage';
5 changes: 4 additions & 1 deletion src/taxonomy/taxonomy-card/TaxonomyCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
} from '@edx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';

import classNames from 'classnames';
import messages from '../messages';
import TaxonomyCardMenu from './TaxonomyCardMenu';
Expand Down Expand Up @@ -101,7 +103,8 @@ const TaxonomyCard = ({ className, original, intl }) => {

return (
<>
<Card className={classNames('taxonomy-card', className)} data-testid={`taxonomy-card-${id}`}>
<Card isClickable className={classNames('taxonomy-card', className)} data-testid={`taxonomy-card-${id}`}>
<Link className="stretched-link" to={`taxonomy-list/${id}`} aria-label="view taxonomy details" />
<Card.Header
title={name}
subtitle={getHeaderSubtitle()}
Expand Down
5 changes: 5 additions & 0 deletions src/taxonomy/taxonomy-card/TaxonomyCard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@
white-space: nowrap;
}

.pgn__card-header-actions, .badge {
z-index: 100;
position: relative;
}

.taxonomy-menu-item:focus {
/**
* There is a bug in the menu that auto focus the first item.
Expand Down
1 change: 1 addition & 0 deletions src/taxonomy/taxonomy-card/TaxonomyCardMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const TaxonomyCardMenu = ({
return (
<>
<IconButton
className="streched-link"
variant="primary"
onClick={() => setMenuIsOpen(true)}
ref={setMenuTarget}
Expand Down
49 changes: 49 additions & 0 deletions src/taxonomy/taxonomy-detail/TagListTable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {
DataTable,
TextFilter,
} from '@edx/paragon';
import Proptypes from 'prop-types';

import { useTagListDataResponse, useTagListDataStatus } from '../api/hooks/selectors';

const TagListTable = ({ taxonomyId }) => {
const useTagListData = () => {
const { isError, isFetched } = useTagListDataStatus(taxonomyId);
const tagList = useTagListDataResponse(taxonomyId);
return { isError, isFetched, tagList };
};

const { tagList } = useTagListData(taxonomyId);

if (!tagList || !tagList.results) {
return 'Loading...';
}

return (
<DataTable
isPaginated
isFilterable
isSortable
defaultColumnValues={{ Filter: TextFilter }}
data={tagList.results}
itemCount={tagList.count}
columns={[
{
Header: 'Value',
accessor: 'value',
},
]}
>
<DataTable.TableControlBar />
<DataTable.Table />
<DataTable.EmptyTable content="No results found" />
<DataTable.TableFooter />
</DataTable>
);
};

TagListTable.propTypes = {
taxonomyId: Proptypes.number.isRequired,
};

export default TagListTable;
Loading

0 comments on commit 3d45969

Please sign in to comment.