Skip to content
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

Nn 1452 location filter #204

Merged
merged 2 commits into from
Nov 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions backend/api/elite2Api.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@ const elite2ApiFactory = client => {
const getSentenceData = (context, offenderNumbers) => post(context, `api/offender-sentences`, offenderNumbers)
const getPrisonerImage = (context, offenderNo) =>
getStream(context, `api/bookings/offenderNo/${offenderNo}/image/data`)
const globalSearch = (context, offenderNo, lastName, firstName) =>
const globalSearch = (context, offenderNo, lastName, firstName, genderFilter, locationFilter) =>
get(
context,
`api/prisoners?offenderNo=${offenderNo}&lastName=${encodeQueryString(lastName)}&firstName=${encodeQueryString(
firstName
)}&partialNameMatch=false&includeAliases=true`
)}&gender=${genderFilter}&location=${locationFilter}&partialNameMatch=false&includeAliases=true`
)
const getLastPrison = (context, body) => post(context, `api/movements/offenders`, body)

Expand Down
4 changes: 2 additions & 2 deletions backend/controllers/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ const factory = (
})

const globalSearch = asyncMiddleware(async (req, res) => {
const { searchText } = req.query
const viewModel = await globalSearchService.globalSearch(res.locals, searchText)
const { searchText, genderFilter, locationFilter } = req.query
const viewModel = await globalSearchService.globalSearch(res.locals, searchText, genderFilter, locationFilter)
res.set(res.locals.responseHeaders)
res.json(viewModel)
})
Expand Down
13 changes: 8 additions & 5 deletions backend/controllers/globalSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ const log = require('../log')
const offenderIdPattern = /^[A-Za-z][0-9]{4}[A-Za-z]{2}$/

const globalSearchFactory = elite2Api => {
const searchByOffender = (context, offenderNo) => elite2Api.globalSearch(context, offenderNo, '', '')
const searchByOffender = (context, offenderNo, genderFilter, locationFilter) =>
elite2Api.globalSearch(context, offenderNo, '', '', genderFilter, locationFilter)

const searchByName = (context, name) => {
const searchByName = (context, name, genderFilter, locationFilter) => {
const [lastName, firstName] = name.split(' ')
return elite2Api.globalSearch(context, '', lastName, firstName || '')
return elite2Api.globalSearch(context, '', lastName, firstName || '', genderFilter, locationFilter)
}

const globalSearch = async (context, searchText) => {
const globalSearch = async (context, searchText, genderFilter, locationFilter) => {
log.info(`In globalSearch, searchText=${searchText}`)
if (!searchText) {
return []
Expand All @@ -21,7 +22,9 @@ const globalSearchFactory = elite2Api => {
.replace(/,/g, ' ')
.replace(/\s\s+/g, ' ')
.trim()
const data = await (offenderIdPattern.test(text) ? searchByOffender(context, text) : searchByName(context, text))
const data = await (offenderIdPattern.test(text)
? searchByOffender(context, text, genderFilter, locationFilter)
: searchByName(context, text, genderFilter, locationFilter))
log.info(data, 'globalSearch data received')

const offenderOutIds = data
Expand Down
32 changes: 16 additions & 16 deletions backend/tests/globalSearch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,12 @@ describe('Global Search controller', async () => {
it('Should return no results as an empty array', async () => {
elite2Api.globalSearch.mockReturnValue([])

const response = await globalSearch({}, 'text')
const response = await globalSearch({}, 'text', '', '')
expect(response).toEqual([])

expect(elite2Api.globalSearch).toHaveBeenCalled()

expect(elite2Api.globalSearch.mock.calls[0]).toEqual([{}, '', 'text', ''])
expect(elite2Api.globalSearch.mock.calls[0]).toEqual([{}, '', 'text', '', '', ''])
})

it('Should return results', async () => {
Expand Down Expand Up @@ -188,56 +188,56 @@ describe('Global Search controller', async () => {
elite2Api.globalSearch.mockReturnValue(apiResponse)

const offenderNo = 'Z4444YY'
await globalSearch({}, offenderNo)
expect(elite2Api.globalSearch.mock.calls[0]).toEqual([{}, offenderNo, '', ''])
await globalSearch({}, offenderNo, '', '', '')
expect(elite2Api.globalSearch.mock.calls[0]).toEqual([{}, offenderNo, '', '', '', ''])
})

it('Should detect an offenderId with lowercase letters', async () => {
const apiResponse = createResponse()
elite2Api.globalSearch.mockReturnValue(apiResponse)

const offenderNo = 'z4444yy'
await globalSearch({}, offenderNo)
expect(elite2Api.globalSearch.mock.calls[0]).toEqual([{}, offenderNo, '', ''])
await globalSearch({}, offenderNo, '', '', '')
expect(elite2Api.globalSearch.mock.calls[0]).toEqual([{}, offenderNo, '', '', '', ''])
})

it('Should detect 2 words', async () => {
const apiResponse = createResponse()
elite2Api.globalSearch.mockReturnValue(apiResponse)

await globalSearch({}, 'last first')
expect(elite2Api.globalSearch.mock.calls[0]).toEqual([{}, '', 'last', 'first'])
await globalSearch({}, 'last first', '', '')
expect(elite2Api.globalSearch.mock.calls[0]).toEqual([{}, '', 'last', 'first', '', ''])
})

it('Should detect 2 words and remove commas', async () => {
const apiResponse = createResponse()
elite2Api.globalSearch.mockReturnValue(apiResponse)

await globalSearch({}, ',last, first,')
expect(elite2Api.globalSearch.mock.calls[0]).toEqual([{}, '', 'last', 'first'])
await globalSearch({}, ',last, first,', '', '')
expect(elite2Api.globalSearch.mock.calls[0]).toEqual([{}, '', 'last', 'first', '', ''])
})

it('Should detect 2 words with no space between comma', async () => {
const apiResponse = createResponse()
elite2Api.globalSearch.mockReturnValue(apiResponse)

await globalSearch({}, ',last, first,')
expect(elite2Api.globalSearch.mock.calls[0]).toEqual([{}, '', 'last', 'first'])
await globalSearch({}, ',last, first,', '', '')
expect(elite2Api.globalSearch.mock.calls[0]).toEqual([{}, '', 'last', 'first', '', ''])
})

it('Should detect 2 words with various spaces and commas', async () => {
const apiResponse = createResponse()
elite2Api.globalSearch.mockReturnValue(apiResponse)

await globalSearch({}, ', last , first other, ')
expect(elite2Api.globalSearch.mock.calls[0]).toEqual([{}, '', 'last', 'first'])
await globalSearch({}, ', last , first other, ', '', '')
expect(elite2Api.globalSearch.mock.calls[0]).toEqual([{}, '', 'last', 'first', '', ''])
})

it('Should ignore leading and trailing whitespace', async () => {
const apiResponse = createResponse()
elite2Api.globalSearch.mockReturnValue(apiResponse)

await globalSearch({}, ' word ')
expect(elite2Api.globalSearch.mock.calls[0]).toEqual([{}, '', 'word', ''])
await globalSearch({}, ' word ', '', '')
expect(elite2Api.globalSearch.mock.calls[0]).toEqual([{}, '', 'word', '', '', ''])
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ class Elite2Api extends WireMockRule {
final totalRecords = String.valueOf(response.size())

this.stubFor(
get("/api/prisoners?offenderNo=${offenderNo}&lastName=${lastName}&firstName=${firstName}&partialNameMatch=false&includeAliases=true")
get("/api/prisoners?offenderNo=${offenderNo}&lastName=${lastName}&firstName=${firstName}&gender=ALL&location=ALL&partialNameMatch=false&includeAliases=true")
.withHeader('page-offset', equalTo('0'))
.withHeader('page-limit', equalTo('10'))
.willReturn(
Expand All @@ -412,7 +412,7 @@ class Elite2Api extends WireMockRule {
.withStatus(200)))
if (response.size() > 10) {
this.stubFor(
get("/api/prisoners?offenderNo=${offenderNo}&lastName=${lastName}&firstName=${firstName}&partialNameMatch=false&includeAliases=true")
get("/api/prisoners?offenderNo=${offenderNo}&lastName=${lastName}&firstName=${firstName}&gender=ALL&location=ALL&partialNameMatch=false&includeAliases=true")
.withHeader('page-offset', equalTo('10'))
.withHeader('page-limit', equalTo('10'))
.willReturn(
Expand Down
4 changes: 4 additions & 0 deletions src/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,10 @@ tr a.link:visited {
font-weight: 700;
}

.visible {
display: block !important;
}

/* Removes headers and footers for printing purposes */
@media print {
@page {
Expand Down
44 changes: 43 additions & 1 deletion src/GlobalSearch/GlobalSearchContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
setGlobalSearchPageNumber,
setGlobalSearchTotalRecords,
setApplicationTitle,
setGlobalSearchLocationFilter,
setGlobalSearchGenderFilter,
} from '../redux/actions'

const axios = require('axios')
Expand All @@ -25,7 +27,10 @@ class GlobalSearchContainer extends Component {
this.doGlobalSearch = this.doGlobalSearch.bind(this)
this.handlePageAction = this.handlePageAction.bind(this)
this.handleSearchTextChange = this.handleSearchTextChange.bind(this)
this.handleSearchLocationFilterChange = this.handleSearchLocationFilterChange.bind(this)
this.handleSearchGenderFilterChange = this.handleSearchGenderFilterChange.bind(this)
this.handleSearch = this.handleSearch.bind(this)
this.clearFilters = this.clearFilters.bind(this)
}

async componentWillMount() {
Expand All @@ -42,11 +47,21 @@ class GlobalSearchContainer extends Component {
}

async doGlobalSearch(pageNumber, searchText) {
const { pageSize, totalRecordsDispatch, dataDispatch, pageNumberDispatch, raiseAnalyticsEvent } = this.props
const {
pageSize,
totalRecordsDispatch,
dataDispatch,
pageNumberDispatch,
raiseAnalyticsEvent,
genderFilter,
locationFilter,
} = this.props

const response = await axios.get('/api/globalSearch', {
params: {
searchText,
genderFilter,
locationFilter,
},
headers: {
'Page-Offset': pageSize * pageNumber,
Expand Down Expand Up @@ -83,6 +98,22 @@ class GlobalSearchContainer extends Component {
searchTextDispatch(event.target.value)
}

handleSearchLocationFilterChange(event) {
const { locationFilterDispatch } = this.props
locationFilterDispatch(event.target.value)
}

handleSearchGenderFilterChange(event) {
const { genderFilterDispatch } = this.props
genderFilterDispatch(event.target.value)
}

clearFilters() {
const { genderFilterDispatch, locationFilterDispatch } = this.props
genderFilterDispatch('ALL')
locationFilterDispatch('ALL')
}

render() {
const { loaded, error } = this.props

Expand All @@ -93,7 +124,10 @@ class GlobalSearchContainer extends Component {
<GlobalSearch
handlePageAction={this.handlePageAction}
handleSearchTextChange={this.handleSearchTextChange}
handleSearchLocationFilterChange={this.handleSearchLocationFilterChange}
handleSearchGenderFilterChange={this.handleSearchGenderFilterChange}
handleSearch={this.handleSearch}
clearFilters={this.clearFilters}
{...this.props}
/>
</div>
Expand All @@ -111,6 +145,8 @@ GlobalSearchContainer.propTypes = {
loaded: PropTypes.bool.isRequired,
agencyId: PropTypes.string.isRequired,
searchText: PropTypes.string.isRequired,
genderFilter: PropTypes.string.isRequired,
locationFilter: PropTypes.string.isRequired,
data: PropTypes.arrayOf(
PropTypes.shape({
offenderNo: PropTypes.string.isRequired,
Expand All @@ -131,6 +167,8 @@ GlobalSearchContainer.propTypes = {
pageNumberDispatch: PropTypes.func.isRequired,
totalRecordsDispatch: PropTypes.func.isRequired,
searchTextDispatch: PropTypes.func.isRequired,
genderFilterDispatch: PropTypes.func.isRequired,
locationFilterDispatch: PropTypes.func.isRequired,
titleDispatch: PropTypes.func.isRequired,

// special
Expand All @@ -149,12 +187,16 @@ const mapStateToProps = state => ({
pageSize: state.globalSearch.pageSize,
totalRecords: state.globalSearch.totalRecords,
searchText: state.globalSearch.searchText,
locationFilter: state.globalSearch.locationFilter,
genderFilter: state.globalSearch.genderFilter,
error: state.app.error,
})

const mapDispatchToProps = dispatch => ({
dataDispatch: data => dispatch(setGlobalSearchResults(data)),
searchTextDispatch: text => dispatch(setGlobalSearchText(text)),
genderFilterDispatch: text => dispatch(setGlobalSearchGenderFilter(text)),
locationFilterDispatch: text => dispatch(setGlobalSearchLocationFilter(text)),
pageNumberDispatch: no => dispatch(setGlobalSearchPageNumber(no)),
totalRecordsDispatch: no => dispatch(setGlobalSearchTotalRecords(no)),
titleDispatch: title => dispatch(setApplicationTitle(title)),
Expand Down
Loading