Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4963124
feat: System-defined tooltip added
ChrisChV Oct 17, 2023
4077397
feat: Taxonomy card menu added. Export menu item added
ChrisChV Oct 18, 2023
e87c545
feat: Modal for export taxonomy
ChrisChV Oct 18, 2023
4922420
feat: Connect with export API
ChrisChV Oct 19, 2023
5e12a8f
test: Tests for API and selectors
ChrisChV Oct 20, 2023
09aa8b6
feat: Use windows.location.href to call the export endpoint
ChrisChV Oct 20, 2023
1925fae
test: ExportModal.test added
ChrisChV Oct 20, 2023
0be804c
style: Delete unnecesary code
ChrisChV Oct 21, 2023
da0156f
Merge branch 'master' into chris/FAL-3528-taxonomy-export-menu
rpenido Oct 25, 2023
0cc473c
Merge branch 'master' into chris/FAL-3528-taxonomy-export-menu
ChrisChV Oct 30, 2023
5af8d66
docs: README updated with taxonomy feature
ChrisChV Oct 30, 2023
a21fac2
style: TaxonomyCard updated to a better code style
ChrisChV Oct 31, 2023
61864d3
style: injectIntl replaced by useIntl on taxonomy pages and components
ChrisChV Oct 31, 2023
b09b86a
refactor: Move and rename taxonomy UI components to match 0002 ADR
ChrisChV Oct 31, 2023
df8432d
refactor: Move api to data to match with 0002 ADR
ChrisChV Oct 31, 2023
1abf034
test: Refactor ExportModal tests
Nov 6, 2023
1d6f9a5
chore: Merge conflicts solved
Nov 6, 2023
5c4c87a
chore: Fix validations
ChrisChV Nov 6, 2023
7e514b6
chore: Lint
ChrisChV Nov 6, 2023
080b03e
refactor: Moving hooks to apiHooks
ChrisChV Nov 8, 2023
56dbdd2
feat: add taxonomy detail page
rpenido Nov 11, 2023
9c582ed
Merge branch 'master' into rpenido/fal-3529-bare-bones-taxonomy-detai…
pomegranited Nov 15, 2023
9df1699
fix: address nits in PR review
pomegranited Nov 15, 2023
0fdaa07
refactor: move data/selectors to data/apiHooks
pomegranited Nov 15, 2023
0e19e99
fix: address nits in PR review
pomegranited Nov 20, 2023
3f0dbaa
fix: replace taxonomy menu ModalPopup with Dropdown menu
pomegranited Nov 20, 2023
0872d5d
Merge branch 'master' into rpenido/fal-3529-bare-bones-taxonomy-detai…
pomegranited Nov 20, 2023
a15e3a4
fix: change taxonomy URLs
pomegranited Nov 20, 2023
924e28e
chore: update this branch with master
bradenmacdonald Nov 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import { AppProvider, ErrorPage } from '@edx/frontend-platform/react';
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
import { Route, Routes } from 'react-router-dom';
import { Navigate, Route, Routes } from 'react-router-dom';
import {
QueryClient,
QueryClientProvider,
Expand All @@ -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 { TaxonomyLayout, TaxonomyDetailPage, TaxonomyListPage } from './taxonomy';
import { ContentTagsDrawer } from './content-tags-drawer';

import 'react-datepicker/dist/react-datepicker.css';
Expand Down Expand Up @@ -55,10 +55,14 @@ const App = () => {
<Route path="/course_rerun/:courseId" element={<CourseRerun />} />
{process.env.ENABLE_TAGGING_TAXONOMY_PAGES === 'true' && (
<>
<Route
path="/taxonomy-list"
element={<TaxonomyListPage />}
/>
{/* TODO: remove this redirect once Studio's link is updated */}
<Route path="/taxonomy-list" element={<Navigate to="/taxonomies" />} />
<Route path="/taxonomies" element={<TaxonomyLayout />}>
<Route index element={<TaxonomyListPage />} />
</Route>
<Route path="/taxonomy" element={<TaxonomyLayout />}>
<Route path="/taxonomy/:taxonomyId" element={<TaxonomyDetailPage />} />
</Route>
<Route
path="/tagging/components/widget/:contentId"
element={<ContentTagsDrawer />}
Expand Down
14 changes: 14 additions & 0 deletions src/taxonomy/TaxonomyLayout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { StudioFooter } from '@edx/frontend-component-footer';
import { Outlet } from 'react-router-dom';

import Header from '../header';

const TaxonomyLayout = () => (
<div className="bg-light-400">
<Header isHiddenMainMenu />
<Outlet />
<StudioFooter />
</div>
);

export default TaxonomyLayout;
48 changes: 48 additions & 0 deletions src/taxonomy/TaxonomyLayout.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { initializeMockApp } from '@edx/frontend-platform';
import { AppProvider } from '@edx/frontend-platform/react';
import { render } from '@testing-library/react';

import initializeStore from '../store';
import TaxonomyLayout from './TaxonomyLayout';

let store;

jest.mock('../header', () => jest.fn(() => <div data-testid="mock-header" />));
jest.mock('@edx/frontend-component-footer', () => ({
StudioFooter: jest.fn(() => <div data-testid="mock-footer" />),
}));
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
Outlet: jest.fn(() => <div data-testid="mock-content" />),
}));

const RootWrapper = () => (
<AppProvider store={store}>
<IntlProvider locale="en" messages={{}}>
<TaxonomyLayout />
</IntlProvider>
</AppProvider>
);

describe('<TaxonomyLayout />', async () => {
beforeEach(async () => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: true,
roles: [],
},
});
store = initializeStore();
});

