-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
UIPFU-77 - Add 'User assignment status' filter group #249
Changes from 8 commits
0024421
b2d41d4
5b390ff
a309cc4
e5287a2
a7bba68
5ad35b6
a016d97
a3adb92
16e11ee
4bd8c31
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,27 @@ | ||
{ | ||
"parser": "@babel/eslint-parser", | ||
"extends": "@folio/eslint-config-stripes" | ||
"extends": "@folio/eslint-config-stripes", | ||
"overrides": [ | ||
{ | ||
"files": [ "src/**/tests/*", "*.test.js", "test/**/*" ], | ||
"rules": { | ||
"react/prop-types": "off", | ||
"import/prefer-default-export": "off" | ||
} | ||
} | ||
], | ||
"env": { | ||
"jest": true | ||
}, | ||
"settings": { | ||
"import/resolver": { | ||
"alias": { | ||
"map": [ | ||
["__mock__", "./test/jest/__mock__"], | ||
["fixtures", "./test/jest/fixtures"], | ||
["helpers", "./test/jest/helpers"] | ||
] | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
node_modules | ||
artifacts | ||
artifacts | ||
junit.xml |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
const path = require('path'); | ||
const config = require('@folio/jest-config-stripes'); | ||
|
||
module.exports = { | ||
...config, | ||
setupFiles: [ | ||
...config.setupFiles, | ||
path.join(__dirname, './test/jest/setupFiles.js'), | ||
], | ||
}; |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -8,7 +8,13 @@ import { | |||||
StripesConnectedSource, | ||||||
} from '@folio/stripes/smart-components'; | ||||||
|
||||||
import filterConfig from './filterConfig'; | ||||||
import filterConfig, { filterConfigWithUserAssignedStatus } from './filterConfig'; | ||||||
import { updateResourceData } from './utils'; | ||||||
import { | ||||||
ASSIGNED_FILTER_KEY, | ||||||
UNASSIGNED_FILTER_KEY, | ||||||
UAS, | ||||||
} from './constants'; | ||||||
|
||||||
const INITIAL_RESULT_COUNT = 30; | ||||||
const RESULT_COUNT_INCREMENT = 30; | ||||||
|
@@ -32,6 +38,28 @@ const compileQuery = template( | |||||
{ interpolate: /%{([\s\S]+?)}/g } | ||||||
); | ||||||
|
||||||
export function buildQuery(queryParams, pathComponents, resourceData, logger, props) { | ||||||
const filters = props.initialSelectedUsers ? filterConfigWithUserAssignedStatus : filterConfig; | ||||||
const updatedResourceData = props.initialSelectedUsers && resourceData?.query?.filters?.includes(`${UAS}`) ? updateResourceData(resourceData) : resourceData; | ||||||
|
||||||
return makeQueryFunction( | ||||||
'cql.allRecords=1', | ||||||
(parsedQuery, _, localProps) => localProps.query.query.trim().split(/\s+/).map(query => compileQuery({ query })).join(' and '), | ||||||
{ | ||||||
// the keys in this object must match those passed to | ||||||
// SearchAndSort's columnMapping prop | ||||||
'active': 'active', | ||||||
'name': 'personal.lastName personal.firstName', | ||||||
'patronGroup': 'patronGroup.group', | ||||||
'username': 'username', | ||||||
'barcode': 'barcode', | ||||||
'email': 'personal.email', | ||||||
}, | ||||||
filters, | ||||||
2, | ||||||
)(queryParams, pathComponents, updatedResourceData, logger, props); | ||||||
} | ||||||
|
||||||
class UserSearchContainer extends React.Component { | ||||||
static manifest = Object.freeze({ | ||||||
initializedFilterConfig: { initialValue: false }, | ||||||
|
@@ -46,24 +74,7 @@ class UserSearchContainer extends React.Component { | |||||
perRequest: 100, | ||||||
path: 'users', | ||||||
GET: { | ||||||
params: { | ||||||
query: makeQueryFunction( | ||||||
'cql.allRecords=1', | ||||||
(parsedQuery, props, localProps) => localProps.query.query.trim().split(/\s+/).map(query => compileQuery({ query })).join(' and '), | ||||||
{ | ||||||
// the keys in this object must match those passed to | ||||||
// SearchAndSort's columnMapping prop | ||||||
'active': 'active', | ||||||
'name': 'personal.lastName personal.firstName', | ||||||
'patronGroup': 'patronGroup.group', | ||||||
'username': 'username', | ||||||
'barcode': 'barcode', | ||||||
'email': 'personal.email', | ||||||
}, | ||||||
filterConfig, | ||||||
2, | ||||||
), | ||||||
}, | ||||||
params: { query: buildQuery }, | ||||||
staticFallback: { params: {} }, | ||||||
}, | ||||||
}, | ||||||
|
@@ -113,6 +124,7 @@ class UserSearchContainer extends React.Component { | |||||
*/ | ||||||
// eslint-disable-next-line react/no-unused-prop-types | ||||||
tenantId: PropTypes.string.isRequired, | ||||||
initialSelectedUsers: PropTypes.object, | ||||||
} | ||||||
|
||||||
constructor(props) { | ||||||
|
@@ -168,6 +180,46 @@ class UserSearchContainer extends React.Component { | |||||
return get(this.props.resources, 'query', {}); | ||||||
} | ||||||
|
||||||
getUsers = () => { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would suggest to move this function outside the component. I will much easier to test all |
||||||
const { | ||||||
resources, | ||||||
initialSelectedUsers, | ||||||
} = this.props; | ||||||
const fetchedUsers = get(resources, 'records.records', []); | ||||||
const activeFilters = get(resources, 'query.filters', ''); | ||||||
|
||||||
if (activeFilters.includes(`${UAS}`)) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
const assignedUsers = Object.values(initialSelectedUsers); | ||||||
const assignedUserIds = Object.keys(initialSelectedUsers); | ||||||
const hasBothUASFilters = activeFilters.includes(`${ASSIGNED_FILTER_KEY}`) && activeFilters.includes(`${UNASSIGNED_FILTER_KEY}`); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
const hasNoneOfUASFilters = !activeFilters.includes(`${ASSIGNED_FILTER_KEY}`) && !activeFilters.includes(`${UNASSIGNED_FILTER_KEY}`); | ||||||
|
||||||
if (hasBothUASFilters || hasNoneOfUASFilters) { | ||||||
return fetchedUsers; | ||||||
} | ||||||
const uasFilterValue = activeFilters.split(',').filter(f => f.includes(`${UAS}`))[0].split('.')[1]; | ||||||
|
||||||
let otherFilterGroups = activeFilters.split(',').filter(f => !f.includes(`${UAS}`)).map(f => f.split('.')[0]); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need check other filter values because this result list is alredy returned from back-end for a search with these filters. We just need to select from fetched users assigned or not assigned based on uas filter value
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you again for your review! |
||||||
if (otherFilterGroups.indexOf('pg') !== -1) { | ||||||
otherFilterGroups = otherFilterGroups.with(otherFilterGroups.indexOf('pg'), 'patronGroup'); | ||||||
} | ||||||
|
||||||
let otherFilterValues = activeFilters.split(',').filter(f => !f.includes(`${UAS}`)).map(f => f.split('.')[1]); | ||||||
if (otherFilterValues.indexOf('active') !== -1) { | ||||||
otherFilterValues = otherFilterValues.with(otherFilterValues.indexOf('active'), true); | ||||||
} | ||||||
|
||||||
if (uasFilterValue === 'Assigned') { | ||||||
if (!otherFilterGroups.length) return assignedUsers; | ||||||
return assignedUsers.filter(u => otherFilterGroups.every((g, i) => u[g] === otherFilterValues[i])); | ||||||
} | ||||||
const unAssignedUsers = fetchedUsers.filter(u => !assignedUserIds.includes(u.id)); | ||||||
if (!otherFilterGroups.length) return unAssignedUsers; | ||||||
return unAssignedUsers.filter(u => otherFilterGroups.every((g, i) => u[g] === otherFilterValues[i])); | ||||||
} | ||||||
return fetchedUsers; | ||||||
} | ||||||
|
||||||
render() { | ||||||
const { | ||||||
resources, | ||||||
|
@@ -188,7 +240,7 @@ class UserSearchContainer extends React.Component { | |||||
resultOffset, | ||||||
data: { | ||||||
patronGroups: (resources.patronGroups || {}).records || [], | ||||||
users: get(resources, 'records.records', []), | ||||||
users: this.getUsers(), | ||||||
}, | ||||||
}); | ||||||
} | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/* eslint-disable import/prefer-default-export */ | ||
export const UAS = 'uas'; | ||
export const ASSIGNED_FILTER_KEY = 'uas.Assigned'; | ||
export const UNASSIGNED_FILTER_KEY = 'uas.Unassigned'; | ||
// export const ASSIGNED = 'Assigned'; | ||
// export const UNASSIGNED = 'Unassigned'; | ||
// export const ACTIVE = 'active'; | ||
// export const INACTIVE = 'inactive'; |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,38 @@ | ||||||
import cloneDeep from 'lodash/cloneDeep'; | ||||||
import { | ||||||
ASSIGNED_FILTER_KEY, | ||||||
UNASSIGNED_FILTER_KEY, | ||||||
UAS, | ||||||
} from './constants'; | ||||||
|
||||||
// eslint-disable-next-line import/prefer-default-export | ||||||
export const updateResourceData = (rData) => { | ||||||
const filterString = rData?.query?.filters; | ||||||
const newRData = cloneDeep(rData); | ||||||
if (filterString === `${UNASSIGNED_FILTER_KEY}` || filterString === `${ASSIGNED_FILTER_KEY},${UNASSIGNED_FILTER_KEY}` || filterString === `${UNASSIGNED_FILTER_KEY},${ASSIGNED_FILTER_KEY}`) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
/* | ||||||
* When Unassigned filter is selected on 'User assignment Status' filter group, with no other filter from other groups, | ||||||
* fetch all the user records. The filter string is adjusted to include both active and inactive status filters. This will result in (cql.allRecords=1) | ||||||
* | ||||||
* The same applies when both Assigned and Unassigned are selected in any sequential order. | ||||||
*/ | ||||||
const alteredfilters = 'active.active,active.inactive'; | ||||||
newRData.query.filters = alteredfilters; | ||||||
} else if (filterString.includes(`${UNASSIGNED_FILTER_KEY}`)) { | ||||||
/* | ||||||
* When UnAssigned filter is selected in combination with any other filters, | ||||||
* filter a string for Unassigned is removed and the rest of the filter string is propagated to makeQueryFunction. | ||||||
*/ | ||||||
const alteredfilters = newRData.query.filters.split(',').filter(str => !str.startsWith(`${UAS}`)).join(','); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Won't There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. uas.Assigned should be removed in any case, as it will never participate in cql formation. So here the logic is appropriate. I see a need to update the comment above this code of line. Thank you. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
newRData.query.filters = alteredfilters; | ||||||
} else if (filterString.includes(`${ASSIGNED_FILTER_KEY}`)) { | ||||||
/* | ||||||
* When Assigned filter is selected on 'User assignment Status' filter group, in any combination of filters in other filter groups, | ||||||
* cql formation is not needed. | ||||||
* hence remove uas filter before propagating it further to makeQueryFunction | ||||||
*/ | ||||||
const alteredfilters = ''; | ||||||
newRData.query.filters = alteredfilters; | ||||||
} | ||||||
return newRData; | ||||||
}; |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,57 @@ | ||||||
import { updateResourceData } from './utils'; | ||||||
import { UNASSIGNED_FILTER_KEY, ASSIGNED_FILTER_KEY } from './constants'; | ||||||
|
||||||
describe('updatedResourceData', () => { | ||||||
describe('when only UnAssigned filter is selected', () => { | ||||||
[`${UNASSIGNED_FILTER_KEY}`, `${ASSIGNED_FILTER_KEY},${UNASSIGNED_FILTER_KEY}`, `${UNASSIGNED_FILTER_KEY},${ASSIGNED_FILTER_KEY}`].forEach(filterStr => ( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
it(`should remove ${filterStr} from filter string and add active and inactive filter strings`, () => { | ||||||
const resourceData = { | ||||||
query: { | ||||||
filters: filterStr, | ||||||
} | ||||||
}; | ||||||
const expectedResourceData = { | ||||||
query: { | ||||||
...resourceData.query, | ||||||
filters: 'active.active,active.inactive', | ||||||
}, | ||||||
}; | ||||||
expect(updateResourceData(resourceData)).toMatchObject(expectedResourceData); | ||||||
}) | ||||||
)); | ||||||
}); | ||||||
|
||||||
describe('when Unassigned filter is selected along with filters from other filter groups', () => { | ||||||
it('should remove Unassigned filter and return the rest', () => { | ||||||
const resourceData = { | ||||||
query: { | ||||||
filters: `${UNASSIGNED_FILTER_KEY},active.active`, | ||||||
} | ||||||
}; | ||||||
const expectedResourceData = { | ||||||
query: { | ||||||
...resourceData.query, | ||||||
filters: 'active.active', | ||||||
}, | ||||||
}; | ||||||
expect(updateResourceData(resourceData)).toMatchObject(expectedResourceData); | ||||||
}); | ||||||
}); | ||||||
|
||||||
describe('when Assigned filter is selected with or without combination of filters from other filter groups', () => { | ||||||
it('should remove filter string', () => { | ||||||
const resourceData = { | ||||||
query: { | ||||||
filters: `${ASSIGNED_FILTER_KEY},active.active`, | ||||||
} | ||||||
}; | ||||||
const expectedResourceData = { | ||||||
query: { | ||||||
...resourceData.query, | ||||||
filters: '', | ||||||
}, | ||||||
}; | ||||||
expect(updateResourceData(resourceData)).toMatchObject(expectedResourceData); | ||||||
}); | ||||||
}); | ||||||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
// See https://github.com/facebook/jest/issues/335#issuecomment-703691592 | ||
// import './__mock__'; | ||
|
||
import 'regenerator-runtime/runtime'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.