Skip to content

Commit

Permalink
UIBULKED-403: Integrate query-plugin with bulk-edit API (#461)
Browse files Browse the repository at this point in the history
  • Loading branch information
UladzislauKutarkin authored Jan 26, 2024
1 parent 3ddd679 commit a3369fa
Show file tree
Hide file tree
Showing 12 changed files with 276 additions and 66 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
* [UIBULKED-400](https://issues.folio.org/browse/UIBULKED-400) Enabling Build query button on Query tab.
* [UIBULKED-399](https://issues.folio.org/browse/UIBULKED-399) Update label verbiage on the Identifier tab.
* [UIBULKED-407](https://issues.folio.org/browse/UIBULKED-407) When selecting action "Find" in Bulk Edit, the last dropdown on the row moves to the right.
* [UIBULKED-403](https://issues.folio.org/browse/UIBULKED-403) Integrate query-plugin with bulk-edit API.

## [4.0.0](https://github.com/folio-org/ui-bulk-edit/tree/v4.0.0) (2023-10-12)

Expand Down
27 changes: 21 additions & 6 deletions src/components/BulkEditList/BulkEditList.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@ import { QueryClientProvider } from 'react-query';

import '../../../test/jest/__mock__';

import userEvent from '@testing-library/user-event';
import { useOkapiKy } from '@folio/stripes/core';
import { queryClient } from '../../../test/jest/utils/queryClient';

import { CAPABILITIES, IDENTIFIERS, CRITERIA } from '../../constants';

import { BulkEditList } from './BulkEditList';

jest.mock('./BulkEditListFilters/BulkEditListFilters', () => {
return {
BulkEditListFilters: jest.fn().mockReturnValue('BulkEditListFilters'),
};
});
jest.mock('../BulkEditLogs/BulkEditLogs', () => {
return jest.fn().mockReturnValue('BulkEditLogs');
});
Expand All @@ -24,6 +21,10 @@ jest.mock('./BulkEditListResult', () => {
BulkEditListResult: jest.fn().mockReturnValue('BulkEditListResult'),
};
});

jest.mock('@folio/stripes/core', () => ({
useOkapiKy: jest.fn(),
}));
jest.mock('./BulkEditListResult/BulkEditManualUploadModal', () => {
return {
BulkEditManualUploadModal: jest.fn().mockReturnValue('BulkEditManualUploadModal'),
Expand Down Expand Up @@ -53,7 +54,7 @@ describe('BulkEditList', () => {
it('should display Filters pane', async () => {
renderBulkEditList({ criteria: CRITERIA.LOGS });

expect(screen.getByText('BulkEditListFilters')).toBeVisible();
expect(screen.getByText(/holdings/i)).toBeVisible();
});

it('should display Logs pane when criteria is logs', async () => {
Expand All @@ -67,4 +68,18 @@ describe('BulkEditList', () => {

expect(screen.getByText(/BulkEditListResult/)).toBeVisible();
});

it('should display Bulk edit query', async () => {
useOkapiKy.mockReturnValue({
get: jest.fn().mockResolvedValue({ json: jest.fn().mockResolvedValue({}) }),
post: jest.fn().mockResolvedValue({ json: jest.fn().mockResolvedValue({}) }),
delete: jest.fn().mockResolvedValue({ json: jest.fn().mockResolvedValue({}) }),
});
renderBulkEditList({ criteria: CRITERIA.QUERY });

userEvent.click(screen.getByText(/Get query/));
userEvent.click(screen.getByText(/Cancel query/));

expect(screen.getByText(/holdings/i)).toBeVisible();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@ import {
import { RootContext } from '../../../context/RootContext';
import {
useUpload,
useBulkOperationStart,
useBulkOperationStart, useQueryPlugin,
} from '../../../hooks/api';
import { useBulkPermissions, useLocationFilters } from '../../../hooks';
import { LogsFilters } from './LogsFilters/LogsFilters';
import { getCapabilityOptions, isCapabilityDisabled } from '../../../utils/helpers';
import FilterTabs from './FilterTabs/FilterTabs';
import Capabilities from './Capabilities/Capabilities';
import { getIsDisabledByPerm } from './utils/getIsDisabledByPerm';
import { useRecordTypes } from '../../../hooks/api/useRecordTypes';
import { getRecordType } from '../../../utils/getRecordType';

export const BulkEditListFilters = ({
filters,
Expand Down Expand Up @@ -65,6 +67,7 @@ export const BulkEditListFilters = ({
const initialCapabilities = search.get('capabilities');
const initialFileName = search.get('fileName');
const initialStep = search.get('step');
const initialRecordType = search.get('recordTypes');
const logFilters = Object.values(FILTERS).map((el) => search.getAll(el));

const isQuery = criteria === CRITERIA.QUERY;
Expand All @@ -83,6 +86,7 @@ export const BulkEditListFilters = ({
criteria: CRITERIA.LOGS,
fileName: initialFileName,
step: initialStep,
recordTypes: initialRecordType,
};

const [
Expand All @@ -105,6 +109,16 @@ export const BulkEditListFilters = ({
const { fileUpload, isLoading } = useUpload();
const { bulkOperationStart } = useBulkOperationStart();

const { recordTypes } = useRecordTypes();

const {
entityTypeDataSource,
queryDetailsDataSource,
testQueryDataSource,
getParamsSource,
cancelQueryDataSource,
} = useQueryPlugin(initialRecordType);

const handleChange = (value, field) => setFilters(prev => ({
...prev, [field]: value,
}));
Expand All @@ -118,13 +132,16 @@ export const BulkEditListFilters = ({
recordIdentifier: '',
}));

const selected = recordTypes?.find(type => type.label === getRecordType(value))?.id;

history.replace({
pathname: '/bulk-edit',
search: buildSearch({
capabilities: value,
identifier: null,
step: null,
fileName: null,
recordTypes: selected
}, location.search),
});

Expand Down Expand Up @@ -312,6 +329,13 @@ export const BulkEditListFilters = ({
componentType="builder"
type="query-builder"
disabled={isQueryBuilderDisabled}
key={capabilities}
entityTypeDataSource={entityTypeDataSource}
testQueryDataSource={testQueryDataSource}
getParamsSource={getParamsSource}
queryDetailsDataSource={queryDetailsDataSource}
onQueryRunFail={() => {}}
cancelQueryDataSource={cancelQueryDataSource}
/>
</>
)}
Expand Down
5 changes: 5 additions & 0 deletions src/constants/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ export const CAPABILITIES = {
USER: 'USER',
};

export const RECORD_TYPES = {
ITEM: 'Items',
USER: 'Users'
};

export const IDENTIFIERS = {
ID: 'ID',
BARCODE: 'BARCODE',
Expand Down
1 change: 1 addition & 0 deletions src/hooks/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from './useUserGroupsMap';
export * from './usePatronGroup';
export * from './useLoanTypes';
export * from './useBulkOperationDetails';
export * from './useQueryPlugin';
50 changes: 50 additions & 0 deletions src/hooks/api/useQueryPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useOkapiKy } from '@folio/stripes/core';

export const useQueryPlugin = (recordType) => {
const ky = useOkapiKy();

const entityTypeDataSource = async () => {
if (recordType) {
const response = ky.get(`entity-types/${recordType}`);
return response.json();
}
return null;
};

const queryDetailsDataSource = async ({ queryId, includeContent, offset, limit }) => {
const searchParams = {
includeResults: includeContent,
offset,
limit
};

const response = ky.get(`query/${queryId}`, { searchParams });

return response.json();
};

const testQueryDataSource = async ({ fqlQuery }) => {
const response = ky.post('query', { json: {
entityTypeId: recordType,
fqlQuery: JSON.stringify(fqlQuery)
} });
return response.json();
};

const getParamsSource = async ({ entityTypeId, columnName, searchValue }) => {
const response = ky.get(`entity-types/${entityTypeId}/columns/${columnName}/values?search=${searchValue}`);
return response.json();
};

const cancelQueryDataSource = async ({ queryId }) => {
return ky.delete(`query/${queryId}`);
};

return {
entityTypeDataSource,
queryDetailsDataSource,
testQueryDataSource,
getParamsSource,
cancelQueryDataSource,
};
};
23 changes: 23 additions & 0 deletions src/hooks/api/useRecordTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useOkapiKy } from '@folio/stripes/core';
import { useQuery } from 'react-query';

const ENTITY_TYPE_HASH = 'entityType';

export const useRecordTypes = () => {
const ky = useOkapiKy();
const { data, isLoading, error } = useQuery({
queryKey: [ENTITY_TYPE_HASH],
queryFn: async () => {
const response = await ky.get('entity-types');

return response.json();
},
refetchOnWindowFocus: false,
});

return ({
recordTypes: data,
isLoading,
error
});
};
63 changes: 63 additions & 0 deletions src/hooks/api/useRecordTypes.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { renderHook } from '@testing-library/react-hooks';
import { useOkapiKy } from '@folio/stripes/core';
import { useQuery } from 'react-query';
import { act } from '@testing-library/react';
import { useRecordTypes } from './useRecordTypes';

jest.mock('@folio/stripes/core', () => ({
useOkapiKy: jest.fn(),
}));

jest.mock('react-query', () => ({
...jest.requireActual('react-query'),
useQuery: jest.fn(),
}));

describe('useRecordTypes', () => {
it('should return recordTypes, loading state, and error state', async () => {
useOkapiKy.mockReturnValue({
get: jest.fn().mockResolvedValue({ json: jest.fn().mockResolvedValue({}) }),
});

useQuery.mockReturnValue({
data: [
{
'id': '1',
'label': 'Loans'
},
{
'id': '2',
'label': 'Users'
},
{
'id': '3',
'label': 'Items'
}
],
isLoading: false,
error: null,
});

let result;
await act(async () => {
result = renderHook(() => useRecordTypes()).result;
});

expect(result.current.recordTypes).toEqual([
{
'id': '1',
'label': 'Loans'
},
{
'id': '2',
'label': 'Users'
},
{
'id': '3',
'label': 'Items'
}
]);
expect(result.current.isLoading).toBeFalsy();
expect(result.current.error).toBeNull();
});
});
1 change: 1 addition & 0 deletions src/hooks/useLocationFilters.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const useLocationFilters = ({
criteria: initialFilter.criteria,
step: initialFilter.step,
fileName: initialFilter.fileName,
recordTypes: initialFilter.recordTypes
}),
});
},
Expand Down
9 changes: 9 additions & 0 deletions src/utils/getRecordType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { CAPABILITIES, RECORD_TYPES } from '../constants';

export const getRecordType = (capability) => {
if (Object.hasOwn(CAPABILITIES, capability)) {
return RECORD_TYPES[CAPABILITIES[capability]];
} else {
return null;
}
};
18 changes: 18 additions & 0 deletions src/utils/getRecordType.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { getRecordType } from './getRecordType';
import { CAPABILITIES, RECORD_TYPES } from '../constants';

describe('getRecordType', () => {
it('should return the correct record type for a valid capability', () => {
// Test each capability
Object.keys(CAPABILITIES).forEach((capability) => {
const result = getRecordType(capability);
expect(result).toEqual(RECORD_TYPES[CAPABILITIES[capability]]);
});
});

it('should return null for an invalid capability', () => {
const invalidCapability = 'INVALID_CAPABILITY';
const result = getRecordType(invalidCapability);
expect(result).toBeNull();
});
});
Loading

0 comments on commit a3369fa

Please sign in to comment.