diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c63e769c..e841e4d8a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -103,6 +103,7 @@
* Use `==` for exact phrase search in Advanced Search for all full-text and term fields. Refs UIIN-2612.
* Provide an instance `tenantId` to the PO line form when creating an order from the instance. Refs UIIN-2614.
* Bump @folio/stripes-acq-components dependency version to 5.0.0. Refs UIIN-2620.
+* ECS: Check when sharing instance with source=MARC is complete before re-fetching it. Refs UIIN-2605.
## [9.4.12](https://github.com/folio-org/ui-inventory/tree/v9.4.12) (2023-09-21)
[Full Changelog](https://github.com/folio-org/ui-inventory/compare/v9.4.11...v9.4.12)
diff --git a/src/Instance/InstanceDetails/InstanceDetails.js b/src/Instance/InstanceDetails/InstanceDetails.js
index 36e7b8d1d..36df56a26 100644
--- a/src/Instance/InstanceDetails/InstanceDetails.js
+++ b/src/Instance/InstanceDetails/InstanceDetails.js
@@ -19,6 +19,7 @@ import {
PaneMenu,
Row,
MessageBanner,
+ Icon,
} from '@folio/stripes/components';
import { InstanceTitle } from './InstanceTitle';
@@ -36,9 +37,17 @@ import { InstanceNewHolding } from './InstanceNewHolding';
import { InstanceAcquisition } from './InstanceAcquisition';
import HelperApp from '../../components/HelperApp';
-import { getAccordionState } from './utils';
import { DataContext } from '../../contexts';
import { ConsortialHoldings } from './ConsortialHoldings';
+import {
+ getAccordionState,
+ getPublishingInfo,
+} from './utils';
+import {
+ getDate,
+ isInstanceShadowCopy,
+ isUserInConsortiumMode,
+} from '../../utils';
const accordions = {
administrative: 'acc01',
@@ -57,21 +66,22 @@ const accordions = {
const InstanceDetails = forwardRef(({
children,
instance,
- paneTitle,
- paneSubtitle,
onClose,
actionMenu,
tagsEnabled,
+ isShared,
+ isLoading,
...rest
}, ref) => {
- const stripes = useStripes();
const intl = useIntl();
+ const stripes = useStripes();
const location = useLocation();
const searchParams = new URLSearchParams(location.search);
const referenceData = useContext(DataContext);
const accordionState = useMemo(() => getAccordionState(instance, accordions), [instance]);
const [helperApp, setHelperApp] = useState();
+
const tags = instance?.tags?.tagList;
const isUserInCentralTenant = checkIfUserInCentralTenant(stripes);
@@ -93,14 +103,61 @@ const InstanceDetails = forwardRef(({
);
}, [tagsEnabled, tags, intl]);
+ if (isLoading) {
+ return (
+ }
+ dismissible
+ onClose={onClose}
+ >
+
+
+
+
+ );
+ }
+
+ const renderPaneTitle = () => {
+ const isInstanceShared = Boolean(isShared || isInstanceShadowCopy(instance?.source));
+
+ return (
+
+ );
+ };
+
+ const renderPaneSubtitle = () => {
+ return (
+
+ );
+ };
+
return (
<>
}
- paneTitle={paneTitle}
- paneSub={paneSubtitle}
+ paneTitle={renderPaneTitle()}
+ paneSub={renderPaneSubtitle()}
dismissible
onClose={onClose}
actionMenu={actionMenu}
@@ -232,15 +289,17 @@ InstanceDetails.propTypes = {
actionMenu: PropTypes.func,
onClose: PropTypes.func.isRequired,
instance: PropTypes.object,
- paneTitle: PropTypes.object,
- paneSubtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
tagsToggle: PropTypes.func,
tagsEnabled: PropTypes.bool,
+ isLoading: PropTypes.bool,
+ isShared: PropTypes.bool,
};
InstanceDetails.defaultProps = {
instance: {},
tagsEnabled: false,
+ isLoading: false,
+ isShared: false,
};
export default InstanceDetails;
diff --git a/src/Instance/InstanceDetails/utils.js b/src/Instance/InstanceDetails/utils.js
index 1d9b3bbbb..3b8082097 100644
--- a/src/Instance/InstanceDetails/utils.js
+++ b/src/Instance/InstanceDetails/utils.js
@@ -20,7 +20,7 @@ export const getPublishingInfo = instance => {
return undefined;
};
-export const getAccordionState = (instance, accordions) => {
+export const getAccordionState = (instance = {}, accordions = {}) => {
const instanceData = pick(
instance,
['hrid', 'source', 'catalogedDate', 'statusId', 'statusUpdatedDate', 'modeOfIssuanceId', 'statisticalCodeIds'],
diff --git a/src/ViewInstance.js b/src/ViewInstance.js
index 7ce69c470..676336d2c 100644
--- a/src/ViewInstance.js
+++ b/src/ViewInstance.js
@@ -12,15 +12,12 @@ import {
} from 'lodash';
import {
- AppIcon,
Pluggable,
stripesConnect,
checkIfUserInMemberTenant,
checkIfUserInCentralTenant,
} from '@folio/stripes/core';
import {
- Pane,
- Icon,
MenuSection,
Callout,
checkScope,
@@ -34,17 +31,16 @@ import ViewHoldingsRecord from './ViewHoldingsRecord';
import makeConnectedInstance from './ConnectedInstance';
import withLocation from './withLocation';
import InstancePlugin from './components/InstancePlugin';
-import { getPublishingInfo } from './Instance/InstanceDetails/utils';
import {
- getDate,
handleKeyCommand,
isInstanceShadowCopy,
isMARCSource,
- isUserInConsortiumMode,
} from './utils';
import {
indentifierTypeNames,
+ INSTANCE_SHARING_STATUSES,
layers,
+ OKAPI_TENANT_HEADER,
REQUEST_OPEN_STATUSES,
} from './constants';
import { DataContext } from './contexts';
@@ -176,6 +172,7 @@ class ViewInstance extends React.Component {
this.log = logger.log.bind(logger);
this.state = {
+ isLoading: false,
marcRecord: null,
findInstancePluginOpened: false,
isItemsMovement: false,
@@ -187,6 +184,7 @@ class ViewInstance extends React.Component {
instancesQuickExportInProgress: false,
};
this.instanceId = null;
+ this.intervalId = null;
this.cViewHoldingsRecord = this.props.stripes.connect(ViewHoldingsRecord);
this.calloutRef = createRef();
@@ -244,6 +242,7 @@ class ViewInstance extends React.Component {
componentWillUnmount() {
this.props.mutator.allInstanceItems.reset();
+ clearInterval(this.intervalId);
}
getMARCRecord = () => {
@@ -414,6 +413,48 @@ class ViewInstance extends React.Component {
this.setState({ isImportRecordModalOpened: false });
}
+ checkInstanceSharingProgress = ({ sourceTenantId, instanceIdentifier }) => {
+ return this.props.mutator.shareInstance.GET({
+ params: { sourceTenantId, instanceIdentifier },
+ headers: {
+ [OKAPI_TENANT_HEADER]: this.props.centralTenantId,
+ 'Content-Type': 'application/json',
+ ...(this.props.okapi.token && { 'X-Okapi-Token': this.props.okapi.token }),
+ },
+ });
+ }
+
+ waitForInstanceSharingComplete = ({ sourceTenantId, instanceIdentifier, instanceTitle }) => {
+ return new Promise((resolve, reject) => {
+ this.intervalId = setInterval(() => {
+ const onError = error => {
+ this.calloutRef.current.sendCallout({
+ type: 'error',
+ message: ,
+ });
+ clearInterval(this.intervalId);
+ reject(error);
+ };
+ const onSuccess = response => {
+ const sharingStatus = response?.sharingInstances[0]?.status;
+
+ if (sharingStatus === INSTANCE_SHARING_STATUSES.COMPLETE) {
+ clearInterval(this.intervalId);
+ resolve(response);
+ }
+
+ if (sharingStatus === INSTANCE_SHARING_STATUSES.ERROR) {
+ onError(response);
+ }
+ };
+
+ this.checkInstanceSharingProgress({ sourceTenantId, instanceIdentifier })
+ .then(onSuccess)
+ .catch(onError);
+ }, 2000);
+ });
+ }
+
handleShareLocalInstance = (instance = {}) => {
const centralTenantId = this.props.centralTenantId;
const sourceTenantId = this.props.okapi.tenant;
@@ -425,21 +466,28 @@ class ViewInstance extends React.Component {
instanceIdentifier,
targetTenantId: centralTenantId,
})
+ .then(async () => {
+ this.setState({
+ isShareLocalInstanceModalOpen: false,
+ isLoading: true
+ });
+
+ await this.waitForInstanceSharingComplete({ sourceTenantId, instanceIdentifier, instanceTitle });
+ })
.then(async () => {
await this.props.refetchInstance();
+ this.setState({ isLoading: false });
this.calloutRef.current.sendCallout({
type: 'success',
message: ,
});
})
.catch(() => {
- this.calloutRef.current.sendCallout({
+ this.setState({ isShareLocalInstanceModalOpen: false });
+ this.calloutRef.current?.sendCallout({
type: 'error',
message: ,
});
- })
- .finally(() => {
- this.setState({ isShareLocalInstanceModalOpen: false });
});
}
@@ -765,26 +813,6 @@ class ViewInstance extends React.Component {
);
};
- renderPaneTitle = (instance) => {
- const {
- stripes,
- isShared,
- } = this.props;
-
- const isInstanceShared = Boolean(isShared || isInstanceShadowCopy(instance?.source));
-
- return (
-
- );
- };
-
render() {
const {
match: { params: { id, holdingsrecordid, itemid } },
@@ -792,12 +820,11 @@ class ViewInstance extends React.Component {
okapi,
onCopy,
onClose,
- paneWidth,
tagsEnabled,
updateLocation,
canUseSingleRecordImport,
- intl,
isCentralTenantPermissionsLoading,
+ isShared,
} = this.props;
const ci = makeConnectedInstance(this.props, stripes.logger);
const instance = ci.instance();
@@ -832,26 +859,7 @@ class ViewInstance extends React.Component {
handler: (e) => collapseAllSections(e, this.accordionStatusRef),
},
];
-
- if (!instance || isCentralTenantPermissionsLoading) {
- return (
- }
- dismissible
- onClose={onClose}
- >
-
-
-
-
- );
- }
+ const isInstanceLoading = this.state.isLoading || !instance || isCentralTenantPermissionsLoading;
return (
@@ -863,21 +871,13 @@ class ViewInstance extends React.Component {
>
- }
onClose={onClose}
actionMenu={this.createActionMenuGetter(instance, data)}
instance={instance}
tagsEnabled={tagsEnabled}
ref={this.accordionStatusRef}
+ isLoading={isInstanceLoading}
+ isShared={isShared}
>
{
(!holdingsrecordid && !itemid) ?
@@ -900,7 +900,7 @@ class ViewInstance extends React.Component {
{this.state.afterCreate &&
}
+ message={}
/>
}
@@ -986,11 +986,13 @@ ViewInstance.propTypes = {
GET: PropTypes.func.isRequired,
reset: PropTypes.func.isRequired,
}).isRequired,
- shareInstance: PropTypes.shape({ POST: PropTypes.func.isRequired }).isRequired,
+ shareInstance: PropTypes.shape({
+ POST: PropTypes.func.isRequired,
+ GET: PropTypes.func.isRequired,
+ }).isRequired,
}),
onClose: PropTypes.func,
onCopy: PropTypes.func,
- paneWidth: PropTypes.string.isRequired,
resources: PropTypes.shape({
allInstanceItems: PropTypes.object.isRequired,
allInstanceHoldings: PropTypes.object.isRequired,
diff --git a/src/ViewInstance.test.js b/src/ViewInstance.test.js
index e98da3da8..bf9529ff9 100644
--- a/src/ViewInstance.test.js
+++ b/src/ViewInstance.test.js
@@ -175,7 +175,10 @@ const defaultProp = {
GET: jest.fn(() => Promise.resolve([])),
reset: jest.fn()
},
- shareInstance: { POST: jest.fn() },
+ shareInstance: {
+ POST: jest.fn(),
+ GET: jest.fn(() => Promise.resolve({ sharingInstances: [{ status: 'COMPLETE' }] })),
+ },
},
onClose: mockonClose,
onCopy: jest.fn(),
@@ -259,6 +262,7 @@ describe('ViewInstance', () => {
jest.clearAllMocks();
StripesConnectedInstance.prototype.instance.mockImplementation(() => instance);
checkIfUserInCentralTenant.mockReturnValue(false);
+ useStripes().hasInterface.mockReturnValue(true);
});
it('should display action menu items', () => {
renderViewInstance();
@@ -273,7 +277,7 @@ describe('ViewInstance', () => {
describe('instance header', () => {
describe('for non-consortia users', () => {
it('should render instance title, publisher, and publication date', () => {
- defaultProp.stripes.hasInterface.mockReturnValue(false);
+ useStripes().hasInterface.mockReturnValue(false);
const { getByText } = renderViewInstance();
const expectedTitle = 'Instance • #youthaction • Information Age Publishing, Inc. • 2015';
diff --git a/src/constants.js b/src/constants.js
index ec12052fa..9e0f42c7a 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -677,3 +677,9 @@ export const CONSORTIUM_PREFIX = 'CONSORTIUM-';
export const OKAPI_TENANT_HEADER = 'X-Okapi-Tenant';
export const DEFAULT_ITEM_TABLE_SORTBY_FIELD = 'barcode';
+
+export const INSTANCE_SHARING_STATUSES = {
+ COMPLETE: 'COMPLETE',
+ ERROR: 'ERROR',
+ IN_PROGRESS: 'IN_PROGRESS',
+};