diff --git a/CHANGELOG.md b/CHANGELOG.md index 60577005..bc14612d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Change history for ui-organizations -## 5.3.0 (IN PROGRESS) +## 6.0.0 (IN PROGRESS) + +* *BREAKING* Display all versions in change log in fourth pane. Refs UIORGS-355. ## [5.2.0](https://github.com/folio-org/ui-organizations/tree/v5.2.0) (2024-10-31) [Full Changelog](https://github.com/folio-org/ui-organizations/compare/v5.1.1...v5.2.0) diff --git a/package.json b/package.json index 3bb204a8..9ca265e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@folio/organizations", - "version": "5.2.0", + "version": "6.0.0", "description": "Organizations", "main": "index.js", "repository": "folio-org/ui-organizations", @@ -19,6 +19,7 @@ "home": "/organizations", "okapiInterfaces": { "acquisition-methods": "1.0", + "acquisition-organization-events": "1.0", "banking-information": "1.0", "data-export-spring": "1.0 2.0", "configuration": "2.0", @@ -66,6 +67,7 @@ "replaces": ["ui-organizations.third-party-services"], "visible": false, "subPermissions": [ + "acquisition.organization.events.get", "acquisitions-units.memberships.collection.get", "acquisitions-units.units.collection.get", "configuration.entries.collection.get", diff --git a/src/Organizations/OrganizationDetails/OrganizationDetails.js b/src/Organizations/OrganizationDetails/OrganizationDetails.js index d0990cf8..fca690ff 100644 --- a/src/Organizations/OrganizationDetails/OrganizationDetails.js +++ b/src/Organizations/OrganizationDetails/OrganizationDetails.js @@ -39,10 +39,12 @@ import { TagsPane, useAcqRestrictions, useModalToggle, + VersionHistoryButton, } from '@folio/stripes-acq-components'; import { NOTES_ROUTE, + ORGANIZATION_VERSIONS_VIEW_ROUTE, ORGANIZATIONS_ROUTE, } from '../../common/constants'; import { @@ -135,6 +137,13 @@ const OrganizationDetails = ({ if (isDetailsPaneInFocus) paneTitleRef.current.focus(); }, [isDetailsPaneInFocus]); + const openVersionHistory = useCallback(() => { + history.push({ + pathname: ORGANIZATION_VERSIONS_VIEW_ROUTE.replace(':id', organization.id), + search: location.search, + }); + }, [history, location.search, organization.id]); + const getActionMenu = useCallback( ({ onToggle }) => { return ( @@ -200,6 +209,7 @@ const OrganizationDetails = ({ tagsQuantity={get(organization, 'tags.tagList', []).length} tagsToggle={toggleTagsPane} /> + ); diff --git a/src/Organizations/OrganizationVersion/OrganizationVersion.js b/src/Organizations/OrganizationVersion/OrganizationVersion.js new file mode 100644 index 00000000..2f52d21f --- /dev/null +++ b/src/Organizations/OrganizationVersion/OrganizationVersion.js @@ -0,0 +1,111 @@ +import get from 'lodash/get'; +import { + memo, + useCallback, + useMemo, +} from 'react'; +import ReactRouterPropTypes from 'react-router-prop-types'; + +import { + useOrganization, + VersionHistoryPane, + VersionView, + VersionViewContextProvider, +} from '@folio/stripes-acq-components'; +import { TitleManager } from '@folio/stripes/core'; + +import { + ORGANIZATION_VERSIONS_VIEW_ROUTE, + ORGANIZATIONS_ROUTE, + VIEW_ORG_DETAILS, +} from '../../common/constants'; +import { HIDDEN_FIELDS_FOR_ORGANIZATION_VERSION_HISTORY } from '../constants'; +import { getOrganizationFieldsLabelMap } from './getOrganizationFieldsLabelMap'; +import { useOrganizationVersions } from './hooks'; + +const OrganizationVersion = ({ + history, + location, + match, +}) => { + const { id: organizationId, versionId } = match.params; + const snapshotPath = 'organizationSnapshot.map'; + + const { + isLoading: isOrganizationLoading, + organization, + } = useOrganization(organizationId); + + const onHistoryClose = useCallback(() => history.push({ + pathname: `${VIEW_ORG_DETAILS}${organizationId}`, + search: location.search, + }), [history, location.search, organizationId]); + + const onVersionClose = useCallback(() => history.push({ + pathname: ORGANIZATIONS_ROUTE, + search: location.search, + }), [history, location.search]); + + const onSelectVersion = useCallback((_versionId) => { + history.push({ + pathname: `${ORGANIZATION_VERSIONS_VIEW_ROUTE.replace(':id', organizationId)}/${_versionId}`, + search: location.search, + }); + }, [history, location.search, organizationId]); + + const { + versions, + isLoading: isHistoryLoading, + } = useOrganizationVersions(organizationId, { + onSuccess: ({ organizationAuditEvents }) => { + if (!versionId && organizationAuditEvents[0]?.id) onSelectVersion(organizationAuditEvents[0].id); + }, + }); + + const isVersionLoading = ( + isOrganizationLoading || isHistoryLoading + ); + + const labelsMap = useMemo(() => getOrganizationFieldsLabelMap(), []); + + return ( + + + + {/* TODO: https://folio-org.atlassian.net/browse/UIORGS-356 */} + + + + + ); +}; + +OrganizationVersion.propTypes = { + history: ReactRouterPropTypes.history, + location: ReactRouterPropTypes.location, + match: ReactRouterPropTypes.match, +}; + +export default memo(OrganizationVersion); diff --git a/src/Organizations/OrganizationVersion/OrganizationVersion.test.js b/src/Organizations/OrganizationVersion/OrganizationVersion.test.js new file mode 100644 index 00000000..2e18d322 --- /dev/null +++ b/src/Organizations/OrganizationVersion/OrganizationVersion.test.js @@ -0,0 +1,141 @@ +import { + QueryClient, + QueryClientProvider, +} from 'react-query'; +import { + MemoryRouter, + Route, + Switch, + withRouter, +} from 'react-router-dom'; + +import { + render, + screen, +} from '@folio/jest-config-stripes/testing-library/react'; +import user from '@folio/jest-config-stripes/testing-library/user-event'; +import { useOkapiKy } from '@folio/stripes/core'; +import { + AUDIT_ACQ_EVENTS_API, + useOrganization, +} from '@folio/stripes-acq-components'; + +import { + organization, + organizationAuditEvent, +} from 'fixtures'; +import { ORGANIZATION_VERSIONS_VIEW_ROUTE } from '../../common/constants'; +import OrganizationVersion from './OrganizationVersion'; + +jest.mock('@folio/stripes-acq-components', () => ({ + ...jest.requireActual('@folio/stripes-acq-components'), + useOrganization: jest.fn(() => ({})), +})); + +const { organizationSnapshot, ...auditEvent } = organizationAuditEvent; + +const latestSnapshot = { + ...organizationSnapshot, + edition: 'Second edition', +}; +const originalSnapshot = { ...organizationSnapshot }; + +const versions = [ + { + ...auditEvent, + id: 'testAuditEventId', + organizationSnapshot: { map: latestSnapshot }, + }, + { + ...auditEvent, + organizationSnapshot: { map: originalSnapshot }, + }, +]; + +const kyMock = { + get: jest.fn((url) => ({ + json: async () => { + const result = {}; + + if (url.startsWith(`${AUDIT_ACQ_EVENTS_API}/organization/`)) { + result.organizationAuditEvents = versions; + } + + return Promise.resolve({ + isLoading: false, + ...result, + }); + }, + })), +}; + +const queryClient = new QueryClient(); +const wrapper = ({ children }) => ( + + + {children} + + +); + +const Component = withRouter(OrganizationVersion); +const mockDefaultContent = 'Hello world'; + +const renderOrganizationVersion = (props = {}) => render( + + ( + + )} + /> + ( +
{mockDefaultContent}
+ )} + /> +
, + { wrapper }, +); + +describe('OrganizationVersion', () => { + beforeEach(() => { + useOkapiKy.mockReturnValue(kyMock); + useOrganization.mockReturnValue({ + isLoading: false, + organization, + }); + }); + + it('should close version view when \'Version close\' button was clicked', async () => { + renderOrganizationVersion(); + + await screen.findAllByRole('button', { name: 'stripes-acq-components.versionHistory.card.select.tooltip' }) + .then(async ([selectVersionBtn]) => user.click(selectVersionBtn)); + + await screen.findAllByRole('button', { name: 'stripes-components.closeItem' }) + .then(async ([closeVersionBtn]) => user.click(closeVersionBtn)); + + expect(screen.queryByText(organization.name)).not.toBeInTheDocument(); + expect(screen.getByText(mockDefaultContent)).toBeInTheDocument(); + }); + + it('should close version view when \'History close\' button was clicked', async () => { + renderOrganizationVersion(); + + await screen.findAllByRole('button', { name: 'stripes-acq-components.versionHistory.card.select.tooltip' }) + .then(async ([selectVersionBtn]) => user.click(selectVersionBtn)); + + await screen.findAllByRole('button', { name: 'stripes-components.closeItem' }) + .then(async ([_, closeHistoryBtn]) => user.click(closeHistoryBtn)); + + expect(screen.queryByText(organization.name)).not.toBeInTheDocument(); + expect(screen.getByText(mockDefaultContent)).toBeInTheDocument(); + }); +}); diff --git a/src/Organizations/OrganizationVersion/getOrganizationFieldsLabelMap.js b/src/Organizations/OrganizationVersion/getOrganizationFieldsLabelMap.js new file mode 100644 index 00000000..f2073cca --- /dev/null +++ b/src/Organizations/OrganizationVersion/getOrganizationFieldsLabelMap.js @@ -0,0 +1,152 @@ +export const getOrganizationFieldsLabelMap = () => { + return { + 'tags': 'stripes-acq-components.label.tags', + 'tags.tagList': 'stripes-acq-components.label.tags', + 'tags.tagList[\\d]': 'stripes-acq-components.label.tags', + + 'metadata.createdByUserId': 'ui-organizations.metadata.createdByUserId', + 'metadata.createdDate': 'ui-organizations.metadata.createdDate', + 'metadata.updatedByUserId': 'ui-organizations.metadata.updatedByUserId', + 'metadata.updatedDate': 'ui-organizations.metadata.updatedDate', + + 'name': 'ui-organizations.summary.name', + 'code': 'ui-organizations.summary.code', + 'description': 'ui-organizations.summary.description', + 'exportToAccounting': 'ui-organizations.vendorInfo.exportToAccounting', + 'status': 'ui-organizations.summary.organizationStatus', + 'organizationTypes': 'ui-organizations.summary.type', + 'organizationTypes[\\d]': 'ui-organizations.summary.type', + 'language': 'ui-organizations.summary.defaultLanguage', + 'aliases': 'ui-organizations.summary.alternativeNames', + 'aliases[\\d]': 'ui-organizations.summary.alternativeNames', + 'aliases[\\d].value': 'ui-organizations.search.aliases', + 'aliases[\\d].description': 'ui-organizations.versionHistory.field.alias.description', + + 'addresses': 'ui-organizations.data.contactTypes.address', + 'addresses[\\d]': 'ui-organizations.data.contactTypes.address', + 'addresses[\\d].addressLine1': 'ui-organizations.contactPeople.addressLine1', + 'addresses[\\d].addressLine2': 'ui-organizations.contactPeople.addressLine2', + 'addresses[\\d].city': 'ui-organizations.contactPeople.city', + 'addresses[\\d].stateRegion': 'ui-organizations.contactPeople.stateRegion', + 'addresses[\\d].zipCode': 'ui-organizations.contactPeople.zipCode', + 'addresses[\\d].country': 'ui-organizations.contactPeople.country', + 'addresses[\\d].isPrimary': 'ui-organizations.primaryItem', + 'addresses[\\d].categories': 'ui-organizations.contactPeople.categories', + 'addresses[\\d].categories[\\d]': 'ui-organizations.contactPeople.categories', + 'addresses[\\d].language': 'ui-organizations.contactPeople.language', + 'phoneNumbers': 'ui-organizations.contactPeople.phoneNumbers', + 'phoneNumbers[\\d]': 'ui-organizations.contactPeople.phoneNumbers', + 'phoneNumbers[\\d].phoneNumber': 'ui-organizations.contactPeople.phoneNumber', + 'phoneNumbers[\\d].categories': 'ui-organizations.contactPeople.categories', + 'phoneNumbers[\\d].categories[\\d]': 'ui-organizations.contactPeople.categories', + 'phoneNumbers[\\d].type': 'ui-organizations.contactPeople.type', + 'phoneNumbers[\\d].isPrimary': 'ui-organizations.primaryItem', + 'phoneNumbers[\\d].language': 'ui-organizations.contactPeople.language', + 'emails': 'ui-organizations.contactPeople.emails', + 'emails[\\d]': 'ui-organizations.contactPeople.emails', + 'emails[\\d].value': 'ui-organizations.contactPeople.emailAddress', + 'emails[\\d].description': 'ui-organizations.contactPeople.description', + 'emails[\\d].isPrimary': 'ui-organizations.primaryItem', + 'emails[\\d].categories': 'ui-organizations.contactPeople.categories', + 'emails[\\d].categories[\\d]': 'ui-organizations.contactPeople.categories', + 'emails[\\d].language': 'ui-organizations.contactPeople.language', + 'urls': 'ui-organizations.contactPeople.urls', + 'urls[\\d]': 'ui-organizations.contactPeople.urls', + 'urls[\\d].value': 'ui-organizations.contactPeople.url', + 'urls[\\d].description': 'ui-organizations.contactPeople.description', + 'urls[\\d].language': 'ui-organizations.contactPeople.language', + 'urls[\\d].isPrimary': 'ui-organizations.primaryItem', + 'urls[\\d].categories': 'ui-organizations.contactPeople.categories', + 'urls[\\d].categories[\\d]': 'ui-organizations.contactPeople.categories', + + 'contacts': 'ui-organizations.contactPeople', + 'contacts[\\d]': 'ui-organizations.contactPeople', + 'privilegedContacts': 'ui-organizations.privilegedDonorInformation', + 'agreements': 'ui-organizations.linkedAgreements.section', + 'agreements[\\d]': 'ui-organizations.linkedAgreements.section', + 'agreements[\\d].name': 'ui-organizations.agreement.name', + 'agreements[\\d].discount': 'ui-organizations.agreement.discount', + 'agreements[\\d].referenceUrl': 'ui-organizations.agreement.referenceUrl', + 'agreements[\\d].notes': 'ui-organizations.agreement.notes', + 'erpCode': 'ui-organizations.summary.accountingCode', + 'paymentMethod': 'ui-organizations.accounts.paymentMethod', + 'accessProvider': 'ui-organizations.versionHistory.field.accessProvider', + 'governmental': 'ui-organizations.versionHistory.field.governmental', + 'licensor': 'ui-organizations.versionHistory.field.governmental', + 'materialSupplier': 'ui-organizations.versionHistory.field.materialSupplier', + 'vendorCurrencies': 'ui-organizations.vendorInfo.vendorCurrencies', + 'vendorCurrencies[\\d]': 'ui-organizations.vendorInfo.vendorCurrencies', + 'claimingInterval': 'ui-organizations.vendorInfo.claimingInterval', + 'discountPercent': 'ui-organizations.vendorInfo.discountPercent', + 'expectedActivationInterval': 'ui-organizations.vendorInfo.expectedActivationInterval', + 'expectedInvoiceInterval': 'ui-organizations.vendorInfo.expectedInvoiceInterval', + 'renewalActivationInterval': 'ui-organizations.vendorInfo.renewalActivationInterval', + 'subscriptionInterval': 'ui-organizations.vendorInfo.subscriptionInterval', + 'expectedReceiptInterval': 'ui-organizations.vendorInfo.expectedReceiptInterval', + 'taxId': 'ui-organizations.vendorInfo.taxID', + 'liableForVat': 'ui-organizations.vendorInfo.liableForVAT', + 'taxPercentage': 'ui-organizations.vendorInfo.taxPercentage', + 'interfaces': 'ui-organizations.interface', + 'interfaces[\\d]': 'ui-organizations.interface', + + 'edi': 'ui-organizations.integration.edi', + 'edi.vendorEdiCode': 'ui-organizations.integration.edi.vendorEDICode', + 'edi.vendorEdiType': 'ui-organizations.integration.edi.vendorEDIType', + 'edi.libEdiCode': 'ui-organizations.integration.edi.libraryEDICode', + 'edi.libEdiType': 'ui-organizations.integration.edi.libraryEDIType', + 'edi.prorateTax': 'ui-organizations.versionHistory.field.edi.prorateTax', + 'edi.prorateFees': 'ui-organizations.versionHistory.field.edi.prorateFees', + 'edi.ediNamingConvention': 'ui-organizations.integration.edi.ediNamingConvention', + 'edi.sendAcctNum': 'integration.edi.sendAccountNumber', + 'edi.supportOrder': 'ui-organizations.integration.edi.orders', + 'edi.supportInvoice': 'ui-organizations.integration.edi.invoices', + 'edi.notes': 'ui-organizations.integration.edi.notes', + 'edi.ediFtp': 'ui-organizations.integration.ftp', + 'edi.ediFtp.ftpFormat': 'ui-organizations.integration.ftp.ftpFormat', + 'edi.ediFtp.serverAddress': 'ui-organizations.integration.ftp.serverAddress', + 'edi.ediFtp.username': 'ui-organizations.integration.ftp.username', + 'edi.ediFtp.password': 'ui-organizations.edit.password', // NOSONAR - it's a label mapping, not the actual password + 'edi.ediFtp.ftpMode': 'ui-organizations.integration.ftp.ftpMode', + 'edi.ediFtp.ftpConnMode': 'ui-organizations.integration.ftp.ftpConnectionMode', + 'edi.ediFtp.ftpPort': 'ui-organizations.integration.ftp.ftpPort', + 'edi.ediFtp.orderDirectory': 'ui-organizations.integration.ftp.orderDirectory', + 'edi.ediFtp.invoiceDirectory': 'ui-organizations.integration.ftp.invoiceDirectory', + 'edi.ediFtp.notes': 'ui-organizations.integration.ftp.notes', + 'edi.ediJob': 'ui-organizations.integration.scheduling', + 'edi.ediJob.scheduleEdi': 'ui-organizations.integration.scheduling.scheduleEDI', + 'edi.ediJob.schedulingDate': 'ui-organizations.integration.scheduling.scheduleDate', + 'edi.ediJob.time': 'ui-organizations.integration.scheduling.scheduleTime', + 'edi.ediJob.isMonday': 'ui-organizations.integration.scheduling.scheduleWeekdays.MONDAY', + 'edi.ediJob.isTuesday': 'ui-organizations.integration.scheduling.scheduleWeekdays.TUESDAY', + 'edi.ediJob.isWednesday': 'ui-organizations.integration.scheduling.scheduleWeekdays.WEDNESDAY', + 'edi.ediJob.isThursday': 'ui-organizations.integration.scheduling.scheduleWeekdays.THURSDAY', + 'edi.ediJob.isFriday': 'ui-organizations.integration.scheduling.scheduleWeekdays.FRIDAY', + 'edi.ediJob.isSaturday': 'ui-organizations.integration.scheduling.scheduleWeekdays.SATURDAY', + 'edi.ediJob.isSunday': 'ui-organizations.integration.scheduling.scheduleWeekdays.SUNDAY', + 'edi.ediJob.sendToEmails': 'ui-organizations.versionHistory.field.edi.ediJob.sendToEmails', + 'edi.ediJob.notifyAllEdi': 'ui-organizations.versionHistory.field.edi.ediJob.notifyAllEdi', + 'edi.ediJob.notifyInvoiceOnly': 'ui-organizations.versionHistory.field.edi.ediJob.notifyInvoiceOnly', + 'edi.ediJob.notifyErrorOnly': 'ui-organizations.versionHistory.field.edi.ediJob.notifyErrorOnly', + 'edi.ediJob.schedulingNotes': 'ui-organizations.versionHistory.field.edi.ediJob.schedulingNotes', + + 'accounts': 'ui-organizations.accounts', + 'accounts.name': 'ui-organizations.accounts.name', + 'accounts.accountNo': 'ui-organizations.accounts.accountNumber', + 'accounts.description': 'ui-organizations.accounts.description', + 'accounts.appSystemNo': 'ui-organizations.accounts.payable', + 'accounts.paymentMethod': 'ui-organizations.accounts.paymentMethod', + 'accounts.accountStatus': 'ui-organizations.accounts.account.accountStatus', + 'accounts.contactInfo': 'ui-organizations.accounts.account.contactInfo', + 'accounts.libraryCode': 'ui-organizations.accounts.libraryCode', + 'accounts.libraryEdiCode': 'ui-organizations.accounts.libraryEDICode', + 'accounts.notes': 'ui-organizations.accounts.notes', + 'accounts.acqUnitIds': 'ui-organizations.versionHistory.field.accounts.acqUnitsIds', + 'accounts.acqUnitIds[\\d]': 'ui-organizations.versionHistory.field.accounts.acqUnitsIds', + + 'isVendor': 'ui-organizations.summary.isVendor', + 'isDonor': 'ui-organizations.summary.isDonor', + 'sanCode': 'ui-organizations.versionHistory.field.sanCode', + 'acqUnitIds': 'stripes-acq-components.label.acqUnits', + 'acqUnitIds[\\d]': 'stripes-acq-components.label.acqUnits', + }; +}; diff --git a/src/Organizations/OrganizationVersion/hooks/index.js b/src/Organizations/OrganizationVersion/hooks/index.js new file mode 100644 index 00000000..ecff7cfd --- /dev/null +++ b/src/Organizations/OrganizationVersion/hooks/index.js @@ -0,0 +1 @@ +export { useOrganizationVersions } from './useOrganizationVersions'; diff --git a/src/Organizations/OrganizationVersion/hooks/useOrganizationVersions/index.js b/src/Organizations/OrganizationVersion/hooks/useOrganizationVersions/index.js new file mode 100644 index 00000000..ecff7cfd --- /dev/null +++ b/src/Organizations/OrganizationVersion/hooks/useOrganizationVersions/index.js @@ -0,0 +1 @@ +export { useOrganizationVersions } from './useOrganizationVersions'; diff --git a/src/Organizations/OrganizationVersion/hooks/useOrganizationVersions/useOrganizationVersions.js b/src/Organizations/OrganizationVersion/hooks/useOrganizationVersions/useOrganizationVersions.js new file mode 100644 index 00000000..ba5d1ccc --- /dev/null +++ b/src/Organizations/OrganizationVersion/hooks/useOrganizationVersions/useOrganizationVersions.js @@ -0,0 +1,34 @@ +import { useQuery } from 'react-query'; + +import { + useNamespace, + useOkapiKy, +} from '@folio/stripes/core'; + +import { AUDIT_ACQ_EVENTS_API } from '@folio/stripes-acq-components'; + +const DEFAULT_DATA = []; + +export const useOrganizationVersions = (organizationId, options = {}) => { + const ky = useOkapiKy(); + const [namespace] = useNamespace({ key: 'organization-versions' }); + + const searchParams = { + sortBy: 'event_date', + sortOrder: 'desc', + }; + + const { isLoading, data } = useQuery( + [namespace, organizationId], + ({ signal }) => ky.get(`${AUDIT_ACQ_EVENTS_API}/organization/${organizationId}`, { signal, searchParams }).json(), + { + enabled: Boolean(organizationId), + ...options, + }, + ); + + return { + isLoading, + versions: data?.organizationAuditEvents || DEFAULT_DATA, + }; +}; diff --git a/src/Organizations/OrganizationVersion/hooks/useOrganizationVersions/useOrganizationVersions.test.js b/src/Organizations/OrganizationVersion/hooks/useOrganizationVersions/useOrganizationVersions.test.js new file mode 100644 index 00000000..06743fe7 --- /dev/null +++ b/src/Organizations/OrganizationVersion/hooks/useOrganizationVersions/useOrganizationVersions.test.js @@ -0,0 +1,53 @@ +import { + QueryClient, + QueryClientProvider, +} from 'react-query'; + +import { + renderHook, + waitFor, +} from '@folio/jest-config-stripes/testing-library/react'; +import { useOkapiKy } from '@folio/stripes/core'; + +import { useOrganizationVersions } from './useOrganizationVersions'; + +const queryClient = new QueryClient(); +const wrapper = ({ children }) => ( + + {children} + +); + +describe('useOrganizationVersions', () => { + const mockGet = jest.fn(); + + beforeEach(() => { + useOkapiKy.mockReturnValue({ get: mockGet }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return default data when organizationId is not provided', async () => { + const { result } = renderHook(() => useOrganizationVersions(), { wrapper }); + + await waitFor(() => expect(result.current.isLoading).toBeFalsy()); + + expect(result.current.versions).toEqual([]); + }); + + it('should fetch and return organization versions', async () => { + const mockData = { organizationAuditEvents: [{ id: '1', name: 'Version 1' }] }; + + mockGet.mockReturnValueOnce({ + json: () => Promise.resolve(mockData), + }); + + const { result } = renderHook(() => useOrganizationVersions('org-1'), { wrapper }); + + await waitFor(() => expect(result.current.isLoading).toBeFalsy()); + + expect(result.current.versions).toEqual(mockData.organizationAuditEvents); + }); +}); diff --git a/src/Organizations/OrganizationVersion/index.js b/src/Organizations/OrganizationVersion/index.js new file mode 100644 index 00000000..8a312a96 --- /dev/null +++ b/src/Organizations/OrganizationVersion/index.js @@ -0,0 +1 @@ +export { default as OrganizationVersion } from './OrganizationVersion'; diff --git a/src/Organizations/OrganizationsList/OrganizationsList.js b/src/Organizations/OrganizationsList/OrganizationsList.js index d054d860..ce3135fa 100644 --- a/src/Organizations/OrganizationsList/OrganizationsList.js +++ b/src/Organizations/OrganizationsList/OrganizationsList.js @@ -44,10 +44,12 @@ import { } from '@folio/plugin-find-organization'; import { + ORGANIZATION_VERSIONS_VIEW_ROUTE, ORGANIZATIONS_ROUTE, VIEW_ORG_DETAILS, } from '../../common/constants'; import { OrganizationDetailsContainer } from '../OrganizationDetails'; +import { OrganizationVersion } from '../OrganizationVersion'; import OrganizationsListLastMenu from './OrganizationsListLastMenu'; const resultsPaneTitle = ; @@ -251,9 +253,16 @@ const OrganizationsList = ({ + + ); diff --git a/src/Organizations/constants.js b/src/Organizations/constants.js index 9f59e372..360b0828 100644 --- a/src/Organizations/constants.js +++ b/src/Organizations/constants.js @@ -46,3 +46,5 @@ export const MAP_FIELD_ACCORDION = { }; export const BANKING_INFORMATION_FIELD_NAME = 'bankingInformation'; + +export const HIDDEN_FIELDS_FOR_ORGANIZATION_VERSION_HISTORY = []; diff --git a/src/common/constants/routes.js b/src/common/constants/routes.js index 455f24f9..c7e40458 100644 --- a/src/common/constants/routes.js +++ b/src/common/constants/routes.js @@ -1,3 +1,4 @@ export const NOTES_ROUTE = '/organizations/notes'; export const ORGANIZATIONS_ROUTE = '/organizations'; export const VIEW_ORG_DETAILS = '/organizations/view/'; +export const ORGANIZATION_VERSIONS_VIEW_ROUTE = `${VIEW_ORG_DETAILS}:id/versions`; diff --git a/test/jest/fixtures/index.js b/test/jest/fixtures/index.js index 7a39fa0b..5a07a642 100644 --- a/test/jest/fixtures/index.js +++ b/test/jest/fixtures/index.js @@ -2,4 +2,5 @@ export * from './contact'; export * from './integrationConfig'; export * from './interface'; export * from './organization'; +export * from './organizationAuditEvent'; export * from './organizationTypes'; diff --git a/test/jest/fixtures/organizationAuditEvent.js b/test/jest/fixtures/organizationAuditEvent.js new file mode 100644 index 00000000..b2ab1a53 --- /dev/null +++ b/test/jest/fixtures/organizationAuditEvent.js @@ -0,0 +1,14 @@ +import { organization } from './organization'; + +export const organizationAuditEvent = { + id: '3635ff84-ede6-4786-95e7-00a4801115ba', + action: 'Edit', + organizationId: '6e5bb3d1-cba7-47a8-87c2-eb9f3b1598fc', + userId: 'dd88964f-22f2-5579-898f-86920b2c1d71', + eventDate: '2024-11-14T08:48:48.340+00:00', + actionDate: '2024-11-14T08:48:48.335+00:00', + organizationSnapshot: { + map: { ...organization }, + empty: false, + }, +}; diff --git a/translations/ui-organizations/en.json b/translations/ui-organizations/en.json index 383f537a..2181146c 100644 --- a/translations/ui-organizations/en.json +++ b/translations/ui-organizations/en.json @@ -439,6 +439,24 @@ "save.error.creds": "Error while saving credentials", "save.error.assignInterface": "Error while assigning interface to the organization", + "versionHistory.field.metadata.createdByUserId": "Created by", + "versionHistory.field.metadata.createdDate": "Created date", + "versionHistory.field.metadata.updatedByUserId": "Updated by", + "versionHistory.field.metadata.updatedDate": "Updated date", + "versionHistory.field.alias.description": "Alias description", + "versionHistory.field.sanCode": "SAN Code", + "versionHistory.field.accounts.acqUnitsIds": "Account acquisition units", + "versionHistory.field.accessProvider": "Access provider", + "versionHistory.field.governmental": "Governmental", + "versionHistory.field.licensor": "Licensor", + "versionHistory.field.edi.prorateTax": "Prorate tax", + "versionHistory.field.edi.prorateFees": "Prorate fees", + "versionHistory.field.edi.ediJob.sendToEmails": "Send to emails", + "versionHistory.field.edi.ediJob.notifyAllEdi": "Notify all", + "versionHistory.field.edi.ediJob.notifyInvoiceOnly": "Notify invoice only", + "versionHistory.field.edi.ediJob.notifyErrorOnly": "Notify error only", + "versionHistory.field.edi.ediJob.schedulingNotes": "Scheduling notes", + "settings.categories": "Categories", "settings.categories.cannotDeleteTermHeader": "Cannot delete category", "settings.categories.cannotDeleteTermMessage": "This category cannot be deleted, as it is in use by one or more records.",