Skip to content

Commit

Permalink
UIIN-3194: Detail view of created Instance record is not loaded after…
Browse files Browse the repository at this point in the history
… saving (#2723)
  • Loading branch information
mariia-aloshyna authored Jan 24, 2025
1 parent 9869a36 commit cf7a882
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 62 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* ECS: Disable opening item details if a user is not affiliated with item's member tenant. Fixes UIIN-3187.
* Display failure message during `Update Ownership` action when Item contains Local reference data. Fixes UIIN-3195.
* Correctly depend on `inflected`. Refs UIIN-3203.
* Detail view of created Instance record is not loaded after saving. Fixes UIIN-3194.

## [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)
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);
});
});

0 comments on commit cf7a882

Please sign in to comment.