Skip to content

Commit

Permalink
feat: Connect with export API
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisChV committed Oct 19, 2023
1 parent 5698718 commit 2070743
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 16 deletions.
43 changes: 38 additions & 5 deletions src/taxonomy/api/hooks/api.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,53 @@
// @ts-check
import { useQuery } from '@tanstack/react-query';
import { useQuery, useMutation } from '@tanstack/react-query';
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { downloadDataAsFile } from '../../../utils';

const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL;
const getTaxonomyListApiUrl = new URL('api/content_tagging/v1/taxonomies/?enabled=true', getApiBaseUrl()).href;
const getTaxonomyListApiUrl = () => new URL('api/content_tagging/v1/taxonomies/?enabled=true', getApiBaseUrl()).href;
const getExportTaxonomyApiUrl = (pk, format) => new URL(
`api/content_tagging/v1/taxonomies/${pk}/export/?output_format=${format}`,
getApiBaseUrl(),
).href;

/**
* @returns {import("../types.mjs").UseQueryResult}
*/
const useTaxonomyListData = () => (
export const useTaxonomyListData = () => (
useQuery({
queryKey: ['taxonomyList'],
queryFn: () => getAuthenticatedHttpClient().get(getTaxonomyListApiUrl)
queryFn: () => getAuthenticatedHttpClient().get(getTaxonomyListApiUrl())

Check warning on line 20 in src/taxonomy/api/hooks/api.js

View check run for this annotation

Codecov / codecov/patch

src/taxonomy/api/hooks/api.js#L20

Added line #L20 was not covered by tests
.then(camelCaseObject),
})
);

export default useTaxonomyListData;
export const useExportTaxonomy = () => {
/**
* Calls the export request and downloads the file.
*
* Extra logic is needed to download the exported file,
* because it is not possible to download the file using the Content-Disposition header
* Ref: https://medium.com/@drevets/you-cant-prompt-a-file-download-with-the-content-disposition-header-using-axios-xhr-sorry-56577aa706d6
*
* @param {import("../types.mjs").ExportRequestParams} params
* @returns {Promise<void>}
*/
const exportTaxonomy = async (params) => {
const { pk, format, name } = params;
const response = await getAuthenticatedHttpClient().get(getExportTaxonomyApiUrl(pk, format));
const contentType = response.headers['content-type'];
let fileExtension = '';

Check warning on line 40 in src/taxonomy/api/hooks/api.js

View check run for this annotation

Codecov / codecov/patch

src/taxonomy/api/hooks/api.js#L36-L40

Added lines #L36 - L40 were not covered by tests
let data;
if (contentType === 'application/json') {
fileExtension = 'json';
data = JSON.stringify(response.data, null, 2);
} else {
fileExtension = 'csv';
data = response.data;

Check warning on line 47 in src/taxonomy/api/hooks/api.js

View check run for this annotation

Codecov / codecov/patch

src/taxonomy/api/hooks/api.js#L43-L47

Added lines #L43 - L47 were not covered by tests
}
downloadDataAsFile(data, contentType, `${name}.${fileExtension}`);

Check warning on line 49 in src/taxonomy/api/hooks/api.js

View check run for this annotation

Codecov / codecov/patch

src/taxonomy/api/hooks/api.js#L49

Added line #L49 was not covered by tests
};

return useMutation(exportTaxonomy);

Check warning on line 52 in src/taxonomy/api/hooks/api.js

View check run for this annotation

Codecov / codecov/patch

src/taxonomy/api/hooks/api.js#L52

Added line #L52 was not covered by tests
};
2 changes: 1 addition & 1 deletion src/taxonomy/api/hooks/api.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useQuery } from '@tanstack/react-query';
import useTaxonomyListData from './api';
import { useTaxonomyListData } from './api';

