Skip to content

Commit

Permalink
UISACQCOMP-228 Add more reusable hooks and utilities (#828)
Browse files Browse the repository at this point in the history
* UISACQCOMP-228 Add more reusable hooks and utilities

* UISACQCOMP-228 Add shared API utils to fetch ACQ entities

* add constant for allRecords cql

* add tests

* add unit test

* update date query builder
  • Loading branch information
usavkov-epam authored Nov 11, 2024
1 parent ad2522d commit d543693
Show file tree
Hide file tree
Showing 23 changed files with 366 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## (6.1.0 IN PROGRESS)

* Add more reusable hooks and utilities. Refs UISACQCOMP-228.

## (6.0.1 IN PROGRESS)

* Added new `subscribeOnReset` prop to `<AcqDateRangeFilter>`. Refs UISACQCOMP-227.
Expand Down
10 changes: 7 additions & 3 deletions lib/AcqList/utils/queryUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const buildArrayFieldQuery = (filterKey, filterValue) => {
};

export const buildDateRangeQuery = (filterKey, filterValue) => {
const [from, to] = filterValue.split(':');
const [from, to] = (Array.isArray(filterValue) ? filterValue.toString() : filterValue).split(':');
const start = moment(from).startOf('day').format(DATE_RANGE_FILTER_FORMAT);
const end = moment(to).endOf('day').format(DATE_RANGE_FILTER_FORMAT);

Expand All @@ -47,7 +47,7 @@ export const buildNumberRangeQuery = (filterKey, filterValue) => {
};

export const buildDateTimeRangeQuery = (filterKey, filterValue) => {
const [from, to] = filterValue.split(':');
const [from, to] = (Array.isArray(filterValue) ? filterValue.toString() : filterValue).split(':');
const start = moment(from).startOf('day').utc().format(DATE_RANGE_FILTER_FORMAT);
const end = moment(to).endOf('day').utc().format(DATE_RANGE_FILTER_FORMAT);

Expand All @@ -60,7 +60,11 @@ export const getFilterParams = (queryParams) => omit(
);

export const getFiltersCount = (filters) => {
return filters && Object.keys(getFilterParams(filters)).filter(k => filters[k] !== undefined).length;
return filters && Object.keys(getFilterParams(filters)).filter((k) => {
return k === SEARCH_PARAMETER
? Boolean(filters[k])
: filters[k] !== undefined;
}).length;
};

export const buildFilterQuery = (
Expand Down
4 changes: 3 additions & 1 deletion lib/CustomFieldsFilters/CustomFieldsFilter.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ const CustomFieldsFilter = ({
customField,
disabled,
onChange,
name: filterNameProp,
}) => {
const FilterComponent = customFieldTypeToFilterMap[customField.type];
const { refId, name, selectField } = customField;
const values = selectField?.options?.values ?? [{ id: 'true', value: name }];
const filterName = `${CUSTOM_FIELDS_FILTER}.${refId}`;
const filterName = `${filterNameProp || CUSTOM_FIELDS_FILTER}.${refId}`;
const dataOptions = values.map(({ id: value, value: label }) => ({
label,
value,
Expand Down Expand Up @@ -85,6 +86,7 @@ CustomFieldsFilter.propTypes = {
customField: PropTypes.object,
disabled: PropTypes.bool,
onChange: PropTypes.func.isRequired,
name: PropTypes.string,
};

CustomFieldsFilter.defaultProps = {
Expand Down
5 changes: 4 additions & 1 deletion lib/CustomFieldsFilters/CustomFieldsFilters.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ export const CustomFieldsFilters = ({
activeFilters,
customFields,
onChange,
name,
...props
}) => {
if (!customFields) return null;

return customFields.map((customField) => (
<CustomFieldsFilter
activeFilters={activeFilters[`${CUSTOM_FIELDS_FILTER}.${customField.refId}`]}
name={name}
activeFilters={activeFilters[`${name || CUSTOM_FIELDS_FILTER}.${customField.refId}`]}
customField={customField}
key={`custom-field-${customField.id}`}
onChange={onChange}
Expand All @@ -28,4 +30,5 @@ CustomFieldsFilters.propTypes = {
customFields: PropTypes.arrayOf(PropTypes.object),
disabled: PropTypes.bool,
onChange: PropTypes.func.isRequired,
name: PropTypes.string,
};
1 change: 1 addition & 0 deletions lib/constants/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const PREFIXES_API = 'orders/configuration/prefixes';
export const PRIVILEGED_CONTACTS_API = 'organizations-storage/privileged-contacts';
export const PUBLICATIONS_API = 'publications';
export const REQUESTS_API = 'circulation/requests';
export const RECEIVING_TITLES_API = 'orders/titles';
export const ROUTING_LIST_API = 'orders/routing-lists';
export const SEARCH_API = 'search';
export const SUFFIXES_API = 'orders/configuration/suffixes';
Expand Down
2 changes: 2 additions & 0 deletions lib/constants/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@ export const CENTRAL_ORDERING_DEFAULT_RECEIVING_SEARCH = {
centralDefault: 'Central default',
activeAffiliationDefault: 'Active affiliation default',
};

export const ALL_RECORDS_CQL = 'cql.allRecords=1';
1 change: 1 addition & 0 deletions lib/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './useAccordionToggle';
export * from './useAcqMethodsOptions';
export * from './useAcquisitionMethods';
export * from './useAcquisitionUnits';
export * from './useAddresses';
export * from './useAsyncDebounce';
export * from './useCampuses';
export * from './useCampusesQuery';
Expand Down
1 change: 1 addition & 0 deletions lib/hooks/useAddresses/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useAddresses } from './useAddresses';
57 changes: 57 additions & 0 deletions lib/hooks/useAddresses/useAddresses.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useQuery } from 'react-query';

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

import {
CONFIG_API,
LIMIT_MAX,
} from '../../constants';
import { getAddresses } from '../../utils';

const MODULE_TENANT = 'TENANT';
const CONFIG_ADDRESSES = 'tenant.addresses';
const DEFAULT_DATA = [];

export const useAddresses = (options = {}) => {
const {
enabled = true,
tenantId,
...queryOptions
} = options;

const [namespace] = useNamespace({ key: 'acquisitions-units' });
const ky = useOkapiKy({ tenant: tenantId });

const searchParams = {
query: `(module=${MODULE_TENANT} and configName=${CONFIG_ADDRESSES})`,
limit: LIMIT_MAX,
};

const {
data,
isFetching,
isLoading,
} = useQuery({
queryKey: [namespace, tenantId],
queryFn: ({ signal }) => {
return ky.get(CONFIG_API, { searchParams, signal })
.json()
.then(({ configs, totalRecords }) => ({
addresses: getAddresses(configs),
totalRecords,
}));
},
enabled,
...queryOptions,
});

return ({
addresses: data?.addresses || DEFAULT_DATA,
totalRecords: data?.totalRecords || 0,
isFetching,
isLoading,
});
};
51 changes: 51 additions & 0 deletions lib/hooks/useAddresses/useAddresses.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { renderHook } from '@testing-library/react-hooks';
import {
QueryClient,
QueryClientProvider,
} from 'react-query';

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

import { getAddresses } from '../../utils';
import { useAddresses } from './useAddresses';

jest.mock('../../utils', () => ({
getAddresses: jest.fn(),
}));

const queryClient = new QueryClient();
const wrapper = ({ children }) => (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);

describe('useAddresses', () => {
const mockGet = jest.fn();

beforeEach(() => {
jest.clearAllMocks();

useOkapiKy.mockReturnValue({
get: mockGet,
});
});

it('should return addresses and totalRecords', async () => {
const configs = [{ value: 'address1' }, { value: 'address2' }];
const totalRecords = 2;

mockGet.mockReturnValue({
json: jest.fn().mockResolvedValue({ configs, totalRecords }),
});

getAddresses.mockReturnValue(['address1', 'address2']);

const { result, waitFor } = renderHook(() => useAddresses(), { wrapper });

await waitFor(() => !result.current.isLoading);

expect(result.current.addresses).toEqual(['address1', 'address2']);
expect(result.current.totalRecords).toBe(2);
});
});
113 changes: 113 additions & 0 deletions lib/utils/api/api.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import {
LIMIT_PARAMETER,
SEARCH_PARAMETER,
} from '../../AcqList/constants';
import {
LIMIT_MAX,
LINES_API,
ORDER_PIECES_API,
ORDERS_API,
RECEIVING_TITLES_API,
VENDORS_API,
} from '../../constants';

import { fetchOrderLines } from './fetchOrderLines';
import { fetchOrderLinesByIds } from './fetchOrderLinesByIds';
import { fetchOrders } from './fetchOrders';
import { fetchOrdersByIds } from './fetchOrdersByIds';
import { fetchOrganizations } from './fetchOrganizations';
import { fetchOrganizationsByIds } from './fetchOrganizationsByIds';
import { fetchPieces } from './fetchPieces';
import { fetchReceivingTitles } from './fetchReceivingTitles';
import { fetchReceivingTitlesByIds } from './fetchReceivingTitlesByIds';

const httpClient = {
get: jest.fn(() => ({
json: jest.fn(() => Promise.resolve({})),
})),
};

const ids = ['1', '2'];
const options = {};
const searchParams = {
[LIMIT_PARAMETER]: LIMIT_MAX,
[SEARCH_PARAMETER]: 'id==1 or id==2',
};

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

describe('fetchOrderLines', () => {
it('should fetch order lines', async () => {
await fetchOrderLines(httpClient)(options);

expect(httpClient.get).toHaveBeenCalledWith(LINES_API, options);
});
});

describe('fetchOrderLinesByIds', () => {
it('should fetch order lines by ids', async () => {
await fetchOrderLinesByIds(httpClient)(ids, options);

expect(httpClient.get).toHaveBeenCalledWith(LINES_API, { searchParams });
});
});

describe('fetchOrders', () => {
it('should fetch orders', async () => {
await fetchOrders(httpClient)(options);

expect(httpClient.get).toHaveBeenCalledWith(ORDERS_API, options);
});
});

describe('fetchOrdersByIds', () => {
it('should fetch orders by ids', async () => {
await fetchOrdersByIds(httpClient)(ids, options);

expect(httpClient.get).toHaveBeenCalledWith(ORDERS_API, { searchParams });
});
});

describe('fetchOrganizations', () => {
it('should fetch organizations', async () => {
await fetchOrganizations(httpClient)(options);

expect(httpClient.get).toHaveBeenCalledWith(VENDORS_API, options);
});
});

describe('fetchOrganizationsByIds', () => {
it('should fetch organizations by ids', async () => {
await fetchOrganizationsByIds(httpClient)(ids, options);

expect(httpClient.get).toHaveBeenCalledWith(VENDORS_API, { searchParams });
});
});

describe('fetchPieces', () => {
it('should fetch pieces', async () => {
await fetchPieces(httpClient)(options);

expect(httpClient.get).toHaveBeenCalledWith(ORDER_PIECES_API, options);
});
});

describe('fetchReceivingTitles', () => {
it('should fetch receiving titles', async () => {
await fetchReceivingTitles(httpClient)(options);

expect(httpClient.get).toHaveBeenCalledWith(RECEIVING_TITLES_API, options);
});
});

describe('fetchReceivingTitlesByIds', () => {
it('should fetch receiving titles by ids', async () => {
await fetchReceivingTitlesByIds(httpClient)(ids, options);

expect(httpClient.get).toHaveBeenCalledWith(RECEIVING_TITLES_API, { searchParams });
});
});
});
5 changes: 5 additions & 0 deletions lib/utils/api/fetchOrderLines.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { LINES_API } from '../../constants';

export const fetchOrderLines = (httpClient) => async (options) => {
return httpClient.get(LINES_API, options).json();
};
19 changes: 19 additions & 0 deletions lib/utils/api/fetchOrderLinesByIds.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { batchRequest } from '../batchFetch';
import { fetchOrderLines } from './fetchOrderLines';

export const fetchOrderLinesByIds = (httpClient) => {
return (ids, options) => {
const requestFn = ({ params }) => {
return fetchOrderLines(httpClient)({ ...options, searchParams: params });
};

return batchRequest(requestFn, ids).then((responses) => {
return responses.reduce((acc, response) => {
return {
poLines: acc.poLines.concat(response.poLines),
totalRecords: acc.totalRecords + response.totalRecords,
};
}, { poLines: [], totalRecords: 0 });
});
};
};
5 changes: 5 additions & 0 deletions lib/utils/api/fetchOrders.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ORDERS_API } from '../../constants';

export const fetchOrders = (httpClient) => async (options) => {
return httpClient.get(ORDERS_API, options).json();
};
19 changes: 19 additions & 0 deletions lib/utils/api/fetchOrdersByIds.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { batchRequest } from '../batchFetch';
import { fetchOrders } from './fetchOrders';

export const fetchOrdersByIds = (httpClient) => {
return (ids, options) => {
const requestFn = ({ params }) => {
return fetchOrders(httpClient)({ ...options, searchParams: params });
};

return batchRequest(requestFn, ids).then((responses) => {
return responses.reduce((acc, response) => {
return {
purchaseOrders: acc.purchaseOrders.concat(response.purchaseOrders),
totalRecords: acc.totalRecords + response.totalRecords,
};
}, { purchaseOrders: [], totalRecords: 0 });
});
};
};
7 changes: 7 additions & 0 deletions lib/utils/api/fetchOrganizations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { VENDORS_API } from '../../constants';

export const fetchOrganizations = (httpClient) => {
return async (options) => {
return httpClient.get(VENDORS_API, options).json();
};
};
Loading

0 comments on commit d543693

Please sign in to comment.