From e9ff69809d956e1047bd900c17ef74abf8b5bc4d Mon Sep 17 00:00:00 2001 From: Mariia_Aloshyna Date: Fri, 22 Sep 2023 16:04:02 +0300 Subject: [PATCH 01/29] Consortial holdings acc --- src/Instance/HoldingsList/Holding/Holding.js | 7 +++ .../HoldingsList/Holding/HoldingAccordion.js | 7 ++- .../Holding/HoldingButtonsGroup.js | 4 ++ .../HoldingsList/Holding/HoldingContainer.js | 9 ++++ src/Instance/HoldingsList/HoldingsList.js | 7 ++- .../HoldingsList/HoldingsListContainer.js | 10 +++- .../ConsortialHoldings/ConsortialHoldings.js | 41 ++++++++++++++++ .../ConsortialHoldings/index.js | 2 + .../InstanceDetails/InstanceDetails.js | 3 ++ .../MemberTenantHoldings.css | 3 ++ .../MemberTenantHoldings.js | 49 +++++++++++++++++++ .../MemberTenantHoldings/index.js | 2 + src/Instance/ItemsList/ItemsListContainer.js | 5 +- src/ViewInstance.js | 1 + src/hooks/useHoldingItemsQuery.js | 4 +- src/providers/HoldingsProvider.js | 9 ++-- translations/ui-inventory/en.json | 1 + 17 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js create mode 100644 src/Instance/InstanceDetails/ConsortialHoldings/index.js create mode 100644 src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.css create mode 100644 src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js create mode 100644 src/Instance/InstanceDetails/MemberTenantHoldings/index.js diff --git a/src/Instance/HoldingsList/Holding/Holding.js b/src/Instance/HoldingsList/Holding/Holding.js index c7e4bd7a1..67d1cea29 100644 --- a/src/Instance/HoldingsList/Holding/Holding.js +++ b/src/Instance/HoldingsList/Holding/Holding.js @@ -19,6 +19,9 @@ const Holding = ({ isHoldingDragSelected, isDraggable, isItemsDroppable, + tenantId, + isViewHoldingsDisabled, + isAddItemDisabled, }) => { return (
@@ -47,11 +50,15 @@ const Holding = ({ holdings={holdings} onViewHolding={onViewHolding} onAddItem={onAddItem} + isViewHoldingsDisabled={isViewHoldingsDisabled} + isAddItemDisabled={isAddItemDisabled} + tenantId={tenantId} > diff --git a/src/Instance/HoldingsList/Holding/HoldingAccordion.js b/src/Instance/HoldingsList/Holding/HoldingAccordion.js index fe73df642..53dadc468 100644 --- a/src/Instance/HoldingsList/Holding/HoldingAccordion.js +++ b/src/Instance/HoldingsList/Holding/HoldingAccordion.js @@ -24,6 +24,9 @@ const HoldingAccordion = ({ onViewHolding, onAddItem, withMoveDropdown, + isViewHoldingsDisabled, + isAddItemDisabled, + tenantId, }) => { const searchParams = { limit: 0, @@ -33,7 +36,7 @@ const HoldingAccordion = ({ const { locationsById } = useContext(DataContext); const [open, setOpen] = useState(false); const [openFirstTime, setOpenFirstTime] = useState(false); - const { totalRecords, isFetching } = useHoldingItemsQuery(holding.id, { searchParams, key: 'itemCount' }); + const { totalRecords, isFetching } = useHoldingItemsQuery(holding.id, tenantId, { searchParams, key: 'itemCount' }); if (!locationsById) return null; @@ -57,6 +60,8 @@ const HoldingAccordion = ({ onAddItem={onAddItem} withMoveDropdown={withMoveDropdown} isOpen={open} + isViewHoldingsDisabled={isViewHoldingsDisabled} + isAddItemDisabled={isAddItemDisabled} />; const location = labelLocation?.isActive ? diff --git a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js index f649a3c43..527c865d7 100644 --- a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js +++ b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js @@ -20,6 +20,8 @@ const HoldingButtonsGroup = ({ onAddItem, itemCount, isOpen, + isAddItemDisabled = false, + isViewHoldingsDisabled = false, }) => ( <> { @@ -35,6 +37,7 @@ const HoldingButtonsGroup = ({ id={`clickable-view-holdings-${holding.id}`} data-test-view-holdings onClick={onViewHolding} + disabled={isViewHoldingsDisabled} > @@ -45,6 +48,7 @@ const HoldingButtonsGroup = ({ data-test-add-item onClick={onAddItem} buttonStyle="primary paneHeaderNewButton" + disabled={isAddItemDisabled} > diff --git a/src/Instance/HoldingsList/Holding/HoldingContainer.js b/src/Instance/HoldingsList/Holding/HoldingContainer.js index dc4116adb..c4ddf0782 100644 --- a/src/Instance/HoldingsList/Holding/HoldingContainer.js +++ b/src/Instance/HoldingsList/Holding/HoldingContainer.js @@ -35,6 +35,7 @@ const DraggableHolding = ({ holding, onViewHolding, onAddItem, + tenantId, ...rest }) => { const rowStyles = useMemo(() => ( @@ -66,6 +67,7 @@ const DraggableHolding = ({ holding={holding} onViewHolding={onViewHolding} onAddItem={onAddItem} + tenantId={tenantId} /> ) } @@ -95,6 +97,9 @@ const HoldingContainer = ({ isDraggable, holdingIndex, draggingHoldingsCount, + tenantId, + isViewHoldingsDisabled, + isAddItemDisabled, ...rest }) => { const onViewHolding = useCallback(() => { @@ -125,6 +130,7 @@ const HoldingContainer = ({ holding={holding} onViewHolding={onViewHolding} onAddItem={onAddItem} + tenantId={tenantId} {...rest} /> )} @@ -135,6 +141,9 @@ const HoldingContainer = ({ holding={holding} onViewHolding={onViewHolding} onAddItem={onAddItem} + tenantId={tenantId} + isViewHoldingsDisabled={isViewHoldingsDisabled} + isAddItemDisabled={isAddItemDisabled} /> ); }; diff --git a/src/Instance/HoldingsList/HoldingsList.js b/src/Instance/HoldingsList/HoldingsList.js index 74e61593a..bcf89cecb 100644 --- a/src/Instance/HoldingsList/HoldingsList.js +++ b/src/Instance/HoldingsList/HoldingsList.js @@ -6,9 +6,11 @@ import { HoldingContainer } from './Holding'; const HoldingsList = ({ instance, holdings, - draggable, droppable, + tenantId, + isViewHoldingsDisabled, + isAddItemDisabled, }) => holdings.map(holding => ( )); diff --git a/src/Instance/HoldingsList/HoldingsListContainer.js b/src/Instance/HoldingsList/HoldingsListContainer.js index e268f2eca..f09814ecd 100644 --- a/src/Instance/HoldingsList/HoldingsListContainer.js +++ b/src/Instance/HoldingsList/HoldingsListContainer.js @@ -9,8 +9,13 @@ import HoldingsList from './HoldingsList'; import { HoldingsListMovement } from '../InstanceMovement/HoldingMovementList'; import { useInstanceHoldingsQuery } from '../../providers'; -const HoldingsListContainer = ({ instance, isHoldingsMove, ...rest }) => { - const { holdingsRecords: holdings, isLoading } = useInstanceHoldingsQuery(instance.id); +const HoldingsListContainer = ({ + instance, + isHoldingsMove, + tenantId, + ...rest +}) => { + const { holdingsRecords: holdings, isLoading } = useInstanceHoldingsQuery(instance.id, { tenantId }); if (isLoading) return ; @@ -26,6 +31,7 @@ const HoldingsListContainer = ({ instance, isHoldingsMove, ...rest }) => { {...rest} holdings={holdings} instance={instance} + tenantId={tenantId} /> ) ); diff --git a/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js b/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js new file mode 100644 index 000000000..4f121efe2 --- /dev/null +++ b/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js @@ -0,0 +1,41 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { + IfInterface, + useStripes, +} from '@folio/stripes/core'; +import { + Accordion, + AccordionSet, +} from '@folio/stripes/components'; + +import { useUserAffiliations } from '@folio/consortia-settings/src/hooks'; +import { MemberTenantHoldings } from '../MemberTenantHoldings'; + +const ConsortialHoldings = ({ instance }) => { + const stripes = useStripes(); + const { affiliations } = useUserAffiliations({ userId: stripes.user.user.id }); + const otherMembers = affiliations.filter(affiliation => !affiliation.isPrimary && (affiliation.tenantId !== stripes.okapi.tenant)); + + return ( + + } + closedByDefault + > + + {otherMembers.map(affiliation => ( + + ))} + + + + ); +}; + +export default ConsortialHoldings; diff --git a/src/Instance/InstanceDetails/ConsortialHoldings/index.js b/src/Instance/InstanceDetails/ConsortialHoldings/index.js new file mode 100644 index 000000000..4a39280fd --- /dev/null +++ b/src/Instance/InstanceDetails/ConsortialHoldings/index.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as ConsortialHoldings } from './ConsortialHoldings'; diff --git a/src/Instance/InstanceDetails/InstanceDetails.js b/src/Instance/InstanceDetails/InstanceDetails.js index 173ca444a..6c0f6046a 100644 --- a/src/Instance/InstanceDetails/InstanceDetails.js +++ b/src/Instance/InstanceDetails/InstanceDetails.js @@ -36,6 +36,7 @@ import HelperApp from '../../components/HelperApp'; import { getAccordionState } from './utils'; import { DataContext } from '../../contexts'; +import { ConsortialHoldings } from './ConsortialHoldings'; const accordions = { administrative: 'acc01', @@ -132,6 +133,8 @@ const InstanceDetails = forwardRef(({ + + { + const { + tenantName, + tenantId, + } = affiliation; + const { holdingsRecords } = useInstanceHoldingsQuery(instance?.id, { tenantId }); + + if (isEmpty(holdingsRecords)) return null; + + return ( + +
+ + + +
+ +
+ ); +}; + +export default MemberTenantHoldings; diff --git a/src/Instance/InstanceDetails/MemberTenantHoldings/index.js b/src/Instance/InstanceDetails/MemberTenantHoldings/index.js new file mode 100644 index 000000000..dd83881bf --- /dev/null +++ b/src/Instance/InstanceDetails/MemberTenantHoldings/index.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as MemberTenantHoldings } from './MemberTenantHoldings'; diff --git a/src/Instance/ItemsList/ItemsListContainer.js b/src/Instance/ItemsList/ItemsListContainer.js index e536d921b..73d912160 100644 --- a/src/Instance/ItemsList/ItemsListContainer.js +++ b/src/Instance/ItemsList/ItemsListContainer.js @@ -15,6 +15,7 @@ import useHoldingItemsQuery from '../../hooks/useHoldingItemsQuery'; import { DEFAULT_ITEM_TABLE_SORTBY_FIELD } from '../../constants'; const ItemsListContainer = ({ + tenantId, holding, draggable, droppable, @@ -36,8 +37,8 @@ const ItemsListContainer = ({ offset, }; - const { isFetching, items } = useHoldingItemsQuery(holding.id, { searchParams }); - const { totalRecords } = useHoldingItemsQuery(holding.id, { searchParams: { limit: 0 }, key: 'itemCount' }); + const { isFetching, items } = useHoldingItemsQuery(holding.id, tenantId, { searchParams }); + const { totalRecords } = useHoldingItemsQuery(holding.id, tenantId, { searchParams: { limit: 0 }, key: 'itemCount' }); useEffect(() => { if (!isEmpty(items)) { diff --git a/src/ViewInstance.js b/src/ViewInstance.js index 0c66ccbaa..5ecb05f8e 100644 --- a/src/ViewInstance.js +++ b/src/ViewInstance.js @@ -892,6 +892,7 @@ class ViewInstance extends React.Component { diff --git a/src/hooks/useHoldingItemsQuery.js b/src/hooks/useHoldingItemsQuery.js index 6891e851b..40740bf1b 100644 --- a/src/hooks/useHoldingItemsQuery.js +++ b/src/hooks/useHoldingItemsQuery.js @@ -12,13 +12,15 @@ import { DEFAULT_ITEM_TABLE_SORTBY_FIELD, LIMIT_MAX, } from '../constants'; +import { useTenantKy } from '../common'; const useHoldingItemsQuery = ( holdingsRecordId, + tenantId, options = { searchParams: {}, key: 'items' }, ) => { const [sortBy, setSortBy] = useState(`${DEFAULT_ITEM_TABLE_SORTBY_FIELD}/sort.ascending`); - const ky = useOkapiKy().extend({ timeout: false }); + const ky = useTenantKy({ tenantId }).extend({ timeout: false }); const [namespace] = useNamespace(); // sortMap contains not all item table's columns because sorting by some columns diff --git a/src/providers/HoldingsProvider.js b/src/providers/HoldingsProvider.js index 3d07a16fe..bf52a5059 100644 --- a/src/providers/HoldingsProvider.js +++ b/src/providers/HoldingsProvider.js @@ -6,8 +6,7 @@ import React, { import { useQuery } from 'react-query'; import { keyBy } from 'lodash'; -import { useOkapiKy } from '@folio/stripes/core'; - +import { useTenantKy } from '../common'; const API = 'holdings-storage/holdings'; const LIMIT = 1000; @@ -31,8 +30,8 @@ export const HoldingsProvider = ({ ...rest }) => { }; -export const useInstanceHoldingsQuery = instanceId => { - const ky = useOkapiKy(); +export const useInstanceHoldingsQuery = (instanceId, { tenantId } = {}) => { + const ky = useTenantKy({ tenantId }); const holdings = useHoldings(); @@ -41,7 +40,7 @@ export const useInstanceHoldingsQuery = instanceId => { query: `instanceId==${instanceId}`, }; - const queryKey = [API, searchParams]; + const queryKey = [API, searchParams, tenantId]; const queryFn = () => ky(API, { searchParams }).json(); const onSuccess = data => holdings?.update(data.holdingsRecords); diff --git a/translations/ui-inventory/en.json b/translations/ui-inventory/en.json index e3c9ae57f..e61d89b9c 100644 --- a/translations/ui-inventory/en.json +++ b/translations/ui-inventory/en.json @@ -443,6 +443,7 @@ "updateHoldingsRecord": "Update holdings record", "duplicateHoldings": "Duplicate", "editHoldings": "Edit", + "consortialHoldings": "Consortial holdings", "instanceData": "Administrative data", "instanceId": "Instance UUID", "authorityId": "Authority UUID", From f5c029cb75eb424933122854e019bf5c77bbbd52 Mon Sep 17 00:00:00 2001 From: Mariia_Aloshyna Date: Mon, 25 Sep 2023 18:09:59 +0300 Subject: [PATCH 02/29] UIIN-2410: Adjustments --- src/Instance/HoldingsList/Holding/Holding.js | 5 +- .../HoldingsList/Holding/HoldingAccordion.js | 17 ++---- .../Holding/HoldingButtonsGroup.js | 4 -- .../HoldingsList/Holding/HoldingContainer.js | 5 +- src/Instance/HoldingsList/HoldingsList.js | 8 +-- .../HoldingsList/HoldingsListContainer.js | 2 + .../ConsortialHoldings/ConsortialHoldings.js | 17 ++++-- .../InstanceDetails/InstanceDetails.js | 4 +- .../MemberTenantHoldings.js | 20 ++++--- .../HoldingsListMovement.js | 2 + src/ViewInstance.js | 11 +--- src/constants.js | 2 + src/hooks/index.js | 2 + src/hooks/useLocationsQuery/index.js | 1 + .../useLocationsQuery/useLocationsQuery.js | 27 +++++++++ src/hooks/useUserAffiliations/index.js | 1 + .../useUserAffiliations.js | 57 +++++++++++++++++++ 17 files changed, 136 insertions(+), 49 deletions(-) create mode 100644 src/hooks/useLocationsQuery/index.js create mode 100644 src/hooks/useLocationsQuery/useLocationsQuery.js create mode 100644 src/hooks/useUserAffiliations/index.js create mode 100644 src/hooks/useUserAffiliations/useUserAffiliations.js diff --git a/src/Instance/HoldingsList/Holding/Holding.js b/src/Instance/HoldingsList/Holding/Holding.js index 67d1cea29..b04382bb0 100644 --- a/src/Instance/HoldingsList/Holding/Holding.js +++ b/src/Instance/HoldingsList/Holding/Holding.js @@ -20,8 +20,6 @@ const Holding = ({ isDraggable, isItemsDroppable, tenantId, - isViewHoldingsDisabled, - isAddItemDisabled, }) => { return (
@@ -50,8 +48,6 @@ const Holding = ({ holdings={holdings} onViewHolding={onViewHolding} onAddItem={onAddItem} - isViewHoldingsDisabled={isViewHoldingsDisabled} - isAddItemDisabled={isAddItemDisabled} tenantId={tenantId} > { const searchParams = { @@ -33,13 +29,14 @@ const HoldingAccordion = ({ offset: 0, }; - const { locationsById } = useContext(DataContext); const [open, setOpen] = useState(false); const [openFirstTime, setOpenFirstTime] = useState(false); const { totalRecords, isFetching } = useHoldingItemsQuery(holding.id, tenantId, { searchParams, key: 'itemCount' }); + const { data: locations } = useLocationsQuery({ tenantId }); - if (!locationsById) return null; + if (!locations) return null; + const locationsById = keyBy(locations, 'id'); const labelLocation = locationsById[holding.permanentLocationId]; const labelLocationName = labelLocation?.name ?? ''; @@ -60,8 +57,6 @@ const HoldingAccordion = ({ onAddItem={onAddItem} withMoveDropdown={withMoveDropdown} isOpen={open} - isViewHoldingsDisabled={isViewHoldingsDisabled} - isAddItemDisabled={isAddItemDisabled} />; const location = labelLocation?.isActive ? diff --git a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js index 527c865d7..f649a3c43 100644 --- a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js +++ b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js @@ -20,8 +20,6 @@ const HoldingButtonsGroup = ({ onAddItem, itemCount, isOpen, - isAddItemDisabled = false, - isViewHoldingsDisabled = false, }) => ( <> { @@ -37,7 +35,6 @@ const HoldingButtonsGroup = ({ id={`clickable-view-holdings-${holding.id}`} data-test-view-holdings onClick={onViewHolding} - disabled={isViewHoldingsDisabled} > @@ -48,7 +45,6 @@ const HoldingButtonsGroup = ({ data-test-add-item onClick={onAddItem} buttonStyle="primary paneHeaderNewButton" - disabled={isAddItemDisabled} > diff --git a/src/Instance/HoldingsList/Holding/HoldingContainer.js b/src/Instance/HoldingsList/Holding/HoldingContainer.js index c4ddf0782..eb8d40cc4 100644 --- a/src/Instance/HoldingsList/Holding/HoldingContainer.js +++ b/src/Instance/HoldingsList/Holding/HoldingContainer.js @@ -98,8 +98,6 @@ const HoldingContainer = ({ holdingIndex, draggingHoldingsCount, tenantId, - isViewHoldingsDisabled, - isAddItemDisabled, ...rest }) => { const onViewHolding = useCallback(() => { @@ -142,8 +140,6 @@ const HoldingContainer = ({ onViewHolding={onViewHolding} onAddItem={onAddItem} tenantId={tenantId} - isViewHoldingsDisabled={isViewHoldingsDisabled} - isAddItemDisabled={isAddItemDisabled} /> ); }; @@ -159,6 +155,7 @@ HoldingContainer.propTypes = { holdingIndex: PropTypes.number, isDraggable: PropTypes.bool, draggingHoldingsCount: PropTypes.number, + tenantId: PropTypes.string, }; export default withRouter(HoldingContainer); diff --git a/src/Instance/HoldingsList/HoldingsList.js b/src/Instance/HoldingsList/HoldingsList.js index bcf89cecb..97c01fff5 100644 --- a/src/Instance/HoldingsList/HoldingsList.js +++ b/src/Instance/HoldingsList/HoldingsList.js @@ -6,11 +6,10 @@ import { HoldingContainer } from './Holding'; const HoldingsList = ({ instance, holdings, + tenantId, + draggable, droppable, - tenantId, - isViewHoldingsDisabled, - isAddItemDisabled, }) => holdings.map(holding => ( )); HoldingsList.propTypes = { instance: PropTypes.object.isRequired, holdings: PropTypes.arrayOf(PropTypes.object), + tenantId: PropTypes.string, draggable: PropTypes.bool, droppable: PropTypes.bool, diff --git a/src/Instance/HoldingsList/HoldingsListContainer.js b/src/Instance/HoldingsList/HoldingsListContainer.js index f09814ecd..be19ea8ee 100644 --- a/src/Instance/HoldingsList/HoldingsListContainer.js +++ b/src/Instance/HoldingsList/HoldingsListContainer.js @@ -25,6 +25,7 @@ const HoldingsListContainer = ({ {...rest} holdings={holdings} instance={instance} + tenantId={tenantId} /> ) : ( { const stripes = useStripes(); const { affiliations } = useUserAffiliations({ userId: stripes.user.user.id }); - const otherMembers = affiliations.filter(affiliation => !affiliation.isPrimary && (affiliation.tenantId !== stripes.okapi.tenant)); + + const memberTenants = affiliations.filter(affiliation => { + const isNotCentralTenant = !affiliation.isPrimary; + const isNotCurrentTenant = (affiliation.tenantId !== stripes.okapi.tenant); + + return isNotCentralTenant && isNotCurrentTenant; + }); return ( @@ -26,9 +33,9 @@ const ConsortialHoldings = ({ instance }) => { closedByDefault > - {otherMembers.map(affiliation => ( + {memberTenants.map(memberTenant => ( ))} @@ -38,4 +45,6 @@ const ConsortialHoldings = ({ instance }) => { ); }; +ConsortialHoldings.propTypes = { instance: PropTypes.object.isRequired }; + export default ConsortialHoldings; diff --git a/src/Instance/InstanceDetails/InstanceDetails.js b/src/Instance/InstanceDetails/InstanceDetails.js index 6c0f6046a..65c4b687f 100644 --- a/src/Instance/InstanceDetails/InstanceDetails.js +++ b/src/Instance/InstanceDetails/InstanceDetails.js @@ -133,7 +133,9 @@ const InstanceDetails = forwardRef(({ - + {instance?.shared && ( + + )} { const { tenantName, tenantId, - } = affiliation; + } = memberTenant; const { holdingsRecords } = useInstanceHoldingsQuery(instance?.id, { tenantId }); if (isEmpty(holdingsRecords)) return null; @@ -33,11 +36,9 @@ const MemberTenantHoldings = ({
@@ -46,4 +47,9 @@ const MemberTenantHoldings = ({ ); }; +MemberTenantHoldings.propTypes = { + instance: PropTypes.object.isRequired, + memberTenant: PropTypes.object.isRequired, +}; + export default MemberTenantHoldings; diff --git a/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js b/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js index b1a8cf8c3..1b8825362 100644 --- a/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js +++ b/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js @@ -22,6 +22,7 @@ const HoldingsListMovement = ({ holdings, draggable, droppable, + tenantId, }) => { const { selectItemsForDrag, @@ -56,6 +57,7 @@ const HoldingsListMovement = ({ getDraggingItems={getDraggingItems} holdingIndex={index} draggingHoldingsCount={draggingHoldingsCount} + tenantId={tenantId} /> )) ) : ( diff --git a/src/ViewInstance.js b/src/ViewInstance.js index 5ecb05f8e..e6ad9abdf 100644 --- a/src/ViewInstance.js +++ b/src/ViewInstance.js @@ -151,13 +151,6 @@ class ViewInstance extends React.Component { tenant: '!{tenantId}', throwErrors: false, }, - locations: { - type: 'okapi', - records: 'locations', - path: 'locations?limit=5000', - tenant: '!{tenantId}', - throwErrors: false, - }, configs: { type: 'okapi', records: 'configs', @@ -796,6 +789,7 @@ class ViewInstance extends React.Component { const { match: { params: { id, holdingsrecordid, itemid } }, stripes, + okapi, onCopy, onClose, paneWidth, @@ -892,7 +886,7 @@ class ViewInstance extends React.Component { @@ -1000,7 +994,6 @@ ViewInstance.propTypes = { resources: PropTypes.shape({ allInstanceItems: PropTypes.object.isRequired, allInstanceHoldings: PropTypes.object.isRequired, - locations: PropTypes.object.isRequired, configs: PropTypes.object.isRequired, instanceRequests: PropTypes.shape({ other: PropTypes.shape({ diff --git a/src/constants.js b/src/constants.js index 4fcc91835..04a524cdd 100644 --- a/src/constants.js +++ b/src/constants.js @@ -664,5 +664,7 @@ export const SOURCE_VALUES = { export const CONSORTIUM_PREFIX = 'CONSORTIUM-'; export const OKAPI_TENANT_HEADER = 'X-Okapi-Tenant'; +export const OKAPI_TOKEN_HEADER = 'X-Okapi-Token'; +export const CONTENT_TYPE_HEADER = 'Content-Type'; export const DEFAULT_ITEM_TABLE_SORTBY_FIELD = 'barcode'; diff --git a/src/hooks/index.js b/src/hooks/index.js index 43324a78c..707d43d92 100644 --- a/src/hooks/index.js +++ b/src/hooks/index.js @@ -6,6 +6,8 @@ export { default as useHoldingMutation } from './useHoldingMutation'; export { default as useInstanceMutation } from './useInstanceMutation'; export { default as useHoldingsQueryByHrids } from './useHoldingsQueryByHrids'; export { default as useInventoryBrowse } from './useInventoryBrowse'; +export { default as useLocationsQuery } from './useLocationsQuery'; export { default as useLastSearchTerms } from './useLastSearchTerms'; export { default as useLogout } from './useLogout'; +export { default as useUserAffiliations } from './useUserAffiliations'; export { default as useUserTenantPermissions } from './useUserTenantPermissions'; diff --git a/src/hooks/useLocationsQuery/index.js b/src/hooks/useLocationsQuery/index.js new file mode 100644 index 000000000..7a97c7364 --- /dev/null +++ b/src/hooks/useLocationsQuery/index.js @@ -0,0 +1 @@ +export { default } from './useLocationsQuery'; diff --git a/src/hooks/useLocationsQuery/useLocationsQuery.js b/src/hooks/useLocationsQuery/useLocationsQuery.js new file mode 100644 index 000000000..e49bf6d87 --- /dev/null +++ b/src/hooks/useLocationsQuery/useLocationsQuery.js @@ -0,0 +1,27 @@ +import { useQuery } from 'react-query'; + +import { useNamespace } from '@folio/stripes/core'; + +import { useTenantKy } from '../../common'; + +import { + CQL_FIND_ALL, + LIMIT_MAX, +} from '../../constants'; + +const useLocationsQuery = ({ tenantId } = {}) => { + const ky = useTenantKy({ tenantId }); + const [namespace] = useNamespace({ key: 'locations' }); + + const query = useQuery({ + queryKey: [namespace, tenantId], + queryFn: () => ky.get(`locations?limit=${LIMIT_MAX}&query=${CQL_FIND_ALL} sortby name`).json(), + }); + + return ({ + ...query, + data: query.data?.locations, + }); +}; + +export default useLocationsQuery; diff --git a/src/hooks/useUserAffiliations/index.js b/src/hooks/useUserAffiliations/index.js new file mode 100644 index 000000000..26a629019 --- /dev/null +++ b/src/hooks/useUserAffiliations/index.js @@ -0,0 +1 @@ +export { default } from './useUserAffiliations'; diff --git a/src/hooks/useUserAffiliations/useUserAffiliations.js b/src/hooks/useUserAffiliations/useUserAffiliations.js new file mode 100644 index 000000000..aa5f1fd94 --- /dev/null +++ b/src/hooks/useUserAffiliations/useUserAffiliations.js @@ -0,0 +1,57 @@ +import { orderBy } from 'lodash'; +import { useQuery } from 'react-query'; + +import { useStripes } from '@folio/stripes/core'; + +import { + OKAPI_TENANT_HEADER, + CONTENT_TYPE_HEADER, + OKAPI_TOKEN_HEADER, +} from '../../constants'; + +const fetchConsortiumUserTenants = ({ okapi }, tenant, { id: consortiumId }) => { + return fetch(`${okapi.url}/consortia/${consortiumId}/_self`, { + credentials: 'include', + headers: { + [OKAPI_TENANT_HEADER]: tenant, + [CONTENT_TYPE_HEADER]: 'application/json', + ...(okapi.token && { [OKAPI_TOKEN_HEADER]: okapi.token }), + }, + }) + .then(resp => resp.json()) + .then(data => orderBy(data.userTenants || [], 'tenantName')); +}; + +const useUserAffiliations = ({ userId } = {}, options = {}) => { + const stripes = useStripes(); + const consortium = stripes.user?.user?.consortium; + + const enabled = Boolean( + consortium?.centralTenantId + && userId, + ); + + const { + isFetching, + isLoading: isAffiliationsLoading, + data: userTenants = [], + refetch, + } = useQuery({ + queryKey: ['consortium', 'self', userId, options.tenant], + queryFn: () => { + return fetchConsortiumUserTenants(stripes, consortium?.centralTenantId, { id: consortium.id }); + }, + enabled, + ...options, + }); + + return ({ + affiliations: userTenants, + totalRecords: userTenants.length, + isFetching, + isLoading: isAffiliationsLoading, + refetch, + }); +}; + +export default useUserAffiliations; From 4a08c0ca6ff0f633e191d24e20fa8d32fa705c5e Mon Sep 17 00:00:00 2001 From: Mariia_Aloshyna Date: Wed, 27 Sep 2023 15:29:30 +0300 Subject: [PATCH 03/29] UIIN-2410: Add new hook --- .../ConsortialHoldings/ConsortialHoldings.js | 18 +++--- .../ConsortialHoldings.test.js | 39 +++++++++++++ .../MemberTenantHoldings.js | 14 ++--- src/hooks/index.js | 2 +- .../useLocationsQuery.test.js | 48 ++++++++++++++++ .../index.js | 1 + .../useSearchForShadowInstanceTenants.js | 35 ++++++++++++ .../useSearchForShadowInstanceTenants.test.js | 56 ++++++++++++++++++ src/hooks/useUserAffiliations/index.js | 1 - .../useUserAffiliations.js | 57 ------------------- 10 files changed, 197 insertions(+), 74 deletions(-) create mode 100644 src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.test.js create mode 100644 src/hooks/useLocationsQuery/useLocationsQuery.test.js create mode 100644 src/hooks/useSearchForShadowInstanceTenants/index.js create mode 100644 src/hooks/useSearchForShadowInstanceTenants/useSearchForShadowInstanceTenants.js create mode 100644 src/hooks/useSearchForShadowInstanceTenants/useSearchForShadowInstanceTenants.test.js delete mode 100644 src/hooks/useUserAffiliations/index.js delete mode 100644 src/hooks/useUserAffiliations/useUserAffiliations.js diff --git a/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js b/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js index f1ed0b788..5e4b81efe 100644 --- a/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js +++ b/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useContext } from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; @@ -12,18 +12,19 @@ import { } from '@folio/stripes/components'; import { MemberTenantHoldings } from '../MemberTenantHoldings'; -import { useUserAffiliations } from '../../../hooks'; +import { DataContext } from '../../../contexts'; +import useSearchForShadowInstanceTenants from '../../../hooks/useSearchForShadowInstanceTenants'; const ConsortialHoldings = ({ instance }) => { const stripes = useStripes(); - const { affiliations } = useUserAffiliations({ userId: stripes.user.user.id }); + const { consortiaTenantsById } = useContext(DataContext); - const memberTenants = affiliations.filter(affiliation => { - const isNotCentralTenant = !affiliation.isPrimary; - const isNotCurrentTenant = (affiliation.tenantId !== stripes.okapi.tenant); + const { tenants } = useSearchForShadowInstanceTenants({ instanceId: instance?.id }); - return isNotCentralTenant && isNotCurrentTenant; - }); + const memberTenants = tenants + .map(tenant => consortiaTenantsById[tenant.id]) + .filter(tenant => !tenant.isCentral && (tenant.id !== stripes.okapi.tenant)) + .sort((a, b) => a.name.localeCompare(b.name)); return ( @@ -35,6 +36,7 @@ const ConsortialHoldings = ({ instance }) => { {memberTenants.map(memberTenant => ( diff --git a/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.test.js b/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.test.js new file mode 100644 index 000000000..1dfab760b --- /dev/null +++ b/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.test.js @@ -0,0 +1,39 @@ +import React from 'react'; + +import { screen } from '@folio/jest-config-stripes/testing-library/react'; + +import '../../../../test/jest/__mock__'; +import { + renderWithIntl, + translationsProperties, +} from '../../../../test/jest/helpers'; + +import { instance } from '../../../../test/fixtures'; + +import ConsortialHoldings from './ConsortialHoldings'; + +jest.mock('react', () => ({ + ...jest.requireActual('react'), + useContext: jest.fn().mockReturnValue({ consortiaTenants: [{ id: 'tenant-id', name: 'tenant-name', isCentral: false }] }) +})); +jest.mock('../MemberTenantHoldings', () => ({ + // ...jest.requireActual('../MemberTenantHoldings'), + MemberTenantHoldings: () => <>MemberTenantHoldings, +})); + +const renderConsortialHoldings = () => { + return renderWithIntl( + , + translationsProperties, + ); +}; + +describe('ConsortialHoldings', () => { + it('should render accordion', () => { + renderConsortialHoldings(); + + screen.debug(); + + expect(screen.getByRole('button', { name: 'Consortial holdings' })).toBeInTheDocument(); + }); +}); diff --git a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js index 011312c2b..06b17ba3d 100644 --- a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js +++ b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { isEmpty } from 'lodash'; import { Accordion } from '@folio/stripes/components'; @@ -10,25 +11,24 @@ import { InstanceNewHolding } from '../InstanceNewHolding'; import { useInstanceHoldingsQuery } from '../../../providers'; import css from './MemberTenantHoldings.css'; -import PropTypes from 'prop-types'; const MemberTenantHoldings = ({ memberTenant, instance, }) => { const { - tenantName, - tenantId, + name, + id, } = memberTenant; - const { holdingsRecords } = useInstanceHoldingsQuery(instance?.id, { tenantId }); + const { holdingsRecords } = useInstanceHoldingsQuery(instance?.id, { tenantId: id }); if (isEmpty(holdingsRecords)) return null; return (
@@ -36,7 +36,7 @@ const MemberTenantHoldings = ({ diff --git a/src/hooks/index.js b/src/hooks/index.js index 707d43d92..8d61406b9 100644 --- a/src/hooks/index.js +++ b/src/hooks/index.js @@ -9,5 +9,5 @@ export { default as useInventoryBrowse } from './useInventoryBrowse'; export { default as useLocationsQuery } from './useLocationsQuery'; export { default as useLastSearchTerms } from './useLastSearchTerms'; export { default as useLogout } from './useLogout'; -export { default as useUserAffiliations } from './useUserAffiliations'; +export { default as useSearchForShadowInstanceTenants } from './useSearchForShadowInstanceTenants'; export { default as useUserTenantPermissions } from './useUserTenantPermissions'; diff --git a/src/hooks/useLocationsQuery/useLocationsQuery.test.js b/src/hooks/useLocationsQuery/useLocationsQuery.test.js new file mode 100644 index 000000000..b0ff341dc --- /dev/null +++ b/src/hooks/useLocationsQuery/useLocationsQuery.test.js @@ -0,0 +1,48 @@ +import React from 'react'; +import { + QueryClient, + QueryClientProvider, +} from 'react-query'; +import { + renderHook, + act, +} from '@folio/jest-config-stripes/testing-library/react'; + +import '../../../test/jest/__mock__'; + +import { useTenantKy } from '../../common'; +import useLocationsQuery from './useLocationsQuery'; + +jest.mock('../../common', () => ({ + ...jest.requireActual('../../common'), + useTenantKy: jest.fn(), +})); + +const queryClient = new QueryClient(); +const wrapper = ({ children }) => ( + + {children} + +); + +describe('useLocationsQuery', () => { + beforeEach(() => { + useTenantKy.mockClear().mockReturnValue({ + get: () => ({ + json: () => Promise.resolve({ locations: [{ id: 'location-id' }] }), + }), + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should fetch locations', async () => { + const { result } = renderHook(() => useLocationsQuery(), { wrapper }); + + await act(() => !result.current.isLoading); + + expect(result.current.data).toEqual([{ id: 'location-id' }]); + }); +}); diff --git a/src/hooks/useSearchForShadowInstanceTenants/index.js b/src/hooks/useSearchForShadowInstanceTenants/index.js new file mode 100644 index 000000000..1fa4db060 --- /dev/null +++ b/src/hooks/useSearchForShadowInstanceTenants/index.js @@ -0,0 +1 @@ +export { default } from './useSearchForShadowInstanceTenants'; diff --git a/src/hooks/useSearchForShadowInstanceTenants/useSearchForShadowInstanceTenants.js b/src/hooks/useSearchForShadowInstanceTenants/useSearchForShadowInstanceTenants.js new file mode 100644 index 000000000..4d1f11f6a --- /dev/null +++ b/src/hooks/useSearchForShadowInstanceTenants/useSearchForShadowInstanceTenants.js @@ -0,0 +1,35 @@ +import { useQuery } from 'react-query'; + +import { + useNamespace, + useStripes, +} from '@folio/stripes/core'; + +import { useTenantKy } from '../../common'; + +const useSearchForShadowInstanceTenants = ({ instanceId } = {}) => { + const stripes = useStripes(); + const consortium = stripes.user?.user?.consortium; + + const ky = useTenantKy({ tenantId: consortium?.centralTenantId }); + const [namespace] = useNamespace({ key: 'search-instance-by-holdingsTenantId-facet' }); + + const { isLoading, data = {} } = useQuery({ + queryKey: [namespace, consortium, instanceId], + queryFn: () => ky.get('search/instances/facets', + { + searchParams: { + facet: 'holdings.tenantId', + query: `id=${instanceId}`, + }, + }).json(), + enabled: Boolean(consortium?.centralTenantId && instanceId), + }); + + return { + tenants: data?.facets?.['holdings.tenantId']?.values || [], + isLoading, + }; +}; + +export default useSearchForShadowInstanceTenants; diff --git a/src/hooks/useSearchForShadowInstanceTenants/useSearchForShadowInstanceTenants.test.js b/src/hooks/useSearchForShadowInstanceTenants/useSearchForShadowInstanceTenants.test.js new file mode 100644 index 000000000..c06469028 --- /dev/null +++ b/src/hooks/useSearchForShadowInstanceTenants/useSearchForShadowInstanceTenants.test.js @@ -0,0 +1,56 @@ +import React from 'react'; +import { + QueryClient, + QueryClientProvider, +} from 'react-query'; +import { + renderHook, + act, +} from '@folio/jest-config-stripes/testing-library/react'; + +import '../../../test/jest/__mock__'; + +import { useTenantKy } from '../../common'; +import useSearchForShadowInstanceTenants from './useSearchForShadowInstanceTenants'; + +jest.mock('../../common', () => ({ + ...jest.requireActual('../../common'), + useTenantKy: jest.fn(), +})); + +const queryClient = new QueryClient(); +const wrapper = ({ children }) => ( + + {children} + +); + +describe('useSearchForShadowInstanceTenants', () => { + beforeEach(() => { + useTenantKy.mockClear().mockReturnValue({ + get: () => ({ + json: () => Promise.resolve({ + facets: { + 'holdings.tenantId': { + values: [{ id: 'tenantId' }], + }, + }, + }), + }), + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should fetch tenants', async () => { + const { result } = renderHook(() => useSearchForShadowInstanceTenants( + { instanceId: 'instanceId' } + ), { wrapper }); + + await act(() => !result.current.isLoading); + + expect(result.current.tenants).toEqual([{ id: 'tenantId' }]); + }); +}); diff --git a/src/hooks/useUserAffiliations/index.js b/src/hooks/useUserAffiliations/index.js deleted file mode 100644 index 26a629019..000000000 --- a/src/hooks/useUserAffiliations/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './useUserAffiliations'; diff --git a/src/hooks/useUserAffiliations/useUserAffiliations.js b/src/hooks/useUserAffiliations/useUserAffiliations.js deleted file mode 100644 index aa5f1fd94..000000000 --- a/src/hooks/useUserAffiliations/useUserAffiliations.js +++ /dev/null @@ -1,57 +0,0 @@ -import { orderBy } from 'lodash'; -import { useQuery } from 'react-query'; - -import { useStripes } from '@folio/stripes/core'; - -import { - OKAPI_TENANT_HEADER, - CONTENT_TYPE_HEADER, - OKAPI_TOKEN_HEADER, -} from '../../constants'; - -const fetchConsortiumUserTenants = ({ okapi }, tenant, { id: consortiumId }) => { - return fetch(`${okapi.url}/consortia/${consortiumId}/_self`, { - credentials: 'include', - headers: { - [OKAPI_TENANT_HEADER]: tenant, - [CONTENT_TYPE_HEADER]: 'application/json', - ...(okapi.token && { [OKAPI_TOKEN_HEADER]: okapi.token }), - }, - }) - .then(resp => resp.json()) - .then(data => orderBy(data.userTenants || [], 'tenantName')); -}; - -const useUserAffiliations = ({ userId } = {}, options = {}) => { - const stripes = useStripes(); - const consortium = stripes.user?.user?.consortium; - - const enabled = Boolean( - consortium?.centralTenantId - && userId, - ); - - const { - isFetching, - isLoading: isAffiliationsLoading, - data: userTenants = [], - refetch, - } = useQuery({ - queryKey: ['consortium', 'self', userId, options.tenant], - queryFn: () => { - return fetchConsortiumUserTenants(stripes, consortium?.centralTenantId, { id: consortium.id }); - }, - enabled, - ...options, - }); - - return ({ - affiliations: userTenants, - totalRecords: userTenants.length, - isFetching, - isLoading: isAffiliationsLoading, - refetch, - }); -}; - -export default useUserAffiliations; From 5dfcf0dd92ce89643e2395dc7566a48cd9b5c64f Mon Sep 17 00:00:00 2001 From: Mariia_Aloshyna Date: Wed, 27 Sep 2023 17:06:06 +0300 Subject: [PATCH 04/29] UIIN-2410: Add tests --- .../ConsortialHoldings/ConsortialHoldings.js | 4 +- .../ConsortialHoldings.test.js | 40 ++++++++----- .../MemberTenantHoldings.js | 19 +++--- .../MemberTenantHoldings.test.js | 60 +++++++++++++++++++ 4 files changed, 96 insertions(+), 27 deletions(-) create mode 100644 src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.test.js diff --git a/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js b/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js index 5e4b81efe..a0976eede 100644 --- a/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js +++ b/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js @@ -13,7 +13,7 @@ import { import { MemberTenantHoldings } from '../MemberTenantHoldings'; import { DataContext } from '../../../contexts'; -import useSearchForShadowInstanceTenants from '../../../hooks/useSearchForShadowInstanceTenants'; +import { useSearchForShadowInstanceTenants } from '../../../hooks'; const ConsortialHoldings = ({ instance }) => { const stripes = useStripes(); @@ -23,7 +23,7 @@ const ConsortialHoldings = ({ instance }) => { const memberTenants = tenants .map(tenant => consortiaTenantsById[tenant.id]) - .filter(tenant => !tenant.isCentral && (tenant.id !== stripes.okapi.tenant)) + .filter(tenant => !tenant?.isCentral && (tenant?.id !== stripes.okapi.tenant)) .sort((a, b) => a.name.localeCompare(b.name)); return ( diff --git a/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.test.js b/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.test.js index 1dfab760b..b63980d39 100644 --- a/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.test.js +++ b/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.test.js @@ -1,5 +1,3 @@ -import React from 'react'; - import { screen } from '@folio/jest-config-stripes/testing-library/react'; import '../../../../test/jest/__mock__'; @@ -10,30 +8,44 @@ import { import { instance } from '../../../../test/fixtures'; +import { DataContext } from '../../../contexts'; import ConsortialHoldings from './ConsortialHoldings'; -jest.mock('react', () => ({ - ...jest.requireActual('react'), - useContext: jest.fn().mockReturnValue({ consortiaTenants: [{ id: 'tenant-id', name: 'tenant-name', isCentral: false }] }) -})); jest.mock('../MemberTenantHoldings', () => ({ - // ...jest.requireActual('../MemberTenantHoldings'), - MemberTenantHoldings: () => <>MemberTenantHoldings, + ...jest.requireActual('../MemberTenantHoldings'), + MemberTenantHoldings: ({ memberTenant }) => <>{memberTenant.name} accordion, +})); +jest.mock('../../../hooks', () => ({ + ...jest.requireActual('../../../hooks'), + useSearchForShadowInstanceTenants: () => ({ tenants: [{ id: 'college' }] }), })); +const providerValue = { + consortiaTenantsById: { + 'college': { id: 'college', name: 'College', isCentral: false }, + } +}; + const renderConsortialHoldings = () => { - return renderWithIntl( - , - translationsProperties, + const component = ( + + + ); + + return renderWithIntl(component, translationsProperties); }; describe('ConsortialHoldings', () => { - it('should render accordion', () => { + it('should render Consortial holdings accordion', () => { renderConsortialHoldings(); - screen.debug(); - expect(screen.getByRole('button', { name: 'Consortial holdings' })).toBeInTheDocument(); }); + + it('should render sub-accordion with tenants\' holdings info', () => { + renderConsortialHoldings(); + + expect(screen.getByText('College accordion')).toBeInTheDocument(); + }); }); diff --git a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js index 06b17ba3d..0d1c3bf12 100644 --- a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js +++ b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js @@ -4,8 +4,7 @@ import { isEmpty } from 'lodash'; import { Accordion } from '@folio/stripes/components'; -import HoldingsList from '../../HoldingsList/HoldingsList'; -import { MoveItemsContext } from '../../MoveItemsContext'; +import { HoldingsList } from '../../HoldingsList'; import { InstanceNewHolding } from '../InstanceNewHolding'; import { useInstanceHoldingsQuery } from '../../../providers'; @@ -32,15 +31,13 @@ const MemberTenantHoldings = ({ closedByDefault >
- - - +
diff --git a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.test.js b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.test.js new file mode 100644 index 000000000..76cf5c0e2 --- /dev/null +++ b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.test.js @@ -0,0 +1,60 @@ +import { BrowserRouter as Router } from 'react-router-dom'; + +import { screen } from '@folio/jest-config-stripes/testing-library/react'; + +import '../../../../test/jest/__mock__'; +import { + renderWithIntl, + translationsProperties, +} from '../../../../test/jest/helpers'; + +import { instance } from '../../../../test/fixtures'; + +import MemberTenantHoldings from './MemberTenantHoldings'; + +jest.mock('../../../providers', () => ({ + ...jest.requireActual('../../../providers'), + useInstanceHoldingsQuery: () => ({ holdingsRecords: [{ id: 'holdings-id' }] }), +})); +jest.mock('../../HoldingsList', () => ({ + ...jest.requireActual('../../HoldingsList'), + HoldingsList: () => <>Holdings, +})); + +const mockMemberTenant = { + id: 'college', + name: 'College', +}; + +const renderMemberTenantHoldings = () => { + const component = ( + + + + ); + + return renderWithIntl(component, translationsProperties); +}; + +describe('MemberTenantHoldings', () => { + it('should render member tenant accordion', () => { + renderMemberTenantHoldings(); + + expect(screen.getByText('College')).toBeInTheDocument(); + }); + + it('should render member tenant\'s holdings', () => { + renderMemberTenantHoldings(); + + expect(screen.getByText('Holdings')).toBeInTheDocument(); + }); + + it('should render Add holdings button', () => { + renderMemberTenantHoldings(); + + expect(screen.getByRole('button', { name: 'Add holdings' })).toBeInTheDocument(); + }); +}); From 188acc32c17fc21f2611e14e88aaf4c0f91882c9 Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko1 Date: Tue, 3 Oct 2023 13:34:45 +0300 Subject: [PATCH 05/29] UIIN-2452: Disable buttons when member tenant does not have permissions --- src/Instance/HoldingsList/Holding/Holding.js | 9 ++++ .../HoldingsList/Holding/HoldingAccordion.js | 7 ++++ .../Holding/HoldingButtonsGroup.js | 6 +++ .../HoldingsList/Holding/HoldingContainer.js | 20 ++++++++- src/Instance/HoldingsList/HoldingsList.js | 11 ++++- .../HoldingsList/HoldingsListContainer.js | 13 ++++++ .../ConsortialHoldings/ConsortialHoldings.js | 11 ++++- .../InstanceDetails/InstanceDetails.js | 17 +++++++- .../InstanceNewHolding/InstanceNewHolding.js | 8 ++-- .../MemberTenantHoldings.js | 33 +++++++++++---- .../HoldingsListMovement.js | 11 ++++- src/Instance/ItemsList/ItemBarcode.js | 37 ++++++++++------ src/Instance/ItemsList/ItemsList.js | 6 ++- src/Instance/ItemsList/ItemsListContainer.js | 6 ++- src/ViewInstance.js | 11 ++++- src/utils.js | 42 +++++++++++++++++++ 16 files changed, 212 insertions(+), 36 deletions(-) diff --git a/src/Instance/HoldingsList/Holding/Holding.js b/src/Instance/HoldingsList/Holding/Holding.js index b04382bb0..8cebe70f2 100644 --- a/src/Instance/HoldingsList/Holding/Holding.js +++ b/src/Instance/HoldingsList/Holding/Holding.js @@ -20,6 +20,9 @@ const Holding = ({ isDraggable, isItemsDroppable, tenantId, + isViewHoldingsDisabled, + isAddItemDisabled, + isBarcodeAsHotlink, }) => { return (
@@ -49,12 +52,15 @@ const Holding = ({ onViewHolding={onViewHolding} onAddItem={onAddItem} tenantId={tenantId} + isViewHoldingsDisabled={isViewHoldingsDisabled} + isAddItemDisabled={isAddItemDisabled} > @@ -74,6 +80,9 @@ Holding.propTypes = { isHoldingDragSelected: PropTypes.func, isItemsDroppable: PropTypes.bool, tenantId: PropTypes.string, + isViewHoldingsDisabled: PropTypes.bool, + isAddItemDisabled: PropTypes.bool, + isBarcodeAsHotlink: PropTypes.bool, }; Holding.defaultProps = { diff --git a/src/Instance/HoldingsList/Holding/HoldingAccordion.js b/src/Instance/HoldingsList/Holding/HoldingAccordion.js index 97b70ef52..6a38d2701 100644 --- a/src/Instance/HoldingsList/Holding/HoldingAccordion.js +++ b/src/Instance/HoldingsList/Holding/HoldingAccordion.js @@ -23,6 +23,8 @@ const HoldingAccordion = ({ onAddItem, withMoveDropdown, tenantId, + isViewHoldingsDisabled, + isAddItemDisabled, }) => { const searchParams = { limit: 0, @@ -57,6 +59,8 @@ const HoldingAccordion = ({ onAddItem={onAddItem} withMoveDropdown={withMoveDropdown} isOpen={open} + isViewHoldingsDisabled={isViewHoldingsDisabled} + isAddItemDisabled={isAddItemDisabled} />; const location = labelLocation?.isActive ? @@ -116,6 +120,9 @@ HoldingAccordion.propTypes = { holdings: PropTypes.arrayOf(PropTypes.object), withMoveDropdown: PropTypes.bool, children: PropTypes.object, + tenantId: PropTypes.string, + isViewHoldingsDisabled: PropTypes.bool, + isAddItemDisabled: PropTypes.bool, }; export default HoldingAccordion; diff --git a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js index f649a3c43..861910a66 100644 --- a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js +++ b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js @@ -20,6 +20,8 @@ const HoldingButtonsGroup = ({ onAddItem, itemCount, isOpen, + isViewHoldingsDisabled, + isAddItemDisabled, }) => ( <> { @@ -35,6 +37,7 @@ const HoldingButtonsGroup = ({ id={`clickable-view-holdings-${holding.id}`} data-test-view-holdings onClick={onViewHolding} + disabled={isViewHoldingsDisabled} > @@ -45,6 +48,7 @@ const HoldingButtonsGroup = ({ data-test-add-item onClick={onAddItem} buttonStyle="primary paneHeaderNewButton" + disabled={isAddItemDisabled} > @@ -62,6 +66,8 @@ HoldingButtonsGroup.propTypes = { onAddItem: PropTypes.func.isRequired, onViewHolding: PropTypes.func.isRequired, withMoveDropdown: PropTypes.bool, + isViewHoldingsDisabled: PropTypes.bool, + isAddItemDisabled: PropTypes.bool, }; diff --git a/src/Instance/HoldingsList/Holding/HoldingContainer.js b/src/Instance/HoldingsList/Holding/HoldingContainer.js index eb8d40cc4..d13634b62 100644 --- a/src/Instance/HoldingsList/Holding/HoldingContainer.js +++ b/src/Instance/HoldingsList/Holding/HoldingContainer.js @@ -36,6 +36,9 @@ const DraggableHolding = ({ onViewHolding, onAddItem, tenantId, + isViewHoldingsDisabled, + isAddItemDisabled, + isBarcodeAsHotlink, ...rest }) => { const rowStyles = useMemo(() => ( @@ -68,6 +71,9 @@ const DraggableHolding = ({ onViewHolding={onViewHolding} onAddItem={onAddItem} tenantId={tenantId} + isViewHoldingsDisabled={isViewHoldingsDisabled} + isAddItemDisabled={isAddItemDisabled} + isBarcodeAsHotlink={isBarcodeAsHotlink} /> ) } @@ -91,7 +97,9 @@ DraggableHolding.propTypes = { const HoldingContainer = ({ location, history, - + isViewHoldingsDisabled, + isAddItemDisabled, + isBarcodeAsHotlink, instance, holding, isDraggable, @@ -129,6 +137,9 @@ const HoldingContainer = ({ onViewHolding={onViewHolding} onAddItem={onAddItem} tenantId={tenantId} + isViewHoldingsDisabled={isViewHoldingsDisabled} + isAddItemDisabled={isAddItemDisabled} + isBarcodeAsHotlink={isBarcodeAsHotlink} {...rest} /> )} @@ -140,6 +151,9 @@ const HoldingContainer = ({ onViewHolding={onViewHolding} onAddItem={onAddItem} tenantId={tenantId} + isViewHoldingsDisabled={isViewHoldingsDisabled} + isAddItemDisabled={isAddItemDisabled} + isBarcodeAsHotlink={isBarcodeAsHotlink} /> ); }; @@ -149,13 +163,15 @@ HoldingContainer.propTypes = { history: PropTypes.object.isRequired, provided: PropTypes.object.isRequired, snapshot: PropTypes.object.isRequired, - instance: PropTypes.object.isRequired, holding: PropTypes.object.isRequired, holdingIndex: PropTypes.number, isDraggable: PropTypes.bool, draggingHoldingsCount: PropTypes.number, tenantId: PropTypes.string, + isViewHoldingsDisabled: PropTypes.bool, + isAddItemDisabled: PropTypes.bool, + isBarcodeAsHotlink: PropTypes.bool, }; export default withRouter(HoldingContainer); diff --git a/src/Instance/HoldingsList/HoldingsList.js b/src/Instance/HoldingsList/HoldingsList.js index 97c01fff5..a7d43cd80 100644 --- a/src/Instance/HoldingsList/HoldingsList.js +++ b/src/Instance/HoldingsList/HoldingsList.js @@ -7,7 +7,9 @@ const HoldingsList = ({ instance, holdings, tenantId, - + isViewHoldingsDisabled, + isAddItemDisabled, + isBarcodeAsHotlink, draggable, droppable, }) => holdings.map(holding => ( @@ -19,6 +21,9 @@ const HoldingsList = ({ droppable={droppable} holdings={holdings} tenantId={tenantId} + isViewHoldingsDisabled={isViewHoldingsDisabled} + isAddItemDisabled={isAddItemDisabled} + isBarcodeAsHotlink={isBarcodeAsHotlink} /> )); @@ -26,7 +31,9 @@ HoldingsList.propTypes = { instance: PropTypes.object.isRequired, holdings: PropTypes.arrayOf(PropTypes.object), tenantId: PropTypes.string, - + isViewHoldingsDisabled: PropTypes.bool, + isAddItemDisabled: PropTypes.bool, + isBarcodeAsHotlink: PropTypes.bool, draggable: PropTypes.bool, droppable: PropTypes.bool, }; diff --git a/src/Instance/HoldingsList/HoldingsListContainer.js b/src/Instance/HoldingsList/HoldingsListContainer.js index be19ea8ee..6b11258b9 100644 --- a/src/Instance/HoldingsList/HoldingsListContainer.js +++ b/src/Instance/HoldingsList/HoldingsListContainer.js @@ -8,15 +8,21 @@ import { import HoldingsList from './HoldingsList'; import { HoldingsListMovement } from '../InstanceMovement/HoldingMovementList'; import { useInstanceHoldingsQuery } from '../../providers'; +import { hasMemberTenantPermission } from '../../utils'; const HoldingsListContainer = ({ instance, isHoldingsMove, tenantId, + userTenantPermissions, ...rest }) => { const { holdingsRecords: holdings, isLoading } = useInstanceHoldingsQuery(instance.id, { tenantId }); + const canViewHoldings = hasMemberTenantPermission(userTenantPermissions, 'ui-inventory.holdings.edit', tenantId); + const canCreateItem = hasMemberTenantPermission(userTenantPermissions, 'ui-inventory.item.create', tenantId); + const canViewItems = hasMemberTenantPermission(userTenantPermissions, 'ui-inventory.item.create', tenantId); + if (isLoading) return ; return ( @@ -26,6 +32,9 @@ const HoldingsListContainer = ({ holdings={holdings} instance={instance} tenantId={tenantId} + isViewHoldingsDisabled={!canViewHoldings} + isAddItemDisabled={!canCreateItem} + isBarcodeAsHotlink={canViewItems} /> ) : ( ) ); @@ -42,6 +54,7 @@ HoldingsListContainer.propTypes = { instance: PropTypes.object.isRequired, isHoldingsMove: PropTypes.bool, tenantId: PropTypes.string, + userTenantPermissions: PropTypes.arrayOf(PropTypes.object), }; export default HoldingsListContainer; diff --git a/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js b/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js index a0976eede..4d5989f5b 100644 --- a/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js +++ b/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js @@ -15,7 +15,10 @@ import { MemberTenantHoldings } from '../MemberTenantHoldings'; import { DataContext } from '../../../contexts'; import { useSearchForShadowInstanceTenants } from '../../../hooks'; -const ConsortialHoldings = ({ instance }) => { +const ConsortialHoldings = ({ + instance, + userTenantPermissions, +}) => { const stripes = useStripes(); const { consortiaTenantsById } = useContext(DataContext); @@ -39,6 +42,7 @@ const ConsortialHoldings = ({ instance }) => { key={memberTenant.id} memberTenant={memberTenant} instance={instance} + userTenantPermissions={userTenantPermissions} /> ))} @@ -47,6 +51,9 @@ const ConsortialHoldings = ({ instance }) => { ); }; -ConsortialHoldings.propTypes = { instance: PropTypes.object.isRequired }; +ConsortialHoldings.propTypes = { + instance: PropTypes.object.isRequired, + userTenantPermissions: PropTypes.arrayOf(PropTypes.object), +}; export default ConsortialHoldings; diff --git a/src/Instance/InstanceDetails/InstanceDetails.js b/src/Instance/InstanceDetails/InstanceDetails.js index 65c4b687f..b4b490b04 100644 --- a/src/Instance/InstanceDetails/InstanceDetails.js +++ b/src/Instance/InstanceDetails/InstanceDetails.js @@ -6,6 +6,7 @@ import PropTypes from 'prop-types'; import { AppIcon, TitleManager, + useStripes, } from '@folio/stripes/core'; import { AccordionSet, @@ -35,6 +36,7 @@ import { InstanceAcquisition } from './InstanceAcquisition'; import HelperApp from '../../components/HelperApp'; import { getAccordionState } from './utils'; +import { hasMemberTenantPermission } from '../../utils'; import { DataContext } from '../../contexts'; import { ConsortialHoldings } from './ConsortialHoldings'; @@ -60,10 +62,12 @@ const InstanceDetails = forwardRef(({ onClose, actionMenu, tagsEnabled, + userTenantPermissions, ...rest }, ref) => { const intl = useIntl(); const location = useLocation(); + const { okapi: { tenant: tenantId } } = useStripes(); const searchParams = new URLSearchParams(location.search); const referenceData = useContext(DataContext); @@ -71,6 +75,8 @@ const InstanceDetails = forwardRef(({ const [helperApp, setHelperApp] = useState(); const tags = instance?.tags?.tagList; + const canCreateHoldings = hasMemberTenantPermission(userTenantPermissions, 'ui-inventory.holdings.create', tenantId); + const detailsLastMenu = useMemo(() => { return ( @@ -131,10 +137,16 @@ const InstanceDetails = forwardRef(({ {children} - + {instance?.shared && ( - + )} { const intl = useIntl(); @@ -33,6 +31,7 @@ const InstanceNewHolding = ({ aria-label={label} buttonStyle="primary" fullWidth + disabled={disabled} > {label} @@ -45,6 +44,7 @@ const InstanceNewHolding = ({ InstanceNewHolding.propTypes = { location: PropTypes.object.isRequired, instance: PropTypes.object, + disabled: PropTypes.bool, }; export default withRouter(InstanceNewHolding); diff --git a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js index 0d1c3bf12..e9561e068 100644 --- a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js +++ b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js @@ -5,15 +5,18 @@ import { isEmpty } from 'lodash'; import { Accordion } from '@folio/stripes/components'; import { HoldingsList } from '../../HoldingsList'; +import { MoveItemsContext } from '../../MoveItemsContext'; import { InstanceNewHolding } from '../InstanceNewHolding'; import { useInstanceHoldingsQuery } from '../../../providers'; +import { hasMemberTenantPermission } from '../../../utils'; import css from './MemberTenantHoldings.css'; const MemberTenantHoldings = ({ memberTenant, instance, + userTenantPermissions, }) => { const { name, @@ -21,6 +24,11 @@ const MemberTenantHoldings = ({ } = memberTenant; const { holdingsRecords } = useInstanceHoldingsQuery(instance?.id, { tenantId: id }); + const canViewHoldings = hasMemberTenantPermission(userTenantPermissions, 'ui-inventory.holdings.edit', id); + const canCreateItem = hasMemberTenantPermission(userTenantPermissions, 'ui-inventory.item.create', id); + const canCreateHoldings = hasMemberTenantPermission(userTenantPermissions, 'ui-inventory.holdings.create', id); + const canViewItems = hasMemberTenantPermission(userTenantPermissions, 'ui-inventory.item.create', id); + if (isEmpty(holdingsRecords)) return null; return ( @@ -31,15 +39,23 @@ const MemberTenantHoldings = ({ closedByDefault >
- + + +
- + ); }; @@ -47,6 +63,7 @@ const MemberTenantHoldings = ({ MemberTenantHoldings.propTypes = { instance: PropTypes.object.isRequired, memberTenant: PropTypes.object.isRequired, + userTenantPermissions: PropTypes.arrayOf(PropTypes.object), }; export default MemberTenantHoldings; diff --git a/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js b/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js index 1b8825362..3591b56c6 100644 --- a/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js +++ b/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js @@ -23,6 +23,9 @@ const HoldingsListMovement = ({ draggable, droppable, tenantId, + isViewHoldingsDisabled, + isAddItemDisabled, + isBarcodeAsHotlink, }) => { const { selectItemsForDrag, @@ -58,6 +61,9 @@ const HoldingsListMovement = ({ holdingIndex={index} draggingHoldingsCount={draggingHoldingsCount} tenantId={tenantId} + isViewHoldingsDisabled={isViewHoldingsDisabled} + isAddItemDisabled={isAddItemDisabled} + isBarcodeAsHotlink={isBarcodeAsHotlink} /> )) ) : ( @@ -76,7 +82,10 @@ const HoldingsListMovement = ({ HoldingsListMovement.propTypes = { instance: PropTypes.object.isRequired, holdings: PropTypes.arrayOf(PropTypes.object), - + tenantId: PropTypes.string, + isViewHoldingsDisabled: PropTypes.bool, + isAddItemDisabled: PropTypes.bool, + isBarcodeAsHotlink: PropTypes.bool, draggable: PropTypes.bool, droppable: PropTypes.bool, }; diff --git a/src/Instance/ItemsList/ItemBarcode.js b/src/Instance/ItemsList/ItemBarcode.js index 621febe1c..285a26782 100644 --- a/src/Instance/ItemsList/ItemBarcode.js +++ b/src/Instance/ItemsList/ItemBarcode.js @@ -24,7 +24,13 @@ import { import css from '../../View.css'; import { QUERY_INDEXES } from '../../constants'; -const ItemBarcode = ({ location, item, holdingId, instanceId }) => { +const ItemBarcode = ({ + location, + item, + holdingId, + instanceId, + isBarcodeAsHotlink, +}) => { const { search } = location; const queryBarcode = queryString.parse(search)?.query; const isQueryByBarcode = queryString.parse(search)?.qindex === QUERY_INDEXES.BARCODE; @@ -43,18 +49,25 @@ const ItemBarcode = ({ location, item, holdingId, instanceId }) => { const highlightableBarcode = isQueryByBarcode ? : item.barcode; + const itemBarcode = ( + + + {item.barcode ? highlightableBarcode : } + + + ); + return ( <> - - - - {item.barcode ? highlightableBarcode : } - - - + {isBarcodeAsHotlink ? ( + + {itemBarcode} + + ) : itemBarcode + } {item.barcode && { ItemBarcode.propTypes = { location: PropTypes.object.isRequired, - item: PropTypes.object.isRequired, holdingId: PropTypes.string.isRequired, instanceId: PropTypes.string.isRequired, + isBarcodeAsHotlink: PropTypes.bool, }; export default withRouter(ItemBarcode); diff --git a/src/Instance/ItemsList/ItemsList.js b/src/Instance/ItemsList/ItemsList.js index 0cbb216fa..67ee6ed7f 100644 --- a/src/Instance/ItemsList/ItemsList.js +++ b/src/Instance/ItemsList/ItemsList.js @@ -40,6 +40,7 @@ const getFormatter = ( holdingsMapById, selectItemsForDrag, ifItemsSelected, + isBarcodeAsHotlink, ) => ({ 'dnd': () => ( {item.discoverySuppress && @@ -160,6 +162,7 @@ const ItemsList = ({ selectItemsForDrag, getDraggingItems, isFetching, + isBarcodeAsHotlink, }) => { const { boundWithHoldings: holdings, isLoading } = useBoundWithHoldings(items); const holdingsMapById = keyBy(holdings, 'id'); @@ -180,7 +183,7 @@ const ItemsList = ({ [holding.id, records, isItemsDragSelected, selectItemsForDrag], ); const formatter = useMemo( - () => getFormatter(intl, locationsById, holdingsMapById, selectItemsForDrag, isItemsDragSelected), + () => getFormatter(intl, locationsById, holdingsMapById, selectItemsForDrag, isItemsDragSelected, isBarcodeAsHotlink), [holdingsMapById, selectItemsForDrag, isItemsDragSelected], ); const rowProps = useMemo(() => ({ @@ -259,6 +262,7 @@ ItemsList.propTypes = { isItemsDragSelected: PropTypes.func.isRequired, getDraggingItems: PropTypes.func.isRequired, isFetching: PropTypes.bool, + isBarcodeAsHotlink: PropTypes.bool, }; ItemsList.defaultProps = { diff --git a/src/Instance/ItemsList/ItemsListContainer.js b/src/Instance/ItemsList/ItemsListContainer.js index 73d912160..ac601b800 100644 --- a/src/Instance/ItemsList/ItemsListContainer.js +++ b/src/Instance/ItemsList/ItemsListContainer.js @@ -19,6 +19,7 @@ const ItemsListContainer = ({ holding, draggable, droppable, + isBarcodeAsHotlink, }) => { const { selectItemsForDrag, @@ -62,6 +63,7 @@ const ItemsListContainer = ({ draggable={draggable} droppable={droppable} isFetching={isFetching} + isBarcodeAsHotlink={isBarcodeAsHotlink} /> ); }; @@ -69,7 +71,9 @@ const ItemsListContainer = ({ ItemsListContainer.propTypes = { holding: PropTypes.object.isRequired, draggable: PropTypes.bool, - droppable: PropTypes.bool + droppable: PropTypes.bool, + tenantId: PropTypes.string, + isBarcodeAsHotlink: PropTypes.bool, }; export default memo(ItemsListContainer); diff --git a/src/ViewInstance.js b/src/ViewInstance.js index e6ad9abdf..fc9736f6e 100644 --- a/src/ViewInstance.js +++ b/src/ViewInstance.js @@ -37,6 +37,7 @@ import InstancePlugin from './components/InstancePlugin'; import { getPublishingInfo } from './Instance/InstanceDetails/utils'; import { getDate, + getUserTenantsPermissions, handleKeyCommand, isInstanceShadowCopy, isMARCSource, @@ -186,6 +187,7 @@ class ViewInstance extends React.Component { isNewOrderModalOpen: false, afterCreate: false, instancesQuickExportInProgress: false, + userTenantPermissions: [], }; this.instanceId = null; this.cViewHoldingsRecord = this.props.stripes.connect(ViewHoldingsRecord); @@ -195,7 +197,10 @@ class ViewInstance extends React.Component { } componentDidMount() { - const { selectedInstance } = this.props; + const { + selectedInstance, + stripes, + } = this.props; const isMARCSourceRecord = isMARCSource(selectedInstance?.source); if (isMARCSourceRecord) { @@ -203,6 +208,8 @@ class ViewInstance extends React.Component { } this.setTlrSettings(); + + getUserTenantsPermissions(stripes).then(userTenantPermissions => this.setState({ userTenantPermissions })); } componentDidUpdate(prevProps) { @@ -878,6 +885,7 @@ class ViewInstance extends React.Component { instance={instance} tagsEnabled={tagsEnabled} ref={this.accordionStatusRef} + userTenantPermissions={this.state.userTenantPermissions} > { (!holdingsrecordid && !itemid) ? @@ -887,6 +895,7 @@ class ViewInstance extends React.Component { instance={instance} draggable={this.state.isItemsMovement} tenantId={okapi.tenant} + userTenantPermissions={this.state.userTenantPermissions} droppable /> diff --git a/src/utils.js b/src/utils.js index 19e11fcc3..0fd853e6f 100644 --- a/src/utils.js +++ b/src/utils.js @@ -34,6 +34,9 @@ import { ERROR_TYPES, SINGLE_ITEM_QUERY_TEMPLATES, CONSORTIUM_PREFIX, + OKAPI_TENANT_HEADER, + CONTENT_TYPE_HEADER, + OKAPI_TOKEN_HEADER, } from './constants'; export const areAllFieldsEmpty = fields => fields.every(item => (isArray(item) @@ -782,3 +785,42 @@ export const isMARCSource = (source) => { export const isUserInConsortiumMode = stripes => stripes.hasInterface('consortia'); export const isInstanceShadowCopy = (source) => [`${CONSORTIUM_PREFIX}FOLIO`, `${CONSORTIUM_PREFIX}MARC`].includes(source); + +export const getUserTenantsPermissions = stripes => { + const { + user: { + user: { + tenants, + id, + }, + }, + okapi: { + url, + token, + } + } = stripes; + const userTenantIds = tenants.map(tenant => tenant.id); + + const promises = userTenantIds.map(async (tenantId) => { + const result = await fetch(`${url}/perms/users/${id}/permissions?full=false&indexField=userId`, { + headers: { + [OKAPI_TENANT_HEADER]: tenantId, + [CONTENT_TYPE_HEADER]: 'application/json', + ...(token && { [OKAPI_TOKEN_HEADER]: token }), + }, + credentials: 'include', + }); + + const json = await result.json(); + + return { tenantId, ...json }; + }); + + return Promise.all(promises); +}; + +export const hasMemberTenantPermission = (permissions, permissionName, tenantId) => { + const tenantPermissions = permissions.find(permission => permission.tenantId === tenantId)?.permissionNames; + + return tenantPermissions?.includes(permissionName); +}; From 58f4cd46d4a2fc6594c80ac5255392ab978f6dcc Mon Sep 17 00:00:00 2001 From: Mariia_Aloshyna Date: Fri, 22 Sep 2023 16:04:02 +0300 Subject: [PATCH 06/29] UIIN-2410: Instance 3rd pane: Add consortial holdings/item accordion --- src/Instance/HoldingsList/Holding/Holding.js | 4 ++ .../HoldingsList/Holding/HoldingAccordion.js | 17 ++--- .../HoldingsList/Holding/HoldingContainer.js | 7 +++ src/Instance/HoldingsList/HoldingsList.js | 3 + .../HoldingsList/HoldingsListContainer.js | 12 +++- .../ConsortialHoldings/ConsortialHoldings.js | 52 ++++++++++++++++ .../ConsortialHoldings.test.js | 51 +++++++++++++++ .../ConsortialHoldings/index.js | 2 + .../InstanceDetails/InstanceDetails.js | 5 ++ .../MemberTenantHoldings.css | 3 + .../MemberTenantHoldings.js | 62 +++++++++++++++++++ .../MemberTenantHoldings.test.js | 60 ++++++++++++++++++ .../MemberTenantHoldings/index.js | 2 + .../HoldingsListMovement.js | 3 + src/Instance/ItemsList/ItemsListContainer.js | 8 ++- src/ViewInstance.js | 10 +-- src/hooks/index.js | 2 + src/hooks/useHoldingItemsQuery.js | 11 ++-- src/hooks/useLocationsQuery/index.js | 1 + .../useLocationsQuery/useLocationsQuery.js | 27 ++++++++ .../useLocationsQuery.test.js | 48 ++++++++++++++ .../index.js | 1 + .../useSearchForShadowInstanceTenants.js | 35 +++++++++++ .../useSearchForShadowInstanceTenants.test.js | 56 +++++++++++++++++ src/providers/HoldingsProvider.js | 9 ++- translations/ui-inventory/en.json | 1 + 26 files changed, 461 insertions(+), 31 deletions(-) create mode 100644 src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js create mode 100644 src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.test.js create mode 100644 src/Instance/InstanceDetails/ConsortialHoldings/index.js create mode 100644 src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.css create mode 100644 src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js create mode 100644 src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.test.js create mode 100644 src/Instance/InstanceDetails/MemberTenantHoldings/index.js create mode 100644 src/hooks/useLocationsQuery/index.js create mode 100644 src/hooks/useLocationsQuery/useLocationsQuery.js create mode 100644 src/hooks/useLocationsQuery/useLocationsQuery.test.js create mode 100644 src/hooks/useSearchForShadowInstanceTenants/index.js create mode 100644 src/hooks/useSearchForShadowInstanceTenants/useSearchForShadowInstanceTenants.js create mode 100644 src/hooks/useSearchForShadowInstanceTenants/useSearchForShadowInstanceTenants.test.js diff --git a/src/Instance/HoldingsList/Holding/Holding.js b/src/Instance/HoldingsList/Holding/Holding.js index c7e4bd7a1..b04382bb0 100644 --- a/src/Instance/HoldingsList/Holding/Holding.js +++ b/src/Instance/HoldingsList/Holding/Holding.js @@ -19,6 +19,7 @@ const Holding = ({ isHoldingDragSelected, isDraggable, isItemsDroppable, + tenantId, }) => { return (
@@ -47,11 +48,13 @@ const Holding = ({ holdings={holdings} onViewHolding={onViewHolding} onAddItem={onAddItem} + tenantId={tenantId} > @@ -70,6 +73,7 @@ Holding.propTypes = { selectHoldingsForDrag: PropTypes.func, isHoldingDragSelected: PropTypes.func, isItemsDroppable: PropTypes.bool, + tenantId: PropTypes.string, }; Holding.defaultProps = { diff --git a/src/Instance/HoldingsList/Holding/HoldingAccordion.js b/src/Instance/HoldingsList/Holding/HoldingAccordion.js index fe73df642..8fb96e77b 100644 --- a/src/Instance/HoldingsList/Holding/HoldingAccordion.js +++ b/src/Instance/HoldingsList/Holding/HoldingAccordion.js @@ -1,9 +1,7 @@ -import React, { - useState, - useContext, -} from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; +import { keyBy } from 'lodash'; import { Accordion, @@ -12,10 +10,10 @@ import { Icon, } from '@folio/stripes/components'; -import { DataContext } from '../../../contexts'; import { callNumberLabel } from '../../../utils'; import HoldingButtonsGroup from './HoldingButtonsGroup'; import useHoldingItemsQuery from '../../../hooks/useHoldingItemsQuery'; +import { useLocationsQuery } from '../../../hooks'; const HoldingAccordion = ({ children, @@ -24,19 +22,21 @@ const HoldingAccordion = ({ onViewHolding, onAddItem, withMoveDropdown, + tenantId, }) => { const searchParams = { limit: 0, offset: 0, }; - const { locationsById } = useContext(DataContext); const [open, setOpen] = useState(false); const [openFirstTime, setOpenFirstTime] = useState(false); - const { totalRecords, isFetching } = useHoldingItemsQuery(holding.id, { searchParams, key: 'itemCount' }); + const { totalRecords, isFetching } = useHoldingItemsQuery(holding.id, { searchParams, key: 'itemCount', tenantId }); + const { data: locations } = useLocationsQuery({ tenantId }); - if (!locationsById) return null; + if (!locations) return null; + const locationsById = keyBy(locations, 'id'); const labelLocation = locationsById[holding.permanentLocationId]; const labelLocationName = labelLocation?.name ?? ''; @@ -116,6 +116,7 @@ HoldingAccordion.propTypes = { holdings: PropTypes.arrayOf(PropTypes.object), withMoveDropdown: PropTypes.bool, children: PropTypes.object, + tenantId: PropTypes.string, }; export default HoldingAccordion; diff --git a/src/Instance/HoldingsList/Holding/HoldingContainer.js b/src/Instance/HoldingsList/Holding/HoldingContainer.js index dc4116adb..93f77f883 100644 --- a/src/Instance/HoldingsList/Holding/HoldingContainer.js +++ b/src/Instance/HoldingsList/Holding/HoldingContainer.js @@ -35,6 +35,7 @@ const DraggableHolding = ({ holding, onViewHolding, onAddItem, + tenantId, ...rest }) => { const rowStyles = useMemo(() => ( @@ -66,6 +67,7 @@ const DraggableHolding = ({ holding={holding} onViewHolding={onViewHolding} onAddItem={onAddItem} + tenantId={tenantId} /> ) } @@ -84,6 +86,7 @@ DraggableHolding.propTypes = { holding: PropTypes.object, onViewHolding: PropTypes.func, onAddItem: PropTypes.func, + tenantId: PropTypes.string, }; const HoldingContainer = ({ @@ -95,6 +98,7 @@ const HoldingContainer = ({ isDraggable, holdingIndex, draggingHoldingsCount, + tenantId, ...rest }) => { const onViewHolding = useCallback(() => { @@ -125,6 +129,7 @@ const HoldingContainer = ({ holding={holding} onViewHolding={onViewHolding} onAddItem={onAddItem} + tenantId={tenantId} {...rest} /> )} @@ -135,6 +140,7 @@ const HoldingContainer = ({ holding={holding} onViewHolding={onViewHolding} onAddItem={onAddItem} + tenantId={tenantId} /> ); }; @@ -150,6 +156,7 @@ HoldingContainer.propTypes = { holdingIndex: PropTypes.number, isDraggable: PropTypes.bool, draggingHoldingsCount: PropTypes.number, + tenantId: PropTypes.string, }; export default withRouter(HoldingContainer); diff --git a/src/Instance/HoldingsList/HoldingsList.js b/src/Instance/HoldingsList/HoldingsList.js index 74e61593a..97c01fff5 100644 --- a/src/Instance/HoldingsList/HoldingsList.js +++ b/src/Instance/HoldingsList/HoldingsList.js @@ -6,6 +6,7 @@ import { HoldingContainer } from './Holding'; const HoldingsList = ({ instance, holdings, + tenantId, draggable, droppable, @@ -17,12 +18,14 @@ const HoldingsList = ({ draggable={draggable} droppable={droppable} holdings={holdings} + tenantId={tenantId} /> )); HoldingsList.propTypes = { instance: PropTypes.object.isRequired, holdings: PropTypes.arrayOf(PropTypes.object), + tenantId: PropTypes.string, draggable: PropTypes.bool, droppable: PropTypes.bool, diff --git a/src/Instance/HoldingsList/HoldingsListContainer.js b/src/Instance/HoldingsList/HoldingsListContainer.js index e268f2eca..be19ea8ee 100644 --- a/src/Instance/HoldingsList/HoldingsListContainer.js +++ b/src/Instance/HoldingsList/HoldingsListContainer.js @@ -9,8 +9,13 @@ import HoldingsList from './HoldingsList'; import { HoldingsListMovement } from '../InstanceMovement/HoldingMovementList'; import { useInstanceHoldingsQuery } from '../../providers'; -const HoldingsListContainer = ({ instance, isHoldingsMove, ...rest }) => { - const { holdingsRecords: holdings, isLoading } = useInstanceHoldingsQuery(instance.id); +const HoldingsListContainer = ({ + instance, + isHoldingsMove, + tenantId, + ...rest +}) => { + const { holdingsRecords: holdings, isLoading } = useInstanceHoldingsQuery(instance.id, { tenantId }); if (isLoading) return ; @@ -20,12 +25,14 @@ const HoldingsListContainer = ({ instance, isHoldingsMove, ...rest }) => { {...rest} holdings={holdings} instance={instance} + tenantId={tenantId} /> ) : ( ) ); @@ -34,6 +41,7 @@ const HoldingsListContainer = ({ instance, isHoldingsMove, ...rest }) => { HoldingsListContainer.propTypes = { instance: PropTypes.object.isRequired, isHoldingsMove: PropTypes.bool, + tenantId: PropTypes.string, }; export default HoldingsListContainer; diff --git a/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js b/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js new file mode 100644 index 000000000..a0976eede --- /dev/null +++ b/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js @@ -0,0 +1,52 @@ +import React, { useContext } from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; + +import { + IfInterface, + useStripes, +} from '@folio/stripes/core'; +import { + Accordion, + AccordionSet, +} from '@folio/stripes/components'; + +import { MemberTenantHoldings } from '../MemberTenantHoldings'; +import { DataContext } from '../../../contexts'; +import { useSearchForShadowInstanceTenants } from '../../../hooks'; + +const ConsortialHoldings = ({ instance }) => { + const stripes = useStripes(); + const { consortiaTenantsById } = useContext(DataContext); + + const { tenants } = useSearchForShadowInstanceTenants({ instanceId: instance?.id }); + + const memberTenants = tenants + .map(tenant => consortiaTenantsById[tenant.id]) + .filter(tenant => !tenant?.isCentral && (tenant?.id !== stripes.okapi.tenant)) + .sort((a, b) => a.name.localeCompare(b.name)); + + return ( + + } + closedByDefault + > + + {memberTenants.map(memberTenant => ( + + ))} + + + + ); +}; + +ConsortialHoldings.propTypes = { instance: PropTypes.object.isRequired }; + +export default ConsortialHoldings; diff --git a/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.test.js b/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.test.js new file mode 100644 index 000000000..b63980d39 --- /dev/null +++ b/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.test.js @@ -0,0 +1,51 @@ +import { screen } from '@folio/jest-config-stripes/testing-library/react'; + +import '../../../../test/jest/__mock__'; +import { + renderWithIntl, + translationsProperties, +} from '../../../../test/jest/helpers'; + +import { instance } from '../../../../test/fixtures'; + +import { DataContext } from '../../../contexts'; +import ConsortialHoldings from './ConsortialHoldings'; + +jest.mock('../MemberTenantHoldings', () => ({ + ...jest.requireActual('../MemberTenantHoldings'), + MemberTenantHoldings: ({ memberTenant }) => <>{memberTenant.name} accordion, +})); +jest.mock('../../../hooks', () => ({ + ...jest.requireActual('../../../hooks'), + useSearchForShadowInstanceTenants: () => ({ tenants: [{ id: 'college' }] }), +})); + +const providerValue = { + consortiaTenantsById: { + 'college': { id: 'college', name: 'College', isCentral: false }, + } +}; + +const renderConsortialHoldings = () => { + const component = ( + + + + ); + + return renderWithIntl(component, translationsProperties); +}; + +describe('ConsortialHoldings', () => { + it('should render Consortial holdings accordion', () => { + renderConsortialHoldings(); + + expect(screen.getByRole('button', { name: 'Consortial holdings' })).toBeInTheDocument(); + }); + + it('should render sub-accordion with tenants\' holdings info', () => { + renderConsortialHoldings(); + + expect(screen.getByText('College accordion')).toBeInTheDocument(); + }); +}); diff --git a/src/Instance/InstanceDetails/ConsortialHoldings/index.js b/src/Instance/InstanceDetails/ConsortialHoldings/index.js new file mode 100644 index 000000000..4a39280fd --- /dev/null +++ b/src/Instance/InstanceDetails/ConsortialHoldings/index.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as ConsortialHoldings } from './ConsortialHoldings'; diff --git a/src/Instance/InstanceDetails/InstanceDetails.js b/src/Instance/InstanceDetails/InstanceDetails.js index 173ca444a..65c4b687f 100644 --- a/src/Instance/InstanceDetails/InstanceDetails.js +++ b/src/Instance/InstanceDetails/InstanceDetails.js @@ -36,6 +36,7 @@ import HelperApp from '../../components/HelperApp'; import { getAccordionState } from './utils'; import { DataContext } from '../../contexts'; +import { ConsortialHoldings } from './ConsortialHoldings'; const accordions = { administrative: 'acc01', @@ -132,6 +133,10 @@ const InstanceDetails = forwardRef(({ + {instance?.shared && ( + + )} + { + const { + name, + id, + } = memberTenant; + const { holdingsRecords, isLoading } = useInstanceHoldingsQuery(instance?.id, { tenantId: id }); + + if (isEmpty(holdingsRecords)) return null; + + return ( + +
+ {isLoading + ? + : ( + + + + )} +
+ +
+ ); +}; + +MemberTenantHoldings.propTypes = { + instance: PropTypes.object.isRequired, + memberTenant: PropTypes.object.isRequired, +}; + +export default MemberTenantHoldings; diff --git a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.test.js b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.test.js new file mode 100644 index 000000000..76cf5c0e2 --- /dev/null +++ b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.test.js @@ -0,0 +1,60 @@ +import { BrowserRouter as Router } from 'react-router-dom'; + +import { screen } from '@folio/jest-config-stripes/testing-library/react'; + +import '../../../../test/jest/__mock__'; +import { + renderWithIntl, + translationsProperties, +} from '../../../../test/jest/helpers'; + +import { instance } from '../../../../test/fixtures'; + +import MemberTenantHoldings from './MemberTenantHoldings'; + +jest.mock('../../../providers', () => ({ + ...jest.requireActual('../../../providers'), + useInstanceHoldingsQuery: () => ({ holdingsRecords: [{ id: 'holdings-id' }] }), +})); +jest.mock('../../HoldingsList', () => ({ + ...jest.requireActual('../../HoldingsList'), + HoldingsList: () => <>Holdings, +})); + +const mockMemberTenant = { + id: 'college', + name: 'College', +}; + +const renderMemberTenantHoldings = () => { + const component = ( + + + + ); + + return renderWithIntl(component, translationsProperties); +}; + +describe('MemberTenantHoldings', () => { + it('should render member tenant accordion', () => { + renderMemberTenantHoldings(); + + expect(screen.getByText('College')).toBeInTheDocument(); + }); + + it('should render member tenant\'s holdings', () => { + renderMemberTenantHoldings(); + + expect(screen.getByText('Holdings')).toBeInTheDocument(); + }); + + it('should render Add holdings button', () => { + renderMemberTenantHoldings(); + + expect(screen.getByRole('button', { name: 'Add holdings' })).toBeInTheDocument(); + }); +}); diff --git a/src/Instance/InstanceDetails/MemberTenantHoldings/index.js b/src/Instance/InstanceDetails/MemberTenantHoldings/index.js new file mode 100644 index 000000000..dd83881bf --- /dev/null +++ b/src/Instance/InstanceDetails/MemberTenantHoldings/index.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as MemberTenantHoldings } from './MemberTenantHoldings'; diff --git a/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js b/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js index b1a8cf8c3..5c0788920 100644 --- a/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js +++ b/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js @@ -22,6 +22,7 @@ const HoldingsListMovement = ({ holdings, draggable, droppable, + tenantId, }) => { const { selectItemsForDrag, @@ -56,6 +57,7 @@ const HoldingsListMovement = ({ getDraggingItems={getDraggingItems} holdingIndex={index} draggingHoldingsCount={draggingHoldingsCount} + tenantId={tenantId} /> )) ) : ( @@ -77,6 +79,7 @@ HoldingsListMovement.propTypes = { draggable: PropTypes.bool, droppable: PropTypes.bool, + tenantId: PropTypes.string, }; HoldingsListMovement.defaultProps = { diff --git a/src/Instance/ItemsList/ItemsListContainer.js b/src/Instance/ItemsList/ItemsListContainer.js index e536d921b..ded0c52cf 100644 --- a/src/Instance/ItemsList/ItemsListContainer.js +++ b/src/Instance/ItemsList/ItemsListContainer.js @@ -15,6 +15,7 @@ import useHoldingItemsQuery from '../../hooks/useHoldingItemsQuery'; import { DEFAULT_ITEM_TABLE_SORTBY_FIELD } from '../../constants'; const ItemsListContainer = ({ + tenantId, holding, draggable, droppable, @@ -36,8 +37,8 @@ const ItemsListContainer = ({ offset, }; - const { isFetching, items } = useHoldingItemsQuery(holding.id, { searchParams }); - const { totalRecords } = useHoldingItemsQuery(holding.id, { searchParams: { limit: 0 }, key: 'itemCount' }); + const { isFetching, items } = useHoldingItemsQuery(holding.id, { searchParams, tenantId }); + const { totalRecords } = useHoldingItemsQuery(holding.id, { searchParams: { limit: 0 }, key: 'itemCount', tenantId }); useEffect(() => { if (!isEmpty(items)) { @@ -68,7 +69,8 @@ const ItemsListContainer = ({ ItemsListContainer.propTypes = { holding: PropTypes.object.isRequired, draggable: PropTypes.bool, - droppable: PropTypes.bool + droppable: PropTypes.bool, + tenantId: PropTypes.string, }; export default memo(ItemsListContainer); diff --git a/src/ViewInstance.js b/src/ViewInstance.js index 0c66ccbaa..e6ad9abdf 100644 --- a/src/ViewInstance.js +++ b/src/ViewInstance.js @@ -151,13 +151,6 @@ class ViewInstance extends React.Component { tenant: '!{tenantId}', throwErrors: false, }, - locations: { - type: 'okapi', - records: 'locations', - path: 'locations?limit=5000', - tenant: '!{tenantId}', - throwErrors: false, - }, configs: { type: 'okapi', records: 'configs', @@ -796,6 +789,7 @@ class ViewInstance extends React.Component { const { match: { params: { id, holdingsrecordid, itemid } }, stripes, + okapi, onCopy, onClose, paneWidth, @@ -892,6 +886,7 @@ class ViewInstance extends React.Component { @@ -999,7 +994,6 @@ ViewInstance.propTypes = { resources: PropTypes.shape({ allInstanceItems: PropTypes.object.isRequired, allInstanceHoldings: PropTypes.object.isRequired, - locations: PropTypes.object.isRequired, configs: PropTypes.object.isRequired, instanceRequests: PropTypes.shape({ other: PropTypes.shape({ diff --git a/src/hooks/index.js b/src/hooks/index.js index 43324a78c..8d61406b9 100644 --- a/src/hooks/index.js +++ b/src/hooks/index.js @@ -6,6 +6,8 @@ export { default as useHoldingMutation } from './useHoldingMutation'; export { default as useInstanceMutation } from './useInstanceMutation'; export { default as useHoldingsQueryByHrids } from './useHoldingsQueryByHrids'; export { default as useInventoryBrowse } from './useInventoryBrowse'; +export { default as useLocationsQuery } from './useLocationsQuery'; export { default as useLastSearchTerms } from './useLastSearchTerms'; export { default as useLogout } from './useLogout'; +export { default as useSearchForShadowInstanceTenants } from './useSearchForShadowInstanceTenants'; export { default as useUserTenantPermissions } from './useUserTenantPermissions'; diff --git a/src/hooks/useHoldingItemsQuery.js b/src/hooks/useHoldingItemsQuery.js index 6891e851b..ddd8353fe 100644 --- a/src/hooks/useHoldingItemsQuery.js +++ b/src/hooks/useHoldingItemsQuery.js @@ -6,19 +6,20 @@ import { import { useQuery } from 'react-query'; import { omit } from 'lodash'; -import { useOkapiKy, useNamespace } from '@folio/stripes/core'; +import { useNamespace } from '@folio/stripes/core'; import { DEFAULT_ITEM_TABLE_SORTBY_FIELD, LIMIT_MAX, } from '../constants'; +import { useTenantKy } from '../common'; const useHoldingItemsQuery = ( holdingsRecordId, - options = { searchParams: {}, key: 'items' }, + options = { searchParams: {}, key: 'items', tenantId: null }, ) => { const [sortBy, setSortBy] = useState(`${DEFAULT_ITEM_TABLE_SORTBY_FIELD}/sort.ascending`); - const ky = useOkapiKy().extend({ timeout: false }); + const ky = useTenantKy({ tenantId: options.tenantId }).extend({ timeout: false }); const [namespace] = useNamespace(); // sortMap contains not all item table's columns because sorting by some columns @@ -34,7 +35,7 @@ const useHoldingItemsQuery = ( }; useEffect(() => { - if (options.searchParams.sortBy) { + if (options.searchParams?.sortBy) { const sortQuery = options.searchParams.sortBy; const sortDirection = sortQuery.startsWith('-') ? 'descending' : 'ascending'; const sortOrder = sortQuery.replace(/^-/, ''); @@ -42,7 +43,7 @@ const useHoldingItemsQuery = ( setSortBy(newSortBy); } - }, [options.searchParams.sortBy]); + }, [options.searchParams?.sortBy]); const defaultSearchParams = { offset: 0, diff --git a/src/hooks/useLocationsQuery/index.js b/src/hooks/useLocationsQuery/index.js new file mode 100644 index 000000000..7a97c7364 --- /dev/null +++ b/src/hooks/useLocationsQuery/index.js @@ -0,0 +1 @@ +export { default } from './useLocationsQuery'; diff --git a/src/hooks/useLocationsQuery/useLocationsQuery.js b/src/hooks/useLocationsQuery/useLocationsQuery.js new file mode 100644 index 000000000..e49bf6d87 --- /dev/null +++ b/src/hooks/useLocationsQuery/useLocationsQuery.js @@ -0,0 +1,27 @@ +import { useQuery } from 'react-query'; + +import { useNamespace } from '@folio/stripes/core'; + +import { useTenantKy } from '../../common'; + +import { + CQL_FIND_ALL, + LIMIT_MAX, +} from '../../constants'; + +const useLocationsQuery = ({ tenantId } = {}) => { + const ky = useTenantKy({ tenantId }); + const [namespace] = useNamespace({ key: 'locations' }); + + const query = useQuery({ + queryKey: [namespace, tenantId], + queryFn: () => ky.get(`locations?limit=${LIMIT_MAX}&query=${CQL_FIND_ALL} sortby name`).json(), + }); + + return ({ + ...query, + data: query.data?.locations, + }); +}; + +export default useLocationsQuery; diff --git a/src/hooks/useLocationsQuery/useLocationsQuery.test.js b/src/hooks/useLocationsQuery/useLocationsQuery.test.js new file mode 100644 index 000000000..b0ff341dc --- /dev/null +++ b/src/hooks/useLocationsQuery/useLocationsQuery.test.js @@ -0,0 +1,48 @@ +import React from 'react'; +import { + QueryClient, + QueryClientProvider, +} from 'react-query'; +import { + renderHook, + act, +} from '@folio/jest-config-stripes/testing-library/react'; + +import '../../../test/jest/__mock__'; + +import { useTenantKy } from '../../common'; +import useLocationsQuery from './useLocationsQuery'; + +jest.mock('../../common', () => ({ + ...jest.requireActual('../../common'), + useTenantKy: jest.fn(), +})); + +const queryClient = new QueryClient(); +const wrapper = ({ children }) => ( + + {children} + +); + +describe('useLocationsQuery', () => { + beforeEach(() => { + useTenantKy.mockClear().mockReturnValue({ + get: () => ({ + json: () => Promise.resolve({ locations: [{ id: 'location-id' }] }), + }), + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should fetch locations', async () => { + const { result } = renderHook(() => useLocationsQuery(), { wrapper }); + + await act(() => !result.current.isLoading); + + expect(result.current.data).toEqual([{ id: 'location-id' }]); + }); +}); diff --git a/src/hooks/useSearchForShadowInstanceTenants/index.js b/src/hooks/useSearchForShadowInstanceTenants/index.js new file mode 100644 index 000000000..1fa4db060 --- /dev/null +++ b/src/hooks/useSearchForShadowInstanceTenants/index.js @@ -0,0 +1 @@ +export { default } from './useSearchForShadowInstanceTenants'; diff --git a/src/hooks/useSearchForShadowInstanceTenants/useSearchForShadowInstanceTenants.js b/src/hooks/useSearchForShadowInstanceTenants/useSearchForShadowInstanceTenants.js new file mode 100644 index 000000000..4d1f11f6a --- /dev/null +++ b/src/hooks/useSearchForShadowInstanceTenants/useSearchForShadowInstanceTenants.js @@ -0,0 +1,35 @@ +import { useQuery } from 'react-query'; + +import { + useNamespace, + useStripes, +} from '@folio/stripes/core'; + +import { useTenantKy } from '../../common'; + +const useSearchForShadowInstanceTenants = ({ instanceId } = {}) => { + const stripes = useStripes(); + const consortium = stripes.user?.user?.consortium; + + const ky = useTenantKy({ tenantId: consortium?.centralTenantId }); + const [namespace] = useNamespace({ key: 'search-instance-by-holdingsTenantId-facet' }); + + const { isLoading, data = {} } = useQuery({ + queryKey: [namespace, consortium, instanceId], + queryFn: () => ky.get('search/instances/facets', + { + searchParams: { + facet: 'holdings.tenantId', + query: `id=${instanceId}`, + }, + }).json(), + enabled: Boolean(consortium?.centralTenantId && instanceId), + }); + + return { + tenants: data?.facets?.['holdings.tenantId']?.values || [], + isLoading, + }; +}; + +export default useSearchForShadowInstanceTenants; diff --git a/src/hooks/useSearchForShadowInstanceTenants/useSearchForShadowInstanceTenants.test.js b/src/hooks/useSearchForShadowInstanceTenants/useSearchForShadowInstanceTenants.test.js new file mode 100644 index 000000000..c06469028 --- /dev/null +++ b/src/hooks/useSearchForShadowInstanceTenants/useSearchForShadowInstanceTenants.test.js @@ -0,0 +1,56 @@ +import React from 'react'; +import { + QueryClient, + QueryClientProvider, +} from 'react-query'; +import { + renderHook, + act, +} from '@folio/jest-config-stripes/testing-library/react'; + +import '../../../test/jest/__mock__'; + +import { useTenantKy } from '../../common'; +import useSearchForShadowInstanceTenants from './useSearchForShadowInstanceTenants'; + +jest.mock('../../common', () => ({ + ...jest.requireActual('../../common'), + useTenantKy: jest.fn(), +})); + +const queryClient = new QueryClient(); +const wrapper = ({ children }) => ( + + {children} + +); + +describe('useSearchForShadowInstanceTenants', () => { + beforeEach(() => { + useTenantKy.mockClear().mockReturnValue({ + get: () => ({ + json: () => Promise.resolve({ + facets: { + 'holdings.tenantId': { + values: [{ id: 'tenantId' }], + }, + }, + }), + }), + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should fetch tenants', async () => { + const { result } = renderHook(() => useSearchForShadowInstanceTenants( + { instanceId: 'instanceId' } + ), { wrapper }); + + await act(() => !result.current.isLoading); + + expect(result.current.tenants).toEqual([{ id: 'tenantId' }]); + }); +}); diff --git a/src/providers/HoldingsProvider.js b/src/providers/HoldingsProvider.js index 3d07a16fe..bf52a5059 100644 --- a/src/providers/HoldingsProvider.js +++ b/src/providers/HoldingsProvider.js @@ -6,8 +6,7 @@ import React, { import { useQuery } from 'react-query'; import { keyBy } from 'lodash'; -import { useOkapiKy } from '@folio/stripes/core'; - +import { useTenantKy } from '../common'; const API = 'holdings-storage/holdings'; const LIMIT = 1000; @@ -31,8 +30,8 @@ export const HoldingsProvider = ({ ...rest }) => { }; -export const useInstanceHoldingsQuery = instanceId => { - const ky = useOkapiKy(); +export const useInstanceHoldingsQuery = (instanceId, { tenantId } = {}) => { + const ky = useTenantKy({ tenantId }); const holdings = useHoldings(); @@ -41,7 +40,7 @@ export const useInstanceHoldingsQuery = instanceId => { query: `instanceId==${instanceId}`, }; - const queryKey = [API, searchParams]; + const queryKey = [API, searchParams, tenantId]; const queryFn = () => ky(API, { searchParams }).json(); const onSuccess = data => holdings?.update(data.holdingsRecords); diff --git a/translations/ui-inventory/en.json b/translations/ui-inventory/en.json index e3c9ae57f..e61d89b9c 100644 --- a/translations/ui-inventory/en.json +++ b/translations/ui-inventory/en.json @@ -443,6 +443,7 @@ "updateHoldingsRecord": "Update holdings record", "duplicateHoldings": "Duplicate", "editHoldings": "Edit", + "consortialHoldings": "Consortial holdings", "instanceData": "Administrative data", "instanceId": "Instance UUID", "authorityId": "Authority UUID", From dc2e0d0dd645353ac3115c6eacc47ed7e7d5106b Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko1 Date: Wed, 4 Oct 2023 00:00:20 +0300 Subject: [PATCH 07/29] UIIN-2452: Add unit tests & switching affiliation when view holdings and add item --- .../HoldingsList/Holding/HoldingAccordion.js | 1 + .../Holding/HoldingAccordion.test.js | 1 + .../Holding/HoldingButtonsGroup.js | 59 +++++++++++-------- .../Holding/HoldingButtonsGroup.test.js | 26 +++++++- .../HoldingsList/Holding/HoldingContainer.js | 13 +--- .../HoldingsListContainer.test.js | 21 ++++++- .../InstanceDetails/InstanceDetails.test.js | 11 ++++ .../InstanceNewHolding/InstanceNewHolding.js | 4 +- .../ItemsList/tests/ItemBarcode.test.js | 31 +++++++++- src/hooks/useHoldingItemsQuery.js | 2 +- src/utils.js | 11 ++++ 11 files changed, 133 insertions(+), 47 deletions(-) diff --git a/src/Instance/HoldingsList/Holding/HoldingAccordion.js b/src/Instance/HoldingsList/Holding/HoldingAccordion.js index 6a38d2701..f31fd8cf5 100644 --- a/src/Instance/HoldingsList/Holding/HoldingAccordion.js +++ b/src/Instance/HoldingsList/Holding/HoldingAccordion.js @@ -59,6 +59,7 @@ const HoldingAccordion = ({ onAddItem={onAddItem} withMoveDropdown={withMoveDropdown} isOpen={open} + tenantId={tenantId} isViewHoldingsDisabled={isViewHoldingsDisabled} isAddItemDisabled={isAddItemDisabled} />; diff --git a/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js b/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js index df8f9380e..71e67cb23 100644 --- a/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js +++ b/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js @@ -35,6 +35,7 @@ const HoldingAccordionSetup = () => ( onViewHolding={noop} onAddItem={noop} withMoveDropdown={false} + tenantId="testTenantId" > <> diff --git a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js index 861910a66..241ea8a47 100644 --- a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js +++ b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js @@ -2,13 +2,18 @@ import React, { memo } from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; -import { IfPermission } from '@folio/stripes/core'; +import { + IfPermission, + useStripes, +} from '@folio/stripes/core'; import { Button, Badge, Icon, } from '@folio/stripes/components'; +import { updateAffiliation } from '../../../utils'; + import { MoveToDropdown } from './MoveToDropdown'; const HoldingButtonsGroup = ({ @@ -20,11 +25,15 @@ const HoldingButtonsGroup = ({ onAddItem, itemCount, isOpen, + tenantId, isViewHoldingsDisabled, isAddItemDisabled, -}) => ( - <> - { +}) => { + const { okapi } = useStripes(); + + return ( + <> + { withMoveDropdown && ( ) } - - - - - {!isOpen && {itemCount ?? }} - -); + + + + + {!isOpen && {itemCount ?? }} + + ); +}; HoldingButtonsGroup.propTypes = { holding: PropTypes.object.isRequired, @@ -68,6 +78,7 @@ HoldingButtonsGroup.propTypes = { withMoveDropdown: PropTypes.bool, isViewHoldingsDisabled: PropTypes.bool, isAddItemDisabled: PropTypes.bool, + tenantId: PropTypes.string, }; diff --git a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js index e095bdd3c..6c9d0abc3 100644 --- a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js +++ b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js @@ -13,7 +13,7 @@ const mockItemCount = 3; const mockOnAddItem = jest.fn(); const mockOnViewHolding = jest.fn(); -const HoldingButtonsGroupSetup = () => ( +const HoldingButtonsGroupSetup = props => ( ( onAddItem={mockOnAddItem} itemCount={mockItemCount} isOpen={false} + isViewHoldingsDisabled={false} + isAddItemDisabled={false} + {...props} > {() => null} ); -const renderHoldingButtonsGroup = () => renderWithIntl( - , +const renderHoldingButtonsGroup = props => renderWithIntl( + , translations ); @@ -53,6 +56,22 @@ describe('HoldingButtonsGroup', () => { expect(getByRole('button', { name: 'Add item' })).toBeDefined(); }); + describe('when user has no permissions to view holdings', () => { + it('should render "View Holdings" button as disabled', () => { + const { getByRole } = renderHoldingButtonsGroup({ isViewHoldingsDisabled: true }); + + expect(getByRole('button', { name: 'View holdings' })).toBeDisabled(); + }); + }); + + describe('when user has no permissions to create items', () => { + it('should render "Add item" button as disabled', () => { + const { getByRole } = renderHoldingButtonsGroup({ isAddItemDisabled: true }); + + expect(getByRole('button', { name: 'Add item' })).toBeDisabled(); + }); + }); + describe('when user click on View holdings button', () => { it('should calls callback', () => { const { getByRole } = renderHoldingButtonsGroup(); @@ -62,6 +81,7 @@ describe('HoldingButtonsGroup', () => { expect(mockOnViewHolding.mock.calls.length).toBe(1); }); }); + describe('when user click on Add item button', () => { it('should calls callback', () => { const { getByRole } = renderHoldingButtonsGroup(); diff --git a/src/Instance/HoldingsList/Holding/HoldingContainer.js b/src/Instance/HoldingsList/Holding/HoldingContainer.js index d13634b62..2f7c673f9 100644 --- a/src/Instance/HoldingsList/Holding/HoldingContainer.js +++ b/src/Instance/HoldingsList/Holding/HoldingContainer.js @@ -96,7 +96,6 @@ DraggableHolding.propTypes = { const HoldingContainer = ({ location, - history, isViewHoldingsDisabled, isAddItemDisabled, isBarcodeAsHotlink, @@ -109,18 +108,12 @@ const HoldingContainer = ({ ...rest }) => { const onViewHolding = useCallback(() => { - history.push({ - pathname: `/inventory/view/${instance.id}/${holding.id}`, - search: location.search, - }); + window.location.href = `/inventory/view/${instance.id}/${holding.id}${location.search}`; }, [location.search, instance.id, holding.id]); const onAddItem = useCallback(() => { - history.push({ - pathname: `/inventory/create/${instance.id}/${holding.id}/item`, - search: location.search, - }); - }, [instance.id, holding.id]); + window.location.href = `/inventory/create/${instance.id}/${holding.id}/item${location.search}`; + }, [location.search, instance.id, holding.id]); return isDraggable ? ( jest.fn().mockReturnValue('Holdings List')); jest.mock('../InstanceMovement/HoldingMovementList/HoldingsListMovement', () => jest.fn().mockReturnValue('HoldingMovementList')); jest.mock('./Holding/HoldingContainer', () => jest.fn().mockReturnValue('HoldingContainer')); +const userTenantPermissions = [{ + tenantId: 'testTenantId', + permissionNames: ['test permission'], +}]; + describe('HoldingsListContainer', () => { const instance = { id: '123', @@ -39,7 +44,10 @@ describe('HoldingsListContainer', () => { render( - + ); expect(screen.getByText('Loading')).toBeInTheDocument(); @@ -53,7 +61,10 @@ describe('HoldingsListContainer', () => { render( - + ); @@ -70,7 +81,11 @@ describe('HoldingsListContainer', () => { const isHoldingsMove = true; render( - + ); diff --git a/src/Instance/InstanceDetails/InstanceDetails.test.js b/src/Instance/InstanceDetails/InstanceDetails.test.js index 2898391a2..86ba79d7e 100644 --- a/src/Instance/InstanceDetails/InstanceDetails.test.js +++ b/src/Instance/InstanceDetails/InstanceDetails.test.js @@ -32,6 +32,11 @@ const instance = { discoverySuppress: false, }; +const userTenantPermissions = [{ + tenantId: 'testTenantId', + permissionNames: ['test permission'], +}]; + const mockReferenceData = { titleTypes:[ { id: '1', name: 'Type 1' }, @@ -59,6 +64,7 @@ describe('InstanceDetails', () => { onClose={onClose} tagsEnabled={tagsEnabled} actionMenu={actionMenu} + userTenantPermissions={userTenantPermissions} />, @@ -112,6 +118,7 @@ describe('InstanceDetails', () => { onClose={onClose} tagsEnabled={tagsEnabled} actionMenu={actionMenu} + userTenantPermissions={userTenantPermissions} />, @@ -137,6 +144,7 @@ describe('InstanceDetails', () => { onClose={onClose} tagsEnabled={tagsEnabled} actionMenu={actionMenu} + userTenantPermissions={userTenantPermissions} />, @@ -162,6 +170,7 @@ describe('InstanceDetails', () => { onClose={onClose} tagsEnabled={tagsEnabled} actionMenu={actionMenu} + userTenantPermissions={userTenantPermissions} />, @@ -182,6 +191,7 @@ describe('InstanceDetails', () => { onClose={onClose} tagsEnabled={tagsEnabled} actionMenu={actionMenu} + userTenantPermissions={userTenantPermissions} />, @@ -221,6 +231,7 @@ describe('InstanceDetails', () => { onClose={onClose} tagsEnabled={tagsEnabled} actionMenu={actionMenu} + userTenantPermissions={userTenantPermissions} />, diff --git a/src/Instance/InstanceDetails/InstanceNewHolding/InstanceNewHolding.js b/src/Instance/InstanceDetails/InstanceNewHolding/InstanceNewHolding.js index 8f21882fe..0b1a86add 100644 --- a/src/Instance/InstanceDetails/InstanceNewHolding/InstanceNewHolding.js +++ b/src/Instance/InstanceDetails/InstanceNewHolding/InstanceNewHolding.js @@ -1,7 +1,5 @@ import React from 'react'; -import { - useIntl, -} from 'react-intl'; +import { useIntl } from 'react-intl'; import PropTypes from 'prop-types'; import { withRouter } from 'react-router'; diff --git a/src/Instance/ItemsList/tests/ItemBarcode.test.js b/src/Instance/ItemsList/tests/ItemBarcode.test.js index 92bc7a47f..170b843d7 100644 --- a/src/Instance/ItemsList/tests/ItemBarcode.test.js +++ b/src/Instance/ItemsList/tests/ItemBarcode.test.js @@ -32,7 +32,8 @@ const itemProp = { const itemBarcodeProps = { item: itemProp, holdingId: 'testId1', - instanceId: 'testId2' + instanceId: 'testId2', + isBarcodeAsHotlink: true, }; const searchItem = qIndex => `?qindex=${qIndex}&query=${itemProp.barcode}`; @@ -41,11 +42,17 @@ const setupItemBarcode = ({ item, holdingId, instanceId, - history + history, + isBarcodeAsHotlink, }) => { const component = ( - + ); @@ -83,4 +90,22 @@ describe('', () => { expect(getByText(itemProp.barcode)).not.toHaveAttribute('data-test-highlighter-mark'); }); + + it('should render barcode as a hotlink', () => { + const history = getHistory(searchItem(QUERY_INDEXES.BARCODE)); + const { container } = setupItemBarcode({ ...itemBarcodeProps, history }); + + expect(container.querySelector('[data-test-item-link="true"]')).toBeInTheDocument(); + }); + + it('should render barcode as a plain text', () => { + const history = getHistory(searchItem(QUERY_INDEXES.BARCODE)); + const { container } = setupItemBarcode({ + ...itemBarcodeProps, + isBarcodeAsHotlink: false, + history, + }); + + expect(container.querySelector('[data-test-item-link="true"]')).not.toBeInTheDocument(); + }); }); diff --git a/src/hooks/useHoldingItemsQuery.js b/src/hooks/useHoldingItemsQuery.js index 40740bf1b..9192e252b 100644 --- a/src/hooks/useHoldingItemsQuery.js +++ b/src/hooks/useHoldingItemsQuery.js @@ -6,7 +6,7 @@ import { import { useQuery } from 'react-query'; import { omit } from 'lodash'; -import { useOkapiKy, useNamespace } from '@folio/stripes/core'; +import { useNamespace } from '@folio/stripes/core'; import { DEFAULT_ITEM_TABLE_SORTBY_FIELD, diff --git a/src/utils.js b/src/utils.js index 0fd853e6f..b934eedb3 100644 --- a/src/utils.js +++ b/src/utils.js @@ -21,6 +21,7 @@ import { } from 'lodash'; import moment from 'moment'; +import { updateTenant } from '@folio/stripes/core'; import { FormattedUTCDate } from '@folio/stripes/components'; import { @@ -824,3 +825,13 @@ export const hasMemberTenantPermission = (permissions, permissionName, tenantId) return tenantPermissions?.includes(permissionName); }; + +export const updateAffiliation = async (okapi, tenantId, move) => { + if (okapi.tenant !== tenantId) { + await updateTenant(okapi, tenantId); + + move(); + } else { + move(); + } +}; From 108f852516db8703d1d74238973df6cc0ecb3f70 Mon Sep 17 00:00:00 2001 From: Mariia_Aloshyna Date: Wed, 4 Oct 2023 11:50:14 +0300 Subject: [PATCH 08/29] UIIN-2410: Fix tests --- .../Holding/HoldingAccordion.test.js | 47 ++++---- .../Holding/HoldingContainer.test.js | 109 +++++++++--------- .../MemberTenantHoldings.test.js | 4 + src/ViewInstance.test.js | 4 + 4 files changed, 88 insertions(+), 76 deletions(-) diff --git a/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js b/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js index df8f9380e..45df04f4c 100644 --- a/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js +++ b/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js @@ -6,8 +6,6 @@ import { screen, waitFor, fireEvent } from '@folio/jest-config-stripes/testing-l import '../../../../test/jest/__mock__'; -import DataContext from '../../../contexts/DataContext'; - import renderWithIntl from '../../../../test/jest/helpers/renderWithIntl'; import translations from '../../../../test/jest/helpers/translationsProperties'; import { items as itemsFixture } from '../../../../test/fixtures/items'; @@ -15,30 +13,33 @@ import HoldingAccordion from './HoldingAccordion'; import useHoldingItemsQuery from '../../../hooks/useHoldingItemsQuery'; jest.mock('../../../hooks/useHoldingItemsQuery', () => jest.fn()); +jest.mock('../../../hooks', () => ({ + ...jest.requireActual('../../../hooks'), + useLocationsQuery: () => ({ + data: [ + { + id: 'inactiveLocation', + name: 'Location 1', + isActive: false, + }, + ], + }) +})); const HoldingAccordionSetup = () => ( - - - <> - - + + <> + ); diff --git a/src/Instance/HoldingsList/Holding/HoldingContainer.test.js b/src/Instance/HoldingsList/Holding/HoldingContainer.test.js index 217fc2e34..1cb2c6129 100644 --- a/src/Instance/HoldingsList/Holding/HoldingContainer.test.js +++ b/src/Instance/HoldingsList/Holding/HoldingContainer.test.js @@ -1,75 +1,78 @@ import React from 'react'; -import { render, screen } from '@folio/jest-config-stripes/testing-library/react'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import '../../../../test/jest/__mock__'; -import { IntlProvider } from 'react-intl'; import { MemoryRouter } from 'react-router-dom'; import HoldingContainer from './HoldingContainer'; +import { + renderWithIntl, + translationsProperties, +} from '../../../../test/jest/helpers'; +import { DataContext } from '../../../contexts'; +import DnDContext from '../../DnDContext'; -jest.mock('react', () => ({ - ...jest.requireActual('react'), - useContext: jest.fn().mockReturnValue({ locationsById: {}, selectedItemsMap: {} }) -})); -jest.mock('react-intl', () => { - const intl = { - formatMessage: ({ id }) => id, - }; - - return { - ...jest.requireActual('react-intl'), - FormattedMessage: jest.fn(({ id, children }) => { - if (children) { - return children([id]); - } - - return id; - }), - useIntl: () => intl, - injectIntl: (Component) => (props) => , - }; -}); jest.mock('../../../hooks/useHoldingItemsQuery', () => jest.fn().mockReturnValue({ totalRecords: 10, isFetching: false })); +jest.mock('../../../hooks', () => ({ + ...jest.requireActual('../../../hooks'), + useLocationsQuery: () => ({ + data: [ + { + id: 'inactiveLocation', + name: 'Location 1', + isActive: false, + }, + ], + }) +})); jest.mock('react-dom', () => ({ ...jest.requireActual('react-dom'), createPortal: jest.fn().mockReturnValue('HoldingAccordion'), })); -const wrapper = ({ children }) => ( - - {children} - -); +const dataContextValue = { + locationsById: { inactiveLocation: { id: 'inactiveLocation', name: 'Location 1', isActive: false } }, +}; +const dndContextValue = { + instances: [{ id: 'instance-id' }], + selectedItemsMap: {}, + allHoldings: [], + onSelect: jest.fn(), +}; -const renderHoldingContainer = (props = {}) => render( - - - , - { wrapper }, +const renderHoldingContainer = (props = {}) => renderWithIntl( + + + + + + + , + translationsProperties, ); describe('HoldingContainer', () => { it('should render HoldingContainer component', () => { renderHoldingContainer({ snapshot: { isDragging: false }, draggingHoldingsCount: 1, isDraggable: false }); - expect(screen.getByText('ui-inventory.holdingsHeader')).toBeInTheDocument(); - expect(screen.getByText('ui-inventory.viewHoldings')).toBeInTheDocument(); - expect(screen.getByText('ui-inventory.addItem')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Holdings: Inactive >' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'View holdings' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Add item' })).toBeInTheDocument(); expect(screen.getByText('10')).toBeInTheDocument(); }); it('should render selectHolding, moveButton toBeInTheDocument', () => { renderHoldingContainer({ snapshot: { isDragging: false }, draggingHoldingsCount: 1, isDraggable: true }); - expect(screen.getByText('ui-inventory.moveItems.selectHolding')).toBeInTheDocument(); - expect(screen.getByText('ui-inventory.moveItems.moveButton')).toBeInTheDocument(); + expect(screen.getByText('Select holdings')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Move to' })).toBeInTheDocument(); }); it('should render HoldingAccordion component', () => { renderHoldingContainer({ snapshot: { isDragging: true }, isDraggable: true }); @@ -79,8 +82,8 @@ describe('HoldingContainer', () => { const { container } = renderHoldingContainer({ snapshot: { isDragging: true }, isDraggable: false }); userEvent.click(container.querySelector('#clickable-view-holdings-123')); userEvent.click(container.querySelector('#clickable-new-item-123')); - userEvent.click(screen.getByText('ui-inventory.addItem')); - const primaryButton = screen.getByRole('button', { name: /ui-inventory.addItem/i }); + userEvent.click(screen.getByRole('button', { name: 'Add item' })); + const primaryButton = screen.getByRole('button', { name: 'Add item' }); expect(primaryButton).toHaveClass('button primary paneHeaderNewButton'); expect(primaryButton).toBeEnabled(); }); diff --git a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.test.js b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.test.js index 76cf5c0e2..09c060e8b 100644 --- a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.test.js +++ b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.test.js @@ -20,6 +20,10 @@ jest.mock('../../HoldingsList', () => ({ ...jest.requireActual('../../HoldingsList'), HoldingsList: () => <>Holdings, })); +jest.mock('../../MoveItemsContext', () => ({ + ...jest.requireActual('../../MoveItemsContext'), + MoveItemsContext: ({ children }) => children, +})); const mockMemberTenant = { id: 'college', diff --git a/src/ViewInstance.test.js b/src/ViewInstance.test.js index 251fa0b80..4f036fbce 100644 --- a/src/ViewInstance.test.js +++ b/src/ViewInstance.test.js @@ -64,6 +64,10 @@ jest.mock('./RemoteStorageService/Check', () => ({ useByLocation: jest.fn(() => false), useByHoldings: jest.fn(() => false), })); +jest.mock('./common/hooks', () => ({ + ...jest.requireActual('./common/hooks'), + useTenantKy: jest.fn(), +})); const spyOncollapseAllSections = jest.spyOn(require('@folio/stripes/components'), 'collapseAllSections'); const spyOnexpandAllSections = jest.spyOn(require('@folio/stripes/components'), 'expandAllSections'); From 9e1b138135b74c5d984540fddd1f31e2d9542088 Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko1 Date: Thu, 5 Oct 2023 13:35:39 +0300 Subject: [PATCH 09/29] UIIN-2452: Add switching affiliation when click on item barcode & Add holdings button --- .../HoldingBoundWith/useBoundWithHoldings.js | 5 ++-- .../InstanceDetails/InstanceDetails.js | 1 + .../InstanceNewHolding/InstanceNewHolding.js | 18 ++++++++++--- .../MemberTenantHoldings.js | 1 + src/Instance/ItemsList/ItemBarcode.js | 25 ++++++++++++------- src/Instance/ItemsList/ItemsList.js | 20 ++++++++++++--- src/Instance/ItemsList/ItemsListContainer.js | 1 + src/View.css | 20 ++++++++++++++- src/hooks/useChunkedCQLFetch.js | 7 +++--- src/views/ItemView.js | 12 ++++----- 10 files changed, 82 insertions(+), 28 deletions(-) diff --git a/src/Holding/ViewHolding/HoldingBoundWith/useBoundWithHoldings.js b/src/Holding/ViewHolding/HoldingBoundWith/useBoundWithHoldings.js index 448627f80..75220c3ac 100644 --- a/src/Holding/ViewHolding/HoldingBoundWith/useBoundWithHoldings.js +++ b/src/Holding/ViewHolding/HoldingBoundWith/useBoundWithHoldings.js @@ -1,6 +1,6 @@ import useChunkedCQLFetch from '../../../hooks/useChunkedCQLFetch'; -const useBoundWithHoldings = (boundWithItems) => { +const useBoundWithHoldings = (boundWithItems, { tenantId }) => { let holdingsRecordIds = boundWithItems?.map(x => x.holdingsRecordId); // De-dup the list of holdingsRecordIds for efficiency @@ -13,7 +13,8 @@ const useBoundWithHoldings = (boundWithItems) => { holdingQueries.reduce((acc, curr) => { return [...acc, ...(curr?.data?.holdingsRecords ?? [])]; }, []) - ) + ), + tenantId, }); return { diff --git a/src/Instance/InstanceDetails/InstanceDetails.js b/src/Instance/InstanceDetails/InstanceDetails.js index b4b490b04..da66efc5e 100644 --- a/src/Instance/InstanceDetails/InstanceDetails.js +++ b/src/Instance/InstanceDetails/InstanceDetails.js @@ -140,6 +140,7 @@ const InstanceDetails = forwardRef(({ {instance?.shared && ( diff --git a/src/Instance/InstanceDetails/InstanceNewHolding/InstanceNewHolding.js b/src/Instance/InstanceDetails/InstanceNewHolding/InstanceNewHolding.js index 0b1a86add..b9e4ebffc 100644 --- a/src/Instance/InstanceDetails/InstanceNewHolding/InstanceNewHolding.js +++ b/src/Instance/InstanceDetails/InstanceNewHolding/InstanceNewHolding.js @@ -1,34 +1,45 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { useIntl } from 'react-intl'; import PropTypes from 'prop-types'; import { withRouter } from 'react-router'; -import { IfPermission } from '@folio/stripes/core'; +import { + IfPermission, + useStripes, +} from '@folio/stripes/core'; import { Row, Col, Button, } from '@folio/stripes/components'; +import { updateAffiliation } from '../../../utils'; + const InstanceNewHolding = ({ location, instance, + tenantId, disabled, }) => { const intl = useIntl(); + const stripes = useStripes(); const label = intl.formatMessage({ id: 'ui-inventory.addHoldings' }); + const onNewHolding = useCallback(() => { + window.location.href = `/inventory/create/${instance?.id}/holding${location.search}`; + }, [location.search, instance.id]); + return (
diff --git a/src/Instance/ItemsList/ItemBarcode.js b/src/Instance/ItemsList/ItemBarcode.js index 285a26782..cbab0ea38 100644 --- a/src/Instance/ItemsList/ItemBarcode.js +++ b/src/Instance/ItemsList/ItemBarcode.js @@ -4,25 +4,23 @@ import React, { } from 'react'; import PropTypes from 'prop-types'; import { withRouter } from 'react-router'; -import { - Link, -} from 'react-router-dom'; -import { - FormattedMessage, -} from 'react-intl'; +import { FormattedMessage } from 'react-intl'; import { CopyToClipboard } from 'react-copy-to-clipboard'; import queryString from 'query-string'; import { CalloutContext, AppIcon, + useStripes, } from '@folio/stripes/core'; import { + Button, Highlighter, IconButton, } from '@folio/stripes/components'; import css from '../../View.css'; import { QUERY_INDEXES } from '../../constants'; +import { updateAffiliation } from '../../utils'; const ItemBarcode = ({ location, @@ -30,11 +28,17 @@ const ItemBarcode = ({ holdingId, instanceId, isBarcodeAsHotlink, + tenantId, }) => { + const stripes = useStripes(); const { search } = location; const queryBarcode = queryString.parse(search)?.query; const isQueryByBarcode = queryString.parse(search)?.qindex === QUERY_INDEXES.BARCODE; + const onViewItem = useCallback(() => { + window.location.href = `/inventory/view/${instanceId}/${holdingId}/${item.id}${search}`; + }, [instanceId, holdingId, item.id, search]); + const callout = useContext(CalloutContext); const onCopyToClipbaord = useCallback(() => { callout.sendCallout({ @@ -60,12 +64,14 @@ const ItemBarcode = ({ return ( <> {isBarcodeAsHotlink ? ( - updateAffiliation(stripes.okapi, tenantId, onViewItem)} data-test-item-link > {itemBarcode} - + ) : itemBarcode } {item.barcode && @@ -95,6 +101,7 @@ ItemBarcode.propTypes = { item: PropTypes.object.isRequired, holdingId: PropTypes.string.isRequired, instanceId: PropTypes.string.isRequired, + tenantId: PropTypes.string, isBarcodeAsHotlink: PropTypes.bool, }; diff --git a/src/Instance/ItemsList/ItemsList.js b/src/Instance/ItemsList/ItemsList.js index 67ee6ed7f..d8e437775 100644 --- a/src/Instance/ItemsList/ItemsList.js +++ b/src/Instance/ItemsList/ItemsList.js @@ -34,14 +34,15 @@ import { DataContext } from '../../contexts'; const getTableAria = (intl) => intl.formatMessage({ id: 'ui-inventory.items' }); -const getFormatter = ( +const getFormatter = ({ intl, locationsById, holdingsMapById, selectItemsForDrag, ifItemsSelected, isBarcodeAsHotlink, -) => ({ + tenantId, +}) => ({ 'dnd': () => ( {item.discoverySuppress && @@ -163,8 +165,9 @@ const ItemsList = ({ getDraggingItems, isFetching, isBarcodeAsHotlink, + tenantId, }) => { - const { boundWithHoldings: holdings, isLoading } = useBoundWithHoldings(items); + const { boundWithHoldings: holdings, isLoading } = useBoundWithHoldings(items, { tenantId }); const holdingsMapById = keyBy(holdings, 'id'); const intl = useIntl(); const [itemsSorting, setItemsSorting] = useState({ @@ -183,7 +186,15 @@ const ItemsList = ({ [holding.id, records, isItemsDragSelected, selectItemsForDrag], ); const formatter = useMemo( - () => getFormatter(intl, locationsById, holdingsMapById, selectItemsForDrag, isItemsDragSelected, isBarcodeAsHotlink), + () => getFormatter({ + intl, + locationsById, + holdingsMapById, + selectItemsForDrag, + isItemsDragSelected, + isBarcodeAsHotlink, + tenantId, + }), [holdingsMapById, selectItemsForDrag, isItemsDragSelected], ); const rowProps = useMemo(() => ({ @@ -262,6 +273,7 @@ ItemsList.propTypes = { isItemsDragSelected: PropTypes.func.isRequired, getDraggingItems: PropTypes.func.isRequired, isFetching: PropTypes.bool, + tenantId: PropTypes.string, isBarcodeAsHotlink: PropTypes.bool, }; diff --git a/src/Instance/ItemsList/ItemsListContainer.js b/src/Instance/ItemsList/ItemsListContainer.js index ac601b800..289fc9a3d 100644 --- a/src/Instance/ItemsList/ItemsListContainer.js +++ b/src/Instance/ItemsList/ItemsListContainer.js @@ -64,6 +64,7 @@ const ItemsListContainer = ({ droppable={droppable} isFetching={isFetching} isBarcodeAsHotlink={isBarcodeAsHotlink} + tenantId={tenantId} /> ); }; diff --git a/src/View.css b/src/View.css index 78d48392e..3ce243665 100644 --- a/src/View.css +++ b/src/View.css @@ -13,4 +13,22 @@ .boundWithIcon { background-color: transparent !important; margin-left: var(--gutter-static-one-third); -} \ No newline at end of file +} + +/* Reset default behaviour for item hotlink */ +.linkWithoutBorder { + &:hover, + &:focus { + outline-style: none !important; + } +} + +.linkWithoutBorder:hover::before { + background: transparent; +} + +.linkWithoutBorder:focus::before { + border: none; + background: transparent; + box-shadow: none; +} diff --git a/src/hooks/useChunkedCQLFetch.js b/src/hooks/useChunkedCQLFetch.js index 34ede67ce..56406f83c 100644 --- a/src/hooks/useChunkedCQLFetch.js +++ b/src/hooks/useChunkedCQLFetch.js @@ -3,16 +3,17 @@ import { useQueries } from 'react-query'; import { chunk } from 'lodash'; -import { useOkapiKy } from '@folio/stripes/core'; +import { useTenantKy } from '../common'; // When fetching from a potentially large list of items, // make sure to chunk the request to avoid hitting limits. const useChunkedCQLFetch = ({ endpoint, // endpoint to hit to fetch items ids, // List of ids to fetch - reduceFunction // Function to reduce fetched objects at the end into single array + reduceFunction, // Function to reduce fetched objects at the end into single array + tenantId, }) => { - const ky = useOkapiKy(); + const ky = useTenantKy({ tenantId }); const CONCURRENT_REQUESTS = 5; // Number of requests to make concurrently const STEP_SIZE = 60; // Number of ids to request for per concurrent request diff --git a/src/views/ItemView.js b/src/views/ItemView.js index 2c844471d..a613e1c90 100644 --- a/src/views/ItemView.js +++ b/src/views/ItemView.js @@ -659,7 +659,7 @@ class ItemView extends React.Component { }, effectiveLocation: { name: get(item, ['effectiveLocation', 'name'], '-'), - isActive: locationsById[item.effectiveLocation.id].isActive, + isActive: locationsById[item.effectiveLocation.id]?.isActive, }, }; @@ -753,7 +753,7 @@ class ItemView extends React.Component { } value={checkIfElementIsEmpty(itemLocation.effectiveLocation.name)} - subValue={!itemLocation.effectiveLocation.isActive && + subValue={!itemLocation.effectiveLocation?.isActive && } data-testid="item-effective-location" @@ -1335,7 +1335,7 @@ class ItemView extends React.Component { } value={checkIfElementIsEmpty(holdingLocation.permanentLocation.name)} - subValue={!holdingLocation.permanentLocation.isActive && + subValue={!holdingLocation.permanentLocation?.isActive && } data-testid="holding-permanent-location" @@ -1345,7 +1345,7 @@ class ItemView extends React.Component { } value={checkIfElementIsEmpty(holdingLocation.temporaryLocation.name)} - subValue={holdingLocation.temporaryLocation.isActive === false && + subValue={holdingLocation.temporaryLocation?.isActive === false && } data-testid="holding-temporary-location" @@ -1371,7 +1371,7 @@ class ItemView extends React.Component { } value={checkIfElementIsEmpty(itemLocation.permanentLocation.name)} - subValue={itemLocation.permanentLocation.isActive === false && + subValue={itemLocation.permanentLocation?.isActive === false && } data-testid="item-permanent-location" @@ -1381,7 +1381,7 @@ class ItemView extends React.Component { } value={checkIfElementIsEmpty(itemLocation.temporaryLocation.name)} - subValue={itemLocation.temporaryLocation.isActive === false && + subValue={itemLocation.temporaryLocation?.isActive === false && } data-testid="item-temporary-location" From 5726f062fd07fb7857117d6c289719d6dccac513 Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko1 Date: Fri, 6 Oct 2023 15:23:18 +0300 Subject: [PATCH 10/29] UIIN-2452: Fix tests --- .../Holding/HoldingButtonsGroup.test.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js index 6c9d0abc3..692602b22 100644 --- a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js +++ b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js @@ -7,8 +7,15 @@ import '../../../../test/jest/__mock__'; import renderWithIntl from '../../../../test/jest/helpers/renderWithIntl'; import translations from '../../../../test/jest/helpers/translationsProperties'; +import { updateAffiliation } from '../../../utils'; + import HoldingButtonsGroup from './HoldingButtonsGroup'; +jest.mock('../../../utils', () => ({ + ...jest.requireActual('../../../utils'), + updateAffiliation: jest.fn(), +})); + const mockItemCount = 3; const mockOnAddItem = jest.fn(); const mockOnViewHolding = jest.fn(); @@ -73,22 +80,22 @@ describe('HoldingButtonsGroup', () => { }); describe('when user click on View holdings button', () => { - it('should calls callback', () => { + it('should call function to update user\'s affiliation', () => { const { getByRole } = renderHoldingButtonsGroup(); fireEvent.click(getByRole('button', { name: 'View holdings' })); - expect(mockOnViewHolding.mock.calls.length).toBe(1); + expect(updateAffiliation.mock.calls.length).toBe(1); }); }); describe('when user click on Add item button', () => { - it('should calls callback', () => { + it('should call function to update user\'s affiliation', () => { const { getByRole } = renderHoldingButtonsGroup(); fireEvent.click(getByRole('button', { name: 'Add item' })); - expect(mockOnAddItem.mock.calls.length).toBe(1); + expect(updateAffiliation.mock.calls.length).toBe(1); }); }); }); From 767a4a174881196776acfb611eb5ae2105b959ea Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko <85172747+OleksandrHladchenko1@users.noreply.github.com> Date: Fri, 6 Oct 2023 17:30:23 +0300 Subject: [PATCH 11/29] Update HoldingAccordion.js --- src/Instance/HoldingsList/Holding/HoldingAccordion.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Instance/HoldingsList/Holding/HoldingAccordion.js b/src/Instance/HoldingsList/Holding/HoldingAccordion.js index 3bfcd4009..e8d64bfda 100644 --- a/src/Instance/HoldingsList/Holding/HoldingAccordion.js +++ b/src/Instance/HoldingsList/Holding/HoldingAccordion.js @@ -41,8 +41,6 @@ const HoldingAccordion = ({ if (!locations) return null; - const locationsById = keyBy(locations, 'id'); - const locationsById = keyBy(locations, 'id'); const labelLocation = locationsById[holding.permanentLocationId]; const labelLocationName = labelLocation?.name ?? ''; From 5769fca3c6d327f0638faff5d9ecd9e94518d132 Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko <85172747+OleksandrHladchenko1@users.noreply.github.com> Date: Fri, 6 Oct 2023 17:47:47 +0300 Subject: [PATCH 12/29] Update HoldingButtonsGroup.js --- src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js index 7e1e1374a..d2495e44f 100644 --- a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js +++ b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js @@ -25,7 +25,7 @@ const HoldingButtonsGroup = ({ onViewHolding, onAddItem, itemCount, - isOpen + isOpen, tenantId, isViewHoldingsDisabled, isAddItemDisabled, From fc4b05a72a8f2fd6c94561383993498dd9f82fe4 Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko1 Date: Fri, 6 Oct 2023 19:14:43 +0300 Subject: [PATCH 13/29] UIIN-2452: Fix tests --- src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js | 6 +++--- src/Instance/InstanceDetails/InstanceDetails.js | 1 + .../MemberTenantHoldings/MemberTenantHoldings.js | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js index 7e1e1374a..91bdbae43 100644 --- a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js +++ b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js @@ -25,7 +25,7 @@ const HoldingButtonsGroup = ({ onViewHolding, onAddItem, itemCount, - isOpen + isOpen, tenantId, isViewHoldingsDisabled, isAddItemDisabled, @@ -47,7 +47,7 @@ const HoldingButtonsGroup = ({ diff --git a/src/Instance/ItemsList/tests/ItemBarcode.test.js b/src/Instance/ItemsList/tests/ItemBarcode.test.js index 170b843d7..043b57cef 100644 --- a/src/Instance/ItemsList/tests/ItemBarcode.test.js +++ b/src/Instance/ItemsList/tests/ItemBarcode.test.js @@ -93,19 +93,20 @@ describe('', () => { it('should render barcode as a hotlink', () => { const history = getHistory(searchItem(QUERY_INDEXES.BARCODE)); - const { container } = setupItemBarcode({ ...itemBarcodeProps, history }); + const { getByRole } = setupItemBarcode({ ...itemBarcodeProps, history }); - expect(container.querySelector('[data-test-item-link="true"]')).toBeInTheDocument(); + expect(getByRole('button', { name: itemProp.barcode })).toBeInTheDocument(); }); it('should render barcode as a plain text', () => { const history = getHistory(searchItem(QUERY_INDEXES.BARCODE)); - const { container } = setupItemBarcode({ + const { queryByRole, getByText } = setupItemBarcode({ ...itemBarcodeProps, isBarcodeAsHotlink: false, history, }); - expect(container.querySelector('[data-test-item-link="true"]')).not.toBeInTheDocument(); + expect(queryByRole('button', { name: itemProp.barcode })).not.toBeInTheDocument(); + expect(getByText(itemProp.barcode)).toBeInTheDocument(); }); }); From 27fc561c7959a59f420f2219de60bb258a622406 Mon Sep 17 00:00:00 2001 From: Mariia_Aloshyna Date: Fri, 13 Oct 2023 20:07:47 +0300 Subject: [PATCH 23/29] Fix tests --- src/ViewHoldingsRecord.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ViewHoldingsRecord.test.js b/src/ViewHoldingsRecord.test.js index f666f5ecd..a3292643b 100644 --- a/src/ViewHoldingsRecord.test.js +++ b/src/ViewHoldingsRecord.test.js @@ -29,7 +29,7 @@ jest.mock('./common', () => ({ jest.mock('./utils', () => ({ ...jest.requireActual('./utils'), - updateAffiliation: jest.fn(), + switchAffiliation: jest.fn(), })); const spyOncollapseAllSections = jest.spyOn(require('@folio/stripes/components'), 'collapseAllSections'); From a603d03f0e8f19ee9de6d9fd5449ca60163574be Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko1 Date: Tue, 17 Oct 2023 19:20:23 +0300 Subject: [PATCH 24/29] UIIN-2452: Switch user affiliation using validateUser --- src/Holding/CreateHolding/CreateHolding.js | 19 ++++++----- .../Holding/HoldingButtonsGroup.js | 4 +-- .../Holding/HoldingButtonsGroup.test.js | 6 ++-- .../HoldingsList/Holding/HoldingContainer.js | 23 +++++++++++-- .../InstanceNewHolding/InstanceNewHolding.js | 13 ++++++-- src/Instance/ItemsList/ItemBarcode.js | 13 ++++++-- src/Item/CreateItem/CreateItem.js | 32 +++++++++++-------- src/Item/CreateItem/CreateItem.test.js | 17 ++++++++-- src/ViewHoldingsRecord.js | 29 ++++++++--------- src/ViewHoldingsRecord.test.js | 14 ++++---- src/routes/CreateHoldingRoute.js | 6 ---- src/routes/CreateItemRoute.js | 7 ---- src/routes/ItemRoute.js | 25 ++++++++------- src/routes/ViewHoldingRoute.js | 8 ----- src/utils.js | 27 ++++++++-------- 15 files changed, 139 insertions(+), 104 deletions(-) diff --git a/src/Holding/CreateHolding/CreateHolding.js b/src/Holding/CreateHolding/CreateHolding.js index e0830461e..ee29fcea3 100644 --- a/src/Holding/CreateHolding/CreateHolding.js +++ b/src/Holding/CreateHolding/CreateHolding.js @@ -10,9 +10,7 @@ import { stripesConnect, stripesShape, } from '@folio/stripes/core'; -import { - LoadingView, -} from '@folio/stripes/components'; +import { LoadingView } from '@folio/stripes/components'; import { useInstance, @@ -22,24 +20,29 @@ import HoldingsForm from '../../edit/holdings/HoldingsForm'; import { switchAffiliation } from '../../utils'; const CreateHolding = ({ + history, location, instanceId, referenceData, stripes, mutator, - tenantFrom, }) => { const callout = useCallout(); const { instance, isLoading: isInstanceLoading } = useInstance(instanceId); const sourceId = referenceData.holdingsSourcesByName?.FOLIO?.id; const goBack = useCallback(() => { - window.location.href = `/inventory/view/${instanceId}${location.search}`; + history.push({ + pathname: `/inventory/view/${instanceId}`, + search: location.search, + }); }, [location.search, instanceId]); const onCancel = useCallback(() => { - switchAffiliation(stripes.okapi, tenantFrom, goBack).then(); - }, [stripes.okapi, tenantFrom, goBack]); + const { location: { state: { tenantFrom } } } = history; + + switchAffiliation(stripes, tenantFrom, goBack).then(); + }, [stripes, goBack]); const onSubmit = useCallback((newHolding) => { return mutator.holding.POST(newHolding) @@ -94,11 +97,11 @@ CreateHolding.manifest = Object.freeze({ CreateHolding.propTypes = { location: PropTypes.object.isRequired, + history: PropTypes.object.isRequired, instanceId: PropTypes.string.isRequired, mutator: PropTypes.object.isRequired, referenceData: PropTypes.object.isRequired, stripes: stripesShape.isRequired, - tenantFrom: PropTypes.string, }; export default withRouter(stripesConnect(CreateHolding)); diff --git a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js index 1d6f2192a..7437816cb 100644 --- a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js +++ b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js @@ -47,7 +47,7 @@ const HoldingButtonsGroup = ({ diff --git a/src/Item/CreateItem/CreateItem.js b/src/Item/CreateItem/CreateItem.js index b8c572d90..55f6b8fdd 100644 --- a/src/Item/CreateItem/CreateItem.js +++ b/src/Item/CreateItem/CreateItem.js @@ -3,13 +3,11 @@ import React, { useMemo, } from 'react'; import PropTypes from 'prop-types'; -import { useLocation } from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; import { FormattedMessage } from 'react-intl'; import { useStripes } from '@folio/stripes/core'; -import { - LoadingView, -} from '@folio/stripes/components'; +import { LoadingView } from '@folio/stripes/components'; import { useInstanceQuery, @@ -24,10 +22,17 @@ const CreateItem = ({ referenceData, instanceId, holdingId, - tenantTo, - tenantFrom, }) => { - const location = useLocation(); + const { + push, + location: { + search, + state: { + tenantTo, + tenantFrom, + }, + }, + } = useHistory(); const { isLoading: isInstanceLoading, instance } = useInstanceQuery(instanceId, { tenantId: tenantTo }); const { isLoading: isHoldingLoading, holding } = useHolding(holdingId, { tenantId: tenantTo }); @@ -40,12 +45,15 @@ const CreateItem = ({ }), [holding.id]); const goBack = useCallback(() => { - window.location.href = `/inventory/view/${instanceId}${location.search}`; - }, [instanceId, location.search]); + push({ + pathname: `/inventory/view/${instanceId}`, + search, + }); + }, [instanceId, search]); const onCancel = useCallback(() => { - switchAffiliation(stripes.okapi, tenantFrom, goBack).then(); - }, [stripes.okapi, tenantFrom, goBack]); + switchAffiliation(stripes, tenantFrom, goBack).then(); + }, [stripes, tenantFrom]); const onSuccess = useCallback(async (response) => { const { hrid } = await response.json(); @@ -91,8 +99,6 @@ CreateItem.propTypes = { instanceId: PropTypes.string.isRequired, holdingId: PropTypes.string.isRequired, referenceData: PropTypes.object.isRequired, - tenantTo: PropTypes.string, - tenantFrom: PropTypes.string, }; export default CreateItem; diff --git a/src/Item/CreateItem/CreateItem.test.js b/src/Item/CreateItem/CreateItem.test.js index 8761679ce..f116a1b5d 100644 --- a/src/Item/CreateItem/CreateItem.test.js +++ b/src/Item/CreateItem/CreateItem.test.js @@ -1,8 +1,9 @@ import '../../../test/jest/__mock__'; -import { MemoryRouter } from 'react-router-dom'; +import { Router } from 'react-router-dom'; import { render, screen } from '@folio/jest-config-stripes/testing-library/react'; import { QueryClient, QueryClientProvider } from 'react-query'; +import { createMemoryHistory } from 'history'; import { instance } from '../../../test/fixtures/instance'; import { @@ -19,6 +20,16 @@ jest.mock('../../common/hooks', () => ({ useHolding: jest.fn().mockReturnValue({ holding: {}, isLoading: false }), })); +const history = createMemoryHistory(); +history.location = { + pathname: '/testPathName', + search: '?filters=test1', + state: { + tenantTo: 'testTenantToId', + tenantFrom: 'testTenantFromId', + } +}; + const defaultProps = { instanceId: instance.id, holdingId: 'holdingId', @@ -28,11 +39,11 @@ const defaultProps = { const queryClient = new QueryClient(); const wrapper = ({ children }) => ( - + {children} - + ); const renderCreateItem = (props = {}) => render( diff --git a/src/ViewHoldingsRecord.js b/src/ViewHoldingsRecord.js index 8192582a7..35c0bc150 100644 --- a/src/ViewHoldingsRecord.js +++ b/src/ViewHoldingsRecord.js @@ -81,7 +81,7 @@ class ViewHoldingsRecord extends React.Component { path: 'holdings-storage/holdings/:{holdingsrecordid}', resourceShouldRefresh: false, accumulate: true, - tenant: '!{tenantTo}' + tenant: '!{location.state.tenantTo}' }, items: { type: 'okapi', @@ -97,7 +97,7 @@ class ViewHoldingsRecord extends React.Component { type: 'okapi', path: 'inventory/instances/:{id}', accumulate: true, - tenant: '!{tenantTo}' + tenant: '!{location.state.tenantTo}' }, tagSettings: { type: 'okapi', @@ -198,22 +198,19 @@ class ViewHoldingsRecord extends React.Component { }); }; - goBack = () => { - const { - id, - location: { search, state: locationState }, - } = this.props; - const pathname = locationState?.backPathname ?? `/inventory/view/${id}`; - - window.location.href = `${pathname}${search}`; - } - onClose = (e) => { if (e) e.preventDefault(); - const { stripes, tenantFrom } = this.props; + const { + history, + location: { search, state: locationState }, + id, + } = this.props; - switchAffiliation(stripes.okapi, tenantFrom, this.goBack); + history.push({ + pathname: locationState?.backPathname ?? `/inventory/view/${id}`, + search, + }); } // Edit Holdings records handlers @@ -482,6 +479,7 @@ class ViewHoldingsRecord extends React.Component { referenceTables, goTo, stripes, + location: { state: { tenantFrom } }, } = this.props; const { instance } = this.state; @@ -728,7 +726,7 @@ class ViewHoldingsRecord extends React.Component { updatedDate: getDate(holdingsRecord?.metadata?.updatedDate), })} dismissible - onClose={this.onClose} + onClose={() => switchAffiliation(stripes, tenantFrom, this.onClose)} actionMenu={this.getPaneHeaderActionMenu} > @@ -1138,7 +1136,6 @@ ViewHoldingsRecord.propTypes = { query: PropTypes.object.isRequired, }), goTo: PropTypes.func.isRequired, - tenantFrom: PropTypes.string, }; export default flowRight( diff --git a/src/ViewHoldingsRecord.test.js b/src/ViewHoldingsRecord.test.js index a3292643b..5287c07bb 100644 --- a/src/ViewHoldingsRecord.test.js +++ b/src/ViewHoldingsRecord.test.js @@ -17,7 +17,6 @@ import { translationsProperties, } from '../test/jest/helpers'; -import { switchAffiliation } from './utils'; import ViewHoldingsRecord from './ViewHoldingsRecord'; jest.mock('./withLocation', () => jest.fn(c => c)); @@ -27,11 +26,6 @@ jest.mock('./common', () => ({ useTenantKy: jest.fn(), })); -jest.mock('./utils', () => ({ - ...jest.requireActual('./utils'), - switchAffiliation: jest.fn(), -})); - const spyOncollapseAllSections = jest.spyOn(require('@folio/stripes/components'), 'collapseAllSections'); const spyOnexpandAllSections = jest.spyOn(require('@folio/stripes/components'), 'expandAllSections'); @@ -87,7 +81,11 @@ const defaultProps = { }, location: { search: '/', - pathname: 'pathname' + pathname: 'pathname', + state: { + tenantTo: 'testTenantToId', + tenantFrom: 'testTenantFromId', + } }, }; @@ -118,7 +116,7 @@ describe('ViewHoldingsRecord actions', () => { it('should close view holding page', async () => { renderViewHoldingsRecord(); fireEvent.click(await screen.findByRole('button', { name: 'confirm' })); - expect(switchAffiliation).toHaveBeenCalled(); + expect(defaultProps.history.push).toHaveBeenCalled(); }); it('should translate to edit holding form page', async () => { diff --git a/src/routes/CreateHoldingRoute.js b/src/routes/CreateHoldingRoute.js index 82e8bb2fd..06ef018a0 100644 --- a/src/routes/CreateHoldingRoute.js +++ b/src/routes/CreateHoldingRoute.js @@ -2,22 +2,16 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; import { withRouter } from 'react-router'; -import { useStripes } from '@folio/stripes/core'; - import { CreateHolding } from '../Holding'; import { DataContext } from '../contexts'; -import { getItem } from '../storage'; const CreateHoldingRoute = ({ match }) => { - const { okapi } = useStripes(); const referenceData = useContext(DataContext); - const tenantIds = getItem('tenantIds'); return ( ); }; diff --git a/src/routes/CreateItemRoute.js b/src/routes/CreateItemRoute.js index 0583aa447..6efb904bb 100644 --- a/src/routes/CreateItemRoute.js +++ b/src/routes/CreateItemRoute.js @@ -1,25 +1,18 @@ import React, { useContext } from 'react'; import { useParams } from 'react-router-dom'; -import { useStripes } from '@folio/stripes/core'; - import { CreateItem } from '../Item'; import { DataContext } from '../contexts'; -import { getItem } from '../storage'; const CreateItemRoute = () => { - const { okapi } = useStripes(); const referenceData = useContext(DataContext); const { id, holdingId } = useParams(); - const tenantIds = getItem('tenantIds'); return ( ); }; diff --git a/src/routes/ItemRoute.js b/src/routes/ItemRoute.js index dda8db1b6..c30e17d37 100644 --- a/src/routes/ItemRoute.js +++ b/src/routes/ItemRoute.js @@ -13,11 +13,7 @@ import withLocation from '../withLocation'; import { ItemView } from '../views'; import { PaneLoading } from '../components'; import { DataContext } from '../contexts'; -import { getItem } from '../storage'; -import { - TENANT_IDS_KEY, - switchAffiliation, -} from '../utils'; +import { switchAffiliation } from '../utils'; const getRequestsPath = `circulation/requests?query=(itemId==:{itemid}) and status==(${requestsStatusString}) sortby requestDate desc&limit=1`; @@ -29,6 +25,7 @@ class ItemRoute extends React.Component { path: 'inventory/items/:{itemid}', POST: { path: 'inventory/items' }, resourceShouldRefresh: true, + tenant: '!{location.state.tenantTo}', }, markItemAsWithdrawn: { type: 'okapi', @@ -105,6 +102,7 @@ class ItemRoute extends React.Component { holdingsRecords: { type: 'okapi', path: 'holdings-storage/holdings/:{holdingsrecordid}', + tenant: '!{location.state.tenantTo}', }, instanceRecords: { type: 'okapi', @@ -164,18 +162,22 @@ class ItemRoute extends React.Component { const { match: { params: { id } }, location: { search }, + history, } = this.props; - window.location.href = `/inventory/view/${id}${search}`; + history.push({ + pathname: `/inventory/view/${id}`, + search, + }); } onClose = () => { - const { stripes: { okapi } } = this.props; - const tenantIds = getItem(TENANT_IDS_KEY); - - const tenantFrom = tenantIds ? tenantIds.tenantFrom : okapi.tenant; + const { + stripes, + location: { state: { tenantFrom } }, + } = this.props; - switchAffiliation(okapi, tenantFrom, this.goBack).then(); + switchAffiliation(stripes, tenantFrom, this.goBack).then(); } isLoading = () => { @@ -222,6 +224,7 @@ ItemRoute.propTypes = { resources: PropTypes.object, stripes: PropTypes.object, tenantFrom: PropTypes.string, + history: PropTypes.object, }; export default flowRight( diff --git a/src/routes/ViewHoldingRoute.js b/src/routes/ViewHoldingRoute.js index ffdaae0f6..7ad407a93 100644 --- a/src/routes/ViewHoldingRoute.js +++ b/src/routes/ViewHoldingRoute.js @@ -1,26 +1,18 @@ import { useContext } from 'react'; import { useParams } from 'react-router-dom'; -import { useStripes } from '@folio/stripes/core'; - import { DataContext } from '../contexts'; import ViewHoldingsRecord from '../ViewHoldingsRecord'; -import { getItem } from '../storage'; -import { TENANT_IDS_KEY } from '../utils'; const ViewHoldingRoute = () => { - const { okapi } = useStripes(); const { id: instanceId, holdingsrecordid } = useParams(); const referenceTables = useContext(DataContext); - const tenantIds = getItem(TENANT_IDS_KEY); return ( ); }; diff --git a/src/utils.js b/src/utils.js index 6cc44fedd..0868e17a5 100644 --- a/src/utils.js +++ b/src/utils.js @@ -22,6 +22,7 @@ import { import moment from 'moment'; import { updateTenant } from '@folio/stripes/core'; +import { validateUser } from '@folio/stripes-core/src/loginServices'; import { FormattedUTCDate } from '@folio/stripes/components'; import { @@ -39,7 +40,6 @@ import { CONTENT_TYPE_HEADER, OKAPI_TOKEN_HEADER, } from './constants'; -import { getItem, removeItem, setItem } from './storage'; export const areAllFieldsEmpty = fields => fields.every(item => (isArray(item) ? (isEmpty(item) || item.every(element => !element || element === '-')) @@ -828,19 +828,20 @@ export const hasMemberTenantPermission = (permissions = [], permissionName, tena return hasPermission; }; -export const TENANT_IDS_KEY = 'tenantIds'; +export const switchAffiliation = async (stripes, tenantId, move) => { + if (stripes.okapi.tenant !== tenantId) { + await updateTenant(stripes.okapi, tenantId); -export const switchAffiliation = async (okapi, tenantId, move) => { - if (okapi.tenant !== tenantId) { - await updateTenant(okapi, tenantId); - - const tenantIds = getItem(TENANT_IDS_KEY); - - if (tenantIds) { - removeItem(TENANT_IDS_KEY); - } else { - setItem(TENANT_IDS_KEY, { tenantTo: tenantId, tenantFrom: okapi.tenant }); - } + validateUser( + stripes.okapi.url, + stripes.store, + tenantId, + { + token: stripes.okapi.token, + user: stripes.user.user, + perms: stripes.user.perms, + }, + ); move(); } else { From e18e43ce7c37944797432e94c925179210344f95 Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko <85172747+OleksandrHladchenko1@users.noreply.github.com> Date: Tue, 17 Oct 2023 21:51:15 +0300 Subject: [PATCH 25/29] Update HoldingAccordion.js --- src/Instance/HoldingsList/Holding/HoldingAccordion.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Instance/HoldingsList/Holding/HoldingAccordion.js b/src/Instance/HoldingsList/Holding/HoldingAccordion.js index ed21a6b01..2592567dd 100644 --- a/src/Instance/HoldingsList/Holding/HoldingAccordion.js +++ b/src/Instance/HoldingsList/Holding/HoldingAccordion.js @@ -11,16 +11,12 @@ import { } from '@folio/stripes/components'; import { callNumberLabel } from '../../../utils'; -import { - useLocationsQuery, - useHoldingItemsQuery, -} from '../../../hooks'; import HoldingButtonsGroup from './HoldingButtonsGroup'; -import useHoldingItemsQuery from '../../../hooks/useHoldingItemsQuery'; import { useHoldingsAccordionState, useLocationsQuery, + useHoldingItemsQuery, } from '../../../hooks'; const HoldingAccordion = ({ From f72699c66165da37b39b0af1aca5cd3d2286ed12 Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko1 Date: Wed, 18 Oct 2023 12:54:04 +0300 Subject: [PATCH 26/29] Supress Add holding & Add item & View holdings buttons if user doesn't have permissions --- src/Instance/HoldingsList/Holding/Holding.js | 12 ++++---- .../HoldingsList/Holding/HoldingAccordion.js | 12 ++++---- .../Holding/HoldingButtonsGroup.js | 29 +++++++++---------- .../Holding/HoldingButtonsGroup.test.js | 16 +++++----- .../HoldingsList/Holding/HoldingContainer.js | 28 +++++++++--------- .../Holding/HoldingContainer.test.js | 2 ++ src/Instance/HoldingsList/HoldingsList.js | 12 ++++---- .../HoldingsList/HoldingsListContainer.js | 8 ++--- .../InstanceDetails/InstanceDetails.js | 3 +- .../InstanceNewHolding/InstanceNewHolding.js | 3 -- .../MemberTenantHoldings.js | 7 ++--- .../MemberTenantHoldings.test.js | 2 +- .../HoldingsListMovement.js | 12 ++++---- 13 files changed, 71 insertions(+), 75 deletions(-) diff --git a/src/Instance/HoldingsList/Holding/Holding.js b/src/Instance/HoldingsList/Holding/Holding.js index cac25c97a..907d2283b 100644 --- a/src/Instance/HoldingsList/Holding/Holding.js +++ b/src/Instance/HoldingsList/Holding/Holding.js @@ -23,8 +23,8 @@ const Holding = ({ isDraggable, isItemsDroppable, tenantId, - isViewHoldingsDisabled, - isAddItemDisabled, + showViewHoldingsButton, + showAddItemButton, isBarcodeAsHotlink, }) => { return ( @@ -55,8 +55,8 @@ const Holding = ({ onViewHolding={onViewHolding} onAddItem={onAddItem} tenantId={tenantId} - isViewHoldingsDisabled={isViewHoldingsDisabled} - isAddItemDisabled={isAddItemDisabled} + showViewHoldingsButton={showViewHoldingsButton} + showAddItemButton={showAddItemButton} > { const searchParams = { limit: 0, @@ -63,8 +63,8 @@ const HoldingAccordion = ({ withMoveDropdown={withMoveDropdown} isOpen={open} tenantId={tenantId} - isViewHoldingsDisabled={isViewHoldingsDisabled} - isAddItemDisabled={isAddItemDisabled} + showViewHoldingsButton={showViewHoldingsButton} + showAddItemButton={showAddItemButton} />; const location = labelLocation?.isActive ? @@ -125,8 +125,8 @@ HoldingAccordion.propTypes = { withMoveDropdown: PropTypes.bool, children: PropTypes.object, tenantId: PropTypes.string, - isViewHoldingsDisabled: PropTypes.bool, - isAddItemDisabled: PropTypes.bool, + showViewHoldingsButton: PropTypes.bool, + showAddItemButton: PropTypes.bool, }; export default HoldingAccordion; diff --git a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js index 7437816cb..a1cf5eb16 100644 --- a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js +++ b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js @@ -27,8 +27,8 @@ const HoldingButtonsGroup = ({ itemCount, isOpen, tenantId, - isViewHoldingsDisabled, - isAddItemDisabled, + showViewHoldingsButton, + showAddItemButton, }) => { const stripes = useStripes(); const isUserInCentralTenant = checkIfUserInCentralTenant(stripes); @@ -44,23 +44,22 @@ const HoldingButtonsGroup = ({ /> ) } - - - {!isUserInCentralTenant && ( + {showViewHoldingsButton && + + } + {!isUserInCentralTenant && showAddItemButton && ( @@ -80,8 +79,8 @@ HoldingButtonsGroup.propTypes = { onAddItem: PropTypes.func.isRequired, onViewHolding: PropTypes.func.isRequired, withMoveDropdown: PropTypes.bool, - isViewHoldingsDisabled: PropTypes.bool, - isAddItemDisabled: PropTypes.bool, + showViewHoldingsButton: PropTypes.bool, + showAddItemButton: PropTypes.bool, tenantId: PropTypes.string, }; diff --git a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js index f23dccea4..4ff6aaf4c 100644 --- a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js +++ b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js @@ -31,8 +31,8 @@ const HoldingButtonsGroupSetup = props => ( onAddItem={mockOnAddItem} itemCount={mockItemCount} isOpen={false} - isViewHoldingsDisabled={false} - isAddItemDisabled={false} + showViewHoldingsButton + showAddItemButton {...props} > {() => null} @@ -64,18 +64,18 @@ describe('HoldingButtonsGroup', () => { }); describe('when user has no permissions to view holdings', () => { - it('should render "View Holdings" button as disabled', () => { - const { getByRole } = renderHoldingButtonsGroup({ isViewHoldingsDisabled: true }); + it('should supress "View Holdings" button', () => { + const { queryByRole } = renderHoldingButtonsGroup({ showViewHoldingsButton: false }); - expect(getByRole('button', { name: 'View holdings' })).toBeDisabled(); + expect(queryByRole('button', { name: 'View holdings' })).not.toBeInTheDocument(); }); }); describe('when user has no permissions to create items', () => { - it('should render "Add item" button as disabled', () => { - const { getByRole } = renderHoldingButtonsGroup({ isAddItemDisabled: true }); + it('should supress "Add item" button', () => { + const { queryByRole } = renderHoldingButtonsGroup({ showAddItemButton: false }); - expect(getByRole('button', { name: 'Add item' })).toBeDisabled(); + expect(queryByRole('button', { name: 'Add item' })).not.toBeInTheDocument(); }); }); diff --git a/src/Instance/HoldingsList/Holding/HoldingContainer.js b/src/Instance/HoldingsList/Holding/HoldingContainer.js index 19295eda9..ae7a4ced9 100644 --- a/src/Instance/HoldingsList/Holding/HoldingContainer.js +++ b/src/Instance/HoldingsList/Holding/HoldingContainer.js @@ -38,8 +38,8 @@ const DraggableHolding = ({ onViewHolding, onAddItem, tenantId, - isViewHoldingsDisabled, - isAddItemDisabled, + showViewHoldingsButton, + showAddItemButton, isBarcodeAsHotlink, ...rest }) => { @@ -73,8 +73,8 @@ const DraggableHolding = ({ onViewHolding={onViewHolding} onAddItem={onAddItem} tenantId={tenantId} - isViewHoldingsDisabled={isViewHoldingsDisabled} - isAddItemDisabled={isAddItemDisabled} + showViewHoldingsButton={showViewHoldingsButton} + showAddItemButton={showAddItemButton} isBarcodeAsHotlink={isBarcodeAsHotlink} /> ) @@ -95,16 +95,16 @@ DraggableHolding.propTypes = { onViewHolding: PropTypes.func, onAddItem: PropTypes.func, tenantId: PropTypes.string, - isViewHoldingsDisabled: PropTypes.bool, - isAddItemDisabled: PropTypes.bool, + showViewHoldingsButton: PropTypes.bool, + showAddItemButton: PropTypes.bool, isBarcodeAsHotlink: PropTypes.bool, }; const HoldingContainer = ({ location, history, - isViewHoldingsDisabled, - isAddItemDisabled, + showViewHoldingsButton, + showAddItemButton, isBarcodeAsHotlink, instance, holding, @@ -153,8 +153,8 @@ const HoldingContainer = ({ onViewHolding={onViewHolding} onAddItem={onAddItem} tenantId={tenantId} - isViewHoldingsDisabled={isViewHoldingsDisabled} - isAddItemDisabled={isAddItemDisabled} + showViewHoldingsButton={showViewHoldingsButton} + showAddItemButton={showAddItemButton} isBarcodeAsHotlink={isBarcodeAsHotlink} {...rest} /> @@ -167,8 +167,8 @@ const HoldingContainer = ({ onViewHolding={onViewHolding} onAddItem={onAddItem} tenantId={tenantId} - isViewHoldingsDisabled={isViewHoldingsDisabled} - isAddItemDisabled={isAddItemDisabled} + showViewHoldingsButton={showViewHoldingsButton} + showAddItemButton={showAddItemButton} isBarcodeAsHotlink={isBarcodeAsHotlink} /> ); @@ -185,8 +185,8 @@ HoldingContainer.propTypes = { isDraggable: PropTypes.bool, draggingHoldingsCount: PropTypes.number, tenantId: PropTypes.string, - isViewHoldingsDisabled: PropTypes.bool, - isAddItemDisabled: PropTypes.bool, + showViewHoldingsButton: PropTypes.bool, + showAddItemButton: PropTypes.bool, isBarcodeAsHotlink: PropTypes.bool, }; diff --git a/src/Instance/HoldingsList/Holding/HoldingContainer.test.js b/src/Instance/HoldingsList/Holding/HoldingContainer.test.js index 1cb2c6129..2a307ce06 100644 --- a/src/Instance/HoldingsList/Holding/HoldingContainer.test.js +++ b/src/Instance/HoldingsList/Holding/HoldingContainer.test.js @@ -54,6 +54,8 @@ const renderHoldingContainer = (props = {}) => renderWithIntl( provided={{ draggableProps: { style: true } }} onViewHolding={jest.fn()} onAddItem={jest.fn()} + showViewHoldingsButton + showAddItemButton {...props} /> diff --git a/src/Instance/HoldingsList/HoldingsList.js b/src/Instance/HoldingsList/HoldingsList.js index a7d43cd80..233329569 100644 --- a/src/Instance/HoldingsList/HoldingsList.js +++ b/src/Instance/HoldingsList/HoldingsList.js @@ -7,8 +7,8 @@ const HoldingsList = ({ instance, holdings, tenantId, - isViewHoldingsDisabled, - isAddItemDisabled, + showViewHoldingsButton, + showAddItemButton, isBarcodeAsHotlink, draggable, droppable, @@ -21,8 +21,8 @@ const HoldingsList = ({ droppable={droppable} holdings={holdings} tenantId={tenantId} - isViewHoldingsDisabled={isViewHoldingsDisabled} - isAddItemDisabled={isAddItemDisabled} + showViewHoldingsButton={showViewHoldingsButton} + showAddItemButton={showAddItemButton} isBarcodeAsHotlink={isBarcodeAsHotlink} /> )); @@ -31,8 +31,8 @@ HoldingsList.propTypes = { instance: PropTypes.object.isRequired, holdings: PropTypes.arrayOf(PropTypes.object), tenantId: PropTypes.string, - isViewHoldingsDisabled: PropTypes.bool, - isAddItemDisabled: PropTypes.bool, + showViewHoldingsButton: PropTypes.bool, + showAddItemButton: PropTypes.bool, isBarcodeAsHotlink: PropTypes.bool, draggable: PropTypes.bool, droppable: PropTypes.bool, diff --git a/src/Instance/HoldingsList/HoldingsListContainer.js b/src/Instance/HoldingsList/HoldingsListContainer.js index b12e2f186..72baed8a4 100644 --- a/src/Instance/HoldingsList/HoldingsListContainer.js +++ b/src/Instance/HoldingsList/HoldingsListContainer.js @@ -32,8 +32,8 @@ const HoldingsListContainer = ({ holdings={holdings} instance={instance} tenantId={tenantId} - isViewHoldingsDisabled={!canViewHoldings} - isAddItemDisabled={!canCreateItem} + showViewHoldingsButton={canViewHoldings} + showAddItemButton={canCreateItem} isBarcodeAsHotlink={canViewItems} /> ) : ( @@ -42,8 +42,8 @@ const HoldingsListContainer = ({ holdings={holdings} instance={instance} tenantId={tenantId} - isViewHoldingsDisabled={!canViewHoldings} - isAddItemDisabled={!canCreateItem} + showViewHoldingsButton={canViewHoldings} + showAddItemButton={canCreateItem} isBarcodeAsHotlink={canViewItems} /> ) diff --git a/src/Instance/InstanceDetails/InstanceDetails.js b/src/Instance/InstanceDetails/InstanceDetails.js index 8190caf2d..51f4338c4 100644 --- a/src/Instance/InstanceDetails/InstanceDetails.js +++ b/src/Instance/InstanceDetails/InstanceDetails.js @@ -197,10 +197,9 @@ const InstanceDetails = forwardRef(({ {children} - {!isUserInCentralTenant && ( + {!isUserInCentralTenant && canCreateHoldings && ( )} diff --git a/src/Instance/InstanceDetails/InstanceNewHolding/InstanceNewHolding.js b/src/Instance/InstanceDetails/InstanceNewHolding/InstanceNewHolding.js index c21dff9c7..257b07fe1 100644 --- a/src/Instance/InstanceDetails/InstanceNewHolding/InstanceNewHolding.js +++ b/src/Instance/InstanceDetails/InstanceNewHolding/InstanceNewHolding.js @@ -20,7 +20,6 @@ const InstanceNewHolding = ({ location, instance, tenantId, - disabled, }) => { const intl = useIntl(); const stripes = useStripes(); @@ -49,7 +48,6 @@ const InstanceNewHolding = ({ buttonStyle="primary" fullWidth onClick={() => switchAffiliation(stripes, tenantId, onNewHolding)} - disabled={disabled} > {label} @@ -63,7 +61,6 @@ InstanceNewHolding.propTypes = { location: PropTypes.object.isRequired, instance: PropTypes.object, tenantId: PropTypes.string, - disabled: PropTypes.bool, }; export default withRouter(InstanceNewHolding); diff --git a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js index 238e0bf05..45bb6c1dd 100644 --- a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js +++ b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js @@ -58,18 +58,17 @@ const MemberTenantHoldings = ({ tenantId={id} draggable={false} droppable={false} - isViewHoldingsDisabled={!canViewHoldings} - isAddItemDisabled={!canCreateItem} + showViewHoldingsButton={canViewHoldings} + showAddItemButton={canCreateItem} isBarcodeAsHotlink={canViewItems} /> )}
- {!isUserInCentralTenant && ( + {!isUserInCentralTenant && canCreateHoldings && ( )} diff --git a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.test.js b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.test.js index 35493a3a9..f6271d607 100644 --- a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.test.js +++ b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.test.js @@ -33,7 +33,7 @@ const mockMemberTenant = { const userTenantPermissions = [{ tenantId: 'college', permissionNames: [{ - permissionName: 'test Permission name 1', + permissionName: 'ui-inventory.holdings.edit', subPermissions: ['test subPermission 1'] }], }]; diff --git a/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js b/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js index 36aeec7ec..8941f41e9 100644 --- a/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js +++ b/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js @@ -23,8 +23,8 @@ const HoldingsListMovement = ({ draggable, droppable, tenantId, - isViewHoldingsDisabled, - isAddItemDisabled, + showViewHoldingsButton, + showAddItemButton, isBarcodeAsHotlink, }) => { const { @@ -61,8 +61,8 @@ const HoldingsListMovement = ({ holdingIndex={index} draggingHoldingsCount={draggingHoldingsCount} tenantId={tenantId} - isViewHoldingsDisabled={isViewHoldingsDisabled} - isAddItemDisabled={isAddItemDisabled} + showViewHoldingsButton={showViewHoldingsButton} + showAddItemButton={showAddItemButton} isBarcodeAsHotlink={isBarcodeAsHotlink} /> )) @@ -82,8 +82,8 @@ const HoldingsListMovement = ({ HoldingsListMovement.propTypes = { instance: PropTypes.object.isRequired, holdings: PropTypes.arrayOf(PropTypes.object), - isViewHoldingsDisabled: PropTypes.bool, - isAddItemDisabled: PropTypes.bool, + showViewHoldingsButton: PropTypes.bool, + showAddItemButton: PropTypes.bool, isBarcodeAsHotlink: PropTypes.bool, draggable: PropTypes.bool, droppable: PropTypes.bool, From 773e3c1f6e9beaa1c94785a001b2192fa7bdd154 Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko1 Date: Wed, 18 Oct 2023 14:50:33 +0300 Subject: [PATCH 27/29] UIIN-2452: Fix comments & add unit tests --- src/Holding/CreateHolding/CreateHolding.js | 2 +- .../InstanceNewHolding.test.js | 24 +++++++- .../MemberTenantHoldings.js | 8 +-- src/Item/CreateItem/CreateItem.js | 2 +- src/routes/ItemRoute.js | 2 +- src/utils.js | 8 ++- src/utils.test.js | 59 +++++++++++++++++++ 7 files changed, 94 insertions(+), 11 deletions(-) diff --git a/src/Holding/CreateHolding/CreateHolding.js b/src/Holding/CreateHolding/CreateHolding.js index ee29fcea3..f07a9348a 100644 --- a/src/Holding/CreateHolding/CreateHolding.js +++ b/src/Holding/CreateHolding/CreateHolding.js @@ -41,7 +41,7 @@ const CreateHolding = ({ const onCancel = useCallback(() => { const { location: { state: { tenantFrom } } } = history; - switchAffiliation(stripes, tenantFrom, goBack).then(); + switchAffiliation(stripes, tenantFrom, goBack); }, [stripes, goBack]); const onSubmit = useCallback((newHolding) => { diff --git a/src/Instance/InstanceDetails/InstanceNewHolding/InstanceNewHolding.test.js b/src/Instance/InstanceDetails/InstanceNewHolding/InstanceNewHolding.test.js index 9e51f5254..8cffc1fae 100644 --- a/src/Instance/InstanceDetails/InstanceNewHolding/InstanceNewHolding.test.js +++ b/src/Instance/InstanceDetails/InstanceNewHolding/InstanceNewHolding.test.js @@ -1,11 +1,24 @@ import React from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; +import { createMemoryHistory } from 'history'; + +import { fireEvent } from '@folio/jest-config-stripes/testing-library/react'; import '../../../../test/jest/__mock__'; import renderWithIntl from '../../../../test/jest/helpers/renderWithIntl'; import InstanceNewHolding from './InstanceNewHolding'; +const mockPush = jest.fn(); + +const history = createMemoryHistory(); +history.push = mockPush; + +jest.mock('../../../utils', () => ({ + ...jest.requireActual('../../../utils'), + switchAffiliation: jest.fn(() => mockPush()), +})); + const props = { location: {}, instance: {}, @@ -13,7 +26,7 @@ const props = { const renderInstanceNewHolding = () => ( renderWithIntl( - + ) @@ -24,4 +37,13 @@ describe('InstanceNewHolding', () => { const { getByText } = renderInstanceNewHolding(); expect(getByText(/ui-inventory.addHoldings/i)).toBeInTheDocument(); }); + + describe('when click "Add holdings" button', () => { + it('should redirect to the Holdings form', () => { + const { getByText } = renderInstanceNewHolding(); + fireEvent.click(getByText(/ui-inventory.addHoldings/i)); + + expect(mockPush).toHaveBeenCalledWith(); + }); + }); }); diff --git a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js index 0df1daf07..347164e0f 100644 --- a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js +++ b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js @@ -40,10 +40,10 @@ const MemberTenantHoldings = ({ const { holdingsRecords, isLoading } = useInstanceHoldingsQuery(instance?.id, { tenantId: id }); const isUserInCentralTenant = checkIfUserInCentralTenant(stripes); - const canViewHoldings = hasMemberTenantPermission(userTenantPermissions, 'ui-inventory.instance.view', id); - const canCreateItem = hasMemberTenantPermission(userTenantPermissions, 'ui-inventory.item.edit', id); - const canCreateHoldings = hasMemberTenantPermission(userTenantPermissions, 'ui-inventory.holdings.edit', id); - const canViewItems = hasMemberTenantPermission(userTenantPermissions, 'ui-inventory.instance.view', id); + const canViewHoldings = hasMemberTenantPermission('ui-inventory.instance.view', id, userTenantPermissions); + const canCreateItem = hasMemberTenantPermission('ui-inventory.item.edit', id, userTenantPermissions); + const canCreateHoldings = hasMemberTenantPermission('ui-inventory.holdings.edit', id, userTenantPermissions); + const canViewItems = hasMemberTenantPermission('ui-inventory.instance.view', id, userTenantPermissions); if (isEmpty(holdingsRecords)) return null; diff --git a/src/Item/CreateItem/CreateItem.js b/src/Item/CreateItem/CreateItem.js index 55f6b8fdd..d538783f4 100644 --- a/src/Item/CreateItem/CreateItem.js +++ b/src/Item/CreateItem/CreateItem.js @@ -52,7 +52,7 @@ const CreateItem = ({ }, [instanceId, search]); const onCancel = useCallback(() => { - switchAffiliation(stripes, tenantFrom, goBack).then(); + switchAffiliation(stripes, tenantFrom, goBack); }, [stripes, tenantFrom]); const onSuccess = useCallback(async (response) => { diff --git a/src/routes/ItemRoute.js b/src/routes/ItemRoute.js index c30e17d37..f3235ab5e 100644 --- a/src/routes/ItemRoute.js +++ b/src/routes/ItemRoute.js @@ -177,7 +177,7 @@ class ItemRoute extends React.Component { location: { state: { tenantFrom } }, } = this.props; - switchAffiliation(stripes, tenantFrom, this.goBack).then(); + switchAffiliation(stripes, tenantFrom, this.goBack); } isLoading = () => { diff --git a/src/utils.js b/src/utils.js index 0868e17a5..0de843be4 100644 --- a/src/utils.js +++ b/src/utils.js @@ -21,8 +21,10 @@ import { } from 'lodash'; import moment from 'moment'; -import { updateTenant } from '@folio/stripes/core'; -import { validateUser } from '@folio/stripes-core/src/loginServices'; +import { + updateTenant, + validateUser, +} from '@folio/stripes/core'; import { FormattedUTCDate } from '@folio/stripes/components'; import { @@ -816,7 +818,7 @@ export const getUserTenantsPermissions = (stripes, tenants = []) => { return Promise.all(promises); }; -export const hasMemberTenantPermission = (permissions = [], permissionName, tenantId) => { +export const hasMemberTenantPermission = (permissionName, tenantId, permissions = []) => { const tenantPermissions = permissions?.find(permission => permission?.tenantId === tenantId)?.permissionNames || []; const hasPermission = tenantPermissions?.some(tenantPermission => tenantPermission?.permissionName === permissionName); diff --git a/src/utils.test.js b/src/utils.test.js index e225ca34e..32900f938 100644 --- a/src/utils.test.js +++ b/src/utils.test.js @@ -1,15 +1,24 @@ import '../test/jest/__mock__'; import { FormattedMessage } from 'react-intl'; + +import { updateTenant } from '@folio/stripes/core'; + import { validateRequiredField, validateFieldLength, validateNumericField, validateAlphaNumericField, getQueryTemplate, + switchAffiliation, } from './utils'; import { browseModeOptions } from './constants'; +jest.mock('@folio/stripes/core', () => ({ + ...jest.requireActual('@folio/stripes/core'), + updateTenant: jest.fn(), +})); + describe('validateRequiredField', () => { const expectedResult = ; @@ -144,3 +153,53 @@ describe('getQueryTemplate', () => { }); }); +describe('switchAffiliation', () => { + global.fetch = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + global.fetch + .mockResolvedValueOnce({ + ok: true, + json: async () => ({ + user: { + id: 'testId', + username: 'testUsername', + personal: {}, + }, + permissions: { + permissions: [], + } + }), + }); + }); + + const moveMock = jest.fn(); + const stripes = { + okapi: { + tenant: 'college', + token: 'testToken', + }, + store: { dispatch: jest.fn() }, + user: { + user: {}, + perms: [], + }, + }; + + describe('when current tenant is the same as tenant to switch', () => { + it('should only move to the next page', () => { + switchAffiliation(stripes, 'college', moveMock); + + expect(moveMock).toHaveBeenCalled(); + }); + }); + + describe('when current tenant is not the same as tenant to switch', () => { + it('should switch affiliation', () => { + switchAffiliation(stripes, 'university', moveMock); + + expect(updateTenant).toHaveBeenCalled(); + }); + }); +}); From 76a05f7a630125bb214fa59d12dc0543b16c33d7 Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko1 Date: Wed, 18 Oct 2023 22:59:11 +0300 Subject: [PATCH 28/29] UIIN-2452: Fixes after review --- src/Holding/CreateHolding/CreateHolding.js | 5 ++- .../Holding/HoldingButtonsGroup.test.js | 12 +++---- .../HoldingsListContainer.test.js | 16 ++-------- .../InstanceDetails/InstanceDetails.test.js | 5 --- src/utils.test.js | 32 +++---------------- test/jest/__mock__/stripesCore.mock.js | 5 +++ 6 files changed, 19 insertions(+), 56 deletions(-) diff --git a/src/Holding/CreateHolding/CreateHolding.js b/src/Holding/CreateHolding/CreateHolding.js index f07a9348a..1cfcceef9 100644 --- a/src/Holding/CreateHolding/CreateHolding.js +++ b/src/Holding/CreateHolding/CreateHolding.js @@ -30,6 +30,7 @@ const CreateHolding = ({ const callout = useCallout(); const { instance, isLoading: isInstanceLoading } = useInstance(instanceId); const sourceId = referenceData.holdingsSourcesByName?.FOLIO?.id; + const { location: { state: { tenantFrom } } } = history; const goBack = useCallback(() => { history.push({ @@ -39,10 +40,8 @@ const CreateHolding = ({ }, [location.search, instanceId]); const onCancel = useCallback(() => { - const { location: { state: { tenantFrom } } } = history; - switchAffiliation(stripes, tenantFrom, goBack); - }, [stripes, goBack]); + }, [stripes, tenantFrom, goBack]); const onSubmit = useCallback((newHolding) => { return mutator.holding.POST(newHolding) diff --git a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js index 4ff6aaf4c..1e803832b 100644 --- a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js +++ b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js @@ -59,15 +59,15 @@ describe('HoldingButtonsGroup', () => { it('should display buttons', () => { const { getByRole } = renderHoldingButtonsGroup(); - expect(getByRole('button', { name: 'View holdings' })).toBeDefined(); - expect(getByRole('button', { name: 'Add item' })).toBeDefined(); + expect(getByRole('button', { name: 'View holdings' })).toBeDefined(); + expect(getByRole('button', { name: 'Add item' })).toBeDefined(); }); describe('when user has no permissions to view holdings', () => { it('should supress "View Holdings" button', () => { const { queryByRole } = renderHoldingButtonsGroup({ showViewHoldingsButton: false }); - expect(queryByRole('button', { name: 'View holdings' })).not.toBeInTheDocument(); + expect(queryByRole('button', { name: 'View holdings' })).not.toBeInTheDocument(); }); }); @@ -75,7 +75,7 @@ describe('HoldingButtonsGroup', () => { it('should supress "Add item" button', () => { const { queryByRole } = renderHoldingButtonsGroup({ showAddItemButton: false }); - expect(queryByRole('button', { name: 'Add item' })).not.toBeInTheDocument(); + expect(queryByRole('button', { name: 'Add item' })).not.toBeInTheDocument(); }); }); @@ -83,7 +83,7 @@ describe('HoldingButtonsGroup', () => { it('should call function to switch user\'s affiliation', () => { const { getByRole } = renderHoldingButtonsGroup(); - fireEvent.click(getByRole('button', { name: 'View holdings' })); + fireEvent.click(getByRole('button', { name: 'View holdings' })); expect(switchAffiliation.mock.calls.length).toBe(1); }); @@ -93,7 +93,7 @@ describe('HoldingButtonsGroup', () => { it('should call function to switch user\'s affiliation', () => { const { getByRole } = renderHoldingButtonsGroup(); - fireEvent.click(getByRole('button', { name: 'Add item' })); + fireEvent.click(getByRole('button', { name: 'Add item' })); expect(switchAffiliation.mock.calls.length).toBe(1); }); diff --git a/src/Instance/HoldingsList/HoldingsListContainer.test.js b/src/Instance/HoldingsList/HoldingsListContainer.test.js index 659b13fc1..d7084f2db 100644 --- a/src/Instance/HoldingsList/HoldingsListContainer.test.js +++ b/src/Instance/HoldingsList/HoldingsListContainer.test.js @@ -12,11 +12,6 @@ jest.mock('./HoldingsList', () => jest.fn().mockReturnValue('Holdings List')); jest.mock('../InstanceMovement/HoldingMovementList/HoldingsListMovement', () => jest.fn().mockReturnValue('HoldingMovementList')); jest.mock('./Holding/HoldingContainer', () => jest.fn().mockReturnValue('HoldingContainer')); -const userTenantPermissions = [{ - tenantId: 'testTenantId', - permissionNames: ['test permission'], -}]; - describe('HoldingsListContainer', () => { const instance = { id: '123', @@ -44,10 +39,7 @@ describe('HoldingsListContainer', () => { render( - + ); expect(screen.getByText('Loading')).toBeInTheDocument(); @@ -61,10 +53,7 @@ describe('HoldingsListContainer', () => { render( - + ); @@ -84,7 +73,6 @@ describe('HoldingsListContainer', () => { ); diff --git a/src/Instance/InstanceDetails/InstanceDetails.test.js b/src/Instance/InstanceDetails/InstanceDetails.test.js index fcfc915cc..daf483dcd 100644 --- a/src/Instance/InstanceDetails/InstanceDetails.test.js +++ b/src/Instance/InstanceDetails/InstanceDetails.test.js @@ -38,11 +38,6 @@ const instance = { shared: false, }; -const userTenantPermissions = [{ - tenantId: 'testTenantId', - permissionNames: ['test permission'], -}]; - const mockReferenceData = { titleTypes:[ { id: '1', name: 'Type 1' }, diff --git a/src/utils.test.js b/src/utils.test.js index 32900f938..1c8fe72d8 100644 --- a/src/utils.test.js +++ b/src/utils.test.js @@ -4,6 +4,8 @@ import { FormattedMessage } from 'react-intl'; import { updateTenant } from '@folio/stripes/core'; +import buildStripes from '../test/jest/__mock__/stripesCore.mock'; + import { validateRequiredField, validateFieldLength, @@ -14,11 +16,6 @@ import { } from './utils'; import { browseModeOptions } from './constants'; -jest.mock('@folio/stripes/core', () => ({ - ...jest.requireActual('@folio/stripes/core'), - updateTenant: jest.fn(), -})); - describe('validateRequiredField', () => { const expectedResult = ; @@ -154,38 +151,17 @@ describe('getQueryTemplate', () => { }); describe('switchAffiliation', () => { - global.fetch = jest.fn(); - beforeEach(() => { jest.clearAllMocks(); - global.fetch - .mockResolvedValueOnce({ - ok: true, - json: async () => ({ - user: { - id: 'testId', - username: 'testUsername', - personal: {}, - }, - permissions: { - permissions: [], - } - }), - }); }); const moveMock = jest.fn(); - const stripes = { + const stripes = buildStripes({ okapi: { tenant: 'college', token: 'testToken', }, - store: { dispatch: jest.fn() }, - user: { - user: {}, - perms: [], - }, - }; + }); describe('when current tenant is the same as tenant to switch', () => { it('should only move to the next page', () => { diff --git a/test/jest/__mock__/stripesCore.mock.js b/test/jest/__mock__/stripesCore.mock.js index 15b036546..6d9bfb5fb 100644 --- a/test/jest/__mock__/stripesCore.mock.js +++ b/test/jest/__mock__/stripesCore.mock.js @@ -39,6 +39,7 @@ const buildStripes = (otherProperties = {}) => ({ }, }, withOkapi: true, + updateTenant: jest.fn().mockImplementation(() => {}), ...otherProperties, }); @@ -101,6 +102,10 @@ const mockStripesCore = { checkIfUserInMemberTenant: jest.fn(() => true), checkIfUserInCentralTenant: jest.fn(() => false), + + updateTenant: jest.fn(() => {}), + + validateUser: jest.fn(() => {}), }; jest.mock('@folio/stripes/core', () => ({ From 355c482ca8e897bd8733b2ed9011252bcfd631d3 Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko1 Date: Wed, 18 Oct 2023 23:17:40 +0300 Subject: [PATCH 29/29] UIIN-2452: Fix code smells --- src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js | 1 - test/jest/__mock__/stripesCore.mock.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js index a1cf5eb16..56cd5a859 100644 --- a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js +++ b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.js @@ -84,5 +84,4 @@ HoldingButtonsGroup.propTypes = { tenantId: PropTypes.string, }; - export default memo(HoldingButtonsGroup); diff --git a/test/jest/__mock__/stripesCore.mock.js b/test/jest/__mock__/stripesCore.mock.js index 6d9bfb5fb..a9ad0a2b4 100644 --- a/test/jest/__mock__/stripesCore.mock.js +++ b/test/jest/__mock__/stripesCore.mock.js @@ -39,7 +39,6 @@ const buildStripes = (otherProperties = {}) => ({ }, }, withOkapi: true, - updateTenant: jest.fn().mockImplementation(() => {}), ...otherProperties, });