jest.mock('@tanstack/react-query', () => ({
useQuery: jest.fn(),
Expand Down
9 changes: 8 additions & 1 deletion src/taxonomy/api/hooks/selectors.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// @ts-check
import useTaxonomyListData from './api';
import {
useTaxonomyListData,
useExportTaxonomy,
} from './api';

/**
* @returns {import("../types.mjs").TaxonomyListData | undefined}
Expand All @@ -18,3 +21,7 @@ export const useTaxonomyListDataResponse = () => {
export const useIsTaxonomyListDataLoaded = () => (
useTaxonomyListData().status === 'success'
);

export const useExportTaxonomyMutation = () => (
useExportTaxonomy()

Check warning on line 26 in src/taxonomy/api/hooks/selectors.js

View check run for this annotation

Codecov / codecov/patch

src/taxonomy/api/hooks/selectors.js#L26

Added line #L26 was not covered by tests
);
4 changes: 2 additions & 2 deletions src/taxonomy/api/hooks/selectors.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useTaxonomyListDataResponse, useIsTaxonomyListDataLoaded } from './selectors';
import useTaxonomyListData from './api';
import { useTaxonomyListData } from './api';

jest.mock('./api', () => ({
__esModule: true,
default: jest.fn(),
useTaxonomyListData: jest.fn(),
}));

describe('useTaxonomyListDataResponse', () => {
Expand Down
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
22 changes: 18 additions & 4 deletions src/taxonomy/modals/ExportModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,30 @@ import {
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from '../messages';
import { useExportTaxonomyMutation } from '../api/hooks/selectors';

const ExportModal = ({
taxonomyId,
taxonomyName,
isOpen,
onClose,
intl,
}) => {
const [modalSize, setModalSize] = useState('csv');
const [outputFormat, setOutputFormat] = useState('csv');
const exportMutation = useExportTaxonomyMutation();

const onClickExport = () => {
onClose();
exportMutation.mutate({

Check warning on line 25 in src/taxonomy/modals/ExportModal.jsx

View check run for this annotation

Codecov / codecov/patch

src/taxonomy/modals/ExportModal.jsx#L24-L25

Added lines #L24 - L25 were not covered by tests
pk: taxonomyId,
format: outputFormat,
name: taxonomyName,
});
};

return (
<ModalDialog
title={intl.formatMessage(messages.exportModalTitle)}
isOpen={isOpen}
onClose={onClose}
size="lg"
Expand All @@ -37,8 +50,8 @@ const ExportModal = ({
</Form.Label>
<Form.RadioSet
name="export-format"
value={modalSize}
onChange={(e) => setModalSize(e.target.value)}
value={outputFormat}
onChange={(e) => setOutputFormat(e.target.value)}

Check warning on line 54 in src/taxonomy/modals/ExportModal.jsx

View check run for this annotation

Codecov / codecov/patch

src/taxonomy/modals/ExportModal.jsx#L54

Added line #L54 was not covered by tests
>
<Form.Radio
key={`export-csv-format-${taxonomyId}`}
Expand All @@ -60,7 +73,7 @@ const ExportModal = ({
<ModalDialog.CloseButton variant="tertiary">
{intl.formatMessage(messages.taxonomyModalsCancelLabel)}
</ModalDialog.CloseButton>
<Button variant="primary">
<Button variant="primary" onClick={onClickExport}>
{intl.formatMessage(messages.exportModalSubmitButtonLabel)}
</Button>
</ActionRow>
Expand All @@ -71,6 +84,7 @@ const ExportModal = ({

ExportModal.propTypes = {
taxonomyId: PropTypes.number.isRequired,
taxonomyName: PropTypes.string.isRequired,
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
intl: intlShape.isRequired,
Expand Down
23 changes: 20 additions & 3 deletions src/taxonomy/taxonomy-card/TaxonomyCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,24 @@ const TaxonomyCard = ({ className, original, intl }) => {
return undefined;
};

const getHeaderActions = () => (
<TaxonomyCardMenu id={id} name={name} onClickMenuItem={onClickMenuItem} />
);
const getHeaderActions = () => {
if (systemDefined) {
// We don't show the export menu, because the system-taxonomies
// can't be exported. The API returns and error.
// The entire menu has been hidden because currently only
// the export menu exists.
//
// TODO When adding more menus, change this logic to hide only the export menu.
return undefined;
}
return (
<TaxonomyCardMenu
id={id}
name={name}
onClickMenuItem={onClickMenuItem}
/>
);
};

const renderModals = () => (
// eslint-disable-next-line react/jsx-no-useless-fragment
Expand All @@ -77,6 +92,8 @@ const TaxonomyCard = ({ className, original, intl }) => {
<ExportModal
isOpen={isExportModalOpen}
onClose={() => setIsExportModalOpen(false)}
taxonomyId={id}
taxonomyName={name}
/>
)}
</>
Expand Down
4 changes: 4 additions & 0 deletions src/taxonomy/taxonomy-card/TaxonomyCard.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import initializeStore from '../../store';

import TaxonomyCard from './TaxonomyCard';

jest.mock('../api/hooks/selectors', () => ({
useExportTaxonomyMutation: jest.fn(),
}));

let store;

const data = {
Expand Down
9 changes: 9 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,12 @@ export const isValidDate = (date) => {

return Boolean(formattedValue.length <= 10);
};

export const downloadDataAsFile = (data, contentType, fileName) => {
const url = window.URL.createObjectURL(new Blob([data], { type: contentType }));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();

Check warning on line 266 in src/utils.js

View check run for this annotation

Codecov / codecov/patch

src/utils.js#L261-L266

Added lines #L261 - L266 were not covered by tests
};

0 comments on commit 2070743

Please sign in to comment.