Skip to content

Commit 608b2f7

Browse files
authored
[FC-0036] Refined taxonomy details page (#833)
* UX refinements on tag list table * Add page size to tag list table * fix Datatable pagination
1 parent 6b57ce3 commit 608b2f7

File tree

6 files changed

+103
-54
lines changed

6 files changed

+103
-54
lines changed

src/taxonomy/index.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
@import "taxonomy/delete-dialog/DeleteDialog";
44
@import "taxonomy/system-defined-badge/SystemDefinedBadge";
55
@import "taxonomy/export-modal/ExportModal";
6+
@import "taxonomy/tag-list/TagListTable";

src/taxonomy/tag-list/TagListTable.jsx

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ SubTagsExpanded.propTypes = {
3939
/**
4040
* An "Expand" toggle to show/hide subtags, but one which is hidden if the given tag row has no subtags.
4141
*/
42-
const OptionalExpandLink = ({ row }) => (row.original.childCount > 0 ? <DataTable.ExpandRow row={row} /> : null);
42+
const OptionalExpandLink = ({ row }) => (
43+
row.original.childCount > 0 ? <div className="d-flex justify-content-end"><DataTable.ExpandRow row={row} /></div> : null
44+
);
4345
OptionalExpandLink.propTypes = DataTable.ExpandRow.propTypes;
4446

4547
/**
@@ -65,6 +67,7 @@ const TagListTable = ({ taxonomyId }) => {
6567
const intl = useIntl();
6668
const [options, setOptions] = useState({
6769
pageIndex: 0,
70+
pageSize: 100,
6871
});
6972
const { isLoading } = useTagListDataStatus(taxonomyId, options);
7073
const tagList = useTagListDataResponse(taxonomyId, options);
@@ -76,38 +79,40 @@ const TagListTable = ({ taxonomyId }) => {
7679
};
7780

7881
return (
79-
<DataTable
80-
isLoading={isLoading}
81-
isPaginated
82-
manualPagination
83-
fetchData={fetchData}
84-
data={tagList?.results || []}
85-
itemCount={tagList?.count || 0}
86-
pageCount={tagList?.numPages || 0}
87-
initialState={options}
88-
isExpandable
89-
// This is a temporary "bare bones" solution for brute-force loading all the child tags. In future we'll match
90-
// the Figma design and do something more sophisticated.
91-
renderRowSubComponent={({ row }) => (
92-
<SubTagsExpanded taxonomyId={taxonomyId} parentTagValue={row.original.value} />
93-
)}
94-
columns={[
95-
{
96-
Header: intl.formatMessage(messages.tagListColumnValueHeader),
97-
Cell: TagValue,
98-
},
99-
{
100-
id: 'expander',
101-
Header: DataTable.ExpandAll,
102-
Cell: OptionalExpandLink,
103-
},
104-
]}
105-
>
106-
<DataTable.TableControlBar />
107-
<DataTable.Table />
108-
<DataTable.EmptyTable content={intl.formatMessage(messages.noResultsFoundMessage)} />
109-
<DataTable.TableFooter />
110-
</DataTable>
82+
<div className="tag-list-table">
83+
<DataTable
84+
isLoading={isLoading}
85+
isPaginated
86+
manualPagination
87+
fetchData={fetchData}
88+
data={tagList?.results || []}
89+
itemCount={tagList?.count || 0}
90+
pageCount={tagList?.numPages || 0}
91+
initialState={options}
92+
isExpandable
93+
// This is a temporary "bare bones" solution for brute-force loading all the child tags. In future we'll match
94+
// the Figma design and do something more sophisticated.
95+
renderRowSubComponent={({ row }) => (
96+
<SubTagsExpanded taxonomyId={taxonomyId} parentTagValue={row.original.value} />
97+
)}
98+
columns={[
99+
{
100+
Header: intl.formatMessage(messages.tagListColumnValueHeader),
101+
Cell: TagValue,
102+
},
103+
{
104+
id: 'expander',
105+
Header: DataTable.ExpandAll,
106+
Cell: OptionalExpandLink,
107+
},
108+
]}
109+
>
110+
<DataTable.Table />
111+
<DataTable.EmptyTable content={intl.formatMessage(messages.noResultsFoundMessage)} />
112+
{tagList?.numPages !== undefined && tagList?.numPages > 1
113+
&& <DataTable.TableFooter />}
114+
</DataTable>
115+
</div>
111116
);
112117
};
113118

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.tag-list-table {
2+
table tr:first-child > th:nth-child(2) > span {
3+
// Used to move "Expand all" button to the right.
4+
// Find the first <span> of the second <th> of the first <tr> of the <table>.
5+
//
6+
// The approach of the expand buttons cannot be applied here since the
7+
// table headers are rendered differently and at the component level
8+
// there is no control of this style.
9+
display: flex;
10+
justify-content: flex-end;
11+
}
12+
}

src/taxonomy/tag-list/TagListTable.test.jsx

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { IntlProvider } from '@edx/frontend-platform/i18n';
33
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
44
import { initializeMockApp } from '@edx/frontend-platform';
55
import { AppProvider } from '@edx/frontend-platform/react';
6-
import { render, waitFor, within } from '@testing-library/react';
6+
import {
7+
render, waitFor, screen, within,
8+
} from '@testing-library/react';
79
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
810
import MockAdapter from 'axios-mock-adapter';
911

@@ -59,7 +61,16 @@ const mockTagsResponse = {
5961
},
6062
],
6163
};
62-
const rootTagsListUrl = 'http://localhost:18010/api/content_tagging/v1/taxonomies/1/tags/?page=1';
64+
const mockTagsPaginationResponse = {
65+
next: null,
66+
previous: null,
67+
count: 103,
68+
num_pages: 2,
69+
current_page: 1,
70+
start: 0,
71+
results: [],
72+
};
73+
const rootTagsListUrl = 'http://localhost:18010/api/content_tagging/v1/taxonomies/1/tags/?page=1&page_size=100';
6374
const subTagsResponse = {
6475
next: null,
6576
previous: null,
@@ -102,22 +113,21 @@ describe('<TagListTable />', () => {
102113
let resolveResponse;
103114
const promise = new Promise(resolve => { resolveResponse = resolve; });
104115
axiosMock.onGet(rootTagsListUrl).reply(() => promise);
105-
const result = render(<RootWrapper />);
106-
const spinner = result.getByRole('status');
116+
render(<RootWrapper />);
117+
const spinner = screen.getByRole('status');
107118
expect(spinner.textContent).toEqual('loading');
108119
resolveResponse([200, {}]);
109-
await waitFor(() => {
110-
expect(result.getByText('No results found')).toBeInTheDocument();
111-
});
120+
const noFoundComponent = await screen.findByText('No results found');
121+
expect(noFoundComponent).toBeInTheDocument();
112122
});
113123

114124
it('should render page correctly', async () => {
115125
axiosMock.onGet(rootTagsListUrl).reply(200, mockTagsResponse);
116-
const result = render(<RootWrapper />);
117-
await waitFor(() => {
118-
expect(result.getByText('root tag 1')).toBeInTheDocument();
119-
});
120-
const rows = result.getAllByRole('row');
126+
render(<RootWrapper />);
127+
const tag = await screen.findByText('root tag 1');
128+
expect(tag).toBeInTheDocument();
129+
130+
const rows = screen.getAllByRole('row');
121131
expect(rows.length).toBe(3 + 1); // 3 items plus header
122132
expect(within(rows[0]).getAllByRole('columnheader')[0].textContent).toEqual('Tag name');
123133
expect(within(rows[1]).getAllByRole('cell')[0].textContent).toEqual('root tag 1 (14)');
@@ -126,11 +136,29 @@ describe('<TagListTable />', () => {
126136
it('should render page correctly with subtags', async () => {
127137
axiosMock.onGet(rootTagsListUrl).reply(200, mockTagsResponse);
128138
axiosMock.onGet(subTagsUrl).reply(200, subTagsResponse);
129-
const result = render(<RootWrapper />);
130-
const expandButton = result.getAllByLabelText('Expand row')[0];
139+
render(<RootWrapper />);
140+
const expandButton = screen.getAllByLabelText('Expand row')[0];
131141
expandButton.click();
142+
const childTag = await screen.findByText('the child tag');
143+
expect(childTag).toBeInTheDocument();
144+
});
145+
146+
it('should not render pagination footer', async () => {
147+
axiosMock.onGet(rootTagsListUrl).reply(200, mockTagsResponse);
148+
render(<RootWrapper />);
132149
await waitFor(() => {
133-
expect(result.getByText('the child tag')).toBeInTheDocument();
150+
expect(screen.queryByRole('navigation', {
151+
name: /table pagination/i,
152+
})).not.toBeInTheDocument();
153+
});
154+
});
155+
156+
it('should render pagination footer', async () => {
157+
axiosMock.onGet(rootTagsListUrl).reply(200, mockTagsPaginationResponse);
158+
render(<RootWrapper />);
159+
const tableFooter = await screen.findByRole('navigation', {
160+
name: /table pagination/i,
134161
});
162+
expect(tableFooter).toBeInTheDocument();
135163
});
136164
});

src/taxonomy/tag-list/data/api.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,24 @@ import { camelCaseObject, getConfig } from '@edx/frontend-platform';
99
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
1010

1111
const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL;
12-
const getTagListApiUrl = (taxonomyId, page) => new URL(
13-
`api/content_tagging/v1/taxonomies/${taxonomyId}/tags/?page=${page + 1}`,
14-
getApiBaseUrl(),
15-
).href;
12+
const getTagListApiUrl = (taxonomyId, page, pageSize) => {
13+
const url = new URL(`api/content_tagging/v1/taxonomies/${taxonomyId}/tags/`, getApiBaseUrl());
14+
url.searchParams.append('page', page + 1);
15+
url.searchParams.append('page_size', pageSize);
16+
return url.href;
17+
};
1618

1719
/**
1820
* @param {number} taxonomyId
1921
* @param {import('./types.mjs').QueryOptions} options
2022
* @returns {import('@tanstack/react-query').UseQueryResult<import('./types.mjs').TagListData>}
2123
*/
2224
export const useTagListData = (taxonomyId, options) => {
23-
const { pageIndex } = options;
25+
const { pageIndex, pageSize } = options;
2426
return useQuery({
2527
queryKey: ['tagList', taxonomyId, pageIndex],
2628
queryFn: async () => {
27-
const { data } = await getAuthenticatedHttpClient().get(getTagListApiUrl(taxonomyId, pageIndex));
29+
const { data } = await getAuthenticatedHttpClient().get(getTagListApiUrl(taxonomyId, pageIndex, pageSize));
2830
return camelCaseObject(data);
2931
},
3032
});

src/taxonomy/tag-list/data/types.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
/**
88
* @typedef {Object} QueryOptions
99
* @property {number} pageIndex
10+
* @property {number} pageSize
1011
*/
1112

1213
/**

0 commit comments

Comments
 (0)