From c723c600ae56894aaca5d7258ebabba9b56ad534 Mon Sep 17 00:00:00 2001 From: alb3rtino Date: Fri, 11 Aug 2023 13:04:37 +0200 Subject: [PATCH] Create a HarvesterInfoModal component that unifies error and success texts Refactor StartHarvester.js and UDP.js to use the HarvesterInfoModal component Use okapiKy to call /start endpoint on harvester --- .../HarvesterInfoModal/HarvesterInfoModal.js | 69 +++++++++ .../HarvesterInfoModal.test.js | 69 +++++++++ src/components/HarvesterInfoModal/index.js | 1 + .../StartHarvesterModal.js | 115 --------------- src/components/StartHarvesterModal/index.js | 1 - src/components/views/UDP.js | 47 +++--- src/settings/StartHarvester/StartHarvester.js | 137 +++++------------- translations/ui-erm-usage/en.json | 5 +- 8 files changed, 211 insertions(+), 233 deletions(-) create mode 100644 src/components/HarvesterInfoModal/HarvesterInfoModal.js create mode 100644 src/components/HarvesterInfoModal/HarvesterInfoModal.test.js create mode 100644 src/components/HarvesterInfoModal/index.js delete mode 100644 src/components/StartHarvesterModal/StartHarvesterModal.js delete mode 100644 src/components/StartHarvesterModal/index.js diff --git a/src/components/HarvesterInfoModal/HarvesterInfoModal.js b/src/components/HarvesterInfoModal/HarvesterInfoModal.js new file mode 100644 index 00000000..0d136f14 --- /dev/null +++ b/src/components/HarvesterInfoModal/HarvesterInfoModal.js @@ -0,0 +1,69 @@ +import PropTypes from 'prop-types'; +import { Button, Modal } from '@folio/stripes-components'; +import { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router-dom'; +import urls from '../../util/urls'; + +const createSuccessText = (udpLabel) => { + const additionalValues = udpLabel ? { provider: true, name: udpLabel } : { provider: false }; + return ( + + + + ), + ...additionalValues, + }} + /> + ); +}; + +const createErrorText = (udpLabel) => { + const values = udpLabel ? { provider: true, name: udpLabel } : { provider: false }; + return ; +}; + +const createLabelText = (isSuccess) => { + const msgId = isSuccess + ? 'ui-erm-usage.harvester.start.started' + : 'ui-erm-usage.harvester.start.failed'; + return ; +}; + +const HarvesterInfoModal = ({ errMessage = null, isSuccess = false, onClose, open = false, udpLabel }) => ( + + + + } + > + {isSuccess ? ( + createSuccessText(udpLabel) + ) : ( + <> + {createErrorText(udpLabel)} +
+ {errMessage} + + )} +
+); + +HarvesterInfoModal.propTypes = { + errMessage: PropTypes.string, + isSuccess: PropTypes.bool, + onClose: PropTypes.func, + open: PropTypes.bool, + udpLabel: PropTypes.string, +}; + +export default HarvesterInfoModal; diff --git a/src/components/HarvesterInfoModal/HarvesterInfoModal.test.js b/src/components/HarvesterInfoModal/HarvesterInfoModal.test.js new file mode 100644 index 00000000..7c0ca11e --- /dev/null +++ b/src/components/HarvesterInfoModal/HarvesterInfoModal.test.js @@ -0,0 +1,69 @@ +import { MemoryRouter } from 'react-router-dom'; +import renderWithIntl from '../../../test/jest/helpers/renderWithIntl'; +import HarvesterInfoModal from './HarvesterInfoModal'; + +const render = (props) => { + return renderWithIntl( + + + + ); +}; + +describe('HarvesterInfoModal', () => { + test('default values', () => { + const { container } = render(); + expect(container.innerHTML).toBe('
'); + }); + + test('open == false', () => { + const { container } = render({ open: false }); + expect(container.innerHTML).toBe('
'); + }); + + test('isSuccess==true, udpLabel', () => { + const { getByText, getByRole } = render({ + open: true, + isSuccess: true, + udpLabel: 'Provider123', + }); + expect(getByText('Harvester started')).toBeInTheDocument(); + expect(getByText(/^A harvesting job for 'Provider123' has been/)).toBeInTheDocument(); + expect(getByText('Harvesting jobs')).toHaveAttribute('href', '/eusage/jobs?sort=-startedAt'); + expect(getByRole('button', { name: 'OK' })).toBeInTheDocument(); + }); + + test('isSuccess==true, no udpLabel', () => { + const { getByText, getByRole } = render({ + open: true, + isSuccess: true, + }); + expect(getByText('Harvester started')).toBeInTheDocument(); + expect(getByText(/^A harvesting job has been/)).toBeInTheDocument(); + expect(getByText('Harvesting jobs')).toHaveAttribute('href', '/eusage/jobs?sort=-startedAt'); + expect(getByRole('button', { name: 'OK' })).toBeInTheDocument(); + }); + + test('isSuccess==false, udpLabel', () => { + const { getByRole, queryByText } = render({ + open: true, + isSuccess: false, + udpLabel: 'Provider123', + }); + expect(queryByText('Harvester failed to start')).toBeInTheDocument(); + expect(queryByText(/^Failed to schedule .* for 'Provider123'/)).toBeInTheDocument(); + expect(queryByText('Harvesting jobs')).toBeNull(); + expect(getByRole('button', { name: 'OK' })).toBeInTheDocument(); + }); + + test('isSuccess==false, no udpLabel', () => { + const { getByRole, queryByText } = render({ + open: true, + isSuccess: false, + }); + expect(queryByText('Harvester failed to start')).toBeInTheDocument(); + expect(queryByText(/^Failed to schedule a harvesting job.$/)).toBeInTheDocument(); + expect(queryByText('Harvesting jobs')).toBeNull(); + expect(getByRole('button', { name: 'OK' })).toBeInTheDocument(); + }); +}); diff --git a/src/components/HarvesterInfoModal/index.js b/src/components/HarvesterInfoModal/index.js new file mode 100644 index 00000000..ceb0606f --- /dev/null +++ b/src/components/HarvesterInfoModal/index.js @@ -0,0 +1 @@ +export { default } from './HarvesterInfoModal'; diff --git a/src/components/StartHarvesterModal/StartHarvesterModal.js b/src/components/StartHarvesterModal/StartHarvesterModal.js deleted file mode 100644 index 16318a3f..00000000 --- a/src/components/StartHarvesterModal/StartHarvesterModal.js +++ /dev/null @@ -1,115 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { get } from 'lodash'; -import { stripesConnect } from '@folio/stripes/core'; -import { injectIntl, FormattedMessage } from 'react-intl'; -import { Button, InfoPopover, Modal } from '@folio/stripes/components'; -import { Link } from 'react-router-dom'; -import urls from '../../util/urls'; - -class StartHarvesterModal extends React.Component { - static manifest = Object.freeze({ - harvesterStart: { - type: 'okapi', - fetch: false, - accumulate: 'true', - GET: { - path: 'erm-usage-harvester/start/!{usageDataProvider.id}', - }, - }, - }); - - static propTypes = { - intl: PropTypes.object, - isHarvesterExistent: PropTypes.bool, - onReloadUDP: PropTypes.func.isRequired, - mutator: PropTypes.shape({ - harvesterStart: PropTypes.object, - }).isRequired, - usageDataProvider: PropTypes.object.isRequired, - onClose: PropTypes.func, - }; - - constructor(props) { - super(props); - - const { usageDataProvider } = props; - this.state = { - modalText: '', - }; - - this.props.mutator.harvesterStart - .GET() - .then(() => { - this.setState({ - modalText: this.createSuccessText(usageDataProvider), - }); - this.props.onReloadUDP(); - }) - .catch((err) => { - const infoText = this.createFailText(usageDataProvider) + ' ' + err.message; - this.setState({ - modalText: infoText, - }); - }); - } - - isInActive = (udp) => { - const status = get(udp, 'harvestingConfig.harvestingStatus', 'inactive'); - return !this.props.isHarvesterExistent || status === 'inactive'; - }; - - createSuccessText = (udp) => { - return - - - ), - provider: true, - name: udp.label - }} - />; - }; - - createFailText = (udp) => { - return `${this.props.intl.formatMessage({ - id: 'ui-erm-usage.harvester.start.fail.single.udp', - })} ${udp.label}...`; - }; - - renderInfoPopover = (udp) => { - if (this.isInActive(udp)) { - return ( - - } - /> - ); - } else { - return null; - } - }; - - render() { - const { usageDataProvider } = this.props; - return ( - <> - {this.renderInfoPopover(usageDataProvider)} - } - open - > -
{this.state.modalText}
- -
- - ); - } -} - -export default stripesConnect(injectIntl(StartHarvesterModal)); diff --git a/src/components/StartHarvesterModal/index.js b/src/components/StartHarvesterModal/index.js deleted file mode 100644 index 0ed6bdbe..00000000 --- a/src/components/StartHarvesterModal/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './StartHarvesterModal'; diff --git a/src/components/views/UDP.js b/src/components/views/UDP.js index 62b49842..60260f95 100644 --- a/src/components/views/UDP.js +++ b/src/components/views/UDP.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { get, isEmpty } from 'lodash'; import { FormattedMessage, injectIntl } from 'react-intl'; -import { IfPermission, TitleManager, Pluggable } from '@folio/stripes/core'; +import { IfPermission, TitleManager, Pluggable, withOkapiKy } from '@folio/stripes/core'; import { Accordion, AccordionSet, @@ -32,7 +32,6 @@ import HelperApp from '../HelperApp'; import { UDPInfoView } from '../UDPInfo'; import { HarvestingConfigurationView } from '../HarvestingConfiguration'; -import StartHarvesterModal from '../StartHarvesterModal'; import CounterUpload from '../ReportUpload/CounterUpload'; import NonCounterUpload from '../ReportUpload/NonCounterUpload'; import CounterStatistics from '../Counter'; @@ -43,6 +42,7 @@ import urls from '../../util/urls'; import groupReportsPerYear from '../../util/groupReportsPerYear'; import createStandardReportFormatter from '../Counter/StandardReportFormatter'; import UDPHeader from '../UDPHeader/UDPHeader'; +import HarvesterInfoModal from '../HarvesterInfoModal/HarvesterInfoModal'; let callout; @@ -54,7 +54,7 @@ class UDP extends React.Component { this.state = { helperApp: null, showDeleteReports: false, - showStartHarvesterModal: false, + harvesterModalState: {}, showCounterUpload: false, showNonCounterUpload: false, }; @@ -169,12 +169,27 @@ class UDP extends React.Component { return !this.props.isHarvesterExistent || status === 'inactive'; }; - openStartHarvesterModal = () => { - this.setState({ showStartHarvesterModal: true }); + openStartHarvesterModal = (udpId) => { + this.props + .okapiKy(`erm-usage-harvester/start/${udpId}`, { + method: 'GET', + retry: 0, + }) + .then(() => { + this.setState({ harvesterModalState: { open: true, isSuccess: true } }); + this.reloadUdp(); + }) + .catch((error) => { + Promise.resolve(error.response?.text?.() ?? error.message) + .catch(() => error.message) + .then((errMessage) => this.setState({ + harvesterModalState: { open: true, isSuccess: false, errMessage } + })); + }); }; closeStartHarvesterModal = () => { - this.setState({ showStartHarvesterModal: false }); + this.setState({ harvesterModalState: { open: false } }); }; getActionMenu = () => ({ onToggle }) => { @@ -193,7 +208,7 @@ class UDP extends React.Component { marginBottom0 disabled={this.isInActive(usageDataProvider)} onClick={() => { - this.openStartHarvesterModal(); + this.openStartHarvesterModal(usageDataProvider.id); onToggle(); }} > @@ -202,14 +217,11 @@ class UDP extends React.Component { - {this.state.showStartHarvesterModal && ( - - )} +
@@ -586,7 +598,8 @@ UDP.propTypes = { location: PropTypes.shape({ pathname: PropTypes.string, search: PropTypes.string - }).isRequired + }).isRequired, + okapiKy: PropTypes.func.isRequired, }; -export default injectIntl(UDP); +export default injectIntl(withOkapiKy(UDP)); diff --git a/src/settings/StartHarvester/StartHarvester.js b/src/settings/StartHarvester/StartHarvester.js index f2c8c6d0..3f7b5c11 100644 --- a/src/settings/StartHarvester/StartHarvester.js +++ b/src/settings/StartHarvester/StartHarvester.js @@ -1,107 +1,50 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; -import { Button, Modal, Pane } from '@folio/stripes/components'; -import { Link } from 'react-router-dom'; -import urls from '../../util/urls'; - -export default class StartHarvester extends React.Component { - static manifest = Object.freeze({ - harvesterStart: { - type: 'okapi', - fetch: false, - accumulate: 'true', - GET: { - path: 'erm-usage-harvester/start', - }, - }, +import { Button, Pane } from '@folio/stripes/components'; +import { useOkapiKy } from '@folio/stripes/core'; +import HarvesterInfoModal from '../../components/HarvesterInfoModal/HarvesterInfoModal'; + +const StartHarvester = () => { + const ky = useOkapiKy(); + const [modalState, setModalState] = useState({ + open: false, }); - static propTypes = { - mutator: PropTypes.shape({ - harvesterStart: PropTypes.object, - }), - }; - - constructor(props) { - super(props); - - this.state = { - showInfoModal: false, - modalText: '', - }; - - this.successText = ( - - - - ), - provider: false, - }} - /> - ); - this.failText = ( - - ); - } - - onClickStartHarvester = () => { - this.props.mutator.harvesterStart - .GET() + const onClickStartHarvester = () => { + ky('erm-usage-harvester/start', { + method: 'GET', + retry: 0, + }) .then(() => { - this.setState({ - showInfoModal: true, - modalText: this.successText, - }); + setModalState({ open: true, isSuccess: true }); }) - .catch((err) => { - const infoText = this.failText + ' ' + err.message; - this.setState({ - showInfoModal: true, - modalText: infoText, - }); + .catch((error) => { + Promise.resolve(error.response?.text?.() ?? error.message) + .catch(() => error.message) + .then((errMessage) => setModalState({ open: true, isSuccess: false, errMessage })); }); }; - handleClose = () => { - this.setState({ showInfoModal: false }); + const handleClose = () => { + setModalState({ open: false }); }; - render() { - const startHarvesterButton = ( - - ); - - return ( - } - > -
- { - - } - {startHarvesterButton} -
- } - > -
{this.state.modalText}
- -
-
- ); - } -} + return ( + } + > +
+ + +
+ +
+ ); +}; + +export default StartHarvester; diff --git a/translations/ui-erm-usage/en.json b/translations/ui-erm-usage/en.json index a73c4bb0..a074f9c1 100644 --- a/translations/ui-erm-usage/en.json +++ b/translations/ui-erm-usage/en.json @@ -253,10 +253,10 @@ "errors.urlRequired": "Invalid URL: http:// or https:// required!", "harvester.start": "Start harvester", + "harvester.start.failed": "Harvester failed to start", + "harvester.start.failure": "Failed to schedule a harvesting job{provider, select, true { for ''{name}''} other {}}.", "harvester.start.started": "Harvester started", "harvester.start.success": "A harvesting job {provider, select, true {for ''{name}''} other {}} has been successfully scheduled for immediate execution. Harvesting jobs usually take some time to complete. You can monitor the progress by checking the {link} page.", - "harvester.start.fail.single.udp": "Something went wrong while starting the harvester for usagedata provider", - "harvester.start.inactiveInfo": "The harvester can only be started for usage data providers with harvesting status 'active'. Please check your harvesting configuration.", "harvester.jobs.show": "Show harvester logs", "harvester.jobs.paneTitle": "Harvesting jobs", "harvester.jobs.filter.type": "Job types", @@ -280,7 +280,6 @@ "settings.harvester": "Harvester", "settings.section.display.settings": "Display settings", "settings.section.number.failed": "Number of failed attempts", - "settings.harvester.start.fail": "Something went wrong while starting the harvester...", "settings.harvester.start.tenant": "Start the harvester for the current tenant: ", "settings.harvester.config": "Harvester configuration",