From b04ad922eb1abf923817e5ddcb8fc5ab87c7a344 Mon Sep 17 00:00:00 2001 From: Mariia Aloshyna <55138456+mariia-aloshyna@users.noreply.github.com> Date: Thu, 13 Jul 2023 16:18:42 +0300 Subject: [PATCH 01/14] UIDATIMP-1440: Update Log JSON screen to support multiple items (#1438) --- CHANGELOG.md | 2 + src/hooks/index.js | 1 + src/hooks/tests/useLocationsQuery.test.js | 31 +++ src/hooks/useLocationsQuery.js | 21 ++ src/routes/ViewJobLog.js | 236 +++++++++++++--------- src/routes/tests/ViewJobLog.test.js | 89 ++++++-- 6 files changed, 271 insertions(+), 109 deletions(-) create mode 100644 src/hooks/tests/useLocationsQuery.test.js create mode 100644 src/hooks/useLocationsQuery.js diff --git a/CHANGELOG.md b/CHANGELOG.md index d5feb77ad..c35bad189 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ * Invoice field mapping: Add info icon to the Acq unit field (UIDATIMP-1427) * View all: Create hotlink from job profile name in log to the job profile details (UIDATIMP-1428) * Fix warnings in unit tests (part 2) (UIDATIMP-1437) +* Update Log JSON screen to support multiple holdings (UIDATIMP-1439) +* Update Log JSON screen to support multiple items (UIDATIMP-1440) * DI Log: change Discarded to No action (UIDATIMP-1446) * DI Log: Make some changes to the Log header (UIDATIMP-1447) * DI Job profiles: Redirect when job profile was deleted (UIDATIMP-1450) diff --git a/src/hooks/index.js b/src/hooks/index.js index 0b2832b0e..8fc5c1650 100644 --- a/src/hooks/index.js +++ b/src/hooks/index.js @@ -5,6 +5,7 @@ export * from './useInventoryItemsByIdQuery'; export * from './useInvoiceLineByIdQuery'; export * from './useInvoicesByIdQuery'; export * from './useJobLogRecordsQuery'; +export * from './useLocationsQuery'; export * from './useOrderByIdQuery'; export * from './usePOLinesByIdQuery'; export * from './useSRSRecordQuery'; diff --git a/src/hooks/tests/useLocationsQuery.test.js b/src/hooks/tests/useLocationsQuery.test.js new file mode 100644 index 000000000..97137047c --- /dev/null +++ b/src/hooks/tests/useLocationsQuery.test.js @@ -0,0 +1,31 @@ +import React from 'react'; +import { renderHook } from '@testing-library/react-hooks'; + +import '../../../test/jest/__mock__'; + +import { useOkapiKy } from '@folio/stripes/core'; + +import { useLocationsQuery } from '../useLocationsQuery'; +import { queryClientWrapper } from '../../../test/jest/helpers'; + +const mockLocationsRecord = [{ id: 'locationId1' }, { id: 'locationId2' }]; + +describe('useLocationsQuery', () => { + beforeEach(() => { + useOkapiKy.mockClear().mockReturnValue({ + get: () => { + return ({ + json: () => Promise.resolve({ locations: mockLocationsRecord }) + }); + }, + }); + }); + + it('should fetch locations', async () => { + const { result, waitFor } = renderHook(() => useLocationsQuery(), { wrapper: queryClientWrapper }); + + await waitFor(() => !result.current.isLoading); + + expect(result.current.data).toEqual(mockLocationsRecord); + }); +}); diff --git a/src/hooks/useLocationsQuery.js b/src/hooks/useLocationsQuery.js new file mode 100644 index 000000000..d4bc5e508 --- /dev/null +++ b/src/hooks/useLocationsQuery.js @@ -0,0 +1,21 @@ +import { useQuery } from 'react-query'; + +import { + useOkapiKy, + useNamespace, +} from '@folio/stripes/core'; + +export const useLocationsQuery = () => { + const ky = useOkapiKy(); + const [namespace] = useNamespace({ key: 'locations' }); + + const query = useQuery({ + queryKey: [namespace], + queryFn: () => ky.get('locations').json(), + }); + + return ({ + ...query, + data: query.data?.locations, + }); +}; diff --git a/src/routes/ViewJobLog.js b/src/routes/ViewJobLog.js index 176d9935e..3d8dadc25 100644 --- a/src/routes/ViewJobLog.js +++ b/src/routes/ViewJobLog.js @@ -1,5 +1,6 @@ import React, { useEffect, + useMemo, useState, } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -25,6 +26,7 @@ import { useInvoicesByIdQuery, useInvoiceLineByIdQuery, useAuthoritiesByIdQuery, + useLocationsQuery, } from '../hooks'; import { @@ -65,6 +67,7 @@ export const ViewJobLog = () => { const { data: invoicesData } = useInvoicesByIdQuery(invoicesIds); const { data: invoiceLineData } = useInvoiceLineByIdQuery(invoiceLineId); const { data: authoritiesData } = useAuthoritiesByIdQuery(authoritiesIds); + const { data: locationsData = [] } = useLocationsQuery(); useEffect(() => { if (!isJobLogLoading && !isJobLogError) { @@ -79,8 +82,8 @@ export const ViewJobLog = () => { } = jobLogData; setInstancesIds(relatedInstanceInfo.idList); - setHoldingsIds(relatedHoldingsInfo.idList); - setItemsIds(relatedItemInfo.idList); + setHoldingsIds(relatedHoldingsInfo.map((item) => item.id).filter((id) => !!id)); + setItemsIds(relatedItemInfo.map((item) => item.id).filter((id) => !!id)); setOrderId(relatedPoLineInfo?.orderId); setPoLinesIds(relatedPoLineInfo?.idList); setInvoicesIds(relatedInvoiceInfo.idList); @@ -89,35 +92,145 @@ export const ViewJobLog = () => { } }, [isJobLogLoading, isJobLogError, jobLogData]); - const getErrorMessage = entityOption => { - if (isJobLogLoading || isJobLogError) return ''; + const srsMarcBibLogs = useMemo(() => { + const { error } = jobLogData; + return [ + { + label: '', + logs: srsRecordData, + error: error || '', + errorBlockId: 'srs-marc-bib-error', + }, + ]; + }, [jobLogData, srsRecordData]); + const instanceLogs = useMemo(() => { + const { relatedInstanceInfo } = jobLogData; + + return [ + { + label: '', + logs: instancesData, + error: relatedInstanceInfo?.error || '', + errorBlockId: 'instance-error', + }, + ]; + }, [instancesData, jobLogData]); + const holdingsLogs = useMemo(() => { + const { relatedHoldingsInfo } = jobLogData; + + const getHoldingsLabel = (holding = {}) => { + const { permanentLocationId } = holding; + + if (!permanentLocationId) return ''; + + const permanentLocation = locationsData.find( + (location) => location.id === permanentLocationId + ); + + return ( + + {permanentLocation?.code} + + ); + }; + + return ( + relatedHoldingsInfo?.map((holding) => ({ + label: getHoldingsLabel(holding), + logs: holdingsData?.find((data) => data.id === holding.id), + error: holding.error || '', + errorBlockId: 'holdings-error', + })) || [{ logs: [] }] + ); + }, [holdingsData, jobLogData, locationsData]); + const itemsLogs = useMemo(() => { + const { relatedItemInfo } = jobLogData; + + const getItemsLabel = (item = {}) => { + const { hrid } = item; + + if (!hrid) return ''; + + return ( + + {hrid} + + ); + }; + + return ( + relatedItemInfo?.map((item) => ({ + label: getItemsLabel(item), + logs: itemsData?.find((data) => data.id === item.id), + error: item.error || '', + errorBlockId: 'item-error', + })) || [{ logs: [] }] + ); + }, [itemsData, jobLogData]); + const authorityLogs = useMemo(() => { + const { relatedAuthorityInfo } = jobLogData; + + return [{ + label: '', + logs: authoritiesData, + error: relatedAuthorityInfo?.error || '', + errorBlockId: 'authority-error', + }]; + }, [authoritiesData, jobLogData]); + const orderLogs = useMemo(() => { + const { relatedPoLineInfo } = jobLogData; + + return [ + { + label: ( + + + + ), + logs: poLinesData, + error: relatedPoLineInfo?.error || '', + errorBlockId: 'order-line-error', + }, + { + label: ( + + + + ), + logs: orderData, + }, + ]; + }, [jobLogData, orderData, poLinesData]); + const invoiceLogs = useMemo(() => { const { - error, - relatedInstanceInfo, - relatedHoldingsInfo, - relatedItemInfo, - relatedAuthorityInfo, - relatedPoLineInfo, - relatedInvoiceInfo, relatedInvoiceLineInfo, + relatedInvoiceInfo, } = jobLogData; - const errors = { - [OPTIONS.SRS_MARC_BIB]: error || '', - [OPTIONS.INSTANCE]: relatedInstanceInfo.error || '', - [OPTIONS.HOLDINGS]: relatedHoldingsInfo.error || '', - [OPTIONS.ITEM]: relatedItemInfo.error || '', - [OPTIONS.ORDER]: relatedPoLineInfo.error || '', - [OPTIONS.AUTHORITY]: relatedAuthorityInfo.error || '', - [OPTIONS.INVOICE]: { - invoiceInfo: relatedInvoiceInfo.error || '', - invoiceLineInfo: relatedInvoiceLineInfo?.error || '', + return [ + { + label: ( + + + + ), + logs: invoiceLineData, + error: relatedInvoiceLineInfo?.error || '', + errorBlockId: 'invoice-line-error', }, - }; - - return errors[entityOption]; - }; + { + label: ( + + + + ), + logs: invoicesData, + error: relatedInvoiceInfo?.error || '', + errorBlockId: 'invoice-error', + }, + ]; + }, [invoiceLineData, invoicesData, jobLogData]); if (isJobLogLoading) { return ( @@ -155,72 +268,13 @@ export const ViewJobLog = () => { }; const logs = { - [OPTIONS.SRS_MARC_BIB]: [{ - label: '', - logs: srsRecordData, - error: getErrorMessage(OPTIONS.SRS_MARC_BIB), - errorBlockId: 'srs-marc-bib-error', - }], - [OPTIONS.INSTANCE]: [{ - label: '', - logs: instancesData, - error: getErrorMessage(OPTIONS.INSTANCE), - errorBlockId: 'instance-error', - }], - [OPTIONS.HOLDINGS]: [{ - label: '', - logs: holdingsData, - error: getErrorMessage(OPTIONS.HOLDINGS), - errorBlockId: 'holdings-error', - }], - [OPTIONS.ITEM]: [{ - label: '', - logs: itemsData, - error: getErrorMessage(OPTIONS.ITEM), - errorBlockId: 'item-error', - }], - [OPTIONS.AUTHORITY]: [{ - label: '', - logs: authoritiesData, - error: getErrorMessage(OPTIONS.AUTHORITY), - errorBlockId: 'authority-error', - }], - [OPTIONS.ORDER]: [{ - label: ( - - - - ), - logs: poLinesData, - error: getErrorMessage(OPTIONS.ORDER), - errorBlockId: 'order-line-error', - }, { - label: ( - - - - ), - logs: orderData, - }], - [OPTIONS.INVOICE]: [{ - label: ( - - - - ), - logs: invoiceLineData, - error: getErrorMessage(OPTIONS.INVOICE).invoiceLineInfo, - errorBlockId: 'invoice-line-error', - }, { - label: ( - - - - ), - logs: invoicesData, - error: getErrorMessage(OPTIONS.INVOICE).invoiceInfo, - errorBlockId: 'invoice-error', - }], + [OPTIONS.SRS_MARC_BIB]: srsMarcBibLogs, + [OPTIONS.INSTANCE]: instanceLogs, + [OPTIONS.HOLDINGS]: holdingsLogs, + [OPTIONS.ITEM]: itemsLogs, + [OPTIONS.AUTHORITY]: authorityLogs, + [OPTIONS.ORDER]: orderLogs, + [OPTIONS.INVOICE]: invoiceLogs, }; return ( diff --git a/src/routes/tests/ViewJobLog.test.js b/src/routes/tests/ViewJobLog.test.js index cd6ae33d2..a408ae737 100644 --- a/src/routes/tests/ViewJobLog.test.js +++ b/src/routes/tests/ViewJobLog.test.js @@ -18,11 +18,34 @@ import { const mockJobLogData = { relatedInstanceInfo: { idList: [faker.random.uuid()] }, - relatedHoldingsInfo: { idList: [faker.random.uuid()] }, - relatedItemInfo: { - error: 'Error message', - idList: [faker.random.uuid()], - }, + relatedHoldingsInfo: [ + { + id: 'holdingId1', + hrid: 'holdingHrid1', + permanentLocationId: 'permanentLocationId1', + error: '', + }, + { + id: 'holdingId2', + hrid: 'holdingHrid2', + permanentLocationId: 'permanentLocationId2', + error: '', + }, + ], + relatedItemInfo: [ + { + id: '', + hrid: '', + holdingsId: '', + error: 'Error message', + }, + { + id: 'itemId2', + hrid: 'itemHrid2', + holdingsId: 'holdingId1', + error: '', + }, + ], relatedPoLineInfo: { idList: [faker.random.uuid()], orderId: faker.random.uuid(), @@ -49,14 +72,17 @@ const mockInstanceJSONData = { source: 'MARC', title: 'Test instance title', }; -const mockHoldingsJSONData = { - id: faker.random.uuid(), - title: 'Test holdings title', -}; -const mockItemsJSONData = { - id: faker.random.uuid(), +const mockHoldingsJSONData = [{ + id: 'holdingId1', + title: 'Test holdings title 1', +}, { + id: 'holdingId2', + title: 'Test holdings title 2', +}]; +const mockItemsJSONData = [{ + id: 'itemId2', title: 'Test item title', -}; +}]; const mockOrderJSONData = { id: faker.random.uuid(), title: 'Test purchase order title', @@ -77,19 +103,27 @@ const mockAuthorityJSONData = { id: faker.random.uuid(), title: 'Test authority title', }; +const mockLocationsData = [{ + id: 'permanentLocationId1', + code: 'permanentLocationCode1', +}, { + id: 'permanentLocationId2', + code: 'permanentLocationCode2', +}]; jest.mock('../../hooks', () => ({ ...jest.requireActual('../../hooks'), useJobLogRecordsQuery: jest.fn(() => ({ isLoading: false, data: mockJobLogData })), useSRSRecordQuery: jest.fn(() => ({ isLoading: false, data: mockSRSMARCBibMARCJSONData })), useInventoryInstancesByIdQuery: jest.fn(() => ({ isLoading: false, data: [mockInstanceJSONData] })), - useInventoryHoldingsByIdQuery: jest.fn(() => ({ isLoading: false, data: [mockHoldingsJSONData] })), - useInventoryItemsByIdQuery: jest.fn(() => ({ isLoading: false, data: [mockItemsJSONData] })), + useInventoryHoldingsByIdQuery: jest.fn(() => ({ isLoading: false, data: mockHoldingsJSONData })), + useInventoryItemsByIdQuery: jest.fn(() => ({ isLoading: false, data: mockItemsJSONData })), useOrderByIdQuery: jest.fn(() => ({ isLoading: false, data: mockOrderJSONData })), usePOLinesByIdQuery: jest.fn(() => ({ isLoading: false, data: [mockPoLineJSONData] })), useInvoicesByIdQuery: jest.fn(() => ({ isLoading: false, data: [mockInvoiceJSONData] })), useInvoiceLineByIdQuery: jest.fn(() => ({ isLoading: false, data: mockInvoiceLineJSONData })), useAuthoritiesByIdQuery: jest.fn(() => ({ isLoading: false, data: [mockAuthorityJSONData] })), + useLocationsQuery: jest.fn(() => ({ isLoading: false, data: mockLocationsData })), })); const renderViewJobLog = () => { @@ -217,6 +251,15 @@ describe('View job log page', () => { }); describe('and Holdings tag is active', () => { + it('should display permanent location code above each holding', () => { + const { getByText } = renderViewJobLog(); + + fireEvent.click(getByText('Holdings')); + + expect(getByText('permanentLocationCode1')).toBeInTheDocument(); + expect(getByText('permanentLocationCode2')).toBeInTheDocument(); + }); + it('should display Holdings JSON details on the screen', () => { const { container, @@ -224,13 +267,23 @@ describe('View job log page', () => { } = renderViewJobLog(); fireEvent.click(getByText('Holdings')); - const codeElement = container.querySelector('code.info'); + const codeElements = container.querySelectorAll('code.info'); - expect(JSON.parse(codeElement.textContent)).toEqual(mockHoldingsJSONData); + [...codeElements].forEach((codeElement, i) => { + expect(JSON.parse(codeElement.textContent)).toEqual(mockHoldingsJSONData[i]); + }); }); }); describe('and Item tag is active', () => { + it('should display item hrid above the JSON details', () => { + const { getByText } = renderViewJobLog(); + + fireEvent.click(getByText('Item*')); + + expect(getByText('itemHrid2')).toBeInTheDocument(); + }); + it('should display Item JSON details on the screen', () => { const { container, @@ -238,9 +291,9 @@ describe('View job log page', () => { } = renderViewJobLog(); fireEvent.click(getByText('Item*')); - const codeElement = container.querySelector('code.info'); + const codeElement = container.querySelectorAll('code.info')[1]; - expect(JSON.parse(codeElement.textContent)).toEqual(mockItemsJSONData); + expect(JSON.parse(codeElement.textContent)).toEqual(mockItemsJSONData[0]); }); }); From 4d960aa823d28e1ec10b4e383ecb64ecb9c2eb4d Mon Sep 17 00:00:00 2001 From: Mariia Aloshyna <55138456+mariia-aloshyna@users.noreply.github.com> Date: Thu, 13 Jul 2023 16:45:11 +0300 Subject: [PATCH 02/14] UIDATIMP-1439: Update Log JSON screen to support multiple holdings (Rancher) (#1437) From f519eb62e87a75134749902cec21931c741c8f70 Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko <85172747+OleksandrHladchenko1@users.noreply.github.com> Date: Thu, 13 Jul 2023 17:02:22 +0300 Subject: [PATCH 03/14] UIDATIMP-1438: Update Log details screen to support multiple holdings & items (Rancher) (#1439) * UIDATIMP-1438: Update Log details screen to support multiple holdings & items * UIDATIMP-1438: Add PropTypes --- CHANGELOG.md | 1 + src/routes/JobSummary/JobSummary.js | 7 + .../JobSummary/components/RecordsTable.js | 254 ++++++------ .../components/RecordsTable.test.js | 383 ++++++------------ .../components/cells/AuthorityCell.js | 44 ++ .../JobSummary/components/cells/ErrorCell.js | 26 ++ .../components/cells/HoldingsCell.js | 62 +++ .../components/cells/InstanceCell.js | 37 ++ .../components/cells/InvoiceCell.js | 52 +++ .../JobSummary/components/cells/ItemCell.js | 50 +++ .../JobSummary/components/cells/OrderCell.js | 29 ++ .../components/cells/RecordNumberCell.js | 12 + .../components/cells/SRSMarcCell.js | 5 + .../JobSummary/components/cells/TitleCell.js | 43 ++ .../JobSummary/components/cells/index.js | 10 + .../cells/tests/AuthorityCell.test.js | 104 +++++ .../components/cells/tests/ErrorCell.test.js | 58 +++ .../cells/tests/HoldingsCell.test.js | 91 +++++ .../cells/tests/InstanceCell.test.js | 70 ++++ .../cells/tests/InvoiceCell.test.js | 86 ++++ .../components/cells/tests/ItemCell.test.js | 77 ++++ .../components/cells/tests/OrderCell.test.js | 99 +++++ .../cells/tests/RecordNumberCell.test.js | 42 ++ .../cells/tests/SRSMarcCell.test.js | 30 ++ .../components/cells/tests/TitleCell.test.js | 85 ++++ src/routes/JobSummary/components/index.js | 1 + src/routes/JobSummary/components/utils.js | 65 +++ src/shared.css | 5 + 28 files changed, 1452 insertions(+), 376 deletions(-) create mode 100644 src/routes/JobSummary/components/cells/AuthorityCell.js create mode 100644 src/routes/JobSummary/components/cells/ErrorCell.js create mode 100644 src/routes/JobSummary/components/cells/HoldingsCell.js create mode 100644 src/routes/JobSummary/components/cells/InstanceCell.js create mode 100644 src/routes/JobSummary/components/cells/InvoiceCell.js create mode 100644 src/routes/JobSummary/components/cells/ItemCell.js create mode 100644 src/routes/JobSummary/components/cells/OrderCell.js create mode 100644 src/routes/JobSummary/components/cells/RecordNumberCell.js create mode 100644 src/routes/JobSummary/components/cells/SRSMarcCell.js create mode 100644 src/routes/JobSummary/components/cells/TitleCell.js create mode 100644 src/routes/JobSummary/components/cells/index.js create mode 100644 src/routes/JobSummary/components/cells/tests/AuthorityCell.test.js create mode 100644 src/routes/JobSummary/components/cells/tests/ErrorCell.test.js create mode 100644 src/routes/JobSummary/components/cells/tests/HoldingsCell.test.js create mode 100644 src/routes/JobSummary/components/cells/tests/InstanceCell.test.js create mode 100644 src/routes/JobSummary/components/cells/tests/InvoiceCell.test.js create mode 100644 src/routes/JobSummary/components/cells/tests/ItemCell.test.js create mode 100644 src/routes/JobSummary/components/cells/tests/OrderCell.test.js create mode 100644 src/routes/JobSummary/components/cells/tests/RecordNumberCell.test.js create mode 100644 src/routes/JobSummary/components/cells/tests/SRSMarcCell.test.js create mode 100644 src/routes/JobSummary/components/cells/tests/TitleCell.test.js create mode 100644 src/routes/JobSummary/components/utils.js diff --git a/CHANGELOG.md b/CHANGELOG.md index c35bad189..d2c4b12cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * Invoice field mapping: Add info icon to the Acq unit field (UIDATIMP-1427) * View all: Create hotlink from job profile name in log to the job profile details (UIDATIMP-1428) * Fix warnings in unit tests (part 2) (UIDATIMP-1437) +* Update Log details screen to support multiple holdings & items (UIDATIMP-1438) * Update Log JSON screen to support multiple holdings (UIDATIMP-1439) * Update Log JSON screen to support multiple items (UIDATIMP-1440) * DI Log: change Discarded to No action (UIDATIMP-1446) diff --git a/src/routes/JobSummary/JobSummary.js b/src/routes/JobSummary/JobSummary.js index e965655a5..e5a5338e3 100644 --- a/src/routes/JobSummary/JobSummary.js +++ b/src/routes/JobSummary/JobSummary.js @@ -283,6 +283,12 @@ JobSummaryComponent.manifest = Object.freeze({ path: 'change-manager/jobExecutions/:{id}', throwErrors: false, }, + locations: { + throwErrors: false, + type: 'okapi', + records: 'locations', + path: 'locations?limit=1000&query=cql.allRecords=1 sortby name', + } }); JobSummaryComponent.propTypes = { @@ -307,6 +313,7 @@ JobSummaryComponent.propTypes = { }), jobLogEntries: PropTypes.shape({ records: PropTypes.arrayOf(PropTypes.object).isRequired }), jobLog: PropTypes.shape({ records: PropTypes.arrayOf(PropTypes.object).isRequired }), + locations: PropTypes.shape({ records: PropTypes.arrayOf(PropTypes.object).isRequired }), }).isRequired, location: PropTypes.oneOfType([ PropTypes.shape({ diff --git a/src/routes/JobSummary/components/RecordsTable.js b/src/routes/JobSummary/components/RecordsTable.js index 06ca7a2d9..eaa67e597 100644 --- a/src/routes/JobSummary/components/RecordsTable.js +++ b/src/routes/JobSummary/components/RecordsTable.js @@ -1,12 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; -import { get } from 'lodash'; +import { get, isEmpty, groupBy } from 'lodash'; -import { FOLIO_RECORD_TYPES } from '@folio/stripes-data-transfer-components'; import { buildSortOrder, - createUrl, SORT_TYPES, } from '@folio/stripes-data-transfer-components/lib/utils'; import { SearchResults } from '@folio/stripes-data-transfer-components/lib/SearchResults'; @@ -18,21 +16,20 @@ import { import { NoValue, MCLPagingTypes, - TextLink, } from '@folio/stripes/components'; import { - RECORD_ACTION_STATUS, - RECORD_ACTION_STATUS_LABEL_IDS, -} from '../../../utils'; - -const getRecordActionStatusLabel = recordType => { - if (!recordType) return ; - - const labelId = RECORD_ACTION_STATUS_LABEL_IDS[recordType]; - - return ; -}; + RecordNumberCell, + TitleCell, + SRSMarcCell, + InstanceCell, + HoldingsCell, + ItemCell, + AuthorityCell, + OrderCell, + InvoiceCell, + ErrorCell, +} from '.'; export const RecordsTable = ({ mutator, @@ -40,7 +37,10 @@ export const RecordsTable = ({ location, history, resources, - resources: { jobLog: { records: jobLogRecords } }, + resources: { + jobLog: { records: jobLogRecords }, + locations, + }, source, maxSortKeys, defaultSort, @@ -53,21 +53,6 @@ export const RecordsTable = ({ // remove empty slots from sparse array const filteredJobLogEntriesRecords = resources.jobLogEntries?.records?.filter(record => !!record); - const getHotlinkCellFormatter = (isHotlink, entityLabel, path, entity) => { - if (isHotlink) { - return ( - - {entityLabel} - - ); - } - - return entityLabel; - }; - const transitionToParams = values => { const nsValues = mapNsKeys(values, nsParams); const url = buildUrl(location, nsValues); @@ -118,11 +103,12 @@ export const RecordsTable = ({ error: , }; const resultsFormatter = { - recordNumber: ({ sourceRecordOrder }) => { - if (isEdifactType) return sourceRecordOrder; - - return parseInt(sourceRecordOrder, 10) + 1; - }, + recordNumber: ({ sourceRecordOrder }) => ( + + ), title: ({ sourceRecordTitle, sourceRecordId, @@ -130,129 +116,154 @@ export const RecordsTable = ({ sourceRecordActionStatus, holdingsActionStatus, invoiceLineJournalRecordId, - }) => { - const jobExecutionId = filteredJobLogEntriesRecords[0]?.jobExecutionId; - const path = createUrl(`/data-import/log/${jobExecutionId}/${sourceRecordId}`, - isEdifactType ? { instanceLineId: invoiceLineJournalRecordId } : {}); - - const isHoldingsRecordImportFailed = sourceRecordType === FOLIO_RECORD_TYPES.MARC_HOLDINGS.type - && (sourceRecordActionStatus === RECORD_ACTION_STATUS.DISCARDED - || holdingsActionStatus === RECORD_ACTION_STATUS.DISCARDED); - - const title = isHoldingsRecordImportFailed - ? 'Holdings' - : sourceRecordTitle; - - return ( - - {title} - - ); - }, - srsMarcStatus: ({ sourceRecordActionStatus }) => getRecordActionStatusLabel(sourceRecordActionStatus), + }) => ( + + ), + srsMarcStatus: ({ sourceRecordActionStatus }) => , instanceStatus: ({ instanceActionStatus, sourceRecordId, - }) => { - const entityLabel = getRecordActionStatusLabel(instanceActionStatus); + }) => ( + + ), + holdingsStatus: ({ sourceRecordId }) => { const sourceRecord = jobLogRecords.find(item => item.sourceRecordId === sourceRecordId); - const entityId = sourceRecord?.relatedInstanceInfo.idList[0]; - const path = `/inventory/view/${entityId}`; + const holdingsInfo = sourceRecord?.relatedHoldingsInfo; + const itemInfo = sourceRecord?.relatedItemInfo; + const instanceId = sourceRecord?.relatedInstanceInfo.idList[0]; - const isPathCorrect = !!entityId; - const isHotlink = isPathCorrect && (instanceActionStatus === RECORD_ACTION_STATUS.CREATED - || instanceActionStatus === RECORD_ACTION_STATUS.UPDATED); + if (isEmpty(holdingsInfo)) return ; - return getHotlinkCellFormatter(isHotlink, entityLabel, path, 'instance'); - }, - holdingsStatus: ({ - holdingsActionStatus, - sourceRecordId, - }) => { - const entityLabel = getRecordActionStatusLabel(holdingsActionStatus); - const sourceRecord = jobLogRecords.find(item => item.sourceRecordId === sourceRecordId); - const instanceId = sourceRecord?.relatedInstanceInfo.idList[0]; - const holdingsId = sourceRecord?.relatedHoldingsInfo.idList[0]; - const path = `/inventory/view/${instanceId}/${holdingsId}`; + holdingsInfo.forEach(holdings => { + const isDiscarded = holdings.actionStatus === 'DISCARDED'; + const holdingsId = holdings.id; - const isPathCorrect = !!(instanceId && holdingsId); - const isHotlink = isPathCorrect && (holdingsActionStatus === RECORD_ACTION_STATUS.CREATED - || holdingsActionStatus === RECORD_ACTION_STATUS.UPDATED); + if (isDiscarded && !itemInfo.find(item => item.holdingsId === holdingsId)) { + itemInfo.push({ holdingsId, error: true }); + } + }); - return getHotlinkCellFormatter(isHotlink, entityLabel, path, 'holdings'); + return ( + + ); }, - itemStatus: ({ - itemActionStatus, - sourceRecordId, - }) => { - const entityLabel = getRecordActionStatusLabel(itemActionStatus); + itemStatus: ({ sourceRecordId }) => { const sourceRecord = jobLogRecords.find(item => item.sourceRecordId === sourceRecordId); + const holdingsData = sourceRecord?.relatedHoldingsInfo; + const itemData = sourceRecord?.relatedItemInfo; const instanceId = sourceRecord?.relatedInstanceInfo.idList[0]; - const holdingsId = sourceRecord?.relatedHoldingsInfo.idList[0]; - const itemId = sourceRecord?.relatedItemInfo.idList[0]; - const path = `/inventory/view/${instanceId}/${holdingsId}/${itemId}`; - const isPathCorrect = !!(instanceId && holdingsId && itemId); - const isHotlink = isPathCorrect && (itemActionStatus === RECORD_ACTION_STATUS.CREATED - || itemActionStatus === RECORD_ACTION_STATUS.UPDATED); + if (isEmpty(itemData)) return ; - return getHotlinkCellFormatter(isHotlink, entityLabel, path, 'item'); + const groupedItemData = groupBy(itemData, 'holdingsId'); + const sortedItemData = holdingsData.map(holdings => groupedItemData[holdings.id]); + + return ( + + ); }, authorityStatus: ({ authorityActionStatus, sourceRecordId, }) => { - const entityLabel = getRecordActionStatusLabel(authorityActionStatus); const sourceRecord = jobLogRecords.find(item => item.sourceRecordId === sourceRecordId); - const authorityId = sourceRecord?.relatedAuthorityInfo.idList[0]; - const path = `/marc-authorities/authorities/${authorityId}`; + const holdingsData = sourceRecord?.relatedHoldingsInfo; + const itemData = sourceRecord?.relatedItemInfo; - const isPathCorrect = !!authorityId; - const isHotlink = isPathCorrect && (authorityActionStatus === RECORD_ACTION_STATUS.CREATED - || authorityActionStatus === RECORD_ACTION_STATUS.UPDATED); + const groupedItemData = groupBy(itemData, 'holdingsId'); + const sortedItemData = holdingsData.map(holdings => groupedItemData[holdings.id]); - return getHotlinkCellFormatter(isHotlink, entityLabel, path, 'authority'); + return ( + + ); }, orderStatus: ({ poLineActionStatus, sourceRecordId, }) => { - const entityLabel = getRecordActionStatusLabel(poLineActionStatus); const sourceRecord = jobLogRecords.find(item => item.sourceRecordId === sourceRecordId); - const orderLineId = sourceRecord?.relatedPoLineInfo.idList[0]; - const path = `/orders/lines/view/${orderLineId}`; + const holdingsData = sourceRecord?.relatedHoldingsInfo; + const itemData = sourceRecord?.relatedItemInfo; - const isPathCorrect = !!orderLineId; - const isHotlink = isPathCorrect && poLineActionStatus === RECORD_ACTION_STATUS.CREATED; + const groupedItemData = groupBy(itemData, 'holdingsId'); + const sortedItemData = holdingsData.map(holdings => groupedItemData[holdings.id]); - return getHotlinkCellFormatter(isHotlink, entityLabel, path, 'order'); + return ( + + ); }, invoiceStatus: ({ invoiceActionStatus, sourceRecordId, sourceRecordOrder, }) => { - const entityLabel = getRecordActionStatusLabel(invoiceActionStatus); - const sourceRecord = jobLogRecords.find(item => { - const isIdEqual = item.sourceRecordId === sourceRecordId; - const isOrderEqual = item.relatedInvoiceLineInfo?.fullInvoiceLineNumber === sourceRecordOrder; + const sourceRecord = jobLogRecords.find(item => item.sourceRecordId === sourceRecordId); + const holdingsData = sourceRecord?.relatedHoldingsInfo; + const itemData = sourceRecord?.relatedItemInfo; - return isIdEqual && isOrderEqual; - }); - const invoiceId = sourceRecord?.relatedInvoiceInfo.idList[0]; - const invoiceLineId = sourceRecord?.relatedInvoiceLineInfo.id; - const path = `/invoice/view/${invoiceId}/line/${invoiceLineId}/view`; + const groupedItemData = groupBy(itemData, 'holdingsId'); + const sortedItemData = holdingsData.map(holdings => groupedItemData[holdings.id]); + + return ( + + ); + }, + error: ({ + error, + sourceRecordId, + }) => { + const sourceRecord = jobLogRecords.find(item => item.sourceRecordId === sourceRecordId); + const holdingsData = sourceRecord?.relatedHoldingsInfo; + const itemData = sourceRecord?.relatedItemInfo; - const isPathCorrect = !!(invoiceId && invoiceLineId); - const isHotlink = isPathCorrect && (invoiceActionStatus === RECORD_ACTION_STATUS.CREATED); + const groupedItemData = groupBy(itemData, 'holdingsId'); + const sortedItemData = holdingsData.map(holdings => groupedItemData[holdings.id]); - return getHotlinkCellFormatter(isHotlink, entityLabel, path, 'invoice'); + return ( + + ); }, - error: ({ error }) => (error ? : ''), }; return ( @@ -272,6 +283,8 @@ export const RecordsTable = ({ columnWidths={{ recordNumber: '90px', title: '30%', + holdingsStatus: '180px', + itemStatus: '180px', }} /> ); @@ -281,6 +294,7 @@ RecordsTable.propTypes = { resources: PropTypes.shape({ jobLogEntries: PropTypes.shape({ records: PropTypes.arrayOf(PropTypes.object).isRequired }), jobLog: PropTypes.shape({ records: PropTypes.arrayOf(PropTypes.object).isRequired }), + locations: PropTypes.shape({ records: PropTypes.arrayOf(PropTypes.object).isRequired }), query: PropTypes.object, }).isRequired, mutator: PropTypes.object.isRequired, diff --git a/src/routes/JobSummary/components/RecordsTable.test.js b/src/routes/JobSummary/components/RecordsTable.test.js index af3a7fea4..199812672 100644 --- a/src/routes/JobSummary/components/RecordsTable.test.js +++ b/src/routes/JobSummary/components/RecordsTable.test.js @@ -1,5 +1,4 @@ import React from 'react'; -import { fireEvent } from '@testing-library/react'; import faker from 'faker'; import { noop } from 'lodash'; import { BrowserRouter } from 'react-router-dom'; @@ -20,218 +19,171 @@ window.open.mockReturnValue({ focus: jest.fn() }); const history = createMemoryHistory(); const firstRecordJobExecutionId = faker.random.uuid(); -const sourceRecordsIds = [ - faker.random.uuid(), - faker.random.uuid(), - faker.random.uuid(), - faker.random.uuid(), - faker.random.uuid(), - faker.random.uuid(), - faker.random.uuid(), -]; -const instanceId = faker.random.uuid(); -const authorityId = faker.random.uuid(); const jobLogEntriesRecords = [{ sourceRecordActionStatus: 'CREATED', sourceRecordType: 'MARC_BIBLIOGRAPHIC', instanceActionStatus: 'CREATED', - authorityActionStatus: 'CREATED', + sourceRecordTitle: 'Test record 1', jobExecutionId: firstRecordJobExecutionId, - sourceRecordId: sourceRecordsIds[0], + sourceRecordId: '59138d56-bc81-4f66-9f72-f57f53621111', sourceRecordOrder: '0', - sourceRecordTitle: 'Test item 1', -}, { - instanceActionStatus: 'UPDATED', - authorityActionStatus: 'UPDATED', - sourceRecordType: 'MARC_BIBLIOGRAPHIC', - jobExecutionId: faker.random.uuid(), - sourceRecordId: sourceRecordsIds[1], - sourceRecordOrder: '1', - sourceRecordTitle: 'Test item 2', -}, { - holdingsActionStatus: 'MULTIPLE', - sourceRecordType: 'MARC_BIBLIOGRAPHIC', - jobExecutionId: faker.random.uuid(), - sourceRecordId: sourceRecordsIds[2], - sourceRecordOrder: '2', - sourceRecordTitle: 'Test item 3', -}, { - itemActionStatus: 'DISCARDED', - sourceRecordType: 'MARC_BIBLIOGRAPHIC', - jobExecutionId: faker.random.uuid(), - sourceRecordId: sourceRecordsIds[3], - sourceRecordOrder: '3', - sourceRecordTitle: 'Test item 4', - error: 'Error message', -}, { - sourceRecordActionStatus: 'CREATED', - holdingsActionStatus: 'CREATED', - holdingsRecordHridList: ['holdingsHrid1'], - sourceRecordType: 'MARC_HOLDINGS', - jobExecutionId: faker.random.uuid(), - sourceRecordId: sourceRecordsIds[4], - sourceRecordOrder: '4', - sourceRecordTitle: 'Test item 5', -}, { - sourceRecordActionStatus: 'DISCARDED', - holdingsActionStatus: 'DISCARDED', - holdingsRecordHridList: ['holdingsHrid2'], - sourceRecordType: 'MARC_HOLDINGS', - jobExecutionId: faker.random.uuid(), - sourceRecordId: sourceRecordsIds[5], - sourceRecordOrder: '5', - sourceRecordTitle: 'Test item 6', }, { sourceRecordActionStatus: 'CREATED', sourceRecordType: 'MARC_BIBLIOGRAPHIC', - poLineActionStatus: 'CREATED', - jobExecutionId: faker.random.uuid(), - sourceRecordId: sourceRecordsIds[6], - sourceRecordOrder: '6', - sourceRecordTitle: 'Test item 7', + instanceActionStatus: 'CREATED', + sourceRecordTitle: 'Test record 2', + jobExecutionId: firstRecordJobExecutionId, + sourceRecordId: '59138d56-bc81-4f66-9f72-f57f53629646', + sourceRecordOrder: '0', }]; + const jobLogEntriesResources = { jobLogEntries: { records: jobLogEntriesRecords, other: { totalRecords: jobLogEntriesRecords.length } }, }; + const jobLogRecords = [{ - sourceRecordId: sourceRecordsIds[0], - sourceRecordOrder: '0', - sourceRecordTitle: 'Test item 1', - relatedInstanceInfo: { - actionStatus: 'CREATED', - idList: [instanceId], - }, - relatedHoldingsInfo: { - actionStatus: 'CREATED', - idList: [faker.random.uuid()], - }, - relatedItemInfo: { - actionStatus: 'CREATED', - idList: [faker.random.uuid()], - }, + jobExecutionId : '467d7627-c0db-4fb7-b333-4b4983dbf781', + sourceRecordId : '59138d56-bc81-4f66-9f72-f57f53621111', + sourceRecordOrder : 0, + sourceRecordTitle : 'Test record 1', + sourceRecordActionStatus : 'CREATED', + instanceActionStatus: 'CREATED', + error : '', + relatedInstanceInfo : { + actionStatus : 'CREATED', + idList : ['720031b9-a792-4936-963c-a7b63fb96574'], + hridList : ['in00000000014'], + error : '' + }, + relatedHoldingsInfo : [], + relatedItemInfo : [], relatedAuthorityInfo: { - actionStatus: 'CREATED', - idList: [authorityId], + idList : [], + hridList : [] }, relatedPoLineInfo: { - actionStatus: 'CREATED', - idList: [faker.random.uuid()], - }, -}, { - sourceRecordId: sourceRecordsIds[1], - sourceRecordOrder: '1', - sourceRecordTitle: 'Test item 2', - relatedInstanceInfo: { - actionStatus: 'UPDATED', - idList: [instanceId], - }, - relatedHoldingsInfo: { - actionStatus: 'UPDATED', - idList: [faker.random.uuid()], + idList: [], + hridList : [] }, - relatedItemInfo: { - actionStatus: 'UPDATED', - idList: [faker.random.uuid()], - }, - relatedAuthorityInfo: { - actionStatus: 'UPDATED', - idList: [authorityId], - }, - relatedPoLineInfo: { - actionStatus: 'CREATED', - idList: [faker.random.uuid()], + relatedInvoiceInfo : { + idList : [], + hridList : [] }, + relatedInvoiceLineInfo : { } }, { - sourceRecordId: sourceRecordsIds[2], - sourceRecordOrder: '2', - sourceRecordTitle: 'Test item 1', - relatedInstanceInfo: { - actionStatus: 'MULTIPLE', - idList: [faker.random.uuid()], - }, - relatedHoldingsInfo: { - actionStatus: 'MULTIPLE', - idList: [faker.random.uuid()], - }, - relatedItemInfo: { - actionStatus: 'MULTIPLE', - idList: [faker.random.uuid()], - }, - relatedAuthorityInfo: { - actionStatus: 'MULTIPLE', - idList: [faker.random.uuid()], - }, - relatedPoLineInfo: { + jobExecutionId : '467d7627-c0db-4fb7-b333-4b4983dbf781', + sourceRecordId : '59138d56-bc81-4f66-9f72-f57f53629646', + sourceRecordOrder : 4, + sourceRecordTitle : 'Test record 2', + sourceRecordActionStatus : 'CREATED', + instanceActionStatus: 'CREATED', + error : '', + relatedInstanceInfo : { + actionStatus : 'CREATED', + idList : ['720031b9-a792-4936-963c-a7b63fb96574'], + hridList : ['in00000000014'], + error : '' + }, + relatedHoldingsInfo : [{ + actionStatus : 'CREATED', + id : 'f648c370-d9d6-432c-a502-b8eb718f867c', + permanentLocationId: '53cf956f-c1df-410b-8bea-27f712cca7c0', + hrid : 'ho00000000017', + error : '' + }, { actionStatus: 'CREATED', - idList: [faker.random.uuid()], - }, -}, { - sourceRecordId: sourceRecordsIds[3], - sourceRecordOrder: '3', - sourceRecordTitle: 'Test item 4', - relatedInstanceInfo: { - actionStatus: 'DISCARDED', - idList: [faker.random.uuid()], - }, - relatedHoldingsInfo: { - actionStatus: 'DISCARDED', - idList: [faker.random.uuid()], - }, - relatedItemInfo: { - actionStatus: 'DISCARDED', - idList: [faker.random.uuid()], - }, + id : '5cadf17f-eb72-475c-a2e0-7e56f54f0000', + permanentLocationId: '758258bc-ecc1-41b8-abca-f7b610822ffd', + hrid : 'ho00000000018', + error : '' + }, { + actionStatus : 'DISCARDED', + id : '5cadf17f-eb72-475c-a2e0-7e56f54fd3b4', + permanentLocationId: 'fcd64ce1-6995-48f0-840e-89ffa2288371', + hrid : 'ho00000000014', + error : '' + }], + relatedItemInfo : [{ + actionStatus : 'CREATED', + id : '37f20cb1-f60c-4195-80a1-00a16c4af5cb', + hrid : 'it00000000015', + holdingsId : 'f648c370-d9d6-432c-a502-b8eb718f867c', + error : '' + }, { + actionStatus : 'DISCARDED', + id : 'ccd19bf0-add1-46bb-899b-c457fd448b51', + hrid : 'it00000000016', + holdingsId : 'f648c370-d9d6-432c-a502-b8eb718f867c', + error : 'test error' + }, { + actionStatus : 'CREATED', + id : 'ccd19bf0-add1-46bb-899b-c457fd441111', + hrid : 'it00000000019', + holdingsId : '5cadf17f-eb72-475c-a2e0-7e56f54f0000', + error : '' + }, { + actionStatus : 'CREATED', + id : '3bcfe427-a747-405f-b3f3-1d842ffb2222', + hrid : 'it00000000018', + holdingsId : '5cadf17f-eb72-475c-a2e0-7e56f54f0000', + error : '' + }, { + actionStatus : 'CREATED', + id : '3bcfe427-a747-405f-b3f3-1d842ffb49c6', + hrid : 'it00000000014', + holdingsId : 'f648c370-d9d6-432c-a502-b8eb718f867c', + error : '' + }], relatedAuthorityInfo: { - actionStatus: 'DISCARDED', - idList: [faker.random.uuid()], + idList : [], + hridList : [] }, relatedPoLineInfo: { - actionStatus: 'CREATED', - idList: [faker.random.uuid()], - }, -}, { - sourceRecordId: sourceRecordsIds[6], - sourceRecordOrder: '6', - sourceRecordTitle: 'Test item 7', - relatedInstanceInfo: { - actionStatus: 'DISCARDED', - idList: [faker.random.uuid()], + idList: [], + hridList : [] }, - relatedHoldingsInfo: { - actionStatus: 'DISCARDED', - idList: [faker.random.uuid()], - }, - relatedItemInfo: { - actionStatus: 'DISCARDED', - idList: [faker.random.uuid()], - }, - relatedAuthorityInfo: { - actionStatus: 'DISCARDED', - idList: [faker.random.uuid()], - }, - relatedPoLineInfo: { - actionStatus: 'CREATED', - idList: [faker.random.uuid()], + relatedInvoiceInfo : { + idList : [], + hridList : [] }, + relatedInvoiceLineInfo : { } }]; + const jobLogResources = { jobLog: { records: jobLogRecords, other: { totalRecords: jobLogRecords.length } }, }; + +const locations = { + records: [{ + id: '53cf956f-c1df-410b-8bea-27f712cca7c0', + code: 'KU/CC/DI/A', + }, { + id: 'fcd64ce1-6995-48f0-840e-89ffa2288371', + code: 'KU/CC/DI/M', + }, { + id: '758258bc-ecc1-41b8-abca-f7b610822ffd', + code: 'KU/CC/DI/O', + }] +}; + const resources = { ...jobLogEntriesResources, ...jobLogResources, + locations, }; + const mutator = { resultCount: { replace: () => {} }, resultOffset: { replace: () => {} }, }; + const source = { records: () => resources.jobLogEntries.records, pending: () => false, @@ -240,11 +192,11 @@ const source = { fetchOffset: noop, }; -const renderRecordsTable = ({ isEdifactType = false }) => { +const renderRecordsTable = () => { const component = ( { }); it('should have proper columns', () => { - const { getByText } = renderRecordsTable({}); + const { getByText } = renderRecordsTable(); /* * Get "Holdings" and "Error" labels by query selector instead of by "getByText" because there are * "Holdings" / "Error" column labels and "Holdings" / "Error" messages in cells on the page @@ -287,91 +239,10 @@ describe('RecordsTable component', () => { expect(errorColumn.innerHTML).toEqual('Error'); }); - describe('record order field', () => { - describe('for EDIFACT data type', () => { - it('should display order as it is', () => { - const { container } = renderRecordsTable({ isEdifactType: true }); - - const cells = container.querySelectorAll('[role="gridcell"]'); - const firstRowRecordOrder = cells[0].innerHTML; - - expect(firstRowRecordOrder).toEqual('0'); - }); - }); - - describe('for MARC data type', () => { - it('should display incremented order', () => { - const { container } = renderRecordsTable({}); - - const cells = container.querySelectorAll('[role="gridcell"]'); - const firstRowRecordOrder = cells[0].innerHTML; - - expect(firstRowRecordOrder).toEqual('1'); - }); - }); - }); - - describe('when clicking on a record title', () => { - it('should navigate to the log details screen', () => { - const { getByText } = renderRecordsTable({}); - - expect(getByText('Test item 1').href).toContain(`/data-import/log/${firstRecordJobExecutionId}/${sourceRecordsIds[0]}`); - }); - }); - - describe('when action status is CREATED', () => { - it('the instance value should be a hotlink', () => { - const { container } = renderRecordsTable({}); - - fireEvent.click(container.querySelector('[data-row-index="row-0"] [data-test-entity-name="instance"]')); - - expect(window.location.href).toContain(`/inventory/view/${instanceId}`); - }); - - it('the authority value should be a hotlink', () => { - const { container } = renderRecordsTable({}); - - fireEvent.click(container.querySelector('[data-row-index="row-0"] [data-test-entity-name="authority"]')); - - expect(window.location.href).toContain(`/marc-authorities/authorities/${authorityId}`); - }); - }); - - describe('when action status is UPDATED', () => { - it('the instance value should be a hotlink', () => { - const { container } = renderRecordsTable({}); - - fireEvent.click(container.querySelector('[data-row-index="row-1"] [data-test-entity-name="instance"]')); - - expect(window.location.href).toContain(`/inventory/view/${instanceId}`); - }); - - it('the authority value should be a hotlink', () => { - const { container } = renderRecordsTable({}); - - fireEvent.click(container.querySelector('[data-row-index="row-1"] [data-test-entity-name="authority"]')); - - expect(window.location.href).toContain(`/marc-authorities/authorities/${authorityId}`); - }); - }); - - describe('when action status is MULTIPLE', () => { - it('the value should be a text', () => { - const { getByText } = renderRecordsTable({}); - - expect(getByText('Multiple')).not.toHaveAttribute('href'); - }); - }); - - describe('when action status is DISCARDED', () => { - it('the value should be a text', () => { - const { getAllByText } = renderRecordsTable({}); - - const noActionStatuses = getAllByText('No action'); + it('should render records', () => { + const { getByText } = renderRecordsTable(); - noActionStatuses.forEach(status => { - expect(status).not.toHaveAttribute('href'); - }); - }); + expect(getByText('Test record 1')).toBeInTheDocument(); + expect(getByText('Test record 2')).toBeInTheDocument(); }); }); diff --git a/src/routes/JobSummary/components/cells/AuthorityCell.js b/src/routes/JobSummary/components/cells/AuthorityCell.js new file mode 100644 index 000000000..f556e6467 --- /dev/null +++ b/src/routes/JobSummary/components/cells/AuthorityCell.js @@ -0,0 +1,44 @@ +import PropTypes from 'prop-types'; +import { isEmpty } from 'lodash'; +import { + getRecordActionStatusLabel, + getHotlinkCellFormatter, + fillCellWithNoValues, +} from '../utils'; + +import { RECORD_ACTION_STATUS } from '../../../../utils'; + +export const AuthorityCell = ({ + authorityActionStatus, + sourceRecordId, + jobLogRecords, + sortedItemData, +}) => { + if (!authorityActionStatus && !isEmpty(sortedItemData)) { + return fillCellWithNoValues(sortedItemData); + } + + const entityLabel = getRecordActionStatusLabel(authorityActionStatus); + const sourceRecord = jobLogRecords.find(item => item.sourceRecordId === sourceRecordId); + const authorityId = sourceRecord?.relatedAuthorityInfo.idList[0]; + const path = `/marc-authorities/authorities/${authorityId}`; + + const isPathCorrect = !!authorityId; + const isHotlink = isPathCorrect && (authorityActionStatus === RECORD_ACTION_STATUS.CREATED + || authorityActionStatus === RECORD_ACTION_STATUS.UPDATED); + + return getHotlinkCellFormatter(isHotlink, entityLabel, path, 'authority'); +}; + +AuthorityCell.propTypes = { + sourceRecordId: PropTypes.string.isRequired, + jobLogRecords: PropTypes.arrayOf(PropTypes.object), + sortedItemData: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.object)), + authorityActionStatus: PropTypes.string, +}; + +AuthorityCell.defaultProps = { + jobLogRecords: [], + sortedItemData: [], + authorityActionStatus: '', +}; diff --git a/src/routes/JobSummary/components/cells/ErrorCell.js b/src/routes/JobSummary/components/cells/ErrorCell.js new file mode 100644 index 000000000..103896467 --- /dev/null +++ b/src/routes/JobSummary/components/cells/ErrorCell.js @@ -0,0 +1,26 @@ +import { FormattedMessage } from 'react-intl'; +import PropTypes from 'prop-types'; +import { isEmpty } from 'lodash'; + +import { fillCellWithNoValues } from '../utils'; + +export const ErrorCell = ({ + error, + sortedItemData +}) => { + if (!error && !isEmpty(sortedItemData)) { + return fillCellWithNoValues(sortedItemData, true); + } + + return error ? : ''; +}; + +ErrorCell.propTypes = { + error: PropTypes.string, + sortedItemData: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.object)), +}; + +ErrorCell.defaultProps = { + error: '', + sortedItemData: [], +}; diff --git a/src/routes/JobSummary/components/cells/HoldingsCell.js b/src/routes/JobSummary/components/cells/HoldingsCell.js new file mode 100644 index 000000000..17b07133b --- /dev/null +++ b/src/routes/JobSummary/components/cells/HoldingsCell.js @@ -0,0 +1,62 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { + getRecordActionStatusLabel, + getHotlinkCellFormatter, + BaseLineCell, +} from '../utils'; + +import { RECORD_ACTION_STATUS } from '../../../../utils'; + +export const HoldingsCell = ({ + instanceId, + holdingsInfo, + itemInfo, + locations, +}) => { + const holdingsInfoCell = holdingsInfo.map((holdings, index) => { + const entityLabel = getRecordActionStatusLabel(holdings.actionStatus); + const holdingsId = holdings.id; + const path = `/inventory/view/${instanceId}/${holdingsId}`; + const locationId = holdings.permanentLocationId; + const isDiscarded = holdings.actionStatus === 'DISCARDED'; + + const locationCode = locations.find(locationItem => locationId === locationItem.id)?.code; + + const itemForHoldingsCount = itemInfo.filter(item => item.holdingsId === holdings.id).length; + + const isPathCorrect = !!(instanceId && holdingsId); + const isHotlink = isPathCorrect && (holdings.actionStatus === RECORD_ACTION_STATUS.CREATED + || holdings.actionStatus === RECORD_ACTION_STATUS.UPDATED); + + const spacing = !isDiscarded ? Array.from({ length: itemForHoldingsCount }, (_, i) =>
) :
; + + return ( +
+ {getHotlinkCellFormatter(isHotlink, entityLabel, path, 'holdings')} + {!isDiscarded ? ` (${locationCode})` : ''} + {spacing} +
+ ); + }); + + return ( + + {holdingsInfoCell} + + ); +}; + +HoldingsCell.propTypes = { + locations: PropTypes.arrayOf(PropTypes.object).isRequired, + instanceId: PropTypes.string, + holdingsInfo: PropTypes.arrayOf(PropTypes.object), + itemInfo: PropTypes.arrayOf(PropTypes.object), +}; + +HoldingsCell.defaultProps = { + instanceId: '', + holdingsInfo: [], + itemInfo: [], +}; diff --git a/src/routes/JobSummary/components/cells/InstanceCell.js b/src/routes/JobSummary/components/cells/InstanceCell.js new file mode 100644 index 000000000..8cbe63f56 --- /dev/null +++ b/src/routes/JobSummary/components/cells/InstanceCell.js @@ -0,0 +1,37 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { + getRecordActionStatusLabel, + getHotlinkCellFormatter, +} from '../utils'; + +import { RECORD_ACTION_STATUS } from '../../../../utils'; + +export const InstanceCell = ({ + instanceActionStatus, + sourceRecordId, + jobLogRecords, +}) => { + const entityLabel = getRecordActionStatusLabel(instanceActionStatus); + const sourceRecord = jobLogRecords.find(item => item.sourceRecordId === sourceRecordId); + const entityId = sourceRecord?.relatedInstanceInfo.idList[0]; + const path = `/inventory/view/${entityId}`; + + const isPathCorrect = !!entityId; + const isHotlink = isPathCorrect && (instanceActionStatus === RECORD_ACTION_STATUS.CREATED + || instanceActionStatus === RECORD_ACTION_STATUS.UPDATED); + + return getHotlinkCellFormatter(isHotlink, entityLabel, path, 'instance'); +}; + +InstanceCell.propTypes = { + sourceRecordId: PropTypes.string.isRequired, + jobLogRecords: PropTypes.arrayOf(PropTypes.object), + instanceActionStatus: PropTypes.string, +}; + +InstanceCell.defaultProps = { + jobLogRecords: [], + instanceActionStatus: '', +}; diff --git a/src/routes/JobSummary/components/cells/InvoiceCell.js b/src/routes/JobSummary/components/cells/InvoiceCell.js new file mode 100644 index 000000000..b2547bc2f --- /dev/null +++ b/src/routes/JobSummary/components/cells/InvoiceCell.js @@ -0,0 +1,52 @@ +import PropTypes from 'prop-types'; +import { isEmpty } from 'lodash'; +import { + getRecordActionStatusLabel, + getHotlinkCellFormatter, + fillCellWithNoValues, +} from '../utils'; + +import { RECORD_ACTION_STATUS } from '../../../../utils'; + +export const InvoiceCell = ({ + invoiceActionStatus, + sourceRecordId, + jobLogRecords, + sortedItemData, + sourceRecordOrder, +}) => { + if (!invoiceActionStatus && !isEmpty(sortedItemData)) { + return fillCellWithNoValues(sortedItemData); + } + + const entityLabel = getRecordActionStatusLabel(invoiceActionStatus); + const sourceRecord = jobLogRecords.find(item => { + const isIdEqual = item.sourceRecordId === sourceRecordId; + const isOrderEqual = item.relatedInvoiceLineInfo?.fullInvoiceLineNumber === sourceRecordOrder; + + return isIdEqual && isOrderEqual; + }); + + const invoiceId = sourceRecord?.relatedInvoiceInfo.idList[0]; + const invoiceLineId = sourceRecord?.relatedInvoiceLineInfo.id; + const path = `/invoice/view/${invoiceId}/line/${invoiceLineId}/view`; + + const isPathCorrect = !!(invoiceId && invoiceLineId); + const isHotlink = isPathCorrect && (invoiceActionStatus === RECORD_ACTION_STATUS.CREATED); + + return getHotlinkCellFormatter(isHotlink, entityLabel, path, 'invoice'); +}; + +InvoiceCell.propTypes = { + sourceRecordId: PropTypes.string.isRequired, + sourceRecordOrder: PropTypes.number.isRequired, + jobLogRecords: PropTypes.arrayOf(PropTypes.object), + sortedItemData: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.object)), + invoiceActionStatus: PropTypes.string, +}; + +InvoiceCell.defaultProps = { + jobLogRecords: [], + sortedItemData: [], + invoiceActionStatus: '', +}; diff --git a/src/routes/JobSummary/components/cells/ItemCell.js b/src/routes/JobSummary/components/cells/ItemCell.js new file mode 100644 index 000000000..28280861d --- /dev/null +++ b/src/routes/JobSummary/components/cells/ItemCell.js @@ -0,0 +1,50 @@ +import React from 'react'; + +import { + getRecordActionStatusLabel, + getHotlinkCellFormatter, + BaseLineCell, +} from '../utils'; + +import { RECORD_ACTION_STATUS } from '../../../../utils'; + +export const ItemCell = ({ + sortedItemData, + instanceId, +}) => { + const itemsInfoCell = sortedItemData.map((sortedItems, itemIndex) => { + const groupOfItems = sortedItems.map((item, index) => { + const entityLabel = getRecordActionStatusLabel(item.actionStatus); + const holdingsId = item.holdingsId; + const itemId = item.id; + const path = `/inventory/view/${instanceId}/${holdingsId}/${itemId}`; + const isDiscarded = item.actionStatus === 'DISCARDED'; + + const hrId = item.hrid; + + const isPathCorrect = !!(instanceId && holdingsId && itemId); + const isHotlink = isPathCorrect && (item.actionStatus === RECORD_ACTION_STATUS.CREATED + || item.actionStatus === RECORD_ACTION_STATUS.UPDATED); + + return ( + + {getHotlinkCellFormatter(isHotlink, entityLabel, path, 'item')} + {!isDiscarded && hrId ? ` (${hrId})` : ''} +
+
+ ); + }); + + return ( +
+ {groupOfItems} +
+ ); + }); + + return ( + + {itemsInfoCell} + + ); +}; diff --git a/src/routes/JobSummary/components/cells/OrderCell.js b/src/routes/JobSummary/components/cells/OrderCell.js new file mode 100644 index 000000000..454dd7c9b --- /dev/null +++ b/src/routes/JobSummary/components/cells/OrderCell.js @@ -0,0 +1,29 @@ +import { isEmpty } from 'lodash'; +import { + getRecordActionStatusLabel, + getHotlinkCellFormatter, + fillCellWithNoValues, +} from '../utils'; + +import { RECORD_ACTION_STATUS } from '../../../../utils'; + +export const OrderCell = ({ + poLineActionStatus, + sourceRecordId, + jobLogRecords, + sortedItemData, +}) => { + if (!poLineActionStatus && !isEmpty(sortedItemData)) { + return fillCellWithNoValues(sortedItemData); + } + + const entityLabel = getRecordActionStatusLabel(poLineActionStatus); + const sourceRecord = jobLogRecords.find(item => item.sourceRecordId === sourceRecordId); + const orderLineId = sourceRecord?.relatedPoLineInfo.idList[0]; + const path = `/orders/lines/view/${orderLineId}`; + + const isPathCorrect = !!orderLineId; + const isHotlink = isPathCorrect && poLineActionStatus === RECORD_ACTION_STATUS.CREATED; + + return getHotlinkCellFormatter(isHotlink, entityLabel, path, 'order'); +}; diff --git a/src/routes/JobSummary/components/cells/RecordNumberCell.js b/src/routes/JobSummary/components/cells/RecordNumberCell.js new file mode 100644 index 000000000..06acbc963 --- /dev/null +++ b/src/routes/JobSummary/components/cells/RecordNumberCell.js @@ -0,0 +1,12 @@ +import React from 'react'; + +import { BaseLineCell } from '../utils'; + +export const RecordNumberCell = ({ + isEdifactType, + sourceRecordOrder, +}) => { + if (isEdifactType) return {sourceRecordOrder}; + + return {parseInt(sourceRecordOrder, 10) + 1}; +}; diff --git a/src/routes/JobSummary/components/cells/SRSMarcCell.js b/src/routes/JobSummary/components/cells/SRSMarcCell.js new file mode 100644 index 000000000..404124b88 --- /dev/null +++ b/src/routes/JobSummary/components/cells/SRSMarcCell.js @@ -0,0 +1,5 @@ +import React from 'react'; + +import { getRecordActionStatusLabel } from '../utils'; + +export const SRSMarcCell = ({ sourceRecordActionStatus }) => getRecordActionStatusLabel(sourceRecordActionStatus); diff --git a/src/routes/JobSummary/components/cells/TitleCell.js b/src/routes/JobSummary/components/cells/TitleCell.js new file mode 100644 index 000000000..ff77d9a18 --- /dev/null +++ b/src/routes/JobSummary/components/cells/TitleCell.js @@ -0,0 +1,43 @@ +import React from 'react'; + +import { TextLink } from '@folio/stripes/components'; +import { FOLIO_RECORD_TYPES } from '@folio/stripes-data-transfer-components'; +import { createUrl } from '@folio/stripes-data-transfer-components/lib/utils'; + +import { BaseLineCell } from '../utils'; +import { RECORD_ACTION_STATUS } from '../../../../utils'; + +export const TitleCell = ({ + isEdifactType, + sourceRecordId, + sourceRecordType, + sourceRecordTitle, + holdingsActionStatus, + sourceRecordActionStatus, + invoiceLineJournalRecordId, + jobLogEntriesRecords, +}) => { + const jobExecutionId = jobLogEntriesRecords[0]?.jobExecutionId; + const path = createUrl(`/data-import/log/${jobExecutionId}/${sourceRecordId}`, + isEdifactType ? { instanceLineId: invoiceLineJournalRecordId } : {}); + + const isHoldingsRecordImportFailed = sourceRecordType === FOLIO_RECORD_TYPES.MARC_HOLDINGS.type + && (sourceRecordActionStatus === RECORD_ACTION_STATUS.DISCARDED + || holdingsActionStatus === RECORD_ACTION_STATUS.DISCARDED); + + const title = isHoldingsRecordImportFailed + ? 'Holdings' + : sourceRecordTitle; + + return ( + + + {title} + + + ); +}; diff --git a/src/routes/JobSummary/components/cells/index.js b/src/routes/JobSummary/components/cells/index.js new file mode 100644 index 000000000..2371eedf5 --- /dev/null +++ b/src/routes/JobSummary/components/cells/index.js @@ -0,0 +1,10 @@ +export * from './RecordNumberCell'; +export * from './TitleCell'; +export * from './SRSMarcCell'; +export * from './InstanceCell'; +export * from './HoldingsCell'; +export * from './ItemCell'; +export * from './AuthorityCell'; +export * from './OrderCell'; +export * from './InvoiceCell'; +export * from './ErrorCell'; diff --git a/src/routes/JobSummary/components/cells/tests/AuthorityCell.test.js b/src/routes/JobSummary/components/cells/tests/AuthorityCell.test.js new file mode 100644 index 000000000..82a50cab0 --- /dev/null +++ b/src/routes/JobSummary/components/cells/tests/AuthorityCell.test.js @@ -0,0 +1,104 @@ +import React from 'react'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { runAxeTest } from '@folio/stripes-testing'; +import faker from 'faker'; + +import '../../../../../../test/jest/__mock__'; + +import { + renderWithIntl, + translationsProperties, +} from '../../../../../../test/jest/helpers'; + +import { AuthorityCell } from '../AuthorityCell'; + +const jobLogRecords = [{ + sourceRecordId: 'testId', + sourceRecordOrder: 0, + sourceRecordTitle: 'Test item 1', + relatedInstanceInfo: { + actionStatus: 'CREATED', + idList: [faker.random.uuid()], + }, + relatedHoldingsInfo: [{ + actionStatus : 'CREATED', + id : 'testHoldingsId', + permanentLocationId: faker.random.uuid(), + hrid : 'ho00000000017', + error : '' + }], + relatedItemInfo: [{ + actionStatus : 'CREATED', + id : '37f20cb1-f60c-4195-80a1-00a16c4af5cb', + hrid : 'it00000000015', + holdingsId : 'testHoldingsId', + error : '' + }], + relatedAuthorityInfo: { + actionStatus: 'CREATED', + idList: ['testAuthorityId'], + }, + relatedPoLineInfo: { + actionStatus: 'CREATED', + idList: [faker.random.uuid()], + }, +}]; + +const sortedItemData = [ + [{ + actionStatus : 'CREATED', + id : '37f20cb1-f60c-4195-80a1-00a16c4af5cb', + hrid : 'it00000000015', + holdingsId : 'testHoldingsId', + error : '' + }] +]; + +const renderAuthorityCell = (authorityActionStatus) => { + const component = ( + + + + ); + + return renderWithIntl(component, translationsProperties); +}; + +describe('AuthorityCell component', () => { + it('should be rendered with no axe errors', async () => { + const { container } = renderAuthorityCell('CREATED'); + + await runAxeTest({ rootNode: container }); + }); + + describe('when authority was created', () => { + it('hotlink should be rendered', () => { + const { container } = renderAuthorityCell('CREATED'); + const authorityHotlink = container.querySelector('[data-test-entity-name="authority"]'); + + expect(authorityHotlink.href).toContain('/marc-authorities/authorities/testAuthorityId'); + }); + }); + + describe('when authority was updated', () => { + it('hotlink should be rendered', () => { + const { container } = renderAuthorityCell('UPDATED'); + const authorityHotlink = container.querySelector('[data-test-entity-name="authority"]'); + + expect(authorityHotlink.href).toContain('/marc-authorities/authorities/testAuthorityId'); + }); + }); + + describe('when authority is empty', () => { + it('should render empty value', () => { + const { getByText } = renderAuthorityCell(); + + expect(getByText('-')).toBeDefined(); + }); + }); +}); diff --git a/src/routes/JobSummary/components/cells/tests/ErrorCell.test.js b/src/routes/JobSummary/components/cells/tests/ErrorCell.test.js new file mode 100644 index 000000000..124e88c86 --- /dev/null +++ b/src/routes/JobSummary/components/cells/tests/ErrorCell.test.js @@ -0,0 +1,58 @@ +import React from 'react'; +import { runAxeTest } from '@folio/stripes-testing'; + +import '../../../../../../test/jest/__mock__'; + +import { + renderWithIntl, + translationsProperties, +} from '../../../../../../test/jest/helpers'; + +import { ErrorCell } from '../ErrorCell'; + +const errorProp = 'test error'; + +const sortedItemDataProp = [ + [{ + actionStatus : 'CREATED', + id : '37f20cb1-f60c-4195-80a1-00a16c4af5cb', + hrid : 'it00000000015', + holdingsId : 'testHoldingsId', + error : 'test error' + }] +]; + +const renderErrorCell = ({ error = '', sortedItemData }) => { + const component = ( + + ); + + return renderWithIntl(component, translationsProperties); +}; + +describe('ErrorCell component', () => { + it('should be rendered with no axe errors', async () => { + const { container } = renderErrorCell({ error: errorProp }); + + await runAxeTest({ rootNode: container }); + }); + + describe('when error was occured', () => { + it('error message should be rendered', () => { + const { getByText } = renderErrorCell({ error: errorProp }); + + expect(getByText('Error')).toBeInTheDocument(); + }); + + describe('when item has an error', () => { + it('error message should be rendered', () => { + const { getByText } = renderErrorCell({ sortedItemData: sortedItemDataProp }); + + expect(getByText('Error')).toBeInTheDocument(); + }); + }); + }); +}); diff --git a/src/routes/JobSummary/components/cells/tests/HoldingsCell.test.js b/src/routes/JobSummary/components/cells/tests/HoldingsCell.test.js new file mode 100644 index 000000000..76b45b82d --- /dev/null +++ b/src/routes/JobSummary/components/cells/tests/HoldingsCell.test.js @@ -0,0 +1,91 @@ +import React from 'react'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { runAxeTest } from '@folio/stripes-testing'; + +import '../../../../../../test/jest/__mock__'; + +import { + renderWithIntl, + translationsProperties, +} from '../../../../../../test/jest/helpers'; + +import { HoldingsCell } from '../HoldingsCell'; + +const locationsProp = [{ + id : 'testLocationId', + name : 'Annex', + code : 'KU/CC/DI/A', +}]; + +const holdingsInfoProp = (actionStatus = 'CREATED') => [{ + actionStatus, + id : 'testHoldingsId', + permanentLocationId: 'testLocationId', + hrid : 'ho00000000017', + error : '' +}]; + +const itemInfo = [{ + actionStatus : 'CREATED', + id : '37f20cb1-f60c-4195-80a1-00a16c4af5cb', + hrid : 'it00000000015', + holdingsId : 'testHoldingsId', + error : '' +}]; + +const renderHoldingsCell = (holdingsInfo) => { + const component = ( + + + + ); + + return renderWithIntl(component, translationsProperties); +}; + +describe('HoldingsCell component', () => { + it('should be rendered with no axe errors', async () => { + const { container } = renderHoldingsCell(holdingsInfoProp()); + + await runAxeTest({ rootNode: container }); + }); + + describe('when holdings was created', () => { + it('hotlink and location should be rendered', () => { + const { + container, + getByText, + } = renderHoldingsCell(holdingsInfoProp()); + const holdingsHotlink = container.querySelector('[data-test-entity-name="holdings"]'); + + expect(holdingsHotlink.href).toContain('/inventory/view/testInstanceId/testHoldingsId'); + expect(getByText('(KU/CC/DI/A)')).toBeInTheDocument(); + }); + }); + + describe('when holdings was updated', () => { + it('hotlink and location should be rendered', () => { + const { + container, + getByText, + } = renderHoldingsCell(holdingsInfoProp('UPDATED')); + const holdingsHotlink = container.querySelector('[data-test-entity-name="holdings"]'); + + expect(holdingsHotlink.href).toContain('/inventory/view/testInstanceId/testHoldingsId'); + expect(getByText('(KU/CC/DI/A)')).toBeInTheDocument(); + }); + }); + + describe('when holdings is empty', () => { + it('should render empty value', () => { + const { getByText } = renderHoldingsCell(holdingsInfoProp(null)); + + expect(getByText('-')).toBeDefined(); + }); + }); +}); diff --git a/src/routes/JobSummary/components/cells/tests/InstanceCell.test.js b/src/routes/JobSummary/components/cells/tests/InstanceCell.test.js new file mode 100644 index 000000000..1e35ea225 --- /dev/null +++ b/src/routes/JobSummary/components/cells/tests/InstanceCell.test.js @@ -0,0 +1,70 @@ +import React from 'react'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { runAxeTest } from '@folio/stripes-testing'; + +import '../../../../../../test/jest/__mock__'; + +import { + renderWithIntl, + translationsProperties, +} from '../../../../../../test/jest/helpers'; + +import { InstanceCell } from '../InstanceCell'; + +const jobLogRecords = [{ + sourceRecordId: 'testId', + sourceRecordOrder: 0, + sourceRecordTitle: 'Test item 1', + relatedInstanceInfo: { + actionStatus: 'CREATED', + idList: ['testInstanceId'], + }, +}]; + +const renderInstanceCell = (instanceActionStatus) => { + const component = ( + + + + ); + + return renderWithIntl(component, translationsProperties); +}; + +describe('InstanceCell component', () => { + it('should be rendered with no axe errors', async () => { + const { container } = renderInstanceCell('CREATED'); + + await runAxeTest({ rootNode: container }); + }); + + describe('when instance was created', () => { + it('hotlink should be rendered', () => { + const { container } = renderInstanceCell('CREATED'); + const instanceHotlink = container.querySelector('[data-test-entity-name="instance"]'); + + expect(instanceHotlink.href).toContain('/inventory/view/testInstanceId'); + }); + }); + + describe('when instance was updated', () => { + it('hotlink should be rendered', () => { + const { container } = renderInstanceCell('UPDATED'); + const instanceHotlink = container.querySelector('[data-test-entity-name="instance"]'); + + expect(instanceHotlink.href).toContain('/inventory/view/testInstanceId'); + }); + }); + + describe('when instance is empty', () => { + it('should render empty value', () => { + const { getByText } = renderInstanceCell(); + + expect(getByText('-')).toBeDefined(); + }); + }); +}); diff --git a/src/routes/JobSummary/components/cells/tests/InvoiceCell.test.js b/src/routes/JobSummary/components/cells/tests/InvoiceCell.test.js new file mode 100644 index 000000000..5744f1b5f --- /dev/null +++ b/src/routes/JobSummary/components/cells/tests/InvoiceCell.test.js @@ -0,0 +1,86 @@ +import React from 'react'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { runAxeTest } from '@folio/stripes-testing'; + +import '../../../../../../test/jest/__mock__'; + +import { + renderWithIntl, + translationsProperties, +} from '../../../../../../test/jest/helpers'; + +import { InvoiceCell } from '../InvoiceCell'; + +const jobLogRecords = [{ + sourceRecordId: 'testId', + sourceRecordOrder: 0, + invoiceActionStatus: 'CREATED', + relatedInvoiceInfo : { + idList : ['testInvoiceId'], + hridList : [] + }, + relatedInvoiceLineInfo: { + fullInvoiceLineNumber: 0, + id: 'testInvoiceLineId', + hridList : [], + }, +}]; + +const sortedItemData = [ + [{ + actionStatus : 'CREATED', + id : '37f20cb1-f60c-4195-80a1-00a16c4af5cb', + hrid : 'it00000000015', + holdingsId : 'testHoldingsId', + error : '' + }] +]; + +const renderInvoiceCell = (invoiceActionStatus) => { + const component = ( + + + + ); + + return renderWithIntl(component, translationsProperties); +}; + +describe('InvoiceCell component', () => { + it('should be rendered with no axe errors', async () => { + const { container } = renderInvoiceCell('CREATED'); + + await runAxeTest({ rootNode: container }); + }); + + describe('when invoice was created', () => { + it('hotlink should be rendered', () => { + const { container } = renderInvoiceCell('CREATED'); + const invoiceHotlink = container.querySelector('[data-test-entity-name="invoice"]'); + + expect(invoiceHotlink.href).toContain('/invoice/view/testInvoiceId/line/testInvoiceLineId/view'); + }); + }); + + describe('when invoice was updated', () => { + it('should render cell with text "Updated"', () => { + const { getByText } = renderInvoiceCell('UPDATED'); + + expect(getByText('Updated')).toBeInTheDocument(); + }); + }); + + describe('when invoice is empty', () => { + it('should render empty value', () => { + const { getByText } = renderInvoiceCell(); + + expect(getByText('-')).toBeDefined(); + }); + }); +}); diff --git a/src/routes/JobSummary/components/cells/tests/ItemCell.test.js b/src/routes/JobSummary/components/cells/tests/ItemCell.test.js new file mode 100644 index 000000000..1fdbfea73 --- /dev/null +++ b/src/routes/JobSummary/components/cells/tests/ItemCell.test.js @@ -0,0 +1,77 @@ +import React from 'react'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { runAxeTest } from '@folio/stripes-testing'; + +import '../../../../../../test/jest/__mock__'; + +import { + renderWithIntl, + translationsProperties, +} from '../../../../../../test/jest/helpers'; + +import { ItemCell } from '../ItemCell'; + +const sortedItemDataProp = (actionStatus) => [ + [{ + actionStatus, + id : 'testItemId', + hrid : 'it00000000015', + holdingsId : 'testHoldingsId', + error : '' + }] +]; + +const renderItemCell = (sortedItemData) => { + const component = ( + + + + ); + + return renderWithIntl(component, translationsProperties); +}; + +describe('ItemCell component', () => { + it('should be rendered with no axe errors', async () => { + const { container } = renderItemCell(sortedItemDataProp('CREATED')); + + await runAxeTest({ rootNode: container }); + }); + + describe('when item was created', () => { + it('hotlink and hrid should be rendered', () => { + const { + container, + getByText, + } = renderItemCell(sortedItemDataProp('CREATED')); + const itemHotlink = container.querySelector('[data-test-entity-name="item"]'); + + expect(itemHotlink.href).toContain('/inventory/view/testInstanceId/testHoldingsId/testItemId'); + expect(getByText('(it00000000015)')).toBeInTheDocument(); + }); + }); + + describe('when item was updated', () => { + it('hotlink and location should be rendered', () => { + const { + container, + getByText, + } = renderItemCell(sortedItemDataProp('UPDATED')); + const itemHotlink = container.querySelector('[data-test-entity-name="item"]'); + + expect(itemHotlink.href).toContain('/inventory/view/testInstanceId/testHoldingsId/testItemId'); + expect(getByText('(it00000000015)')).toBeInTheDocument(); + }); + }); + + describe('when item is empty', () => { + it('should render empty value', () => { + const { getByText } = renderItemCell(sortedItemDataProp(null)); + + expect(getByText('-')).toBeDefined(); + }); + }); +}); diff --git a/src/routes/JobSummary/components/cells/tests/OrderCell.test.js b/src/routes/JobSummary/components/cells/tests/OrderCell.test.js new file mode 100644 index 000000000..7e5bdb2bb --- /dev/null +++ b/src/routes/JobSummary/components/cells/tests/OrderCell.test.js @@ -0,0 +1,99 @@ +import React from 'react'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { runAxeTest } from '@folio/stripes-testing'; +import faker from 'faker'; + +import '../../../../../../test/jest/__mock__'; + +import { + renderWithIntl, + translationsProperties, +} from '../../../../../../test/jest/helpers'; + +import { OrderCell } from '../OrderCell'; + +const jobLogRecords = [{ + sourceRecordId: 'testId', + sourceRecordOrder: 0, + sourceRecordTitle: 'Test item 1', + relatedInstanceInfo: { + actionStatus: 'CREATED', + idList: [faker.random.uuid()], + }, + relatedHoldingsInfo: [{ + actionStatus : 'CREATED', + id : 'testHoldingsId', + permanentLocationId: faker.random.uuid(), + hrid : 'ho00000000017', + error : '' + }], + relatedItemInfo: [{ + actionStatus : 'CREATED', + id : '37f20cb1-f60c-4195-80a1-00a16c4af5cb', + hrid : 'it00000000015', + holdingsId : 'testHoldingsId', + error : '' + }], + relatedPoLineInfo: { + actionStatus: 'CREATED', + idList: ['testPOLineId'], + }, +}]; + +const sortedItemData = [ + [{ + actionStatus : 'CREATED', + id : '37f20cb1-f60c-4195-80a1-00a16c4af5cb', + hrid : 'it00000000015', + holdingsId : 'testHoldingsId', + error : '' + }] +]; + +const renderOrderCell = (poLineActionStatus) => { + const component = ( + + + + ); + + return renderWithIntl(component, translationsProperties); +}; + +describe('OrderCell component', () => { + it('should be rendered with no axe errors', async () => { + const { container } = renderOrderCell('CREATED'); + + await runAxeTest({ rootNode: container }); + }); + + describe('when order was created', () => { + it('hotlink should be rendered', () => { + const { container } = renderOrderCell('CREATED'); + const orderHotlink = container.querySelector('[data-test-entity-name="order"]'); + + expect(orderHotlink.href).toContain('/orders/lines/view/testPOLineId'); + }); + }); + + describe('when order was updated', () => { + it('should render cell with text "Updated"', () => { + const { getByText } = renderOrderCell('UPDATED'); + + expect(getByText('Updated')).toBeInTheDocument(); + }); + }); + + describe('when order is empty', () => { + it('should render empty value', () => { + const { getByText } = renderOrderCell(); + + expect(getByText('-')).toBeDefined(); + }); + }); +}); diff --git a/src/routes/JobSummary/components/cells/tests/RecordNumberCell.test.js b/src/routes/JobSummary/components/cells/tests/RecordNumberCell.test.js new file mode 100644 index 000000000..de336f4b2 --- /dev/null +++ b/src/routes/JobSummary/components/cells/tests/RecordNumberCell.test.js @@ -0,0 +1,42 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { runAxeTest } from '@folio/stripes-testing'; + +import '../../../../../../test/jest/__mock__'; + +import { RecordNumberCell } from '../RecordNumberCell'; + +const renderRecordNumberCell = (isEdifactType) => { + const component = ( + + ); + + return render(component); +}; + +describe('RecordNumberCell component', () => { + it('should be rendered with no axe errors', async () => { + const { container } = renderRecordNumberCell(true); + + await runAxeTest({ rootNode: container }); + }); + + describe('when is EDIFACT type', () => { + it('should display order as it is', () => { + const { getByText } = renderRecordNumberCell(true); + + expect(getByText('1')).toBeInTheDocument(); + }); + }); + + describe('when is MARC type', () => { + it('should display incremented order', () => { + const { getByText } = renderRecordNumberCell(false); + + expect(getByText('2')).toBeInTheDocument(); + }); + }); +}); diff --git a/src/routes/JobSummary/components/cells/tests/SRSMarcCell.test.js b/src/routes/JobSummary/components/cells/tests/SRSMarcCell.test.js new file mode 100644 index 000000000..e70dce1b8 --- /dev/null +++ b/src/routes/JobSummary/components/cells/tests/SRSMarcCell.test.js @@ -0,0 +1,30 @@ +import React from 'react'; +import { runAxeTest } from '@folio/stripes-testing'; + +import '../../../../../../test/jest/__mock__'; +import { + renderWithIntl, + translationsProperties, +} from '../../../../../../test/jest/helpers'; + +import { SRSMarcCell } from '../SRSMarcCell'; + +const renderSRSMarcCell = () => { + const component = ; + + return renderWithIntl(component, translationsProperties); +}; + +describe('SRSMarcCell component', () => { + it('should be rendered with no axe errors', async () => { + const { container } = renderSRSMarcCell(); + + await runAxeTest({ rootNode: container }); + }); + + it('should be rendered', () => { + const { getByText } = renderSRSMarcCell(); + + expect(getByText('Created')).toBeInTheDocument(); + }); +}); diff --git a/src/routes/JobSummary/components/cells/tests/TitleCell.test.js b/src/routes/JobSummary/components/cells/tests/TitleCell.test.js new file mode 100644 index 000000000..85f952d8e --- /dev/null +++ b/src/routes/JobSummary/components/cells/tests/TitleCell.test.js @@ -0,0 +1,85 @@ +import React from 'react'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { runAxeTest } from '@folio/stripes-testing'; + +import '../../../../../../test/jest/__mock__'; + +import { + renderWithIntl, + translationsProperties, +} from '../../../../../../test/jest/helpers'; + +import { TitleCell } from '../TitleCell'; + +const jobLogEntriesRecordsProp = [{ jobExecutionId: 'jobExecutionId' }]; + +const renderTitleCell = ({ + sourceRecordType, + holdingsActionStatus, + sourceRecordActionStatus, +}) => { + const component = ( + + + + ); + + return renderWithIntl(component, translationsProperties); +}; + +describe('TitleCell component', () => { + describe('when source record type is holdings', () => { + const titleForHoldingsProps = { + sourceRecordType: 'MARC_HOLDINGS', + holdingsActionStatus: 'DISCARDED', + sourceRecordActionStatus: 'DISCARDED', + }; + + it('should be rendered with no axe errors', async () => { + const { container } = renderTitleCell(titleForHoldingsProps); + + await runAxeTest({ rootNode: container }); + }); + + it('should render hotlink with text "Holdings"', () => { + const { container } = renderTitleCell(titleForHoldingsProps); + + const titleHotlink = container.querySelector('[data-test-text-link="true"]'); + + expect(titleHotlink.innerHTML).toContain('Holdings'); + expect(titleHotlink.href).toContain('/data-import/log/jobExecutionId/sourceRecordId?instanceLineId=journalRecordId'); + }); + }); + + describe('when source record type is not holdings', () => { + const titleForNotHoldingsProps = { + sourceRecordType: 'INSTANCE', + holdingsActionStatus: 'CREATED', + sourceRecordActionStatus: 'CREATED', + }; + + it('should be rendered with no axe errors', async () => { + const { container } = renderTitleCell(titleForNotHoldingsProps); + + await runAxeTest({ rootNode: container }); + }); + + it('should render hotlink with title', () => { + const { container } = renderTitleCell(titleForNotHoldingsProps); + + const titleHotlink = container.querySelector('[data-test-text-link="true"]'); + + expect(titleHotlink.innerHTML).toContain('Test title'); + expect(titleHotlink.href).toContain('/data-import/log/jobExecutionId/sourceRecordId?instanceLineId=journalRecordId'); + }); + }); +}); diff --git a/src/routes/JobSummary/components/index.js b/src/routes/JobSummary/components/index.js index 976c75d65..dcf95c74d 100644 --- a/src/routes/JobSummary/components/index.js +++ b/src/routes/JobSummary/components/index.js @@ -1,2 +1,3 @@ export * from './RecordsTable'; export * from './SummaryTable'; +export * from './cells'; diff --git a/src/routes/JobSummary/components/utils.js b/src/routes/JobSummary/components/utils.js new file mode 100644 index 000000000..a06c8afdf --- /dev/null +++ b/src/routes/JobSummary/components/utils.js @@ -0,0 +1,65 @@ +import { FormattedMessage } from 'react-intl'; + +import { + TextLink, + NoValue, +} from '@folio/stripes/components'; + +import { RECORD_ACTION_STATUS_LABEL_IDS } from '../../../utils'; + +import sharedCss from '../../../shared.css'; + +export const BaseLineCell = ({ children }) =>
{children}
; + +export const getHotlinkCellFormatter = (isHotlink, entityLabel, path, entity) => { + if (isHotlink) { + return ( + + + {entityLabel} + + + ); + } + + return {entityLabel}; +}; + +export const getRecordActionStatusLabel = recordType => { + if (!recordType) { + return ( + + + + ); + } + + const labelId = RECORD_ACTION_STATUS_LABEL_IDS[recordType]; + + return ; +}; + +export const fillCellWithNoValues = (itemData, isErrorColumn = false) => { + const itemsInfoCell = itemData.map((sortedItems, itemIndex) => { + const groupOfItems = sortedItems.map((item, index) => ( +
+ + {item.error && isErrorColumn ? : null} + {!isErrorColumn ? : null} + +
+
+ )); + + return
{groupOfItems}
; + }); + + return ( + + {itemsInfoCell} + + ); +}; diff --git a/src/shared.css b/src/shared.css index 8d38912d4..ac95c2a90 100644 --- a/src/shared.css +++ b/src/shared.css @@ -78,3 +78,8 @@ hr { margin: 0; } + +.baselineCell { + display: inline-block; + align-self: baseline; +} From e360d19a258560a940ffcbf442cec76fd8fc05bd Mon Sep 17 00:00:00 2001 From: Mariia Aloshyna <55138456+mariia-aloshyna@users.noreply.github.com> Date: Thu, 13 Jul 2023 17:12:47 +0300 Subject: [PATCH 04/14] UIDATIMP-1443: Improve mapping profile for Holdings for handling multiple Holdings mapping (#1436) --- CHANGELOG.md | 1 + .../edit/HoldingsDetailsSections/Location.js | 41 +++++++++++++------ .../view/HoldingsDetailsSections/Location.js | 6 ++- .../initialDetails/HOLDINGS.js | 23 +++++++++-- .../MappingProfiles/initialDetails/index.js | 13 +++--- 5 files changed, 62 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2c4b12cd..8611f8724 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ * Update Log details screen to support multiple holdings & items (UIDATIMP-1438) * Update Log JSON screen to support multiple holdings (UIDATIMP-1439) * Update Log JSON screen to support multiple items (UIDATIMP-1440) +* Improve mapping profile for Holdings for handling multiple Holdings mapping (UIDATIMP-1443) * DI Log: change Discarded to No action (UIDATIMP-1446) * DI Log: Make some changes to the Log header (UIDATIMP-1447) * DI Job profiles: Redirect when job profile was deleted (UIDATIMP-1450) diff --git a/src/settings/MappingProfiles/detailsSections/edit/HoldingsDetailsSections/Location.js b/src/settings/MappingProfiles/detailsSections/edit/HoldingsDetailsSections/Location.js index 775111aea..cbf2d335f 100644 --- a/src/settings/MappingProfiles/detailsSections/edit/HoldingsDetailsSections/Location.js +++ b/src/settings/MappingProfiles/detailsSections/edit/HoldingsDetailsSections/Location.js @@ -19,6 +19,8 @@ import { import { getAcceptedValuesPath, getFieldName, + getRepeatableAcceptedValuesPath, + getSubfieldName, renderFieldLabelWithInfo, } from '../../utils'; import { @@ -30,6 +32,21 @@ export const Location = ({ setReferenceTables, okapi, }) => { + const LOCATION_FIELDS_MAP = { + PERMANENT: getSubfieldName(6, 0, 0), + PERMANENT_ACCEPTED_VALUES: getRepeatableAcceptedValuesPath(6, 0, 0), + TEMPORARY: getFieldName(7), + TEMPORARY_ACCEPTED_VALUES: getAcceptedValuesPath(7), + SHELVING_ORDER: getFieldName(8), + SHELVING_TITLE: getFieldName(9), + COPY_NUMBER: getFieldName(10), + CALL_NUMBER_TYPE: getFieldName(11), + CALL_NUMBER_TYPE_ACCEPTED_VALUES: getAcceptedValuesPath(11), + CALL_NUMBER_PREFIX: getFieldName(12), + CALL_NUMBER: getFieldName(13), + CALL_NUMBER_SUFFIX: getFieldName(14), + }; + const permanentLocationLabel = renderFieldLabelWithInfo( `${TRANSLATION_ID_PREFIX}.location.field.permanent`, `${TRANSLATION_ID_PREFIX}.item.requiredWhenCreatingHoldings.info`, @@ -47,7 +64,7 @@ export const Location = ({ > @@ -68,7 +85,7 @@ export const Location = ({ > } optionValue="name" optionLabel="name" @@ -79,7 +96,7 @@ export const Location = ({ }]} isRemoveValueAllowed setAcceptedValues={setReferenceTables} - acceptedValuesPath={getAcceptedValuesPath(7)} + acceptedValuesPath={LOCATION_FIELDS_MAP.TEMPORARY_ACCEPTED_VALUES} optionTemplate="**name** (**code**)" okapi={okapi} /> @@ -92,7 +109,7 @@ export const Location = ({ > } disabled /> @@ -105,7 +122,7 @@ export const Location = ({ {validation => ( } validate={[validation]} /> @@ -122,7 +139,7 @@ export const Location = ({ {validation => ( } validate={[validation]} /> @@ -137,7 +154,7 @@ export const Location = ({ > } optionValue="name" optionLabel="name" @@ -148,7 +165,7 @@ export const Location = ({ }]} isRemoveValueAllowed setAcceptedValues={setReferenceTables} - acceptedValuesPath={getAcceptedValuesPath(11)} + acceptedValuesPath={LOCATION_FIELDS_MAP.CALL_NUMBER_TYPE_ACCEPTED_VALUES} okapi={okapi} /> @@ -160,7 +177,7 @@ export const Location = ({ {validation => ( } validate={[validation]} /> @@ -175,7 +192,7 @@ export const Location = ({ {validation => ( } validate={[validation]} /> @@ -191,7 +208,7 @@ export const Location = ({ {validation => ( } validate={[validation]} /> diff --git a/src/settings/MappingProfiles/detailsSections/view/HoldingsDetailsSections/Location.js b/src/settings/MappingProfiles/detailsSections/view/HoldingsDetailsSections/Location.js index c07c5f7b4..2ac523446 100644 --- a/src/settings/MappingProfiles/detailsSections/view/HoldingsDetailsSections/Location.js +++ b/src/settings/MappingProfiles/detailsSections/view/HoldingsDetailsSections/Location.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; +import { get } from 'lodash'; import { Accordion, @@ -9,14 +10,15 @@ import { KeyValue, } from '@folio/stripes/components'; -import { ProhibitionIcon } from '../../../../../components/ProhibitionIcon'; +import { ProhibitionIcon } from '../../../../../components'; import { getFieldValue } from '../../utils'; import { TRANSLATION_ID_PREFIX } from '../../constants'; import { mappingProfileFieldShape } from '../../../../../utils'; export const Location = ({ mappingDetails }) => { - const permanentLocation = getFieldValue(mappingDetails, 'permanentLocationId', 'value'); + const permanentLocationSubfields = getFieldValue(mappingDetails, 'permanentLocationId', 'subfields'); + const permanentLocation = get(permanentLocationSubfields, '[0].fields[0].value', ''); const temporaryLocation = getFieldValue(mappingDetails, 'temporaryLocationId', 'value'); const shelvingOrder = getFieldValue(mappingDetails, 'shelvingOrder', 'value'); const shelvingTitle = getFieldValue(mappingDetails, 'shelvingTitle', 'value'); diff --git a/src/settings/MappingProfiles/initialDetails/HOLDINGS.js b/src/settings/MappingProfiles/initialDetails/HOLDINGS.js index 34075788a..250e486e7 100644 --- a/src/settings/MappingProfiles/initialDetails/HOLDINGS.js +++ b/src/settings/MappingProfiles/initialDetails/HOLDINGS.js @@ -69,10 +69,27 @@ const HOLDINGS = { }, { name: 'permanentLocationId', enabled: true, - path: 'holdings.permanentLocationId', + required: false, + path: 'holdings.permanentLocationId[]', value: '', - subfields: [], - acceptedValues: {}, + repeatableFieldAction: 'EXTEND_EXISTING', + subfields: [ + { + order: 0, + path: 'holdings.permanentLocationId[]', + fields: [ + { + name: 'permanentLocationId', + enabled: true, + required: false, + path: 'holdings.permanentLocationId[]', + value: '', + subfields: [], + acceptedValues: {}, + }, + ], + }, + ], }, { name: 'temporaryLocationId', enabled: true, diff --git a/src/settings/MappingProfiles/initialDetails/index.js b/src/settings/MappingProfiles/initialDetails/index.js index 0d978c615..b4d596ec0 100644 --- a/src/settings/MappingProfiles/initialDetails/index.js +++ b/src/settings/MappingProfiles/initialDetails/index.js @@ -24,6 +24,11 @@ const initialValues = { MARC_AUTHORITY, }; +const KEEP_SUBFIELDS_INITIAL = { + [FOLIO_RECORD_TYPES.INVOICE.type]: ['acqUnitIds'], + [FOLIO_RECORD_TYPES.HOLDINGS.type]: ['permanentLocationId'], +}; + const modifyInvoiceLinesFieldDetails = field => { const invoiceLineSubfield = { ...field.subfields[0] }; @@ -98,15 +103,13 @@ export const getInitialDetails = (entity, stripRepeatableFields = false) => { if (field.name === 'invoiceLines') { return modifyInvoiceLinesFieldDetails(field); } - - if (field.name === 'acqUnitIds') { - return field; - } } + const keepSubfieldsInitial = KEEP_SUBFIELDS_INITIAL[entity]?.some(fieldName => fieldName === field.name); + return { ...field, - subfields: [], + subfields: keepSubfieldsInitial ? field.subfields : [], }; }); From 8c14ce88a81fbd2d8e66fd3c93bf17c1e48f4f1b Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko1 Date: Tue, 25 Jul 2023 16:01:44 +0300 Subject: [PATCH 05/14] multiple-feature: Fix error in records table --- src/routes/JobSummary/components/RecordsTable.js | 8 ++++---- src/routes/JobSummary/components/cells/HoldingsCell.js | 2 +- src/routes/JobSummary/components/utils.js | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/routes/JobSummary/components/RecordsTable.js b/src/routes/JobSummary/components/RecordsTable.js index eaa67e597..ddb4f79c2 100644 --- a/src/routes/JobSummary/components/RecordsTable.js +++ b/src/routes/JobSummary/components/RecordsTable.js @@ -193,7 +193,7 @@ export const RecordsTable = ({ const itemData = sourceRecord?.relatedItemInfo; const groupedItemData = groupBy(itemData, 'holdingsId'); - const sortedItemData = holdingsData.map(holdings => groupedItemData[holdings.id]); + const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]); return ( groupedItemData[holdings.id]); + const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]); return ( groupedItemData[holdings.id]); + const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]); return ( groupedItemData[holdings.id]); + const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]); return ( {getHotlinkCellFormatter(isHotlink, entityLabel, path, 'holdings')} - {!isDiscarded ? ` (${locationCode})` : ''} + {!isDiscarded && locationCode ? ` (${locationCode})` : ''} {spacing} ); diff --git a/src/routes/JobSummary/components/utils.js b/src/routes/JobSummary/components/utils.js index a06c8afdf..31367f511 100644 --- a/src/routes/JobSummary/components/utils.js +++ b/src/routes/JobSummary/components/utils.js @@ -43,8 +43,8 @@ export const getRecordActionStatusLabel = recordType => { }; export const fillCellWithNoValues = (itemData, isErrorColumn = false) => { - const itemsInfoCell = itemData.map((sortedItems, itemIndex) => { - const groupOfItems = sortedItems.map((item, index) => ( + const itemsInfoCell = itemData?.map((sortedItems, itemIndex) => { + const groupOfItems = sortedItems?.map((item, index) => (
{item.error && isErrorColumn ? : null} From 150134bf76cb21c42eeeeac03306476f5df16c2d Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko1 Date: Tue, 25 Jul 2023 16:19:28 +0300 Subject: [PATCH 06/14] multiple-feature: add no value if no record found --- src/routes/JobSummary/components/RecordsTable.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/routes/JobSummary/components/RecordsTable.js b/src/routes/JobSummary/components/RecordsTable.js index ddb4f79c2..cc1dda27a 100644 --- a/src/routes/JobSummary/components/RecordsTable.js +++ b/src/routes/JobSummary/components/RecordsTable.js @@ -193,6 +193,8 @@ export const RecordsTable = ({ const itemData = sourceRecord?.relatedItemInfo; const groupedItemData = groupBy(itemData, 'holdingsId'); + if (isEmpty(groupedItemData)) return ; + const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]); return ( @@ -213,6 +215,8 @@ export const RecordsTable = ({ const itemData = sourceRecord?.relatedItemInfo; const groupedItemData = groupBy(itemData, 'holdingsId'); + if (isEmpty(groupedItemData)) return ; + const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]); return ( @@ -234,6 +238,7 @@ export const RecordsTable = ({ const itemData = sourceRecord?.relatedItemInfo; const groupedItemData = groupBy(itemData, 'holdingsId'); + if (isEmpty(groupedItemData)) return ; const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]); return ( @@ -255,6 +260,8 @@ export const RecordsTable = ({ const itemData = sourceRecord?.relatedItemInfo; const groupedItemData = groupBy(itemData, 'holdingsId'); + if (isEmpty(groupedItemData)) return ; + const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]); return ( From a35097c27c02261cc94aa1d34dd1d3e024c717ff Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko1 Date: Wed, 26 Jul 2023 17:04:23 +0300 Subject: [PATCH 07/14] Fixes for map --- src/routes/JobSummary/components/RecordsTable.js | 2 +- src/routes/JobSummary/components/cells/HoldingsCell.js | 2 +- src/routes/JobSummary/components/cells/ItemCell.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/routes/JobSummary/components/RecordsTable.js b/src/routes/JobSummary/components/RecordsTable.js index cc1dda27a..0ae66efc1 100644 --- a/src/routes/JobSummary/components/RecordsTable.js +++ b/src/routes/JobSummary/components/RecordsTable.js @@ -175,7 +175,7 @@ export const RecordsTable = ({ if (isEmpty(itemData)) return ; const groupedItemData = groupBy(itemData, 'holdingsId'); - const sortedItemData = holdingsData.map(holdings => groupedItemData[holdings.id]); + const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]); return ( { - const holdingsInfoCell = holdingsInfo.map((holdings, index) => { + const holdingsInfoCell = holdingsInfo?.map((holdings, index) => { const entityLabel = getRecordActionStatusLabel(holdings.actionStatus); const holdingsId = holdings.id; const path = `/inventory/view/${instanceId}/${holdingsId}`; diff --git a/src/routes/JobSummary/components/cells/ItemCell.js b/src/routes/JobSummary/components/cells/ItemCell.js index 28280861d..4e875dc2b 100644 --- a/src/routes/JobSummary/components/cells/ItemCell.js +++ b/src/routes/JobSummary/components/cells/ItemCell.js @@ -12,8 +12,8 @@ export const ItemCell = ({ sortedItemData, instanceId, }) => { - const itemsInfoCell = sortedItemData.map((sortedItems, itemIndex) => { - const groupOfItems = sortedItems.map((item, index) => { + const itemsInfoCell = sortedItemData?.map((sortedItems, itemIndex) => { + const groupOfItems = sortedItems?.map((item, index) => { const entityLabel = getRecordActionStatusLabel(item.actionStatus); const holdingsId = item.holdingsId; const itemId = item.id; From 35f2f03a7a55b23df6544a28cf73fe9458b8efec Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko1 Date: Tue, 1 Aug 2023 13:24:01 +0300 Subject: [PATCH 08/14] multiple-feature: Adjust RecordsTable component --- .../JobSummary/components/RecordsTable.js | 61 ++++++++++++++----- .../components/cells/AuthorityCell.js | 4 +- .../JobSummary/components/cells/ErrorCell.js | 8 +-- .../JobSummary/components/cells/ItemCell.js | 2 +- .../JobSummary/components/cells/OrderCell.js | 3 +- .../components/cells/tests/ErrorCell.test.js | 11 ++-- src/routes/JobSummary/components/utils.js | 31 +++++++--- 7 files changed, 82 insertions(+), 38 deletions(-) diff --git a/src/routes/JobSummary/components/RecordsTable.js b/src/routes/JobSummary/components/RecordsTable.js index 0ae66efc1..c62f0b42f 100644 --- a/src/routes/JobSummary/components/RecordsTable.js +++ b/src/routes/JobSummary/components/RecordsTable.js @@ -30,6 +30,7 @@ import { InvoiceCell, ErrorCell, } from '.'; +import { BaseLineCell } from './utils'; export const RecordsTable = ({ mutator, @@ -142,10 +143,11 @@ export const RecordsTable = ({ holdingsStatus: ({ sourceRecordId }) => { const sourceRecord = jobLogRecords.find(item => item.sourceRecordId === sourceRecordId); const holdingsInfo = sourceRecord?.relatedHoldingsInfo; - const itemInfo = sourceRecord?.relatedItemInfo; - const instanceId = sourceRecord?.relatedInstanceInfo.idList[0]; - if (isEmpty(holdingsInfo)) return ; + if (isEmpty(holdingsInfo)) return ; + + const itemInfo = [...sourceRecord?.relatedItemInfo]; + const instanceId = sourceRecord?.relatedInstanceInfo.idList[0]; holdingsInfo.forEach(holdings => { const isDiscarded = holdings.actionStatus === 'DISCARDED'; @@ -172,9 +174,30 @@ export const RecordsTable = ({ const itemData = sourceRecord?.relatedItemInfo; const instanceId = sourceRecord?.relatedInstanceInfo.idList[0]; - if (isEmpty(itemData)) return ; + if (isEmpty(itemData) && isEmpty(holdingsData)) return ; - const groupedItemData = groupBy(itemData, 'holdingsId'); + if (isEmpty(itemData)) { + const emptyValues = holdingsData?.map((_, index) =>
); + + return ( + + {emptyValues} + + ); + } + + const itemInfo = [...sourceRecord?.relatedItemInfo]; + + holdingsData.forEach(holdings => { + const isDiscarded = holdings.actionStatus === 'DISCARDED'; + const holdingsId = holdings.id; + + if (isDiscarded && !itemInfo.find(item => item.holdingsId === holdingsId)) { + itemInfo.push({ holdingsId, error: true }); + } + }); + + const groupedItemData = groupBy(itemInfo, 'holdingsId'); const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]); return ( @@ -193,9 +216,9 @@ export const RecordsTable = ({ const itemData = sourceRecord?.relatedItemInfo; const groupedItemData = groupBy(itemData, 'holdingsId'); - if (isEmpty(groupedItemData)) return ; - const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]); + const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]) + .map(element => (!element ? [{}] : element)); return ( ; - const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]); + const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]) + .map(element => (element || [{}])); return ( ; - const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]); + + const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]) + .map(element => (element || [{}])); return ( item.error)) { + return ( + [item])} + /> + ); + } + const groupedItemData = groupBy(itemData, 'holdingsId'); - if (isEmpty(groupedItemData)) return ; - const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]); + const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]) + .map(element => (element || [{ error: true }])); return ( ); diff --git a/src/routes/JobSummary/components/cells/AuthorityCell.js b/src/routes/JobSummary/components/cells/AuthorityCell.js index f556e6467..14058061c 100644 --- a/src/routes/JobSummary/components/cells/AuthorityCell.js +++ b/src/routes/JobSummary/components/cells/AuthorityCell.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import { isEmpty } from 'lodash'; + import { getRecordActionStatusLabel, getHotlinkCellFormatter, @@ -14,7 +14,7 @@ export const AuthorityCell = ({ jobLogRecords, sortedItemData, }) => { - if (!authorityActionStatus && !isEmpty(sortedItemData)) { + if (!authorityActionStatus) { return fillCellWithNoValues(sortedItemData); } diff --git a/src/routes/JobSummary/components/cells/ErrorCell.js b/src/routes/JobSummary/components/cells/ErrorCell.js index 103896467..92c898926 100644 --- a/src/routes/JobSummary/components/cells/ErrorCell.js +++ b/src/routes/JobSummary/components/cells/ErrorCell.js @@ -2,17 +2,17 @@ import { FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; import { isEmpty } from 'lodash'; -import { fillCellWithNoValues } from '../utils'; +import { BaseLineCell, fillCellWithNoValues } from '../utils'; export const ErrorCell = ({ error, - sortedItemData + sortedItemData, }) => { - if (!error && !isEmpty(sortedItemData)) { + if (error && !isEmpty(sortedItemData)) { return fillCellWithNoValues(sortedItemData, true); } - return error ? : ''; + return error ? : ''; }; ErrorCell.propTypes = { diff --git a/src/routes/JobSummary/components/cells/ItemCell.js b/src/routes/JobSummary/components/cells/ItemCell.js index 4e875dc2b..991b3d24d 100644 --- a/src/routes/JobSummary/components/cells/ItemCell.js +++ b/src/routes/JobSummary/components/cells/ItemCell.js @@ -36,7 +36,7 @@ export const ItemCell = ({ }); return ( -
+
{groupOfItems}
); diff --git a/src/routes/JobSummary/components/cells/OrderCell.js b/src/routes/JobSummary/components/cells/OrderCell.js index 454dd7c9b..f1ca24332 100644 --- a/src/routes/JobSummary/components/cells/OrderCell.js +++ b/src/routes/JobSummary/components/cells/OrderCell.js @@ -1,4 +1,3 @@ -import { isEmpty } from 'lodash'; import { getRecordActionStatusLabel, getHotlinkCellFormatter, @@ -13,7 +12,7 @@ export const OrderCell = ({ jobLogRecords, sortedItemData, }) => { - if (!poLineActionStatus && !isEmpty(sortedItemData)) { + if (!poLineActionStatus) { return fillCellWithNoValues(sortedItemData); } diff --git a/src/routes/JobSummary/components/cells/tests/ErrorCell.test.js b/src/routes/JobSummary/components/cells/tests/ErrorCell.test.js index 124e88c86..0992ad755 100644 --- a/src/routes/JobSummary/components/cells/tests/ErrorCell.test.js +++ b/src/routes/JobSummary/components/cells/tests/ErrorCell.test.js @@ -10,8 +10,6 @@ import { import { ErrorCell } from '../ErrorCell'; -const errorProp = 'test error'; - const sortedItemDataProp = [ [{ actionStatus : 'CREATED', @@ -35,21 +33,24 @@ const renderErrorCell = ({ error = '', sortedItemData }) => { describe('ErrorCell component', () => { it('should be rendered with no axe errors', async () => { - const { container } = renderErrorCell({ error: errorProp }); + const { container } = renderErrorCell({ error: 'test error' }); await runAxeTest({ rootNode: container }); }); describe('when error was occured', () => { it('error message should be rendered', () => { - const { getByText } = renderErrorCell({ error: errorProp }); + const { getByText } = renderErrorCell({ error: 'test error' }); expect(getByText('Error')).toBeInTheDocument(); }); describe('when item has an error', () => { it('error message should be rendered', () => { - const { getByText } = renderErrorCell({ sortedItemData: sortedItemDataProp }); + const { getByText } = renderErrorCell({ + error: 'test error', + sortedItemData: sortedItemDataProp, + }); expect(getByText('Error')).toBeInTheDocument(); }); diff --git a/src/routes/JobSummary/components/utils.js b/src/routes/JobSummary/components/utils.js index 31367f511..efae29c99 100644 --- a/src/routes/JobSummary/components/utils.js +++ b/src/routes/JobSummary/components/utils.js @@ -5,6 +5,7 @@ import { NoValue, } from '@folio/stripes/components'; +import { isEmpty } from 'lodash'; import { RECORD_ACTION_STATUS_LABEL_IDS } from '../../../utils'; import sharedCss from '../../../shared.css'; @@ -42,17 +43,19 @@ export const getRecordActionStatusLabel = recordType => { return ; }; -export const fillCellWithNoValues = (itemData, isErrorColumn = false) => { +const renderNoValues = (itemData, isErrorColumn) => { const itemsInfoCell = itemData?.map((sortedItems, itemIndex) => { - const groupOfItems = sortedItems?.map((item, index) => ( -
- - {item.error && isErrorColumn ? : null} - {!isErrorColumn ? : null} - -
-
- )); + const groupOfItems = sortedItems?.map((item, index) => { + return ( +
+ + {item.error && isErrorColumn ? : null} + {!isErrorColumn ? : null} + +
+
+ ); + }); return
{groupOfItems}
; }); @@ -63,3 +66,11 @@ export const fillCellWithNoValues = (itemData, isErrorColumn = false) => { ); }; + +export const fillCellWithNoValues = (itemData, isErrorColumn = false) => { + if (isEmpty(itemData)) { + return ; + } + + return renderNoValues(itemData, isErrorColumn); +}; From 5bd6911cd31236c301bdceb7dad9a4d9b78cfae7 Mon Sep 17 00:00:00 2001 From: Mariia Aloshyna Date: Fri, 4 Aug 2023 14:35:01 +0300 Subject: [PATCH 09/14] Revert "UIDATIMP-1443: Improve mapping profile for Holdings for handling multiple Holdings mapping (#1436)" This reverts commit e360d19a258560a940ffcbf442cec76fd8fc05bd. --- CHANGELOG.md | 1 - .../edit/HoldingsDetailsSections/Location.js | 41 ++++++------------- .../view/HoldingsDetailsSections/Location.js | 6 +-- .../initialDetails/HOLDINGS.js | 23 ++--------- .../MappingProfiles/initialDetails/index.js | 13 +++--- 5 files changed, 22 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8611f8724..d2c4b12cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,6 @@ * Update Log details screen to support multiple holdings & items (UIDATIMP-1438) * Update Log JSON screen to support multiple holdings (UIDATIMP-1439) * Update Log JSON screen to support multiple items (UIDATIMP-1440) -* Improve mapping profile for Holdings for handling multiple Holdings mapping (UIDATIMP-1443) * DI Log: change Discarded to No action (UIDATIMP-1446) * DI Log: Make some changes to the Log header (UIDATIMP-1447) * DI Job profiles: Redirect when job profile was deleted (UIDATIMP-1450) diff --git a/src/settings/MappingProfiles/detailsSections/edit/HoldingsDetailsSections/Location.js b/src/settings/MappingProfiles/detailsSections/edit/HoldingsDetailsSections/Location.js index cbf2d335f..775111aea 100644 --- a/src/settings/MappingProfiles/detailsSections/edit/HoldingsDetailsSections/Location.js +++ b/src/settings/MappingProfiles/detailsSections/edit/HoldingsDetailsSections/Location.js @@ -19,8 +19,6 @@ import { import { getAcceptedValuesPath, getFieldName, - getRepeatableAcceptedValuesPath, - getSubfieldName, renderFieldLabelWithInfo, } from '../../utils'; import { @@ -32,21 +30,6 @@ export const Location = ({ setReferenceTables, okapi, }) => { - const LOCATION_FIELDS_MAP = { - PERMANENT: getSubfieldName(6, 0, 0), - PERMANENT_ACCEPTED_VALUES: getRepeatableAcceptedValuesPath(6, 0, 0), - TEMPORARY: getFieldName(7), - TEMPORARY_ACCEPTED_VALUES: getAcceptedValuesPath(7), - SHELVING_ORDER: getFieldName(8), - SHELVING_TITLE: getFieldName(9), - COPY_NUMBER: getFieldName(10), - CALL_NUMBER_TYPE: getFieldName(11), - CALL_NUMBER_TYPE_ACCEPTED_VALUES: getAcceptedValuesPath(11), - CALL_NUMBER_PREFIX: getFieldName(12), - CALL_NUMBER: getFieldName(13), - CALL_NUMBER_SUFFIX: getFieldName(14), - }; - const permanentLocationLabel = renderFieldLabelWithInfo( `${TRANSLATION_ID_PREFIX}.location.field.permanent`, `${TRANSLATION_ID_PREFIX}.item.requiredWhenCreatingHoldings.info`, @@ -64,7 +47,7 @@ export const Location = ({ > @@ -85,7 +68,7 @@ export const Location = ({ > } optionValue="name" optionLabel="name" @@ -96,7 +79,7 @@ export const Location = ({ }]} isRemoveValueAllowed setAcceptedValues={setReferenceTables} - acceptedValuesPath={LOCATION_FIELDS_MAP.TEMPORARY_ACCEPTED_VALUES} + acceptedValuesPath={getAcceptedValuesPath(7)} optionTemplate="**name** (**code**)" okapi={okapi} /> @@ -109,7 +92,7 @@ export const Location = ({ > } disabled /> @@ -122,7 +105,7 @@ export const Location = ({ {validation => ( } validate={[validation]} /> @@ -139,7 +122,7 @@ export const Location = ({ {validation => ( } validate={[validation]} /> @@ -154,7 +137,7 @@ export const Location = ({ > } optionValue="name" optionLabel="name" @@ -165,7 +148,7 @@ export const Location = ({ }]} isRemoveValueAllowed setAcceptedValues={setReferenceTables} - acceptedValuesPath={LOCATION_FIELDS_MAP.CALL_NUMBER_TYPE_ACCEPTED_VALUES} + acceptedValuesPath={getAcceptedValuesPath(11)} okapi={okapi} /> @@ -177,7 +160,7 @@ export const Location = ({ {validation => ( } validate={[validation]} /> @@ -192,7 +175,7 @@ export const Location = ({ {validation => ( } validate={[validation]} /> @@ -208,7 +191,7 @@ export const Location = ({ {validation => ( } validate={[validation]} /> diff --git a/src/settings/MappingProfiles/detailsSections/view/HoldingsDetailsSections/Location.js b/src/settings/MappingProfiles/detailsSections/view/HoldingsDetailsSections/Location.js index 2ac523446..c07c5f7b4 100644 --- a/src/settings/MappingProfiles/detailsSections/view/HoldingsDetailsSections/Location.js +++ b/src/settings/MappingProfiles/detailsSections/view/HoldingsDetailsSections/Location.js @@ -1,7 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; -import { get } from 'lodash'; import { Accordion, @@ -10,15 +9,14 @@ import { KeyValue, } from '@folio/stripes/components'; -import { ProhibitionIcon } from '../../../../../components'; +import { ProhibitionIcon } from '../../../../../components/ProhibitionIcon'; import { getFieldValue } from '../../utils'; import { TRANSLATION_ID_PREFIX } from '../../constants'; import { mappingProfileFieldShape } from '../../../../../utils'; export const Location = ({ mappingDetails }) => { - const permanentLocationSubfields = getFieldValue(mappingDetails, 'permanentLocationId', 'subfields'); - const permanentLocation = get(permanentLocationSubfields, '[0].fields[0].value', ''); + const permanentLocation = getFieldValue(mappingDetails, 'permanentLocationId', 'value'); const temporaryLocation = getFieldValue(mappingDetails, 'temporaryLocationId', 'value'); const shelvingOrder = getFieldValue(mappingDetails, 'shelvingOrder', 'value'); const shelvingTitle = getFieldValue(mappingDetails, 'shelvingTitle', 'value'); diff --git a/src/settings/MappingProfiles/initialDetails/HOLDINGS.js b/src/settings/MappingProfiles/initialDetails/HOLDINGS.js index 250e486e7..34075788a 100644 --- a/src/settings/MappingProfiles/initialDetails/HOLDINGS.js +++ b/src/settings/MappingProfiles/initialDetails/HOLDINGS.js @@ -69,27 +69,10 @@ const HOLDINGS = { }, { name: 'permanentLocationId', enabled: true, - required: false, - path: 'holdings.permanentLocationId[]', + path: 'holdings.permanentLocationId', value: '', - repeatableFieldAction: 'EXTEND_EXISTING', - subfields: [ - { - order: 0, - path: 'holdings.permanentLocationId[]', - fields: [ - { - name: 'permanentLocationId', - enabled: true, - required: false, - path: 'holdings.permanentLocationId[]', - value: '', - subfields: [], - acceptedValues: {}, - }, - ], - }, - ], + subfields: [], + acceptedValues: {}, }, { name: 'temporaryLocationId', enabled: true, diff --git a/src/settings/MappingProfiles/initialDetails/index.js b/src/settings/MappingProfiles/initialDetails/index.js index b4d596ec0..0d978c615 100644 --- a/src/settings/MappingProfiles/initialDetails/index.js +++ b/src/settings/MappingProfiles/initialDetails/index.js @@ -24,11 +24,6 @@ const initialValues = { MARC_AUTHORITY, }; -const KEEP_SUBFIELDS_INITIAL = { - [FOLIO_RECORD_TYPES.INVOICE.type]: ['acqUnitIds'], - [FOLIO_RECORD_TYPES.HOLDINGS.type]: ['permanentLocationId'], -}; - const modifyInvoiceLinesFieldDetails = field => { const invoiceLineSubfield = { ...field.subfields[0] }; @@ -103,13 +98,15 @@ export const getInitialDetails = (entity, stripRepeatableFields = false) => { if (field.name === 'invoiceLines') { return modifyInvoiceLinesFieldDetails(field); } - } - const keepSubfieldsInitial = KEEP_SUBFIELDS_INITIAL[entity]?.some(fieldName => fieldName === field.name); + if (field.name === 'acqUnitIds') { + return field; + } + } return { ...field, - subfields: keepSubfieldsInitial ? field.subfields : [], + subfields: [], }; }); From 8c218c22877b4117fc695e815f13fa3bfd5cf258 Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko1 Date: Mon, 7 Aug 2023 10:15:37 +0300 Subject: [PATCH 10/14] multiple-feature: Add general items error displaying --- .../JobSummary/components/RecordsTable.js | 56 +++++++++++++++---- .../components/cells/HoldingsCell.js | 2 +- .../JobSummary/components/cells/ItemCell.js | 2 +- src/routes/JobSummary/components/utils.js | 18 +++++- 4 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/routes/JobSummary/components/RecordsTable.js b/src/routes/JobSummary/components/RecordsTable.js index c62f0b42f..9471723f5 100644 --- a/src/routes/JobSummary/components/RecordsTable.js +++ b/src/routes/JobSummary/components/RecordsTable.js @@ -30,7 +30,8 @@ import { InvoiceCell, ErrorCell, } from '.'; -import { BaseLineCell } from './utils'; +import { BaseLineCell, isGeneralItemsError } from './utils'; +import { RECORD_ACTION_STATUS } from '../../../utils'; export const RecordsTable = ({ mutator, @@ -103,6 +104,7 @@ export const RecordsTable = ({ invoiceStatus: , error: , }; + const resultsFormatter = { recordNumber: ({ sourceRecordOrder }) => ( ; - const itemInfo = [...sourceRecord?.relatedItemInfo]; + const itemInfo = sourceRecord?.relatedItemInfo ? [...sourceRecord?.relatedItemInfo] : [{}]; const instanceId = sourceRecord?.relatedInstanceInfo.idList[0]; - holdingsInfo.forEach(holdings => { - const isDiscarded = holdings.actionStatus === 'DISCARDED'; + holdingsInfo?.forEach(holdings => { + const isDiscarded = holdings.actionStatus === RECORD_ACTION_STATUS.DISCARDED; const holdingsId = holdings.id; if (isDiscarded && !itemInfo.find(item => item.holdingsId === holdingsId)) { @@ -168,16 +170,39 @@ export const RecordsTable = ({ /> ); }, - itemStatus: ({ sourceRecordId }) => { + itemStatus: ({ + sourceRecordId, + itemActionStatus, + }) => { const sourceRecord = jobLogRecords.find(item => item.sourceRecordId === sourceRecordId); const holdingsData = sourceRecord?.relatedHoldingsInfo; - const itemData = sourceRecord?.relatedItemInfo; + const itemData = sourceRecord?.relatedItemInfo || []; const instanceId = sourceRecord?.relatedInstanceInfo.idList[0]; - if (isEmpty(itemData) && isEmpty(holdingsData)) return ; + const isGeneralItemError = isGeneralItemsError(itemData, itemActionStatus); + + if (isGeneralItemError) { + return ( + + + + ); + } + + if (isEmpty(itemData) && isEmpty(holdingsData)) { + return ( + + + + ); + } if (isEmpty(itemData)) { - const emptyValues = holdingsData?.map((_, index) =>
); + const emptyValues = holdingsData?.map((_, index) => ( +
+ +
+ )); return ( @@ -186,10 +211,10 @@ export const RecordsTable = ({ ); } - const itemInfo = [...sourceRecord?.relatedItemInfo]; + const itemInfo = sourceRecord?.relatedItemInfo ? [...sourceRecord?.relatedItemInfo] : [{}]; holdingsData.forEach(holdings => { - const isDiscarded = holdings.actionStatus === 'DISCARDED'; + const isDiscarded = holdings.actionStatus === RECORD_ACTION_STATUS.DISCARDED; const holdingsId = holdings.id; if (isDiscarded && !itemInfo.find(item => item.holdingsId === holdingsId)) { @@ -278,11 +303,22 @@ export const RecordsTable = ({ error: ({ error, sourceRecordId, + itemActionStatus, }) => { const sourceRecord = jobLogRecords.find(item => item.sourceRecordId === sourceRecordId); const holdingsData = sourceRecord?.relatedHoldingsInfo; const itemData = sourceRecord?.relatedItemInfo; + const isGeneralItemError = isGeneralItemsError(itemData, itemActionStatus); + + if (isGeneralItemError) { + return ( + + + + ); + } + if (isEmpty(itemData) && holdingsData?.some(item => item.error)) { return ( locationId === locationItem.id)?.code; diff --git a/src/routes/JobSummary/components/cells/ItemCell.js b/src/routes/JobSummary/components/cells/ItemCell.js index 991b3d24d..5ad30ba8b 100644 --- a/src/routes/JobSummary/components/cells/ItemCell.js +++ b/src/routes/JobSummary/components/cells/ItemCell.js @@ -18,7 +18,7 @@ export const ItemCell = ({ const holdingsId = item.holdingsId; const itemId = item.id; const path = `/inventory/view/${instanceId}/${holdingsId}/${itemId}`; - const isDiscarded = item.actionStatus === 'DISCARDED'; + const isDiscarded = item.actionStatus === RECORD_ACTION_STATUS.DISCARDED; const hrId = item.hrid; diff --git a/src/routes/JobSummary/components/utils.js b/src/routes/JobSummary/components/utils.js index efae29c99..5ecc4e654 100644 --- a/src/routes/JobSummary/components/utils.js +++ b/src/routes/JobSummary/components/utils.js @@ -6,7 +6,10 @@ import { } from '@folio/stripes/components'; import { isEmpty } from 'lodash'; -import { RECORD_ACTION_STATUS_LABEL_IDS } from '../../../utils'; +import { + RECORD_ACTION_STATUS, + RECORD_ACTION_STATUS_LABEL_IDS, +} from '../../../utils'; import sharedCss from '../../../shared.css'; @@ -74,3 +77,16 @@ export const fillCellWithNoValues = (itemData, isErrorColumn = false) => { return renderNoValues(itemData, isErrorColumn); }; + +/* + When there is general item error, BE sends response like this + realtedItemInfo: [{ + actionStatus: "DISCARDED", + error: "Error", + hrid: "", + }]; + This array always contains one item with status 'DISCARDED' and nas no holdingsId. +*/ +export const isGeneralItemsError = (itemData, itemStatus) => { + return itemData?.length === 1 && !itemData[0].holdingsId && itemStatus === RECORD_ACTION_STATUS.DISCARDED; +}; From 5aefcd5e4c166e8327ecccd64ef081b1f23c06d2 Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko1 Date: Mon, 7 Aug 2023 12:37:06 +0300 Subject: [PATCH 11/14] multiple-feature: Replace Error with No action --- src/routes/JobSummary/components/RecordsTable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/JobSummary/components/RecordsTable.js b/src/routes/JobSummary/components/RecordsTable.js index 9471723f5..8a6fe3924 100644 --- a/src/routes/JobSummary/components/RecordsTable.js +++ b/src/routes/JobSummary/components/RecordsTable.js @@ -184,7 +184,7 @@ export const RecordsTable = ({ if (isGeneralItemError) { return ( - + ); } From 474789f56f2fa78f12e9dc164dc4b20be3a090af Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko1 Date: Mon, 7 Aug 2023 18:29:52 +0300 Subject: [PATCH 12/14] multiple-feature: refactor code --- .../JobSummary/components/RecordsTable.js | 101 ++++++++++-------- src/routes/JobSummary/components/utils.js | 51 ++++++++- 2 files changed, 101 insertions(+), 51 deletions(-) diff --git a/src/routes/JobSummary/components/RecordsTable.js b/src/routes/JobSummary/components/RecordsTable.js index 8a6fe3924..0e6ab6423 100644 --- a/src/routes/JobSummary/components/RecordsTable.js +++ b/src/routes/JobSummary/components/RecordsTable.js @@ -1,7 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; -import { get, isEmpty, groupBy } from 'lodash'; +import { + get, + isEmpty, +} from 'lodash'; import { buildSortOrder, @@ -30,7 +33,12 @@ import { InvoiceCell, ErrorCell, } from '.'; -import { BaseLineCell, isGeneralItemsError } from './utils'; +import { + BaseLineCell, + getRelatedInfo, + groupAndSortDataForRender, + isGeneralItemsError, +} from './utils'; import { RECORD_ACTION_STATUS } from '../../../utils'; export const RecordsTable = ({ @@ -143,15 +151,24 @@ export const RecordsTable = ({ /> ), holdingsStatus: ({ sourceRecordId }) => { - const sourceRecord = jobLogRecords.find(item => item.sourceRecordId === sourceRecordId); - const holdingsInfo = sourceRecord?.relatedHoldingsInfo; + const { + instanceData, + holdingsData, + itemData, + } = getRelatedInfo(jobLogRecords, sourceRecordId); - if (isEmpty(holdingsInfo)) return ; + if (isEmpty(holdingsData)) { + return ( + + + + ); + } - const itemInfo = sourceRecord?.relatedItemInfo ? [...sourceRecord?.relatedItemInfo] : [{}]; - const instanceId = sourceRecord?.relatedInstanceInfo.idList[0]; + const itemInfo = itemData ? [...itemData] : [{}]; + const instanceId = instanceData?.idList[0]; - holdingsInfo?.forEach(holdings => { + holdingsData?.forEach(holdings => { const isDiscarded = holdings.actionStatus === RECORD_ACTION_STATUS.DISCARDED; const holdingsId = holdings.id; @@ -163,8 +180,7 @@ export const RecordsTable = ({ return ( @@ -174,10 +190,11 @@ export const RecordsTable = ({ sourceRecordId, itemActionStatus, }) => { - const sourceRecord = jobLogRecords.find(item => item.sourceRecordId === sourceRecordId); - const holdingsData = sourceRecord?.relatedHoldingsInfo; - const itemData = sourceRecord?.relatedItemInfo || []; - const instanceId = sourceRecord?.relatedInstanceInfo.idList[0]; + const { + instanceData, + holdingsData, + itemData, + } = getRelatedInfo(jobLogRecords, sourceRecordId); const isGeneralItemError = isGeneralItemsError(itemData, itemActionStatus); @@ -211,9 +228,10 @@ export const RecordsTable = ({ ); } - const itemInfo = sourceRecord?.relatedItemInfo ? [...sourceRecord?.relatedItemInfo] : [{}]; + const itemInfo = itemData ? [...itemData] : [{}]; + const instanceId = instanceData?.idList[0]; - holdingsData.forEach(holdings => { + holdingsData?.forEach(holdings => { const isDiscarded = holdings.actionStatus === RECORD_ACTION_STATUS.DISCARDED; const holdingsId = holdings.id; @@ -222,8 +240,7 @@ export const RecordsTable = ({ } }); - const groupedItemData = groupBy(itemInfo, 'holdingsId'); - const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]); + const sortedItemData = groupAndSortDataForRender(itemData, holdingsData); return ( { - const sourceRecord = jobLogRecords.find(item => item.sourceRecordId === sourceRecordId); - const holdingsData = sourceRecord?.relatedHoldingsInfo; - const itemData = sourceRecord?.relatedItemInfo; - - const groupedItemData = groupBy(itemData, 'holdingsId'); + const { + holdingsData, + itemData, + } = getRelatedInfo(jobLogRecords, sourceRecordId); - const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]) - .map(element => (!element ? [{}] : element)); + const sortedItemData = groupAndSortDataForRender(itemData, holdingsData); return ( { - const sourceRecord = jobLogRecords.find(item => item.sourceRecordId === sourceRecordId); - const holdingsData = sourceRecord?.relatedHoldingsInfo; - const itemData = sourceRecord?.relatedItemInfo; + const { + holdingsData, + itemData, + } = getRelatedInfo(jobLogRecords, sourceRecordId); - const groupedItemData = groupBy(itemData, 'holdingsId'); - - const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]) - .map(element => (element || [{}])); + const sortedItemData = groupAndSortDataForRender(itemData, holdingsData); return ( { - const sourceRecord = jobLogRecords.find(item => item.sourceRecordId === sourceRecordId); - const holdingsData = sourceRecord?.relatedHoldingsInfo; - const itemData = sourceRecord?.relatedItemInfo; - - const groupedItemData = groupBy(itemData, 'holdingsId'); + const { + holdingsData, + itemData, + } = getRelatedInfo(jobLogRecords, sourceRecordId); - const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]) - .map(element => (element || [{}])); + const sortedItemData = groupAndSortDataForRender(itemData, holdingsData); return ( { - const sourceRecord = jobLogRecords.find(item => item.sourceRecordId === sourceRecordId); - const holdingsData = sourceRecord?.relatedHoldingsInfo; - const itemData = sourceRecord?.relatedItemInfo; + const { + holdingsData, + itemData, + } = getRelatedInfo(jobLogRecords, sourceRecordId); const isGeneralItemError = isGeneralItemsError(itemData, itemActionStatus); @@ -328,10 +340,7 @@ export const RecordsTable = ({ ); } - const groupedItemData = groupBy(itemData, 'holdingsId'); - - const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]) - .map(element => (element || [{ error: true }])); + const sortedItemData = groupAndSortDataForRender(itemData, holdingsData, true); return (
{children}
; +export const BaseLineCell = ({ children }) => ( +
+ {children} +
+); export const getHotlinkCellFormatter = (isHotlink, entityLabel, path, entity) => { if (isHotlink) { @@ -43,7 +50,11 @@ export const getRecordActionStatusLabel = recordType => { const labelId = RECORD_ACTION_STATUS_LABEL_IDS[recordType]; - return ; + return ( + + + + ); }; const renderNoValues = (itemData, isErrorColumn) => { @@ -60,7 +71,11 @@ const renderNoValues = (itemData, isErrorColumn) => { ); }); - return
{groupOfItems}
; + return ( +
+ {groupOfItems} +
+ ); }); return ( @@ -72,7 +87,11 @@ const renderNoValues = (itemData, isErrorColumn) => { export const fillCellWithNoValues = (itemData, isErrorColumn = false) => { if (isEmpty(itemData)) { - return ; + return ( + + + + ); } return renderNoValues(itemData, isErrorColumn); @@ -90,3 +109,25 @@ export const fillCellWithNoValues = (itemData, isErrorColumn = false) => { export const isGeneralItemsError = (itemData, itemStatus) => { return itemData?.length === 1 && !itemData[0].holdingsId && itemStatus === RECORD_ACTION_STATUS.DISCARDED; }; + +export const getRelatedInfo = (jobLogRecords, sourceRecordId) => { + const sourceRecord = jobLogRecords?.find(item => item.sourceRecordId === sourceRecordId); + const instanceData = sourceRecord?.relatedInstanceInfo; + const holdingsData = sourceRecord?.relatedHoldingsInfo; + const itemData = sourceRecord?.relatedItemInfo; + + return { + instanceData, + holdingsData, + itemData, + }; +}; + +export const groupAndSortDataForRender = (itemData, holdingsData, isError = false) => { + const groupedItemData = groupBy(itemData, 'holdingsId'); + const sortedItemData = holdingsData?.map(holdings => groupedItemData[holdings.id]); + + return isError + ? sortedItemData?.map(element => (element || [{ error: true }])) + : sortedItemData?.map(element => (element || [{}])); +}; From 991788c5fce14fe1f349e90015531a73ef479755 Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko1 Date: Mon, 7 Aug 2023 18:42:18 +0300 Subject: [PATCH 13/14] multiple-feature: fix code smells --- src/hooks/tests/useLocationsQuery.test.js | 5 ++++- src/routes/JobSummary/components/cells/ErrorCell.js | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/hooks/tests/useLocationsQuery.test.js b/src/hooks/tests/useLocationsQuery.test.js index 97137047c..d37b6bf76 100644 --- a/src/hooks/tests/useLocationsQuery.test.js +++ b/src/hooks/tests/useLocationsQuery.test.js @@ -22,7 +22,10 @@ describe('useLocationsQuery', () => { }); it('should fetch locations', async () => { - const { result, waitFor } = renderHook(() => useLocationsQuery(), { wrapper: queryClientWrapper }); + const { + result, + waitFor, + } = renderHook(() => useLocationsQuery(), { wrapper: queryClientWrapper }); await waitFor(() => !result.current.isLoading); diff --git a/src/routes/JobSummary/components/cells/ErrorCell.js b/src/routes/JobSummary/components/cells/ErrorCell.js index 92c898926..136210c8a 100644 --- a/src/routes/JobSummary/components/cells/ErrorCell.js +++ b/src/routes/JobSummary/components/cells/ErrorCell.js @@ -2,7 +2,10 @@ import { FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; import { isEmpty } from 'lodash'; -import { BaseLineCell, fillCellWithNoValues } from '../utils'; +import { + BaseLineCell, + fillCellWithNoValues, +} from '../utils'; export const ErrorCell = ({ error, From dcc5f4642c8fd1ef69ec520e7cc774ed75f2135d Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko1 Date: Tue, 8 Aug 2023 19:34:10 +0300 Subject: [PATCH 14/14] multiple-feature: fixes after review --- src/routes/JobSummary/JobSummary.js | 3 +- .../components/RecordsTable.test.js | 148 +++++++++--------- .../cells/tests/AuthorityCell.test.js | 32 ++-- .../components/cells/tests/ErrorCell.test.js | 14 +- .../cells/tests/HoldingsCell.test.js | 26 +-- .../cells/tests/InstanceCell.test.js | 4 +- .../cells/tests/InvoiceCell.test.js | 20 +-- .../components/cells/tests/ItemCell.test.js | 16 +- .../components/cells/tests/OrderCell.test.js | 32 ++-- 9 files changed, 148 insertions(+), 147 deletions(-) diff --git a/src/routes/JobSummary/JobSummary.js b/src/routes/JobSummary/JobSummary.js index e5a5338e3..d3374b30b 100644 --- a/src/routes/JobSummary/JobSummary.js +++ b/src/routes/JobSummary/JobSummary.js @@ -39,6 +39,7 @@ import { DATA_TYPES, storage, PREVIOUS_LOCATIONS_KEY, + PER_REQUEST_LIMIT, } from '../../utils'; const INITIAL_RESULT_COUNT = 100; @@ -287,7 +288,7 @@ JobSummaryComponent.manifest = Object.freeze({ throwErrors: false, type: 'okapi', records: 'locations', - path: 'locations?limit=1000&query=cql.allRecords=1 sortby name', + path: `locations?limit=${PER_REQUEST_LIMIT}&query=cql.allRecords=1 sortby name`, } }); diff --git a/src/routes/JobSummary/components/RecordsTable.test.js b/src/routes/JobSummary/components/RecordsTable.test.js index 199812672..40ad3549f 100644 --- a/src/routes/JobSummary/components/RecordsTable.test.js +++ b/src/routes/JobSummary/components/RecordsTable.test.js @@ -46,111 +46,111 @@ const jobLogEntriesResources = { }; const jobLogRecords = [{ - jobExecutionId : '467d7627-c0db-4fb7-b333-4b4983dbf781', - sourceRecordId : '59138d56-bc81-4f66-9f72-f57f53621111', + jobExecutionId: '467d7627-c0db-4fb7-b333-4b4983dbf781', + sourceRecordId: '59138d56-bc81-4f66-9f72-f57f53621111', sourceRecordOrder : 0, - sourceRecordTitle : 'Test record 1', - sourceRecordActionStatus : 'CREATED', + sourceRecordTitle: 'Test record 1', + sourceRecordActionStatus: 'CREATED', instanceActionStatus: 'CREATED', - error : '', - relatedInstanceInfo : { - actionStatus : 'CREATED', - idList : ['720031b9-a792-4936-963c-a7b63fb96574'], - hridList : ['in00000000014'], - error : '' + error: '', + relatedInstanceInfo: { + actionStatus: 'CREATED', + idList: ['720031b9-a792-4936-963c-a7b63fb96574'], + hridList: ['in00000000014'], + error: '', }, - relatedHoldingsInfo : [], - relatedItemInfo : [], + relatedHoldingsInfo: [], + relatedItemInfo: [], relatedAuthorityInfo: { - idList : [], - hridList : [] + idList: [], + hridList: [], }, relatedPoLineInfo: { idList: [], - hridList : [] + hridList: [], }, - relatedInvoiceInfo : { - idList : [], - hridList : [] + relatedInvoiceInfo: { + idList: [], + hridList: [], }, - relatedInvoiceLineInfo : { } + relatedInvoiceLineInfo: { }, }, { - jobExecutionId : '467d7627-c0db-4fb7-b333-4b4983dbf781', - sourceRecordId : '59138d56-bc81-4f66-9f72-f57f53629646', + jobExecutionId: '467d7627-c0db-4fb7-b333-4b4983dbf781', + sourceRecordId: '59138d56-bc81-4f66-9f72-f57f53629646', sourceRecordOrder : 4, - sourceRecordTitle : 'Test record 2', - sourceRecordActionStatus : 'CREATED', + sourceRecordTitle: 'Test record 2', + sourceRecordActionStatus: 'CREATED', instanceActionStatus: 'CREATED', - error : '', - relatedInstanceInfo : { - actionStatus : 'CREATED', - idList : ['720031b9-a792-4936-963c-a7b63fb96574'], - hridList : ['in00000000014'], - error : '' + error: '', + relatedInstanceInfo: { + actionStatus: 'CREATED', + idList: ['720031b9-a792-4936-963c-a7b63fb96574'], + hridList: ['in00000000014'], + error: '', }, - relatedHoldingsInfo : [{ - actionStatus : 'CREATED', - id : 'f648c370-d9d6-432c-a502-b8eb718f867c', + relatedHoldingsInfo: [{ + actionStatus: 'CREATED', + id: 'f648c370-d9d6-432c-a502-b8eb718f867c', permanentLocationId: '53cf956f-c1df-410b-8bea-27f712cca7c0', - hrid : 'ho00000000017', - error : '' + hrid: 'ho00000000017', + error: '', }, { actionStatus: 'CREATED', - id : '5cadf17f-eb72-475c-a2e0-7e56f54f0000', + id: '5cadf17f-eb72-475c-a2e0-7e56f54f0000', permanentLocationId: '758258bc-ecc1-41b8-abca-f7b610822ffd', - hrid : 'ho00000000018', - error : '' + hrid: 'ho00000000018', + error: '', }, { - actionStatus : 'DISCARDED', - id : '5cadf17f-eb72-475c-a2e0-7e56f54fd3b4', + actionStatus: 'DISCARDED', + id: '5cadf17f-eb72-475c-a2e0-7e56f54fd3b4', permanentLocationId: 'fcd64ce1-6995-48f0-840e-89ffa2288371', - hrid : 'ho00000000014', - error : '' + hrid: 'ho00000000014', + error: '', }], - relatedItemInfo : [{ - actionStatus : 'CREATED', - id : '37f20cb1-f60c-4195-80a1-00a16c4af5cb', - hrid : 'it00000000015', - holdingsId : 'f648c370-d9d6-432c-a502-b8eb718f867c', - error : '' + relatedItemInfo: [{ + actionStatus: 'CREATED', + id: '37f20cb1-f60c-4195-80a1-00a16c4af5cb', + hrid: 'it00000000015', + holdingsId: 'f648c370-d9d6-432c-a502-b8eb718f867c', + error: '', }, { - actionStatus : 'DISCARDED', - id : 'ccd19bf0-add1-46bb-899b-c457fd448b51', - hrid : 'it00000000016', - holdingsId : 'f648c370-d9d6-432c-a502-b8eb718f867c', - error : 'test error' + actionStatus: 'DISCARDED', + id: 'ccd19bf0-add1-46bb-899b-c457fd448b51', + hrid: 'it00000000016', + holdingsId: 'f648c370-d9d6-432c-a502-b8eb718f867c', + error: 'test error', }, { - actionStatus : 'CREATED', - id : 'ccd19bf0-add1-46bb-899b-c457fd441111', - hrid : 'it00000000019', - holdingsId : '5cadf17f-eb72-475c-a2e0-7e56f54f0000', - error : '' + actionStatus: 'CREATED', + id: 'ccd19bf0-add1-46bb-899b-c457fd441111', + hrid: 'it00000000019', + holdingsId: '5cadf17f-eb72-475c-a2e0-7e56f54f0000', + error: '', }, { - actionStatus : 'CREATED', - id : '3bcfe427-a747-405f-b3f3-1d842ffb2222', - hrid : 'it00000000018', - holdingsId : '5cadf17f-eb72-475c-a2e0-7e56f54f0000', - error : '' + actionStatus: 'CREATED', + id: '3bcfe427-a747-405f-b3f3-1d842ffb2222', + hrid: 'it00000000018', + holdingsId: '5cadf17f-eb72-475c-a2e0-7e56f54f0000', + error: '', }, { - actionStatus : 'CREATED', - id : '3bcfe427-a747-405f-b3f3-1d842ffb49c6', - hrid : 'it00000000014', - holdingsId : 'f648c370-d9d6-432c-a502-b8eb718f867c', - error : '' + actionStatus: 'CREATED', + id: '3bcfe427-a747-405f-b3f3-1d842ffb49c6', + hrid: 'it00000000014', + holdingsId: 'f648c370-d9d6-432c-a502-b8eb718f867c', + error: '', }], relatedAuthorityInfo: { - idList : [], - hridList : [] + idList: [], + hridList: [], }, relatedPoLineInfo: { idList: [], - hridList : [] + hridList: [], }, - relatedInvoiceInfo : { - idList : [], - hridList : [] + relatedInvoiceInfo: { + idList: [], + hridList: [], }, - relatedInvoiceLineInfo : { } + relatedInvoiceLineInfo: { }, }]; const jobLogResources = { diff --git a/src/routes/JobSummary/components/cells/tests/AuthorityCell.test.js b/src/routes/JobSummary/components/cells/tests/AuthorityCell.test.js index 82a50cab0..16c3b8af7 100644 --- a/src/routes/JobSummary/components/cells/tests/AuthorityCell.test.js +++ b/src/routes/JobSummary/components/cells/tests/AuthorityCell.test.js @@ -21,18 +21,18 @@ const jobLogRecords = [{ idList: [faker.random.uuid()], }, relatedHoldingsInfo: [{ - actionStatus : 'CREATED', - id : 'testHoldingsId', + actionStatus: 'CREATED', + id: 'testHoldingsId', permanentLocationId: faker.random.uuid(), - hrid : 'ho00000000017', - error : '' + hrid: 'ho00000000017', + error: '', }], relatedItemInfo: [{ - actionStatus : 'CREATED', - id : '37f20cb1-f60c-4195-80a1-00a16c4af5cb', - hrid : 'it00000000015', - holdingsId : 'testHoldingsId', - error : '' + actionStatus: 'CREATED', + id: '37f20cb1-f60c-4195-80a1-00a16c4af5cb', + hrid: 'it00000000015', + holdingsId: 'testHoldingsId', + error: '', }], relatedAuthorityInfo: { actionStatus: 'CREATED', @@ -46,11 +46,11 @@ const jobLogRecords = [{ const sortedItemData = [ [{ - actionStatus : 'CREATED', - id : '37f20cb1-f60c-4195-80a1-00a16c4af5cb', - hrid : 'it00000000015', - holdingsId : 'testHoldingsId', - error : '' + actionStatus: 'CREATED', + id: '37f20cb1-f60c-4195-80a1-00a16c4af5cb', + hrid: 'it00000000015', + holdingsId: 'testHoldingsId', + error: '', }] ]; @@ -77,7 +77,7 @@ describe('AuthorityCell component', () => { }); describe('when authority was created', () => { - it('hotlink should be rendered', () => { + it('should render hotlink', () => { const { container } = renderAuthorityCell('CREATED'); const authorityHotlink = container.querySelector('[data-test-entity-name="authority"]'); @@ -86,7 +86,7 @@ describe('AuthorityCell component', () => { }); describe('when authority was updated', () => { - it('hotlink should be rendered', () => { + it('should render hotlink', () => { const { container } = renderAuthorityCell('UPDATED'); const authorityHotlink = container.querySelector('[data-test-entity-name="authority"]'); diff --git a/src/routes/JobSummary/components/cells/tests/ErrorCell.test.js b/src/routes/JobSummary/components/cells/tests/ErrorCell.test.js index 0992ad755..5ab7f9fb3 100644 --- a/src/routes/JobSummary/components/cells/tests/ErrorCell.test.js +++ b/src/routes/JobSummary/components/cells/tests/ErrorCell.test.js @@ -12,11 +12,11 @@ import { ErrorCell } from '../ErrorCell'; const sortedItemDataProp = [ [{ - actionStatus : 'CREATED', - id : '37f20cb1-f60c-4195-80a1-00a16c4af5cb', - hrid : 'it00000000015', - holdingsId : 'testHoldingsId', - error : 'test error' + actionStatus: 'CREATED', + id: '37f20cb1-f60c-4195-80a1-00a16c4af5cb', + hrid: 'it00000000015', + holdingsId: 'testHoldingsId', + error: 'test error', }] ]; @@ -39,14 +39,14 @@ describe('ErrorCell component', () => { }); describe('when error was occured', () => { - it('error message should be rendered', () => { + it('should render error message', () => { const { getByText } = renderErrorCell({ error: 'test error' }); expect(getByText('Error')).toBeInTheDocument(); }); describe('when item has an error', () => { - it('error message should be rendered', () => { + it('should render error message', () => { const { getByText } = renderErrorCell({ error: 'test error', sortedItemData: sortedItemDataProp, diff --git a/src/routes/JobSummary/components/cells/tests/HoldingsCell.test.js b/src/routes/JobSummary/components/cells/tests/HoldingsCell.test.js index 76b45b82d..fb1721e4a 100644 --- a/src/routes/JobSummary/components/cells/tests/HoldingsCell.test.js +++ b/src/routes/JobSummary/components/cells/tests/HoldingsCell.test.js @@ -12,25 +12,25 @@ import { import { HoldingsCell } from '../HoldingsCell'; const locationsProp = [{ - id : 'testLocationId', - name : 'Annex', - code : 'KU/CC/DI/A', + id: 'testLocationId', + name: 'Annex', + code: 'KU/CC/DI/A', }]; const holdingsInfoProp = (actionStatus = 'CREATED') => [{ actionStatus, - id : 'testHoldingsId', + id: 'testHoldingsId', permanentLocationId: 'testLocationId', - hrid : 'ho00000000017', - error : '' + hrid: 'ho00000000017', + error: '', }]; const itemInfo = [{ - actionStatus : 'CREATED', - id : '37f20cb1-f60c-4195-80a1-00a16c4af5cb', - hrid : 'it00000000015', - holdingsId : 'testHoldingsId', - error : '' + actionStatus: 'CREATED', + id: '37f20cb1-f60c-4195-80a1-00a16c4af5cb', + hrid: 'it00000000015', + holdingsId: 'testHoldingsId', + error: '', }]; const renderHoldingsCell = (holdingsInfo) => { @@ -56,7 +56,7 @@ describe('HoldingsCell component', () => { }); describe('when holdings was created', () => { - it('hotlink and location should be rendered', () => { + it('should render hotlink and location', () => { const { container, getByText, @@ -69,7 +69,7 @@ describe('HoldingsCell component', () => { }); describe('when holdings was updated', () => { - it('hotlink and location should be rendered', () => { + it('should render hotlink and location', () => { const { container, getByText, diff --git a/src/routes/JobSummary/components/cells/tests/InstanceCell.test.js b/src/routes/JobSummary/components/cells/tests/InstanceCell.test.js index 1e35ea225..9f2b0a1eb 100644 --- a/src/routes/JobSummary/components/cells/tests/InstanceCell.test.js +++ b/src/routes/JobSummary/components/cells/tests/InstanceCell.test.js @@ -43,7 +43,7 @@ describe('InstanceCell component', () => { }); describe('when instance was created', () => { - it('hotlink should be rendered', () => { + it('should render hotlink', () => { const { container } = renderInstanceCell('CREATED'); const instanceHotlink = container.querySelector('[data-test-entity-name="instance"]'); @@ -52,7 +52,7 @@ describe('InstanceCell component', () => { }); describe('when instance was updated', () => { - it('hotlink should be rendered', () => { + it('should render hotlink', () => { const { container } = renderInstanceCell('UPDATED'); const instanceHotlink = container.querySelector('[data-test-entity-name="instance"]'); diff --git a/src/routes/JobSummary/components/cells/tests/InvoiceCell.test.js b/src/routes/JobSummary/components/cells/tests/InvoiceCell.test.js index 5744f1b5f..405f8e6e7 100644 --- a/src/routes/JobSummary/components/cells/tests/InvoiceCell.test.js +++ b/src/routes/JobSummary/components/cells/tests/InvoiceCell.test.js @@ -15,24 +15,24 @@ const jobLogRecords = [{ sourceRecordId: 'testId', sourceRecordOrder: 0, invoiceActionStatus: 'CREATED', - relatedInvoiceInfo : { - idList : ['testInvoiceId'], - hridList : [] + relatedInvoiceInfo: { + idList: ['testInvoiceId'], + hridList: [], }, relatedInvoiceLineInfo: { fullInvoiceLineNumber: 0, id: 'testInvoiceLineId', - hridList : [], + hridList: [], }, }]; const sortedItemData = [ [{ - actionStatus : 'CREATED', - id : '37f20cb1-f60c-4195-80a1-00a16c4af5cb', - hrid : 'it00000000015', - holdingsId : 'testHoldingsId', - error : '' + actionStatu: 'CREATED', + id: '37f20cb1-f60c-4195-80a1-00a16c4af5cb', + hrid: 'it00000000015', + holdingsId: 'testHoldingsId', + error: '', }] ]; @@ -60,7 +60,7 @@ describe('InvoiceCell component', () => { }); describe('when invoice was created', () => { - it('hotlink should be rendered', () => { + it('should render hotlink', () => { const { container } = renderInvoiceCell('CREATED'); const invoiceHotlink = container.querySelector('[data-test-entity-name="invoice"]'); diff --git a/src/routes/JobSummary/components/cells/tests/ItemCell.test.js b/src/routes/JobSummary/components/cells/tests/ItemCell.test.js index 1fdbfea73..bbbff8a32 100644 --- a/src/routes/JobSummary/components/cells/tests/ItemCell.test.js +++ b/src/routes/JobSummary/components/cells/tests/ItemCell.test.js @@ -11,17 +11,17 @@ import { import { ItemCell } from '../ItemCell'; -const sortedItemDataProp = (actionStatus) => [ +const sortedItemDataProp = actionStatus => [ [{ actionStatus, - id : 'testItemId', - hrid : 'it00000000015', - holdingsId : 'testHoldingsId', - error : '' + id: 'testItemId', + hrid: 'it00000000015', + holdingsId: 'testHoldingsId', + error: '', }] ]; -const renderItemCell = (sortedItemData) => { +const renderItemCell = sortedItemData => { const component = ( { }); describe('when item was created', () => { - it('hotlink and hrid should be rendered', () => { + it('should render hotlink and hrid', () => { const { container, getByText, @@ -55,7 +55,7 @@ describe('ItemCell component', () => { }); describe('when item was updated', () => { - it('hotlink and location should be rendered', () => { + it('should render hotlink and hrid', () => { const { container, getByText, diff --git a/src/routes/JobSummary/components/cells/tests/OrderCell.test.js b/src/routes/JobSummary/components/cells/tests/OrderCell.test.js index 7e5bdb2bb..0589d65cc 100644 --- a/src/routes/JobSummary/components/cells/tests/OrderCell.test.js +++ b/src/routes/JobSummary/components/cells/tests/OrderCell.test.js @@ -21,18 +21,18 @@ const jobLogRecords = [{ idList: [faker.random.uuid()], }, relatedHoldingsInfo: [{ - actionStatus : 'CREATED', - id : 'testHoldingsId', + actionStatus: 'CREATED', + id: 'testHoldingsId', permanentLocationId: faker.random.uuid(), - hrid : 'ho00000000017', - error : '' + hrid: 'ho00000000017', + error: '', }], relatedItemInfo: [{ - actionStatus : 'CREATED', - id : '37f20cb1-f60c-4195-80a1-00a16c4af5cb', - hrid : 'it00000000015', - holdingsId : 'testHoldingsId', - error : '' + actionStatus: 'CREATED', + id: '37f20cb1-f60c-4195-80a1-00a16c4af5cb', + hrid: 'it00000000015', + holdingsId: 'testHoldingsId', + error: '', }], relatedPoLineInfo: { actionStatus: 'CREATED', @@ -42,15 +42,15 @@ const jobLogRecords = [{ const sortedItemData = [ [{ - actionStatus : 'CREATED', - id : '37f20cb1-f60c-4195-80a1-00a16c4af5cb', - hrid : 'it00000000015', - holdingsId : 'testHoldingsId', - error : '' + actionStatus: 'CREATED', + id: '37f20cb1-f60c-4195-80a1-00a16c4af5cb', + hrid: 'it00000000015', + holdingsId: 'testHoldingsId', + error: '', }] ]; -const renderOrderCell = (poLineActionStatus) => { +const renderOrderCell = poLineActionStatus => { const component = ( { }); describe('when order was created', () => { - it('hotlink should be rendered', () => { + it('should render hotlink', () => { const { container } = renderOrderCell('CREATED'); const orderHotlink = container.querySelector('[data-test-entity-name="order"]');