Skip to content

Commit

Permalink
Merge branch 'master' into UIIN-3160
Browse files Browse the repository at this point in the history
  • Loading branch information
OleksandrHladchenko1 authored Jan 28, 2025
2 parents b78d672 + 0179297 commit cf265c0
Show file tree
Hide file tree
Showing 11 changed files with 398 additions and 208 deletions.
16 changes: 14 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,22 @@
* Add "linked-data 1.0" interface to "optionalOkapiInterfaces". Refs UIIN-3166.
* Remove hover-over text next to "Effective call number" on the Item record detail view. Refs UIIN-3131.
* Change import of `exportToCsv` from `stripes-util` to `stripes-components`. Refs UIIN-3025.
* ECS: Disable opening item details if a user is not affiliated with item's member tenant. Fixes UIIN-3187.
* Correctly depend on `inflected`. Refs UIIN-3203.
* Detail view of created Instance record is not loaded after saving. Fixes UIIN-3194.
* Decrease the amount of rerenders in `ConsortialHoldings` component. Fixes UIIN-3196.

## [12.0.11](https://github.com/folio-org/ui-inventory/tree/v12.0.11) (2025-01-24)
[Full Changelog](https://github.com/folio-org/ui-inventory/compare/v12.0.10...v12.0.11)

* Display failure message during `Update Ownership` action when Item contains Local reference data. Fixes UIIN-3195.

## [12.0.10](https://github.com/folio-org/ui-inventory/tree/v12.0.10) (2025-01-20)
[Full Changelog](https://github.com/folio-org/ui-inventory/compare/v12.0.9...v12.0.10)

* Fix '"location name" is undefined' error when trying to open instance details on ECS. Fixes UIIN-3196.
* Display `Shared` facet when user opens "Move holdings/items to another instance" modal. Refs UIIN-3198.
* ECS - Allow 'Move holdings/items to another instance' if instance is shared. Refs UIIN-3188.
* Fix '"location name" is undefined' error when trying to open instance details on ECS. Fixes UIIN-3196.
* Decrease the amount of rerenders in `ConsortialHoldings` component. Fixes UIIN-3196.

## [12.0.9](https://github.com/folio-org/ui-inventory/tree/v12.0.9) (2025-01-13)
[Full Changelog](https://github.com/folio-org/ui-inventory/compare/v12.0.8...v12.0.9)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1031,7 +1031,6 @@
"eslint-plugin-filenames": "^1.3.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jest": "^24.0.0",
"inflected": "^2.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-intl": "^6.4.4",
Expand All @@ -1048,6 +1047,7 @@
"final-form": "^4.18.2",
"final-form-arrays": "^3.0.1",
"history": "^4.10.0",
"inflected": "^2.1.0",
"ky": "^0.23.0",
"lodash": "^4.17.4",
"moment": "~2.29.4",
Expand Down
24 changes: 19 additions & 5 deletions src/common/hooks/useInstance.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,28 @@ import { useStripes } from '@folio/stripes/core';
import useSearchInstanceByIdQuery from './useSearchInstanceByIdQuery';
import useInstanceQuery from './useInstanceQuery';

import { isUserInConsortiumMode } from '../../utils';

const useInstance = (id) => {
const stripes = useStripes();
const centralTenantId = stripes.user.user?.consortium?.centralTenantId;
const isUserInConsortium = isUserInConsortiumMode(stripes);

let isShared = false;
let instanceTenantId = stripes?.okapi.tenant;

// search instance by id (only in consortium mode) to get information about tenant and shared status
const {
refetch: refetchSearch,
isLoading: isSearchInstanceByIdLoading,
instance: _instance,
} = useSearchInstanceByIdQuery(id);
} = useSearchInstanceByIdQuery(id, { enabled: Boolean(isUserInConsortium) });

if (isUserInConsortium) {
const centralTenantId = stripes.user.user?.consortium?.centralTenantId;

const isShared = _instance?.shared;
const instanceTenantId = isShared ? centralTenantId : _instance?.tenantId;
isShared = _instance?.shared;
instanceTenantId = isShared ? centralTenantId : _instance?.tenantId;
}

const {
refetch: refetchInstance,
Expand Down Expand Up @@ -47,7 +57,11 @@ const useInstance = (id) => {
[isSearchInstanceByIdLoading, isInstanceLoading],
);
const refetch = useCallback(() => {
refetchSearch().then(() => refetchInstance());
if (isUserInConsortium) {
refetchSearch().then(refetchInstance);
} else {
refetchInstance();
}
});

return {
Expand Down
134 changes: 93 additions & 41 deletions src/common/hooks/useInstance.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,76 +3,128 @@ import {
waitFor,
} from '@folio/jest-config-stripes/testing-library/react';

import { useStripes } from '@folio/stripes/core';

import useInstance from './useInstance';

import useSearchInstanceByIdQuery from './useSearchInstanceByIdQuery';
import useInstanceQuery from './useInstanceQuery';

jest.mock('@folio/stripes/core', () => ({
...jest.requireActual('@folio/stripes/core'),
useStripes: jest.fn().mockReturnValue({
hasInterface: () => false,
okapi: { tenant: 'tenantId' },
user: {},
}),
}));

jest.mock('./useSearchInstanceByIdQuery', () => jest.fn());
jest.mock('./useInstanceQuery', () => jest.fn());

const TENANT_ID = 'tenantId';
const INSTANCE_ID_1 = 123;
const INSTANCE_ID_2 = 456;
const INSTANCE_NAME_1 = 'Test';
const INSTANCE_NAME_2 = 'Test 2';
const mockUseSearchInstanceByIdQuery = (shared = false) => {
useSearchInstanceByIdQuery.mockReturnValue({
instance: { shared, tenantId: TENANT_ID },
isLoading: false,
});
};

const mockUseInstanceQuery = (instance = {}) => {
useInstanceQuery.mockReturnValueOnce({
instance,
isLoading: false,
isFetching: false,
isError: false,
error: null,
});
};

describe('useInstance', () => {
beforeEach(() => {
jest.clearAllMocks();
});

useSearchInstanceByIdQuery.mockReturnValue({
instance: {
shared: false,
tenantId: 'tenantId',
},
it('should fetch instance data and return the instance and loading status', async () => {
mockUseSearchInstanceByIdQuery();
mockUseInstanceQuery({ id: INSTANCE_ID_1, name: INSTANCE_NAME_1 });

const { result } = renderHook(() => useInstance(INSTANCE_ID_1));

const expectedInstance = {
id: INSTANCE_ID_1,
name: INSTANCE_NAME_1,
shared: false,
tenantId: TENANT_ID,
};

await waitFor(() => expect(result.current.isLoading).toBe(false));

expect(result.current).toEqual({
instance: expectedInstance,
isLoading: false,
isFetching: false,
refetch: expect.any(Function),
isError: false,
error: null,
});
});

it('fetch instance data and return the instance and loading status', async () => {
useInstanceQuery.mockReturnValueOnce({
instance: {
id: 123,
name: 'Test',
},
isLoading: false,
it('should re-fetch instance data if id changes', async () => {
mockUseSearchInstanceByIdQuery();
mockUseInstanceQuery({ id: INSTANCE_ID_1, name: INSTANCE_NAME_1 });
mockUseInstanceQuery({ id: INSTANCE_ID_2, name: INSTANCE_NAME_2 });

const { result, rerender } = renderHook(({ id }) => useInstance(id), {
initialProps: { id: INSTANCE_ID_1 },
});
const { result } = renderHook(() => useInstance(123));

const expectedInstance = { id: 123, name: 'Test', shared: false, tenantId: 'tenantId' };
await waitFor(() => {
expect(result.current.instance).toEqual({
id: INSTANCE_ID_1,
name: INSTANCE_NAME_1,
shared: false,
tenantId: TENANT_ID,
});
});

rerender({ id: INSTANCE_ID_2 });

await waitFor(() => {
expect(result.current.isLoading).toBe(false);
expect(result.current.instance).toEqual(expectedInstance);
expect(result.current.instance).toEqual({
id: INSTANCE_ID_2,
name: INSTANCE_NAME_2,
shared: false,
tenantId: TENANT_ID,
});
});
});
it('re-fetch instance data if id changes', async () => {
useInstanceQuery.mockReturnValueOnce({
instance: {
id: 123,
name: 'Test',
},
isLoading: false,
}).mockReturnValueOnce({
instance: {
id: 456,
name: 'Test 2',
},
isLoading: false,
});
const { result, rerender } = renderHook(({ id }) => useInstance(id), {
initialProps: { id: 123 },
});

const expectedInstance = { id: 123, name: 'Test', shared: false, tenantId: 'tenantId' };
it('should correctly handle consortium mode', async () => {
const CENTRAL_TENANT_ID = 'centralTenant';

await waitFor(() => {
expect(result.current.isLoading).toBe(false);
expect(result.current.instance).toEqual(expectedInstance);
useStripes.mockReturnValue({
hasInterface: () => true,
okapi: { tenant: TENANT_ID },
user: { user: { consortium: { centralTenantId: CENTRAL_TENANT_ID } } },
});

rerender({ id: 456 });
mockUseSearchInstanceByIdQuery(true);
mockUseInstanceQuery({ id: INSTANCE_ID_1, name: INSTANCE_NAME_1 });

const expectedInstanceAfterRerender = { id: 456, name: 'Test 2', shared: false, tenantId: 'tenantId' };
const { result } = renderHook(() => useInstance(INSTANCE_ID_1));

await waitFor(() => {
expect(result.current.isLoading).toBe(false);
expect(result.current.instance).toEqual(expectedInstanceAfterRerender);
expect(result.current.instance).toEqual({
id: INSTANCE_ID_1,
name: INSTANCE_NAME_1,
shared: true,
tenantId: CENTRAL_TENANT_ID,
});
});
});
});
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
import { useQuery } from 'react-query';

import {
useOkapiKy,
useNamespace,
useOkapiKy,
} from '@folio/stripes/core';

const useSearchInstanceByIdQuery = (instanceId) => {
const NO_RECORDS_FOUND_ERROR = 'No records found';

const useSearchInstanceByIdQuery = (instanceId, { enabled = true } = {}) => {
const ky = useOkapiKy();
const [namespace] = useNamespace({ key: 'search-instance' });

const queryFn = async () => {
const response = await ky.get(`search/instances?query=id==${instanceId}`).json();

if (response.totalRecords === 0) {
throw new Error(NO_RECORDS_FOUND_ERROR); // this triggers the retry mechanism
}
return response;
};

const { refetch, isLoading, data = {} } = useQuery(
{
queryKey: [namespace, instanceId],
queryFn: () => ky.get(`search/instances?query=id==${instanceId}`).json(),
enabled: Boolean(instanceId),
queryFn,
enabled: Boolean(enabled && instanceId),
retry: (failureCount, error) => {
if (failureCount >= 3) return false; // stop after 3 attempts
if (error.message === NO_RECORDS_FOUND_ERROR) return true; // retry if no records
return false; // stop retrying on other errors
},
retryDelay: () => 3000,
},
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
} from 'react-query';
import {
renderHook,
act,
waitFor,
} from '@folio/jest-config-stripes/testing-library/react';
import { useOkapiKy } from '@folio/stripes/core';

Expand All @@ -23,23 +23,23 @@ const wrapper = ({ children }) => (
const instanceId = 'instanceId';

describe('useSearchInstanceByIdQuery', () => {
it('should fetch instance', async () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('should fetch instance successfully', async () => {
useOkapiKy.mockClear().mockReturnValue({
get: () => ({
json: () => ({
instances: [{
id: instanceId,
}]
get: jest.fn(() => ({
json: jest.fn().mockResolvedValue({
totalRecords: 1,
instances: [{ id: instanceId }],
}),
}),
})),
});

const { result } = renderHook(() => useSearchInstanceByIdQuery(instanceId), { wrapper });

await act(() => {
return !result.current.isLoading;
});

await waitFor(() => expect(result.current.isLoading).toBe(false));
expect(result.current.instance.id).toBe(instanceId);
});
});
Loading

0 comments on commit cf265c0

Please sign in to comment.