From 9757c565b7f0ce8f6b5dbbc72c99be3da2e3f291 Mon Sep 17 00:00:00 2001 From: vashjs Date: Wed, 18 Dec 2024 10:19:37 +0100 Subject: [PATCH] UIBULKED-568 Populating Are you sure? form --- CHANGELOG.md | 1 + .../BulkEditLogsActions.js | 4 +- .../BulkEditInAppLayer/BulkEditInAppLayer.js | 45 +++---- .../BulkEditInAppPreviewModal.test.js | 48 ++----- .../BulkEditPreviewModal.js | 70 +---------- .../BulkEditPreviewModalFooter.js | 68 +++++++--- .../BulkEditPreviewModalFooter.test.js | 118 ++++++++++++++++++ .../BulkEditMarcLayer/BulkEditMarcLayer.js | 48 +++---- src/components/BulkEditPane/BulkEditPane.js | 4 +- src/constants/files.js | 6 + src/hooks/api/useFileDownload.js | 10 +- src/hooks/useCommitChanges.js | 46 +++++++ src/hooks/useCommitChanges.test.js | 117 +++++++++++++++++ src/hooks/useConfirmChanges.js | 22 +--- src/hooks/useConfirmChanges.test.js | 18 +-- src/utils/files.js | 11 +- src/utils/files.test.js | 14 +-- translations/ui-bulk-edit/en.json | 2 +- 18 files changed, 418 insertions(+), 234 deletions(-) create mode 100644 src/components/BulkEditPane/BulkEditListResult/BulkEditInAppPreviewModal/BulkEditPreviewModalFooter.test.js create mode 100644 src/hooks/useCommitChanges.js create mode 100644 src/hooks/useCommitChanges.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index c0ab2c8f..741082ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * [UIBULKED-585](https://folio-org.atlassian.net/browse/UUIBULKED-585) Adding missing translation and filters. * [UIBULKED-561](https://folio-org.atlassian.net/browse/UUIBULKED-561) Add administrative data accordion to MARC bulk edit form. * [UIBULKED-562](https://folio-org.atlassian.net/browse/UIBULKED-562) Include statistical code option on Instances bulk edit forms. +* [UIBULKED-568](https://folio-org.atlassian.net/browse/UIBULKED-568) Populating Are you sure? form ## [4.2.2](https://github.com/folio-org/ui-bulk-edit/tree/v4.2.2) (2024-11-15) diff --git a/src/components/BulkEditLogs/BulkEditLogsActions/BulkEditLogsActions.js b/src/components/BulkEditLogs/BulkEditLogsActions/BulkEditLogsActions.js index 2b4f8c38..3da8acff 100644 --- a/src/components/BulkEditLogs/BulkEditLogsActions/BulkEditLogsActions.js +++ b/src/components/BulkEditLogs/BulkEditLogsActions/BulkEditLogsActions.js @@ -33,9 +33,7 @@ const BulkEditLogsActions = ({ item }) => { queryKey: QUERY_KEY_DOWNLOAD_LOGS, enabled: false, id: item.id, - fileInfo: { - fileContentType: linkNamesMap[triggeredFile], - }, + fileContentType: linkNamesMap[triggeredFile], onSuccess: data => { saveAs(new Blob([data]), getFileName(item, triggeredFile)); setTriggeredFile(null); diff --git a/src/components/BulkEditPane/BulkEditInAppLayer/BulkEditInAppLayer.js b/src/components/BulkEditPane/BulkEditInAppLayer/BulkEditInAppLayer.js index cf37c46c..634fae42 100644 --- a/src/components/BulkEditPane/BulkEditInAppLayer/BulkEditInAppLayer.js +++ b/src/components/BulkEditPane/BulkEditInAppLayer/BulkEditInAppLayer.js @@ -9,13 +9,11 @@ import { getMappedContentUpdates, isContentUpdatesFormValid } from '../BulkEditListResult/BulkEditInApp/ContentUpdatesForm/helpers'; -import { - QUERY_KEY_DOWNLOAD_PREVIEW_MODAL, - useContentUpdate, -} from '../../../hooks/api'; +import { useContentUpdate } from '../../../hooks/api'; import { useConfirmChanges } from '../../../hooks/useConfirmChanges'; -import { savePreviewFile } from '../../../utils/files'; import { useOptionsWithTenants } from '../../../hooks/useOptionsWithTenants'; +import { BulkEditPreviewModalFooter } from '../BulkEditListResult/BulkEditInAppPreviewModal/BulkEditPreviewModalFooter'; +import { useCommitChanges } from '../../../hooks/useCommitChanges'; export const BulkEditInAppLayer = ({ @@ -37,28 +35,20 @@ export const BulkEditInAppLayer = ({ isPreviewLoading, bulkDetails, totalRecords, - downloadFile, confirmChanges, closePreviewModal, - } = useConfirmChanges({ - queryDownloadKey: QUERY_KEY_DOWNLOAD_PREVIEW_MODAL, - bulkOperationId, - onDownloadSuccess: (fileData, searchParams) => { - const { approach, initialFileName } = searchParams; + } = useConfirmChanges({ bulkOperationId }); - savePreviewFile({ - bulkOperationId, - fileData, - approach, - initialFileName, - }); - }, + const { commitChanges } = useCommitChanges({ + bulkOperationId, + onChangesCommited: () => { + closePreviewModal(); + onInAppLayerClose(); + } }); - const handleChangesCommited = () => { - closePreviewModal(); - onInAppLayerClose(); - }; + const isCsvFileReady = bulkDetails?.linkToModifiedRecordsCsvFile + || !isPreviewLoading; const handleConfirm = () => { const contentUpdateBody = getContentUpdatesBody({ @@ -93,9 +83,14 @@ export const BulkEditInAppLayer = ({ isPreviewLoading={isPreviewLoading} bulkDetails={bulkDetails} open={isPreviewModalOpened} - onDownload={downloadFile} - onKeepEditing={closePreviewModal} - onChangesCommited={handleChangesCommited} + modalFooter={ + + } /> ); diff --git a/src/components/BulkEditPane/BulkEditListResult/BulkEditInAppPreviewModal/BulkEditInAppPreviewModal.test.js b/src/components/BulkEditPane/BulkEditListResult/BulkEditInAppPreviewModal/BulkEditInAppPreviewModal.test.js index aa149b57..d6b1ddab 100644 --- a/src/components/BulkEditPane/BulkEditListResult/BulkEditInAppPreviewModal/BulkEditInAppPreviewModal.test.js +++ b/src/components/BulkEditPane/BulkEditListResult/BulkEditInAppPreviewModal/BulkEditInAppPreviewModal.test.js @@ -2,10 +2,8 @@ import { QueryClientProvider } from 'react-query'; import { MemoryRouter } from 'react-router'; import { - act, render, screen, - fireEvent } from '@testing-library/react'; import { useOkapiKy } from '@folio/stripes/core'; @@ -16,11 +14,7 @@ import { bulkEditLogsData } from '../../../../../test/jest/__mock__/fakeData'; import { queryClient } from '../../../../../test/jest/utils/queryClient'; import { RootContext } from '../../../../context/RootContext'; -import { - ACTIONS, - OPTIONS, - JOB_STATUSES, -} from '../../../../constants'; +import { JOB_STATUSES } from '../../../../constants'; import { useBulkOperationDetails, useRecordsPreview, @@ -37,14 +31,12 @@ const bulkOperation = bulkEditLogsData[0]; const visibleColumns = []; const setVisibleColumns = jest.fn(); const onKeepEditing = jest.fn(); -const onChangesCommited = jest.fn(); const defaultProps = { open: true, - bulkOperationId: bulkOperation.id.toString(), onKeepEditing, - onChangesCommited, - contentUpdates: undefined, + isPreviewLoading: false, + modalFooter:
Footer
, }; const renderPreviewModal = (props = defaultProps, fileName = 'barcodes.csv') => { @@ -92,28 +84,7 @@ describe('BulkEditInAppPreviewModal', () => { }); }); - it('should call all footer handlers', () => { - renderPreviewModal(); - - fireEvent.click(screen.getByText('ui-bulk-edit.previewModal.keepEditing')); - expect(onKeepEditing).toHaveBeenCalled(); - }); - - it('should call all footer handlers without fileName', () => { - renderPreviewModal(defaultProps, ''); - - fireEvent.click(screen.getByText('ui-bulk-edit.previewModal.downloadPreview')); - }); - it('should display preview records when available', async () => { - const contentUpdates = [ - { - option: OPTIONS.STATUS, - actions: [{ - type: ACTIONS.CLEAR_FIELD, - }], - }, - ]; const uuidColumn = { value: 'uuid', label: 'uuid', @@ -129,12 +100,7 @@ describe('BulkEditInAppPreviewModal', () => { status: JOB_STATUSES.REVIEW_CHANGES, }); - await act(async () => { - renderPreviewModal({ - ...defaultProps, - contentUpdates, - }); - }); + await renderPreviewModal(); expect(screen.getByText('ui-bulk-edit.previewModal.previewToBeChanged')).toBeInTheDocument(); @@ -142,4 +108,10 @@ describe('BulkEditInAppPreviewModal', () => { rootNode: document.body, }); }); + + it('should display footer if provided', async () => { + await renderPreviewModal(); + + expect(screen.getByText('Footer')).toBeInTheDocument(); + }); }); diff --git a/src/components/BulkEditPane/BulkEditListResult/BulkEditInAppPreviewModal/BulkEditPreviewModal.js b/src/components/BulkEditPane/BulkEditListResult/BulkEditInAppPreviewModal/BulkEditPreviewModal.js index d5023f6a..cf0f8791 100644 --- a/src/components/BulkEditPane/BulkEditListResult/BulkEditInAppPreviewModal/BulkEditPreviewModal.js +++ b/src/components/BulkEditPane/BulkEditListResult/BulkEditInAppPreviewModal/BulkEditPreviewModal.js @@ -1,88 +1,26 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; -import { useHistory } from 'react-router-dom'; -import { useQueryClient } from 'react-query'; import PropTypes from 'prop-types'; import { Modal } from '@folio/stripes/components'; -import { buildSearch } from '@folio/stripes-acq-components'; import { Preloader } from '@folio/stripes-data-transfer-components'; -import { - APPROACHES, - EDITING_STEPS, - FILE_KEYS, - JOB_STATUSES, -} from '../../../../constants'; -import { BULK_OPERATION_DETAILS_KEY, useBulkOperationStart } from '../../../../hooks/api'; -import { BulkEditPreviewModalFooter } from './BulkEditPreviewModalFooter'; -import { useSearchParams } from '../../../../hooks'; import { BulkEditPreviewModalList } from './BulkEditPreviewModalList'; -import { useErrorMessages } from '../../../../hooks/useErrorMessages'; export const BulkEditPreviewModal = ({ open, - bulkDetails, isPreviewLoading, + modalFooter, onKeepEditing, - onDownload, - onChangesCommited, }) => { - const history = useHistory(); - const { criteria, approach } = useSearchParams(); - const { showErrorMessage } = useErrorMessages(); - const { bulkOperationStart } = useBulkOperationStart(); - const queryClient = useQueryClient(); - - const hasLinkForDownload = bulkDetails?.[FILE_KEYS.PROPOSED_CHANGES_LINK_MARC] || bulkDetails?.[FILE_KEYS.PROPOSED_CHANGES_LINK]; - - const downloadLabel = approach === APPROACHES.MARC - ? - : ; - - const handleBulkOperationStart = async () => { - try { - await bulkOperationStart({ - id: bulkDetails?.id, - approach: APPROACHES.IN_APP, - step: EDITING_STEPS.COMMIT, - }); - - queryClient.resetQueries(BULK_OPERATION_DETAILS_KEY); - - onChangesCommited(); - - history.replace({ - pathname: `/bulk-edit/${bulkDetails?.id}/preview`, - search: buildSearch({ - progress: criteria, - }, history.location.search), - }); - } catch (e) { - showErrorMessage(e); - } - }; - - const isModalButtonDisabled = !hasLinkForDownload || isPreviewLoading || bulkDetails?.status !== JOB_STATUSES.REVIEW_CHANGES; - return ( } aria-label="PreviewModal" - footer={ - - } + footer={modalFooter} dismissible onClose={onKeepEditing} > @@ -100,8 +38,6 @@ export const BulkEditPreviewModal = ({ BulkEditPreviewModal.propTypes = { open: PropTypes.bool, isPreviewLoading: PropTypes.bool, - bulkDetails: PropTypes.object, + modalFooter: PropTypes.node, onKeepEditing: PropTypes.func, - onChangesCommited: PropTypes.func, - onDownload: PropTypes.func, }; diff --git a/src/components/BulkEditPane/BulkEditListResult/BulkEditInAppPreviewModal/BulkEditPreviewModalFooter.js b/src/components/BulkEditPane/BulkEditListResult/BulkEditInAppPreviewModal/BulkEditPreviewModalFooter.js index ac959b46..4800c640 100644 --- a/src/components/BulkEditPane/BulkEditListResult/BulkEditInAppPreviewModal/BulkEditPreviewModalFooter.js +++ b/src/components/BulkEditPane/BulkEditListResult/BulkEditInAppPreviewModal/BulkEditPreviewModalFooter.js @@ -5,25 +5,67 @@ import { FormattedMessage } from 'react-intl'; import { Button } from '@folio/stripes/components'; import css from './BulkEditInAppPreviewModal.css'; - +import { useSearchParams } from '../../../../hooks'; +import { APPROACHES, FILE_EXTENSION, FILE_SEARCH_PARAMS } from '../../../../constants'; +import { + QUERY_KEY_DOWNLOAD_ADMINISTRATIVE_PREVIEW_MODAL, + QUERY_KEY_DOWNLOAD_MARC_PREVIEW_MODAL, + useFileDownload +} from '../../../../hooks/api'; +import { changeExtension, savePreviewFile } from '../../../../utils/files'; export const BulkEditPreviewModalFooter = ({ - onDownload, - downloadLabel, - isCommitBtnDisabled, - isDownloadBtnDisabled, + bulkOperationId, + buttonsDisabled, onKeepEditing, - onSave, + onCommitChanges, }) => { + const { approach, initialFileName } = useSearchParams(); + + const { refetch: downloadCsvPreview } = useFileDownload({ + queryKey: QUERY_KEY_DOWNLOAD_ADMINISTRATIVE_PREVIEW_MODAL, + enabled: false, + id: bulkOperationId, + fileContentType: FILE_SEARCH_PARAMS.PROPOSED_CHANGES_FILE, + onSuccess: (fileData) => { + savePreviewFile({ + bulkOperationId, + fileData, + initialFileName, + extension: FILE_EXTENSION.CSV, + }); + }, + }); + + const { refetch: downloadMarcPreview } = useFileDownload({ + queryKey: QUERY_KEY_DOWNLOAD_MARC_PREVIEW_MODAL, + enabled: false, + id: bulkOperationId, + fileContentType: FILE_SEARCH_PARAMS.PROPOSED_CHANGES_MARC_FILE, + onSuccess: (fileData) => { + savePreviewFile({ + bulkOperationId, + fileData, + initialFileName: changeExtension(initialFileName, FILE_EXTENSION.MRC), + extension: FILE_EXTENSION.MRC, + }); + }, + }); + return (
- - + )} +
@@ -31,10 +73,8 @@ export const BulkEditPreviewModalFooter = ({ }; BulkEditPreviewModalFooter.propTypes = { - isDownloadBtnDisabled: PropTypes.bool, - isCommitBtnDisabled: PropTypes.bool, - downloadLabel: PropTypes.node, + bulkOperationId: PropTypes.string.isRequired, + buttonsDisabled: PropTypes.bool, onKeepEditing: PropTypes.func, - onDownload: PropTypes.func, - onSave: PropTypes.func, + onCommitChanges: PropTypes.func, }; diff --git a/src/components/BulkEditPane/BulkEditListResult/BulkEditInAppPreviewModal/BulkEditPreviewModalFooter.test.js b/src/components/BulkEditPane/BulkEditListResult/BulkEditInAppPreviewModal/BulkEditPreviewModalFooter.test.js new file mode 100644 index 00000000..bca1b229 --- /dev/null +++ b/src/components/BulkEditPane/BulkEditListResult/BulkEditInAppPreviewModal/BulkEditPreviewModalFooter.test.js @@ -0,0 +1,118 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; + +import '../../../../../test/jest/__mock__'; + +import { BulkEditPreviewModalFooter } from './BulkEditPreviewModalFooter'; +import { useSearchParams } from '../../../../hooks'; +import { + QUERY_KEY_DOWNLOAD_ADMINISTRATIVE_PREVIEW_MODAL, + QUERY_KEY_DOWNLOAD_MARC_PREVIEW_MODAL, + useFileDownload +} from '../../../../hooks/api'; +import { APPROACHES } from '../../../../constants'; + +jest.mock('../../../../hooks', () => ({ + useSearchParams: jest.fn(), +})); + +jest.mock('../../../../hooks/api', () => ({ + useFileDownload: jest.fn(), +})); + +const mockSavePreviewFile = jest.fn(); + +jest.mock('../../../../utils/files', () => ({ + savePreviewFile: jest.fn((...args) => mockSavePreviewFile(...args)), + changeExtension: (name, ext) => `${name}.${ext}`, +})); + +const renderModalFooter = (overwrite = {}) => { + return render( + + ); +}; + +describe('BulkEditPreviewModalFooter', () => { + let mockDownloadCsvPreview; + let mockDownloadMarcPreview; + + beforeEach(() => { + mockDownloadCsvPreview = jest.fn(); + mockDownloadMarcPreview = jest.fn(); + + useSearchParams.mockReturnValue({ approach: APPROACHES.MARC, initialFileName: 'testFile' }); + + useFileDownload.mockImplementation(({ queryKey }) => { + if (queryKey === QUERY_KEY_DOWNLOAD_ADMINISTRATIVE_PREVIEW_MODAL) { + return { refetch: mockDownloadCsvPreview }; + } + if (queryKey === QUERY_KEY_DOWNLOAD_MARC_PREVIEW_MODAL) { + return { refetch: mockDownloadMarcPreview }; + } + return {}; + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('renders all buttons correctly', () => { + renderModalFooter(); + + expect(screen.getByText('ui-bulk-edit.previewModal.keepEditing')).toBeInTheDocument(); + expect(screen.getByText('ui-bulk-edit.previewModal.downloadPreview')).toBeInTheDocument(); + expect(screen.getByText('ui-bulk-edit.previewModal.downloadPreview.marc')).toBeInTheDocument(); + expect(screen.getByText('ui-bulk-edit.previewModal.saveAndClose')).toBeInTheDocument(); + }); + + it('handles the keep editing button click', () => { + const onKeepEditingMock = jest.fn(); + renderModalFooter({ onKeepEditing: onKeepEditingMock }); + + fireEvent.click(screen.getByText('ui-bulk-edit.previewModal.keepEditing')); + expect(onKeepEditingMock).toHaveBeenCalled(); + }); + + it('handles the save and close button click', () => { + const onCommitChangesMock = jest.fn(); + + renderModalFooter({ onCommitChanges: onCommitChangesMock, buttonsDisabled: false }); + + fireEvent.click(screen.getByText('ui-bulk-edit.previewModal.saveAndClose')); + + expect(onCommitChangesMock).toHaveBeenCalled(); + }); + + it('downloads CSV preview on button click', () => { + renderModalFooter({ buttonsDisabled: false }); + + fireEvent.click(screen.getByText('ui-bulk-edit.previewModal.downloadPreview')); + expect(mockDownloadCsvPreview).toHaveBeenCalled(); + }); + + it('disables buttons when buttonsDisabled is true', () => { + renderModalFooter(); + + expect(screen.getByRole('button', { name: 'ui-bulk-edit.previewModal.keepEditing' })).not.toBeDisabled(); + expect(screen.getByRole('button', { name: 'ui-bulk-edit.previewModal.downloadPreview' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'ui-bulk-edit.previewModal.downloadPreview.marc' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'ui-bulk-edit.previewModal.saveAndClose' })).toBeDisabled(); + }); + + it('should not render download marc if approach is not MARC', () => { + useSearchParams.mockReturnValue({ approach: APPROACHES.IN_APP, initialFileName: 'testFile' }); + + renderModalFooter(); + + expect(screen.queryByText('ui-bulk-edit.previewModal.downloadPreview.marc')).not.toBeInTheDocument(); + }); +}); diff --git a/src/components/BulkEditPane/BulkEditMarcLayer/BulkEditMarcLayer.js b/src/components/BulkEditPane/BulkEditMarcLayer/BulkEditMarcLayer.js index 3d2939ea..934226cb 100644 --- a/src/components/BulkEditPane/BulkEditMarcLayer/BulkEditMarcLayer.js +++ b/src/components/BulkEditPane/BulkEditMarcLayer/BulkEditMarcLayer.js @@ -1,23 +1,26 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; - import { useIntl } from 'react-intl'; import uniqueId from 'lodash/uniqueId'; + import { BulkEditLayer } from '../BulkEditListResult/BulkEditInAppLayer/BulkEditLayer'; import { BulkEditMarc } from '../BulkEditListResult/BulkEditMarc/BulkEditMarc'; import { BulkEditPreviewModal } from '../BulkEditListResult/BulkEditInAppPreviewModal/BulkEditPreviewModal'; import { getMarcFieldTemplate, getTransformedField } from '../BulkEditListResult/BulkEditMarc/helpers'; import { useMarcContentUpdate } from '../../../hooks/api/useMarcContentUpdate'; import { useConfirmChanges } from '../../../hooks/useConfirmChanges'; -import { QUERY_KEY_DOWNLOAD_MARC_PREVIEW_MODAL, useContentUpdate } from '../../../hooks/api'; -import { savePreviewFile } from '../../../utils/files'; +import { useContentUpdate } from '../../../hooks/api'; import { - getContentUpdatesBody, getMappedContentUpdates, + getContentUpdatesBody, + getMappedContentUpdates, isContentUpdatesFormValid } from '../BulkEditListResult/BulkEditInApp/ContentUpdatesForm/helpers'; import { getMarcFormErrors } from '../BulkEditListResult/BulkEditMarc/validation'; import { getAdministrativeDataOptions } from '../../../constants'; import { sortAlphabetically } from '../../../utils/sortAlphabetically'; +import { BulkEditPreviewModalFooter } from '../BulkEditListResult/BulkEditInAppPreviewModal/BulkEditPreviewModalFooter'; +import { useCommitChanges } from '../../../hooks/useCommitChanges'; + export const BulkEditMarcLayer = ({ bulkOperationId, @@ -48,28 +51,20 @@ export const BulkEditMarcLayer = ({ isPreviewLoading, bulkDetails, totalRecords, - downloadFile, confirmChanges, closePreviewModal, - } = useConfirmChanges({ - queryDownloadKey: QUERY_KEY_DOWNLOAD_MARC_PREVIEW_MODAL, - bulkOperationId, - onDownloadSuccess: (fileData, searchParams) => { - const { approach, initialFileName } = searchParams; + } = useConfirmChanges({ bulkOperationId }); - savePreviewFile({ - bulkOperationId, - fileData, - approach, - initialFileName, - }); - }, + const { commitChanges } = useCommitChanges({ + bulkOperationId, + onChangesCommited: () => { + closePreviewModal(); + onMarcLayerClose(); + } }); - const handleChangesCommited = () => { - closePreviewModal(); - onMarcLayerClose(); - }; + const hasBothFiles = bulkDetails?.linkToModifiedRecordsCsvFile && bulkDetails?.linkToModifiedRecordsMarcFile; + const areMarcAndCsvReady = hasBothFiles || !isPreviewLoading; const handleConfirm = () => { const bulkOperationMarcRules = marcFields @@ -116,11 +111,16 @@ export const BulkEditMarcLayer = ({ + } /> ); diff --git a/src/components/BulkEditPane/BulkEditPane.js b/src/components/BulkEditPane/BulkEditPane.js index 875baf18..c4d48bd3 100644 --- a/src/components/BulkEditPane/BulkEditPane.js +++ b/src/components/BulkEditPane/BulkEditPane.js @@ -103,9 +103,7 @@ export const BulkEditPane = () => { queryKey: QUERY_KEY_DOWNLOAD_ACTION_MENU, enabled: !!fileInfo, id: bulkOperationId, - fileInfo: { - fileContentType: FILE_SEARCH_PARAMS[fileInfo?.param]?.replace('_MARC', ''), - }, + fileContentType: FILE_SEARCH_PARAMS[fileInfo?.param]?.replace('_MARC', ''), onSuccess: data => { /* istanbul ignore next */ saveAs(new Blob([data]), fileInfo?.bulkDetails[FILE_TO_LINK[fileInfo?.param]].split('/')[1]); diff --git a/src/constants/files.js b/src/constants/files.js index aaa4bd0e..ac2499e1 100644 --- a/src/constants/files.js +++ b/src/constants/files.js @@ -14,6 +14,11 @@ export const FILE_KEYS = { TRIGGERING_FILE: 'linkToTriggeringCsvFile', }; +export const FILE_EXTENSION = { + CSV: 'csv', + MRC: 'mrc', +}; + // use as API key for /download export const FILE_SEARCH_PARAMS = { MATCHED_RECORDS_FILE: 'MATCHED_RECORDS_FILE', @@ -21,6 +26,7 @@ export const FILE_SEARCH_PARAMS = { COMMITTED_RECORDS_FILE: 'COMMITTED_RECORDS_FILE', COMMITTING_CHANGES_ERROR_FILE: 'COMMITTING_CHANGES_ERROR_FILE', PROPOSED_CHANGES_FILE: 'PROPOSED_CHANGES_FILE', + PROPOSED_CHANGES_MARC_FILE: 'PROPOSED_CHANGES_MARC_FILE', COMMITTED_RECORDS_FILE_MARC: 'COMMITTED_RECORDS_FILE_MARC', }; diff --git a/src/hooks/api/useFileDownload.js b/src/hooks/api/useFileDownload.js index 5804046e..e327f5b5 100644 --- a/src/hooks/api/useFileDownload.js +++ b/src/hooks/api/useFileDownload.js @@ -4,12 +4,12 @@ import { useErrorMessages } from '../useErrorMessages'; export const QUERY_KEY_DOWNLOAD_LOGS = 'downloadLogs'; export const QUERY_KEY_DOWNLOAD_ACTION_MENU = 'downloadActionMenu'; -export const QUERY_KEY_DOWNLOAD_PREVIEW_MODAL = 'downloadPreviewModal'; +export const QUERY_KEY_DOWNLOAD_ADMINISTRATIVE_PREVIEW_MODAL = 'downloadPreviewModal'; export const QUERY_KEY_DOWNLOAD_MARC_PREVIEW_MODAL = 'downloadMarcPreviewModal'; export const useFileDownload = ({ id, - fileInfo, + fileContentType, onSuccess, onSettled, queryKey, @@ -21,11 +21,11 @@ export const useFileDownload = ({ const { refetch, isFetching } = useQuery( { - queryKey: [namespaceKey, id, fileInfo], + queryKey: [namespaceKey, id, fileContentType], queryFn: () => ky.get(`bulk-operations/${id}/download`, { - searchParams: { fileContentType: fileInfo?.fileContentType }, + searchParams: { fileContentType }, }).blob(), - enabled: !!fileInfo, + enabled: !!fileContentType, onSuccess: response => { showErrorMessage(response); onSuccess?.(response); diff --git a/src/hooks/useCommitChanges.js b/src/hooks/useCommitChanges.js new file mode 100644 index 00000000..3a462b9e --- /dev/null +++ b/src/hooks/useCommitChanges.js @@ -0,0 +1,46 @@ +import { useHistory } from 'react-router-dom'; +import { useQueryClient } from 'react-query'; + +import { buildSearch } from '@folio/stripes-acq-components'; + +import { useSearchParams } from './useSearchParams'; +import { useErrorMessages } from './useErrorMessages'; +import { BULK_OPERATION_DETAILS_KEY, useBulkOperationStart } from './api'; +import { APPROACHES, EDITING_STEPS } from '../constants'; + + +export const useCommitChanges = ({ + bulkOperationId, + onChangesCommited, +}) => { + const history = useHistory(); + const queryClient = useQueryClient(); + const { criteria } = useSearchParams(); + const { showErrorMessage } = useErrorMessages(); + const { bulkOperationStart } = useBulkOperationStart(); + + const commitChanges = async () => { + try { + await bulkOperationStart({ + id: bulkOperationId, + approach: APPROACHES.IN_APP, + step: EDITING_STEPS.COMMIT, + }); + + await queryClient.resetQueries(BULK_OPERATION_DETAILS_KEY); + + onChangesCommited(); + + history.replace({ + pathname: `/bulk-edit/${bulkOperationId}/preview`, + search: buildSearch({ + progress: criteria, + }, history.location.search), + }); + } catch (e) { + showErrorMessage(e); + } + }; + + return { commitChanges }; +}; diff --git a/src/hooks/useCommitChanges.test.js b/src/hooks/useCommitChanges.test.js new file mode 100644 index 00000000..4bd67ffd --- /dev/null +++ b/src/hooks/useCommitChanges.test.js @@ -0,0 +1,117 @@ +import { useHistory } from 'react-router-dom'; +import { useQueryClient } from 'react-query'; +import { renderHook, act } from '@testing-library/react-hooks'; + +import { buildSearch } from '@folio/stripes-acq-components'; + +import { useCommitChanges } from './useCommitChanges'; +import { useSearchParams } from './useSearchParams'; +import { useErrorMessages } from './useErrorMessages'; +import { useBulkOperationStart } from './api'; +import { APPROACHES, EDITING_STEPS } from '../constants'; + + +jest.mock('react-router-dom', () => ({ + useHistory: jest.fn(), +})); + +jest.mock('react-query', () => ({ + useQueryClient: jest.fn(), +})); + +jest.mock('@folio/stripes-acq-components', () => ({ + buildSearch: jest.fn(), +})); + +jest.mock('./useSearchParams', () => ({ + useSearchParams: jest.fn(), +})); + +jest.mock('./useErrorMessages', () => ({ + useErrorMessages: jest.fn(), +})); + +jest.mock('./api', () => ({ + useBulkOperationStart: jest.fn(), + BULK_OPERATION_DETAILS_KEY: 'bulkOperationDetailsKey', +})); + +const mockBulkOperationStart = jest.fn(); +const mockResetQueries = jest.fn(); +const mockReplace = jest.fn(); +const mockShowErrorMessage = jest.fn(); + +describe('useCommitChanges', () => { + beforeEach(() => { + jest.clearAllMocks(); + + useHistory.mockReturnValue({ + replace: mockReplace, + location: { search: '?query=test' }, + }); + + useQueryClient.mockReturnValue({ + resetQueries: mockResetQueries, + }); + + buildSearch.mockImplementation(({ progress }, search) => `?progress=${progress}&${search}`); + + useSearchParams.mockReturnValue({ + criteria: 'testCriteria', + }); + + useErrorMessages.mockReturnValue({ + showErrorMessage: mockShowErrorMessage, + }); + + useBulkOperationStart.mockReturnValue({ + bulkOperationStart: mockBulkOperationStart, + }); + }); + + it('should commit changes successfully', async () => { + const onChangesCommited = jest.fn(); + + mockBulkOperationStart.mockResolvedValueOnce(); + + const { result } = renderHook(() => useCommitChanges({ bulkOperationId: '123', onChangesCommited })); + + await act(async () => { + await result.current.commitChanges(); + }); + + expect(mockBulkOperationStart).toHaveBeenCalledWith({ + id: '123', + approach: APPROACHES.IN_APP, + step: EDITING_STEPS.COMMIT, + }); + expect(mockResetQueries).toHaveBeenCalledWith('bulkOperationDetailsKey'); + expect(onChangesCommited).toHaveBeenCalled(); + expect(mockReplace).toHaveBeenCalledWith({ + pathname: '/bulk-edit/123/preview', + search: '?progress=testCriteria&?query=test', + }); + }); + + it('should show an error message when commit fails', async () => { + const onChangesCommited = jest.fn(); + const error = new Error('Commit failed'); + + mockBulkOperationStart.mockRejectedValueOnce(error); + + const { result } = renderHook(() => useCommitChanges({ bulkOperationId: '123', onChangesCommited })); + + await act(async () => { + await result.current.commitChanges(); + }); + + expect(mockBulkOperationStart).toHaveBeenCalledWith({ + id: '123', + approach: APPROACHES.IN_APP, + step: EDITING_STEPS.COMMIT, + }); + expect(mockShowErrorMessage).toHaveBeenCalledWith(error); + expect(onChangesCommited).not.toHaveBeenCalled(); + expect(mockReplace).not.toHaveBeenCalled(); + }); +}); diff --git a/src/hooks/useConfirmChanges.js b/src/hooks/useConfirmChanges.js index 554b33ac..9b9fc635 100644 --- a/src/hooks/useConfirmChanges.js +++ b/src/hooks/useConfirmChanges.js @@ -7,27 +7,19 @@ import { BULK_OPERATION_DETAILS_KEY, useBulkOperationDetails, useBulkOperationStart, - useFileDownload } from './api'; import { APPROACHES, EDITING_STEPS, - FILE_SEARCH_PARAMS, JOB_STATUSES, } from '../constants'; -import { useSearchParams } from './useSearchParams'; import { useErrorMessages } from './useErrorMessages'; import { pollForStatus } from '../utils/pollForStatus'; -export const useConfirmChanges = ({ - queryDownloadKey, - bulkOperationId, - onDownloadSuccess, -}) => { +export const useConfirmChanges = ({ bulkOperationId }) => { const queryClient = useQueryClient(); const ky = useOkapiKy(); - const searchParams = useSearchParams(); const { showErrorMessage } = useErrorMessages(); const [isPreviewModalOpened, setIsPreviewModalOpened] = useState(false); @@ -74,24 +66,12 @@ export const useConfirmChanges = ({ }); }; - const { refetch: downloadFile, isFetching: isFileDownloading } = useFileDownload({ - queryKey: queryDownloadKey, - enabled: false, // to prevent automatic file fetch in preview modal - id: bulkOperationId, - fileInfo: { - fileContentType: FILE_SEARCH_PARAMS.PROPOSED_CHANGES_FILE, - }, - onSuccess: (data) => onDownloadSuccess(data, searchParams), - }); - return { totalRecords, bulkDetails, isPreviewModalOpened, isPreviewLoading, setIsPreviewLoading, - isFileDownloading, - downloadFile, openPreviewModal, closePreviewModal, confirmChanges, diff --git a/src/hooks/useConfirmChanges.test.js b/src/hooks/useConfirmChanges.test.js index c163c231..3b4efee6 100644 --- a/src/hooks/useConfirmChanges.test.js +++ b/src/hooks/useConfirmChanges.test.js @@ -5,7 +5,7 @@ import { useShowCallout } from '@folio/stripes-acq-components'; import '../../test/jest/__mock__/reactIntl.mock'; -import { useBulkOperationDetails, useBulkOperationStart, useFileDownload } from './api'; +import { useBulkOperationDetails, useBulkOperationStart } from './api'; import { useSearchParams } from './useSearchParams'; import { useConfirmChanges } from './useConfirmChanges'; import { pollForStatus } from '../utils/pollForStatus'; @@ -27,7 +27,6 @@ jest.mock('@folio/stripes-acq-components', () => ({ jest.mock('./api', () => ({ useBulkOperationDetails: jest.fn(), useBulkOperationStart: jest.fn(), - useFileDownload: jest.fn(), })); jest.mock('./useSearchParams', () => ({ @@ -46,7 +45,6 @@ describe('useConfirmChanges', () => { }; const mockBulkOperationDetails = { bulkDetails: { totalNumOfRecords: 100 } }; const mockBulkOperationStart = jest.fn(); - const mockDownloadFile = jest.fn(); beforeEach(() => { useShowCallout.mockReturnValue(mockCallout); @@ -54,7 +52,6 @@ describe('useConfirmChanges', () => { useBulkOperationDetails.mockReturnValue(mockBulkOperationDetails); useBulkOperationStart.mockReturnValue({ bulkOperationStart: mockBulkOperationStart }); useSearchParams.mockReturnValue({ criteria: 'testCriteria', initialFileName: 'initialFileName' }); - useFileDownload.mockReturnValue({ refetch: mockDownloadFile, isFetching: false }); pollForStatus.mockImplementation(() => Promise.resolve()); jest.clearAllMocks(); @@ -129,17 +126,4 @@ describe('useConfirmChanges', () => { expect(result.current.isPreviewLoading).toBe(false); expect(result.current.isPreviewModalOpened).toBe(false); // Modal should close on error }); - - it('should call downloadFile from useFileDownload', () => { - const { result } = renderHook(() => useConfirmChanges({ - queryDownloadKey: 'testKey', - bulkOperationId: '123', - })); - - act(() => { - result.current.downloadFile(); - }); - - expect(mockDownloadFile).toHaveBeenCalled(); - }); }); diff --git a/src/utils/files.js b/src/utils/files.js index 9a69ce50..e67ce4ab 100644 --- a/src/utils/files.js +++ b/src/utils/files.js @@ -1,7 +1,6 @@ import { saveAs } from 'file-saver'; import { getFormattedFilePrefixDate } from './date'; -import { APPROACHES } from '../constants'; export const getFileName = (item, triggeredFile) => { @@ -32,16 +31,10 @@ export const changeExtension = (fileName, extension) => { export const savePreviewFile = ({ bulkOperationId, fileData, - approach, + extension, initialFileName, }) => { - const extension = approach === APPROACHES.MARC ? 'mrc' : 'csv'; - - const initialFileNameByApproach = approach === APPROACHES.MARC - ? changeExtension(initialFileName, extension) - : initialFileName; - - const fileName = initialFileNameByApproach || `Query-${bulkOperationId}.${extension}`; + const fileName = initialFileName || `Query-${bulkOperationId}.${extension}`; saveAs(new Blob([fileData]), `${getFormattedFilePrefixDate()}-Updates-Preview-${fileName}`); }; diff --git a/src/utils/files.test.js b/src/utils/files.test.js index 374cc0a7..b806c61e 100644 --- a/src/utils/files.test.js +++ b/src/utils/files.test.js @@ -57,15 +57,15 @@ describe('files', () => { it('should save the file with the correct name and extension for MARC approach', () => { const bulkOperationId = '123'; const fileData = 'data'; - const initialFileName = 'abc.csv'; - const approach = APPROACHES.MARC; + const initialFileName = 'abc.mrc'; + const extension = 'mrc'; getFormattedFilePrefixDate.mockReturnValue('2024-08-09'); savePreviewFile({ bulkOperationId, fileData, - approach, + extension, initialFileName, }); @@ -79,14 +79,14 @@ describe('files', () => { const bulkOperationId = '123'; const fileData = 'data'; const initialFileName = 'abc.csv'; - const approach = APPROACHES.IN_APP; + const extension = 'csv'; getFormattedFilePrefixDate.mockReturnValue('2024-08-09'); savePreviewFile({ bulkOperationId, fileData, - approach, + extension, initialFileName, }); @@ -100,14 +100,14 @@ describe('files', () => { const bulkOperationId = '123'; const fileData = 'data'; const initialFileName = ''; - const approach = APPROACHES.MARC; + const extension = 'mrc'; getFormattedFilePrefixDate.mockReturnValue('2024-08-09'); savePreviewFile({ bulkOperationId, fileData, - approach, + extension, initialFileName, }); diff --git a/translations/ui-bulk-edit/en.json b/translations/ui-bulk-edit/en.json index 1d8c16bd..1ed4501c 100644 --- a/translations/ui-bulk-edit/en.json +++ b/translations/ui-bulk-edit/en.json @@ -440,7 +440,7 @@ "previewModal.message.empty.marc": "All instances have source FOLIO. Use “Instances and Administrative data” option for bulk edit.", "previewModal.previewToBeChanged": "Preview of records to be changed", "previewModal.keepEditing": "Keep editing", - "previewModal.downloadPreview": "Download preview", + "previewModal.downloadPreview": "Download preview in CSV format", "previewModal.downloadPreview.marc": "Download preview in MARC format", "previewModal.saveAndClose": "Commit changes", "previewModal.areYouSure": "Are you sure?",