-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
UIIN-3175: Item: Display all versions in View history second pane (#2763
- Loading branch information
1 parent
f19ea5c
commit 5093383
Showing
11 changed files
with
378 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import { useContext, useState } from 'react'; | ||
import { useIntl } from 'react-intl'; | ||
import PropTypes from 'prop-types'; | ||
|
||
import { AuditLogPane } from '@folio/stripes/components'; | ||
|
||
import { DataContext } from '../../contexts'; | ||
import { | ||
useItemAuditDataQuery, | ||
useInventoryVersionHistory, | ||
} from '../../hooks'; | ||
|
||
import { getDateWithTime } from '../../utils'; | ||
|
||
export const createFieldFormatter = (referenceData, circulationHistory) => ({ | ||
discoverySuppress: value => value.toString(), | ||
typeId: value => referenceData.callNumberTypes?.find(type => type.id === value)?.name, | ||
itemLevelCallNumberTypeId: value => referenceData.callNumberTypes?.find(type => type.id === value)?.name, | ||
itemDamagedStatusId: value => referenceData.itemDamagedStatuses?.find(type => type.id === value)?.name, | ||
permanentLocationId: value => referenceData.locationsById[value]?.name, | ||
temporaryLocationId: value => referenceData.locationsById[value]?.name, | ||
effectiveLocationId: value => referenceData.locationsById[value]?.name, | ||
permanentLoanTypeId: value => referenceData.loanTypes?.find(type => type.id === value)?.name, | ||
temporaryLoanTypeId: value => referenceData.loanTypes?.find(type => type.id === value)?.name, | ||
materialTypeId: value => referenceData.materialTypes?.find(type => type.id === value)?.name, | ||
statisticalCodeIds: value => { | ||
const statisticalCode = referenceData.statisticalCodes?.find(code => code.id === value); | ||
|
||
return `${statisticalCode.statisticalCodeType.name}: ${statisticalCode.code} - ${statisticalCode.name}`; | ||
}, | ||
relationshipId: value => referenceData.electronicAccessRelationships?.find(type => type.id === value)?.name, | ||
staffOnly: value => value.toString(), | ||
itemNoteTypeId: value => referenceData.itemNoteTypes?.find(type => type.id === value)?.name, | ||
date: value => getDateWithTime(value), | ||
servicePointId: () => circulationHistory.servicePointName, | ||
staffMemberId: () => circulationHistory.source, | ||
dateTime: value => getDateWithTime(value), | ||
source: value => `${value.personal.lastName}, ${value.personal.firstName}`, | ||
}); | ||
|
||
const ItemVersionHistory = ({ | ||
onClose, | ||
itemId, | ||
circulationHistory, | ||
}) => { | ||
const { formatMessage } = useIntl(); | ||
const referenceData = useContext(DataContext); | ||
|
||
const [lastVersionEventTs, setLastVersionEventTs] = useState(null); | ||
|
||
const { | ||
data, | ||
totalRecords, | ||
isLoading, | ||
} = useItemAuditDataQuery(itemId, lastVersionEventTs); | ||
const { | ||
actionsMap, | ||
versions, | ||
isLoadMoreVisible, | ||
} = useInventoryVersionHistory({ data, totalRecords }); | ||
|
||
const fieldLabelsMap = { | ||
accessionNumber: formatMessage({ id: 'ui-inventory.accessionNumber' }), | ||
administrativeNotes: formatMessage({ id: 'ui-inventory.administrativeNotes' }), | ||
barcode: formatMessage({ id: 'ui-inventory.itemBarcode' }), | ||
callNumber: formatMessage({ id: 'ui-inventory.effectiveCallNumber' }), | ||
circulationNotes: formatMessage({ id: 'ui-inventory.circulationHistory' }), | ||
chronology: formatMessage({ id: 'ui-inventory.chronology' }), | ||
copyNumber: formatMessage({ id: 'ui-inventory.copyNumber' }), | ||
date: formatMessage({ id: 'ui-inventory.date' }), | ||
dateTime: formatMessage({ id: 'ui-inventory.checkInDate' }), | ||
descriptionOfPieces: formatMessage({ id: 'ui-inventory.descriptionOfPieces' }), | ||
discoverySuppress: formatMessage({ id: 'ui-inventory.discoverySuppress' }), | ||
displaySummary: formatMessage({ id: 'ui-inventory.displaySummary' }), | ||
effectiveLocationId: formatMessage({ id: 'ui-inventory.effectiveLocation' }), | ||
electronicAccess: formatMessage({ id: 'ui-inventory.electronicAccess' }), | ||
enumeration: formatMessage({ id: 'ui-inventory.enumeration' }), | ||
formerIds: formatMessage({ id: 'ui-inventory.formerId' }), | ||
itemDamagedStatusDate: formatMessage({ id: 'ui-inventory.itemDamagedStatusDate' }), | ||
itemDamagedStatusId: formatMessage({ id: 'ui-inventory.itemDamagedStatus' }), | ||
itemIdentifier: formatMessage({ id: 'ui-inventory.itemIdentifier' }), | ||
itemLevelCallNumber: formatMessage({ id: 'ui-inventory.callNumber' }), | ||
itemLevelCallNumberPrefix: formatMessage({ id: 'ui-inventory.callNumberPrefix' }), | ||
itemLevelCallNumberSuffix: formatMessage({ id: 'ui-inventory.callNumberSuffix' }), | ||
itemLevelCallNumberTypeId: formatMessage({ id: 'ui-inventory.callNumberType' }), | ||
materialTypeId: formatMessage({ id: 'ui-inventory.materialType' }), | ||
missingPieces: formatMessage({ id: 'ui-inventory.missingPieces' }), | ||
missingPiecesDate: formatMessage({ id: 'ui-inventory.date' }), | ||
name: formatMessage({ id: 'ui-inventory.item.availability.itemStatus' }), | ||
notes: formatMessage({ id: 'ui-inventory.itemNotes' }), | ||
numberOfMissingPieces: formatMessage({ id: 'ui-inventory.numberOfMissingPieces' }), | ||
numberOfPieces: formatMessage({ id: 'ui-inventory.numberOfPieces' }), | ||
permanentLoanTypeId: formatMessage({ id: 'ui-inventory.permanentLoantype' }), | ||
permanentLocationId: formatMessage({ id: 'ui-inventory.permanentLocation' }), | ||
prefix: formatMessage({ id: 'ui-inventory.callNumberPrefix' }), | ||
servicePointId: formatMessage({ id: 'ui-inventory.servicePoint' }), | ||
staffMemberId: formatMessage({ id: 'ui-inventory.source' }), | ||
statisticalCodeIds: formatMessage({ id: 'ui-inventory.statisticalCodes' }), | ||
suffix: formatMessage({ id: 'ui-inventory.callNumberSuffix' }), | ||
temporaryLoanTypeId: formatMessage({ id: 'ui-inventory.temporaryLoantype' }), | ||
temporaryLocationId: formatMessage({ id: 'ui-inventory.temporaryLocation' }), | ||
typeId: formatMessage({ id: 'ui-inventory.callNumberType' }), | ||
volume: formatMessage({ id: 'ui-inventory.volume' }), | ||
yearCaption: formatMessage({ id: 'ui-inventory.yearCaption' }), | ||
}; | ||
|
||
const fieldFormatter = createFieldFormatter(referenceData, circulationHistory); | ||
|
||
const handleLoadMore = lastEventTs => { | ||
setLastVersionEventTs(lastEventTs); | ||
}; | ||
|
||
return ( | ||
<AuditLogPane | ||
versions={versions} | ||
onClose={onClose} | ||
isLoadedMoreVisible={isLoadMoreVisible} | ||
handleLoadMore={handleLoadMore} | ||
isLoading={isLoading} | ||
fieldLabelsMap={fieldLabelsMap} | ||
fieldFormatter={fieldFormatter} | ||
actionsMap={actionsMap} | ||
/> | ||
); | ||
}; | ||
|
||
ItemVersionHistory.propTypes = { | ||
itemId: PropTypes.string.isRequired, | ||
onClose: PropTypes.func.isRequired, | ||
circulationHistory: PropTypes.object.isRequired, | ||
}; | ||
|
||
export default ItemVersionHistory; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
import { act } from 'react'; | ||
import { | ||
QueryClient, | ||
QueryClientProvider, | ||
} from 'react-query'; | ||
import { screen } from '@folio/jest-config-stripes/testing-library/react'; | ||
import { runAxeTest } from '@folio/stripes-testing'; | ||
|
||
import { | ||
renderWithIntl, | ||
translationsProperties, | ||
} from '../../../test/jest/helpers'; | ||
|
||
import ItemVersionHistory, { createFieldFormatter } from './ItemVersionHistory'; | ||
import { DataContext } from '../../contexts'; | ||
|
||
jest.mock('@folio/stripes/components', () => ({ | ||
...jest.requireActual('@folio/stripes/components'), | ||
AuditLogPane: () => <div>Version history</div>, | ||
})); | ||
|
||
jest.mock('../../utils', () => ({ | ||
getDateWithTime: jest.fn(date => `Formatted Date: ${date}`), | ||
})); | ||
|
||
jest.mock('../../hooks', () => ({ | ||
...jest.requireActual('../../hooks'), | ||
useItemAuditDataQuery: jest.fn().mockReturnValue({ data: [{}], isLoading: false }), | ||
useInventoryVersionHistory: () => ({ versions: [], isLoadMoreVisible: true }), | ||
})); | ||
|
||
const queryClient = new QueryClient(); | ||
|
||
const onCloseMock = jest.fn(); | ||
const itemId = 'itemId'; | ||
const date = '2024-02-26T12:00:00Z'; | ||
const mockReferenceData = { | ||
callNumberTypes: [{ id: '123', name: 'Test Call Number Type' }], | ||
itemDamagedStatuses: [{ id: 'damaged-1', name: 'Damaged' }], | ||
locationsById: { 'location-1': { name: 'Main Library' } }, | ||
loanTypes: [{ id: 'loan-1', name: 'Short Term' }], | ||
materialTypes: [{ id: 'material-1', name: 'Book' }], | ||
statisticalCodes: [{ id: 'stat-1', statisticalCodeType: { name: 'Category' }, code: '001', name: 'Stat Code' }], | ||
electronicAccessRelationships: [{ id: 'rel-1', name: 'Online Access' }], | ||
itemNoteTypes: [{ id: 'note-1', name: 'Public Note' }], | ||
}; | ||
const mockCirculationHistory = { | ||
servicePointName: 'Main Desk', | ||
source: 'Librarian User', | ||
}; | ||
|
||
const renderItemVersionHistory = () => { | ||
const component = ( | ||
<QueryClientProvider client={queryClient}> | ||
<DataContext.Provider value={{}}> | ||
<ItemVersionHistory | ||
itemId={itemId} | ||
onClose={onCloseMock} | ||
circulationHistory={{}} | ||
/> | ||
</DataContext.Provider> | ||
</QueryClientProvider> | ||
); | ||
|
||
return renderWithIntl(component, translationsProperties); | ||
}; | ||
|
||
describe('ItemVersionHistory', () => { | ||
it('should be rendered with no axe errors', async () => { | ||
const { container } = await act(async () => renderItemVersionHistory()); | ||
|
||
await runAxeTest({ rootNode: container }); | ||
}); | ||
|
||
it('should render View history pane', () => { | ||
renderItemVersionHistory(); | ||
|
||
expect(screen.getByText('Version history')).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
describe('createFieldFormatter', () => { | ||
const fieldFormatter = createFieldFormatter(mockReferenceData, mockCirculationHistory); | ||
|
||
it('should format discoverySuppress field correctly', () => { | ||
expect(fieldFormatter.discoverySuppress(true)).toBe('true'); | ||
expect(fieldFormatter.discoverySuppress(false)).toBe('false'); | ||
}); | ||
|
||
it('should format typeId field correctly', () => { | ||
expect(fieldFormatter.typeId('123')).toBe('Test Call Number Type'); | ||
}); | ||
|
||
it('should format typeId itemLevelCallNumberTypeId correctly', () => { | ||
expect(fieldFormatter.itemLevelCallNumberTypeId('123')).toBe('Test Call Number Type'); | ||
}); | ||
|
||
it('should format itemDamagedStatusId field correctly', () => { | ||
expect(fieldFormatter.itemDamagedStatusId('damaged-1')).toBe('Damaged'); | ||
}); | ||
|
||
it('should format location IDs field correctly', () => { | ||
expect(fieldFormatter.permanentLocationId('location-1')).toBe('Main Library'); | ||
expect(fieldFormatter.effectiveLocationId('location-1')).toBe('Main Library'); | ||
expect(fieldFormatter.temporaryLocationId('location-1')).toBe('Main Library'); | ||
}); | ||
|
||
it('should format loan types field correctly', () => { | ||
expect(fieldFormatter.permanentLoanTypeId('loan-1')).toBe('Short Term'); | ||
expect(fieldFormatter.temporaryLoanTypeId('loan-1')).toBe('Short Term'); | ||
}); | ||
|
||
it('should format material types field correctly', () => { | ||
expect(fieldFormatter.materialTypeId('material-1')).toBe('Book'); | ||
}); | ||
|
||
it('should format statistical codes field correctly', () => { | ||
expect(fieldFormatter.statisticalCodeIds('stat-1')).toBe('Category: 001 - Stat Code'); | ||
}); | ||
|
||
it('should format electronic access relationships field correctly', () => { | ||
expect(fieldFormatter.relationshipId('rel-1')).toBe('Online Access'); | ||
}); | ||
|
||
it('should format item note types field correctly', () => { | ||
expect(fieldFormatter.itemNoteTypeId('note-1')).toBe('Public Note'); | ||
}); | ||
|
||
it('should format staffOnly field correctly', () => { | ||
expect(fieldFormatter.staffOnly(true)).toBe('true'); | ||
expect(fieldFormatter.staffOnly(false)).toBe('false'); | ||
}); | ||
|
||
it('should format date field correctly', () => { | ||
expect(fieldFormatter.date(date)).toBe(`Formatted Date: ${date}`); | ||
}); | ||
|
||
it('should format dateTime field correctly', () => { | ||
expect(fieldFormatter.dateTime(date)).toBe(`Formatted Date: ${date}`); | ||
}); | ||
|
||
it('should format servicePointId field correctly', () => { | ||
expect(fieldFormatter.servicePointId()).toBe('Main Desk'); | ||
}); | ||
|
||
it('should format staffMemberId field correctly', () => { | ||
expect(fieldFormatter.staffMemberId()).toBe('Librarian User'); | ||
}); | ||
|
||
it('should format source field correctly', () => { | ||
expect(fieldFormatter.source({ personal: { firstName: 'John', lastName: 'Doe' } })).toBe('Doe, John'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default as ItemVersionHistory } from './ItemVersionHistory'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from './useItemAuditDataQuery'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { useQuery } from 'react-query'; | ||
|
||
import { | ||
useNamespace, | ||
useOkapiKy, | ||
} from '@folio/stripes/core'; | ||
|
||
const useItemAuditDataQuery = (itemId, eventTs) => { | ||
const ky = useOkapiKy(); | ||
const [namespace] = useNamespace({ key: 'item-audit-data' }); | ||
|
||
// eventTs param is used to load more data | ||
const { isLoading, data = {} } = useQuery({ | ||
queryKey: [namespace, itemId, eventTs], | ||
queryFn: () => ky.get(`audit-data/inventory/item/${itemId}`, { | ||
searchParams: { | ||
...(eventTs && { eventTs }) | ||
} | ||
}).json(), | ||
enabled: Boolean(itemId), | ||
}); | ||
|
||
return { | ||
data: data?.inventoryAuditItems || [], | ||
totalRecords: data?.totalRecords, | ||
isLoading, | ||
}; | ||
}; | ||
|
||
export default useItemAuditDataQuery; |
46 changes: 46 additions & 0 deletions
46
src/hooks/useItemAuditDataQuery/useItemAuditDataQuery.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import React, { act } from 'react'; | ||
import { | ||
QueryClient, | ||
QueryClientProvider, | ||
} from 'react-query'; | ||
|
||
import { renderHook } from '@folio/jest-config-stripes/testing-library/react'; | ||
import { useOkapiKy } from '@folio/stripes/core'; | ||
|
||
import '../../../test/jest/__mock__'; | ||
|
||
import useItemAuditDataQuery from './useItemAuditDataQuery'; | ||
|
||
jest.mock('@folio/stripes/core', () => ({ | ||
...jest.requireActual('@folio/stripes/core'), | ||
useOkapiKy: jest.fn(), | ||
})); | ||
|
||
const queryClient = new QueryClient(); | ||
const wrapper = ({ children }) => ( | ||
<QueryClientProvider client={queryClient}> | ||
{children} | ||
</QueryClientProvider> | ||
); | ||
|
||
describe('useItemAuditDataQuery', () => { | ||
beforeEach(() => { | ||
useOkapiKy.mockClear().mockReturnValue({ | ||
get: () => ({ | ||
json: () => Promise.resolve({ inventoryAuditItems: [{ action: 'UPDATE' }] }), | ||
}), | ||
}); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('should fetch item version history', async () => { | ||
const { result } = renderHook(() => useItemAuditDataQuery('itemId'), { wrapper }); | ||
|
||
await act(() => !result.current.isLoading); | ||
|
||
expect(result.current.data).toEqual([{ action: 'UPDATE' }]); | ||
}); | ||
}); |
Oops, something went wrong.