it('should render page correctly', async () => {
const { getByTestId } = render(<RootWrapper />);
expect(getByTestId('mock-header')).toBeInTheDocument();
expect(getByTestId('mock-content')).toBeInTheDocument();
expect(getByTestId('mock-footer')).toBeInTheDocument();
});
});
11 changes: 0 additions & 11 deletions src/taxonomy/TaxonomyListPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import {
DataTable,
Spinner,
} from '@edx/paragon';
import { StudioFooter } from '@edx/frontend-component-footer';
import { useIntl } from '@edx/frontend-platform/i18n';
import Header from '../header';
import SubHeader from '../generic/sub-header/SubHeader';
import messages from './messages';
import TaxonomyCard from './taxonomy-card';
Expand Down Expand Up @@ -37,14 +35,6 @@ const TaxonomyListPage = () => {

return (
<>
<style>
{`
body {
background-color: #E9E6E4; /* light-400 */
}
`}
</style>
<Header isHiddenMainMenu />
<div className="pt-4.5 pr-4.5 pl-4.5 pb-2 bg-light-100 box-shadow-down-2">
<Container size="xl">
<SubHeader
Expand Down Expand Up @@ -93,7 +83,6 @@ const TaxonomyListPage = () => {
)}
</Container>
</div>
<StudioFooter />
</>
);
};
Expand Down
6 changes: 5 additions & 1 deletion src/taxonomy/export-modal/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ const ExportModal = ({
<ModalDialog.CloseButton variant="tertiary">
{intl.formatMessage(messages.taxonomyModalsCancelLabel)}
</ModalDialog.CloseButton>
<Button variant="primary" onClick={onClickExport}>
<Button
variant="primary"
onClick={onClickExport}
data-testid={`export-button-${taxonomyId}`}
>
{intl.formatMessage(messages.exportModalSubmitButtonLabel)}
</Button>
</ActionRow>
Expand Down
3 changes: 2 additions & 1 deletion src/taxonomy/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
// eslint-disable-next-line import/prefer-default-export
export { default as TaxonomyListPage } from './TaxonomyListPage';
export { default as TaxonomyLayout } from './TaxonomyLayout';
export { TaxonomyDetailPage } from './taxonomy-detail';
56 changes: 56 additions & 0 deletions src/taxonomy/tag-list/TagListTable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// ts-check
import { useIntl } from '@edx/frontend-platform/i18n';
import {
DataTable,
} from '@edx/paragon';
import _ from 'lodash';
import Proptypes from 'prop-types';
import { useState } from 'react';

import messages from './messages';
import { useTagListDataResponse, useTagListDataStatus } from './data/apiHooks';

const TagListTable = ({ taxonomyId }) => {
const intl = useIntl();
const [options, setOptions] = useState({
pageIndex: 0,
});
const { isLoading } = useTagListDataStatus(taxonomyId, options);
const tagList = useTagListDataResponse(taxonomyId, options);

const fetchData = (args) => {
if (!_.isEqual(args, options)) {
setOptions({ ...args });
}
};

return (
<DataTable
isLoading={isLoading}
isPaginated
manualPagination
fetchData={fetchData}
data={tagList?.results || []}
itemCount={tagList?.count || 0}
pageCount={tagList?.numPages || 0}
initialState={options}
columns={[
{
Header: intl.formatMessage(messages.tagListColumnValueHeader),
accessor: 'value',
},
]}
>
<DataTable.TableControlBar />
<DataTable.Table />
<DataTable.EmptyTable content={intl.formatMessage(messages.noResultsFoundMessage)} />
<DataTable.TableFooter />
</DataTable>
);
};

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

export default TagListTable;
67 changes: 67 additions & 0 deletions src/taxonomy/tag-list/TagListTable.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { initializeMockApp } from '@edx/frontend-platform';
import { AppProvider } from '@edx/frontend-platform/react';
import { render } from '@testing-library/react';

import { useTagListData } from './data/api';
import initializeStore from '../../store';
import TagListTable from './TagListTable';

let store;

jest.mock('./data/api', () => ({
useTagListData: jest.fn(),
}));

const RootWrapper = () => (
<AppProvider store={store}>
<IntlProvider locale="en" messages={{}}>
<TagListTable taxonomyId="1" />
</IntlProvider>
</AppProvider>
);

describe('<TagListPage />', async () => {
beforeEach(async () => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: true,
roles: [],
},
});
store = initializeStore();
});

