diff --git a/src/taxonomy/TaxonomyListPage.jsx b/src/taxonomy/TaxonomyListPage.jsx index 5d73ef6644..bce0a4f2da 100644 --- a/src/taxonomy/TaxonomyListPage.jsx +++ b/src/taxonomy/TaxonomyListPage.jsx @@ -1,18 +1,38 @@ import React from 'react'; import { + Button, CardView, Container, DataTable, Spinner, } from '@edx/paragon'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { + Add, +} from '@edx/paragon/icons'; +import { injectIntl, intlShape, useIntl } from '@edx/frontend-platform/i18n'; import { StudioFooter } from '@edx/frontend-component-footer'; + import Header from '../header'; import SubHeader from '../generic/sub-header/SubHeader'; +import { actions as importActions } from './import-tags'; import messages from './messages'; import TaxonomyCard from './TaxonomyCard'; import { useTaxonomyListDataResponse, useIsTaxonomyListDataLoaded } from './api/hooks/selectors'; +const TaxonomyListHeaderButtons = () => { + const intl = useIntl(); + return ( + <> + + + + ); +}; + const TaxonomyListPage = ({ intl }) => { const useTaxonomyListData = () => { const taxonomyListData = useTaxonomyListDataResponse(); @@ -22,12 +42,6 @@ const TaxonomyListPage = ({ intl }) => { 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 @@ -49,7 +63,7 @@ const TaxonomyListPage = ({ intl }) => { } hideBorder /> diff --git a/src/taxonomy/import-tags/data/actions.js b/src/taxonomy/import-tags/data/actions.js new file mode 100644 index 0000000000..b905138ad7 --- /dev/null +++ b/src/taxonomy/import-tags/data/actions.js @@ -0,0 +1,85 @@ +import messages from '../messages'; +import { importNewTaxonomy } from './api'; + +const importTaxonomy = async (intl) => { + /* + * This function is a temporary "Barebones" implementation of the import + * functionality with `prompt` and `alert`. It is intended to be replaced + * with a component that shows a `ModalDialog` in the future. + * See: https://github.com/openedx/modular-learning/issues/116 + */ + /* eslint-disable no-alert */ + /* eslint-disable no-console */ + + const selectFile = async () => new Promise((resolve) => { + /* + * This function get a file from the user. It does this by creating a + * file input element, and then clicking it. This allows us to get a file + * from the user without using a form. The file input element is created + * and appended to the DOM, then clicked. When the user selects a file, + * the change event is fired, and the file is resolved. + * The file input element is then removed from the DOM. + */ + const fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = '.json,.csv'; + fileInput.addEventListener('change', (event) => { + const file = event.target.files[0]; + if (!file) { + resolve(null); + } + resolve(file); + document.body.removeChild(fileInput); + }); + + document.body.appendChild(fileInput); + fileInput.click(); + }); + + const getTaxonomyName = () => { + let taxonomyName = null; + while (!taxonomyName) { + taxonomyName = prompt(intl.formatMessage(messages.promptTaxonomyName)); + + if (taxonomyName == null) { + break; + } + + if (!taxonomyName) { + alert(intl.formatMessage(messages.promptTaxonomyNameRequired)); + } + } + return taxonomyName; + }; + + const getTaxonomyDescription = () => prompt(intl.formatMessage(messages.promptTaxonomyDescription)); + + const file = await selectFile(); + + if (!file) { + return; + } + + const taxonomyName = getTaxonomyName(); + if (taxonomyName == null) { + return; + } + + const taxonomyDescription = getTaxonomyDescription(); + if (taxonomyDescription == null) { + return; + } + + importNewTaxonomy(taxonomyName, taxonomyDescription, file) + .then(() => { + alert(intl.formatMessage(messages.importTaxonomySuccess)); + }) + .catch((error) => { + alert(intl.formatMessage(messages.importTaxonomyError)); + console.error(error.response); + }); +}; + +export default { + importTaxonomy, +}; diff --git a/src/taxonomy/import-tags/data/api.js b/src/taxonomy/import-tags/data/api.js new file mode 100644 index 0000000000..2ac634287a --- /dev/null +++ b/src/taxonomy/import-tags/data/api.js @@ -0,0 +1,28 @@ +// @ts-check +import { camelCaseObject, getConfig } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; + +const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL; + +const getTaxonomyImportApiUrl = () => new URL( + 'api/content_tagging/v1/taxonomies/import/', + getApiBaseUrl(), +).href; + +/** + * Import a new taxonomy + * @param {string} taxonomyName + * @param {string} taxonomyDescription + * @param {File} file + * @returns {Promise} + */ // eslint-disable-next-line import/prefer-default-export +export async function importNewTaxonomy(taxonomyName, taxonomyDescription, file) { + const formData = new FormData(); + formData.append('taxonomy_name', taxonomyName); + formData.append('taxonomy_description', taxonomyDescription); + formData.append('file', file); + + const { data } = await getAuthenticatedHttpClient().post(getTaxonomyImportApiUrl(), formData); + + return camelCaseObject(data); +} diff --git a/src/taxonomy/import-tags/index.js b/src/taxonomy/import-tags/index.js new file mode 100644 index 0000000000..831d75323e --- /dev/null +++ b/src/taxonomy/import-tags/index.js @@ -0,0 +1,5 @@ +import actions from './data/actions'; + +export { + actions, // eslint-disable-line import/prefer-default-export +}; diff --git a/src/taxonomy/import-tags/messages.js b/src/taxonomy/import-tags/messages.js new file mode 100644 index 0000000000..f9a1ff273b --- /dev/null +++ b/src/taxonomy/import-tags/messages.js @@ -0,0 +1,26 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + promptTaxonomyName: { + id: 'course-authoring.import-tags.prompt.taxonomy-name', + defaultMessage: 'Enter a name for the new taxonomy', + }, + promptTaxonomyNameRequired: { + id: 'course-authoring.import-tags.prompt.taxonomy-name.required', + defaultMessage: 'You must enter a name for the new taxonomy', + }, + promptTaxonomyDescription: { + id: 'course-authoring.import-tags.prompt.taxonomy-description', + defaultMessage: 'Enter a description for the new taxonomy', + }, + importTaxonomySuccess: { + id: 'course-authoring.import-tags.success', + defaultMessage: 'Taxonomy imported successfully', + }, + importTaxonomyError: { + id: 'course-authoring.import-tags.error', + defaultMessage: 'Import failed - see details in the browser console', + }, +}); + +export default messages;