feat: add download template button to taxonomy list [FC-0036] (#674)
This commit adds a new button in the Taxonomy List to allow users to download a sample taxonomy template in the format used to import taxonomies.
This commit is contained in:
@@ -1,19 +1,69 @@
|
||||
import React, { useContext } from 'react';
|
||||
import {
|
||||
Button,
|
||||
CardView,
|
||||
Container,
|
||||
DataTable,
|
||||
Dropdown,
|
||||
OverlayTrigger,
|
||||
Spinner,
|
||||
Tooltip,
|
||||
} from '@edx/paragon';
|
||||
import {
|
||||
Add,
|
||||
} from '@edx/paragon/icons';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import SubHeader from '../generic/sub-header/SubHeader';
|
||||
import getPageHeadTitle from '../generic/utils';
|
||||
import messages from './messages';
|
||||
import TaxonomyCard from './taxonomy-card';
|
||||
import { getTaxonomyTemplateApiUrl } from './data/api';
|
||||
import { useTaxonomyListDataResponse, useIsTaxonomyListDataLoaded, useDeleteTaxonomy } from './data/apiHooks';
|
||||
import { TaxonomyContext } from './common/context';
|
||||
|
||||
const TaxonomyListHeaderButtons = () => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<>
|
||||
<OverlayTrigger
|
||||
placement="top"
|
||||
overlay={(
|
||||
<Tooltip>
|
||||
{intl.formatMessage(messages.downloadTemplateButtonHint)}
|
||||
</Tooltip>
|
||||
)}
|
||||
>
|
||||
<Dropdown>
|
||||
<Dropdown.Toggle
|
||||
variant="outline-primary"
|
||||
data-testid="taxonomy-download-template"
|
||||
>
|
||||
{intl.formatMessage(messages.downloadTemplateButtonLabel)}
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item
|
||||
href={getTaxonomyTemplateApiUrl('csv')}
|
||||
data-testid="taxonomy-download-template-csv"
|
||||
>
|
||||
{intl.formatMessage(messages.downloadTemplateButtonCSVLabel)}
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
href={getTaxonomyTemplateApiUrl('json')}
|
||||
data-testid="taxonomy-download-template-json"
|
||||
>
|
||||
{intl.formatMessage(messages.downloadTemplateButtonJSONLabel)}
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</OverlayTrigger>
|
||||
<Button iconBefore={Add} disabled>
|
||||
{intl.formatMessage(messages.importButtonLabel)}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const TaxonomyListPage = () => {
|
||||
const intl = useIntl();
|
||||
const deleteTaxonomy = useDeleteTaxonomy();
|
||||
@@ -37,12 +87,6 @@ const TaxonomyListPage = () => {
|
||||
};
|
||||
const { taxonomyListData, isLoaded } = useTaxonomyListData();
|
||||
|
||||
const getHeaderButtons = () => (
|
||||
// Download template and import buttons.
|
||||
// TODO Add functionality to this buttons.
|
||||
undefined
|
||||
);
|
||||
|
||||
const getOrgSelect = () => (
|
||||
// Organization select component
|
||||
// TODO Add functionality to this component
|
||||
@@ -59,7 +103,7 @@ const TaxonomyListPage = () => {
|
||||
<SubHeader
|
||||
title={intl.formatMessage(messages.headerTitle)}
|
||||
titleActions={getOrgSelect()}
|
||||
headerActions={getHeaderButtons()}
|
||||
headerActions={<TaxonomyListHeaderButtons />}
|
||||
hideBorder
|
||||
/>
|
||||
</Container>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { AppProvider } from '@edx/frontend-platform/react';
|
||||
import { act, render, fireEvent } from '@testing-library/react';
|
||||
|
||||
import initializeStore from '../store';
|
||||
|
||||
import { getTaxonomyTemplateApiUrl } from './data/api';
|
||||
import TaxonomyListPage from './TaxonomyListPage';
|
||||
import { useTaxonomyListDataResponse, useIsTaxonomyListDataLoaded } from './data/apiHooks';
|
||||
import { TaxonomyContext } from './common/context';
|
||||
@@ -84,6 +84,24 @@ describe('<TaxonomyListPage />', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each(['CSV', 'JSON'])('downloads the taxonomy template %s', async (fileFormat) => {
|
||||
useIsTaxonomyListDataLoaded.mockReturnValue(true);
|
||||
useTaxonomyListDataResponse.mockReturnValue({
|
||||
results: [{
|
||||
id: 1,
|
||||
name: 'Taxonomy',
|
||||
description: 'This is a description',
|
||||
}],
|
||||
});
|
||||
const { findByRole } = render(<RootWrapper />);
|
||||
const templateMenu = await findByRole('button', { name: 'Download template' });
|
||||
fireEvent.click(templateMenu);
|
||||
const templateButton = await findByRole('link', { name: `${fileFormat} template` });
|
||||
fireEvent.click(templateButton);
|
||||
|
||||
expect(templateButton.href).toBe(getTaxonomyTemplateApiUrl(fileFormat.toLowerCase()));
|
||||
});
|
||||
|
||||
it('should show the success toast after delete', async () => {
|
||||
useIsTaxonomyListDataLoaded.mockReturnValue(true);
|
||||
useTaxonomyListDataResponse.mockReturnValue({
|
||||
|
||||
@@ -18,6 +18,10 @@ export const getExportTaxonomyApiUrl = (pk, format) => new URL(
|
||||
`api/content_tagging/v1/taxonomies/${pk}/export/?output_format=${format}&download=1`,
|
||||
getApiBaseUrl(),
|
||||
).href;
|
||||
export const getTaxonomyTemplateApiUrl = (format) => new URL(
|
||||
`api/content_tagging/v1/taxonomies/import/template.${format}`,
|
||||
getApiBaseUrl(),
|
||||
).href;
|
||||
export const getTaxonomyApiUrl = (pk) => new URL(`api/content_tagging/v1/taxonomies/${pk}/`, getApiBaseUrl()).href;
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,10 +5,10 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { taxonomyListMock } from '../__mocks__';
|
||||
|
||||
import {
|
||||
getTaxonomyListApiUrl,
|
||||
getExportTaxonomyApiUrl,
|
||||
getTaxonomyListData,
|
||||
getTaxonomyExportFile,
|
||||
getTaxonomyListApiUrl,
|
||||
getTaxonomyListData,
|
||||
getTaxonomyApiUrl,
|
||||
deleteTaxonomy,
|
||||
} from './api';
|
||||
@@ -26,6 +26,7 @@ describe('taxonomy api calls', () => {
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
});
|
||||
|
||||
@@ -44,14 +45,6 @@ describe('taxonomy api calls', () => {
|
||||
window.location = location;
|
||||
});
|
||||
|
||||
it('should get taxonomy list data', async () => {
|
||||
axiosMock.onGet(getTaxonomyListApiUrl()).reply(200, taxonomyListMock);
|
||||
const result = await getTaxonomyListData();
|
||||
|
||||
expect(axiosMock.history.get[0].url).toEqual(getTaxonomyListApiUrl());
|
||||
expect(result).toEqual(taxonomyListMock);
|
||||
});
|
||||
|
||||
it('should get taxonomy list data with org', async () => {
|
||||
const org = 'testOrg';
|
||||
axiosMock.onGet(getTaxonomyListApiUrl(org)).reply(200, taxonomyListMock);
|
||||
@@ -68,7 +61,7 @@ describe('taxonomy api calls', () => {
|
||||
expect(axiosMock.history.delete[0].url).toEqual(getTaxonomyApiUrl());
|
||||
});
|
||||
|
||||
it('should set window.location.href correctly', () => {
|
||||
it('Export should set window.location.href correctly', () => {
|
||||
const pk = 1;
|
||||
const format = 'json';
|
||||
|
||||
|
||||
@@ -9,6 +9,18 @@ const messages = defineMessages({
|
||||
id: 'course-authoring.taxonomy-list.button.download-template.label',
|
||||
defaultMessage: 'Download template',
|
||||
},
|
||||
downloadTemplateButtonCSVLabel: {
|
||||
id: 'course-authoring.taxonomy-list.button.download-template.csv.label',
|
||||
defaultMessage: 'CSV template',
|
||||
},
|
||||
downloadTemplateButtonJSONLabel: {
|
||||
id: 'course-authoring.taxonomy-list.button.download-template.json.label',
|
||||
defaultMessage: 'JSON template',
|
||||
},
|
||||
downloadTemplateButtonHint: {
|
||||
id: 'course-authoring.taxonomy-list.butotn.download-template.hint',
|
||||
defaultMessage: 'Download example taxonomy',
|
||||
},
|
||||
importButtonLabel: {
|
||||
id: 'course-authoring.taxonomy-list.button.import.label',
|
||||
defaultMessage: 'Import',
|
||||
|
||||
Reference in New Issue
Block a user