it('shows the spinner before the query is complete', async () => {
useTagListData.mockReturnValue({
isLoading: true,
isFetched: false,
});
const { getByRole } = render(<RootWrapper />);
const spinner = getByRole('status');
expect(spinner.textContent).toEqual('loading');
});

it('should render page correctly', async () => {
useTagListData.mockReturnValue({
isSuccess: true,
isFetched: true,
isError: false,
data: {
count: 3,
numPages: 1,
results: [
{ value: 'Tag 1' },
{ value: 'Tag 2' },
{ value: 'Tag 3' },
],
},
});
const { getAllByRole } = render(<RootWrapper />);
const rows = getAllByRole('row');
expect(rows.length).toBe(3 + 1); // 3 items plus header
});
});
27 changes: 27 additions & 0 deletions src/taxonomy/tag-list/data/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// @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<import('./types.mjs').TagListData>}
*/ // eslint-disable-next-line import/prefer-default-export
export const useTagListData = (taxonomyId, options) => {
const { pageIndex } = options;
return useQuery({
queryKey: ['tagList', taxonomyId, pageIndex],
queryFn: async () => {
const { data } = await getAuthenticatedHttpClient().get(getTagListApiUrl(taxonomyId, pageIndex));
return camelCaseObject(data);
},
});
};
27 changes: 27 additions & 0 deletions src/taxonomy/tag-list/data/api.test.js
Original file line number Diff line number Diff line change
@@ -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),
});
});
});
41 changes: 41 additions & 0 deletions src/taxonomy/tag-list/data/apiHooks.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// @ts-check
import {
useTagListData,
} from './api';

/* eslint-disable max-len */
/**
* @param {number} taxonomyId
* @param {import("./types.mjs").QueryOptions} options
* @returns {Pick<import('@tanstack/react-query').UseQueryResult, "error" | "isError" | "isFetched" | "isLoading" | "isSuccess" >}
*/ /* 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").TagListData | undefined}
*/
export const useTagListDataResponse = (taxonomyId, options) => {
const { isSuccess, data } = useTagListData(taxonomyId, options);
if (isSuccess) {
return data;
}

return undefined;
};
Loading