Skip to content
This repository was archived by the owner on Jan 7, 2020. It is now read-only.

Commit e7baf3d

Browse files
authored
Merge pull request #214 from cfpb/filter-leis-by-geography
Filter leis by geography
2 parents eb80281 + 7abd157 commit e7baf3d

13 files changed

+237
-121
lines changed

src/api.js

+28-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { isNationwide } from '../src/geo/selectUtils'
22

3+
const API_BASE_URL = '/v2/data-browser-api/view'
4+
35
export function addVariableParams(obj={}) {
46
let qs = ''
57
const vars = obj.variables
@@ -25,45 +27,53 @@ export function addYears(url='') {
2527
}
2628

2729
export function createItemQuerystring(obj = {}) {
28-
const nationwide = isNationwide(obj.category)
29-
const category = nationwide ? 'leis' : obj.category
30-
const items = nationwide ? obj.leis : obj.items
31-
32-
if (items && items.length) return `?${category}=${items.join(',')}`
33-
30+
if (isNationwide(obj.category)) return ''
31+
if (obj.items && obj.items.length)
32+
return createQueryString(obj.category, obj.items, true)
3433
return ''
3534
}
3635

36+
export function createQueryString(category, items, first=false){
37+
let qs = first ? '?' : '&'
38+
return `${qs}${category}=${items.join(',')}`
39+
}
40+
3741
export function makeUrl(obj, isCSV, includeVariables=true) {
3842
if(!obj) return ''
39-
let url = '/v2/data-browser-api/view'
43+
let url = API_BASE_URL
4044

4145
const nationwide = isNationwide(obj.category)
4246
const hasItems = obj.items && obj.items.length
4347
const hasLeis = obj.leis && obj.leis.length
4448

49+
if (!nationwide && !hasItems) return ''
4550
if (nationwide && !hasLeis) url += '/nationwide'
4651

4752
if(isCSV) url += '/csv'
4853
else url += '/aggregations'
4954

50-
if(nationwide){
51-
url += createItemQuerystring(obj)
52-
if(includeVariables && obj.variables) {
53-
url += hasLeis ? '&' : '?'
54-
url += addVariableParams(obj).slice(1)
55-
}
56-
}else {
57-
if(!hasItems) return ''
58-
url += createItemQuerystring(obj)
59-
if(includeVariables) url += addVariableParams(obj)
60-
}
55+
if(hasItems) url += createQueryString(obj.category, obj.items, hasItems)
56+
if(hasLeis) url += createQueryString('leis', obj.leis, !hasItems)
57+
if(!hasItems && !hasLeis) url += '?'
58+
if(includeVariables) url += addVariableParams(obj)
6159

6260
url += addYears(url)
6361

6462
return url
6563
}
6664

65+
export function makeFilersUrl(obj){
66+
if(!obj) return ''
67+
let url = API_BASE_URL + '/filers'
68+
69+
if(isNationwide(obj.category))
70+
return url + addYears(url)
71+
72+
url += createItemQuerystring(obj)
73+
url += addYears(url)
74+
return url
75+
}
76+
6777
export function runFetch(url) {
6878

6979
let headers = { Accept: 'application/json' }

src/api.test.js

+15-4
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import {
33
addYears,
44
createItemQuerystring,
55
makeUrl,
6+
makeFilersUrl,
67
runFetch,
78
makeCSVName,
89
getSubsetDetails,
910
getCSV,
1011
getItemCSV,
11-
getSubsetCSV
12+
getSubsetCSV,
1213
} from './api.js'
1314

1415
it('adds variable params from an object', () => {
@@ -42,9 +43,9 @@ it('creates an empty item qs', () => {
4243
expect(qs).toBe('')
4344
})
4445

45-
it('creates a qs with LEIs for nationwide', () => {
46-
const qs = createItemQuerystring({ leis: ['a', 'b'], category: 'nationwide' })
47-
expect(qs).toBe('?leis=a,b')
46+
it('creates a qs with for nationwide', () => {
47+
const qs = createItemQuerystring({ items: ['a', 'b'], category: 'nationwide' })
48+
expect(qs).toBe('')
4849
})
4950

5051
it('makes a url', () => {
@@ -191,3 +192,13 @@ it('calls getCSV on makeURL and makeCSVName on getSubsetCSV call', () => {
191192
expect(setAttribute.mock.calls[1][1]).toBe('abc-a.csv')
192193
expect(a.href).toBe('/v2/data-browser-api/view/csv?states=abc&a=b&years=2018')
193194
})
195+
196+
describe('makeFilersUrl', () => {
197+
it('request all for nationwide', () => {
198+
expect(makeFilersUrl({category: 'nationwide'})).toBe('/v2/data-browser-api/view/filers?years=2018');
199+
})
200+
201+
it('request geo-specific for non-nationwide', () => {
202+
expect(makeFilersUrl({category: 'states', items: ['CA', 'WA']})).toBe('/v2/data-browser-api/view/filers?states=CA,WA&years=2018');
203+
})
204+
})

src/constants/leiCounts.js

-1
This file was deleted.

src/constants/leis.js

-3
This file was deleted.

src/geo/Aggregations.jsx

+12-6
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import React from 'react'
22
import STATEOBJ from '../constants/stateObj.js'
33
import MSATONAME from '../constants/msaToName.js'
44
import COUNTIES from '../constants/counties.js'
5-
import LEIS from '../constants/leis.js'
65
import VARIABLES from '../constants/variables.js'
76
import DocLink from './DocLink.jsx'
87
import { formatWithCommas } from './selectUtils.js'
8+
import LoadingIcon from '../common/LoadingIcon'
99

1010
function buildRows(aggregations, orderedVariables) {
1111
return aggregations.map((row, i) => {
@@ -19,13 +19,14 @@ function buildRows(aggregations, orderedVariables) {
1919
})
2020
}
2121

22-
function makeHeader(params, orderedVariables, year) {
22+
function makeHeader(params, orderedVariables, year, leis) {
2323
const list = []
2424
if(params.state) list.push(<li key="0"><h4>State:</h4><ul className="sublist"><li>{params.state.split(',').map(v => STATEOBJ[v]).join(', ')}</li></ul></li>)
2525
else if(params.msamd) list.push(<li key="0"><h4>MSA/MD:</h4><ul className="sublist"><li>{params.msamd.split(',').map(v => `${v}\u00A0-\u00A0${MSATONAME[v]}`).join(', ')}</li></ul></li>)
2626
else if(params.county) list.push(<li key="0"><h4>County:</h4><ul className="sublist"><li>{params.county.split(',').map(v => COUNTIES[v]).join(', ')}</li></ul></li>)
27-
else if(params.lei) list.push(<li key="0"><h4>Institutions:</h4><ul className="sublist"><li>{params.lei.split(',').map(v => LEIS[v]).join(', ')}</li></ul></li>)
28-
27+
28+
if(params.lei) list.push(<li key="1"><h4>Institutions:</h4><ul className="sublist"><li>{institutionsOrLoading(params.lei, leis)}</li></ul></li>)
29+
2930
orderedVariables.forEach((variable) => {
3031
list.push(
3132
<li key={variable}>
@@ -40,10 +41,15 @@ function makeHeader(params, orderedVariables, year) {
4041
</li>
4142
)
4243
})
43-
44+
4445
return <ul>{list}</ul>
4546
}
4647

48+
function institutionsOrLoading(selected, details){
49+
if(details.loading) return <LoadingIcon className="LoadingInline" />
50+
return selected.split(',').map(v => details.leis[v].name).join(', ')
51+
}
52+
4753
// eslint-disable-next-line
4854
const Aggregations = React.forwardRef((props, ref) => {
4955
const { aggregations, parameters } = props.details
@@ -57,7 +63,7 @@ const Aggregations = React.forwardRef((props, ref) => {
5763
return (
5864
<div ref={ref} className="Aggregations">
5965
<h2>Data Summary</h2>
60-
{makeHeader(parameters, ordered, props.year)}
66+
{makeHeader(parameters, ordered, props.year, props.leis)}
6167
<table>
6268
<thead>
6369
<tr>

src/geo/Geography.css

+14-17
Original file line numberDiff line numberDiff line change
@@ -88,34 +88,31 @@ svg {
8888
border: 1px solid #0071bc;
8989
}
9090

91-
.QueryButton:hover {
92-
background-color: #205493;
93-
}
94-
95-
.QueryButton:active {
96-
background-color: #112e51;
97-
}
98-
99-
.QueryButton.disabled {
100-
background-color: #888;
101-
cursor: not-allowed;
102-
}
103-
10491
.QueryButton.secondary {
10592
color:#254b87;
10693
color:#002d72;
10794
background:#d6e8fa;
10895
border-color:#254b87;
10996
}
11097

98+
.QueryButton:hover {
99+
background-color: #205493;
100+
}
101+
111102
.QueryButton.secondary:hover {
112-
background: #afd2f2
103+
background-color: #afd2f2
113104
}
114105

106+
.QueryButton:active {
107+
background-color: #112e51;
108+
}
109+
110+
.QueryButton.disabled,
115111
.QueryButton.secondary.disabled {
116-
color: #000;
117-
border-color: #000;
118-
background: #e7ddd7
112+
color: #fff;
113+
background-color: #888;
114+
border-color: #888;
115+
cursor: not-allowed;
119116
}
120117

121118
.Aggregations h2 {

src/geo/Geography.jsx

+50-16
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import React, { Component } from 'react'
22
import { Link } from 'react-router-dom'
3+
import { isEqual } from 'lodash'
34
import { getItemCSV, getSubsetCSV, getSubsetDetails } from '../api.js'
45
import Header from '../common/Header.jsx'
5-
import LEI_COUNTS from '../constants/leiCounts.js'
66
import MSAMD_COUNTS from '../constants/msamdCounts.js'
77
import STATE_COUNTS from '../constants/stateCounts.js'
88
import { makeSearchFromState, makeStateFromSearch } from '../query.js'
@@ -11,6 +11,7 @@ import Aggregations from './Aggregations.jsx'
1111
import './Geography.css'
1212
import InstitutionSelect from './InstitutionSelect'
1313
import ItemSelect from './ItemSelect.jsx'
14+
import { fetchLeis, filterLeis } from './leiUtils'
1415
import {
1516
createItemOptions,
1617
createVariableOptions,
@@ -36,6 +37,8 @@ class Geography extends Component {
3637
this.setStateAndRoute = this.setStateAndRoute.bind(this)
3738
this.updateSearch = this.updateSearch.bind(this)
3839
this.scrollToTable = this.scrollToTable.bind(this)
40+
this.fetchLeis = fetchLeis.bind(this)
41+
this.filterLeis = filterLeis.bind(this)
3942

4043
this.itemOptions = createItemOptions(props)
4144
this.variableOptions = createVariableOptions()
@@ -55,7 +58,12 @@ class Geography extends Component {
5558
orderedVariables: [],
5659
details: {},
5760
loadingDetails: false,
58-
error: null
61+
error: null,
62+
leiDetails: {
63+
loading: true,
64+
counts: {},
65+
leis: {}
66+
}
5967
}
6068

6169
const newState = makeStateFromSearch(this.props.location.search, defaultState, this.requestSubset, this.updateSearch)
@@ -64,6 +72,22 @@ class Geography extends Component {
6472
return newState
6573
}
6674

75+
componentDidMount(){
76+
this.fetchLeis()
77+
this.filterLeis()
78+
this.setState({ isLargeFile: this.checkIfLargeFile(this.state.category, this.state.items) })
79+
}
80+
81+
componentDidUpdate(prevProps, prevState){
82+
const geographyChanged = !isEqual(prevState.items, this.state.items)
83+
const leisReloaded = prevState.leiDetails.loading && !this.state.leiDetails.loading
84+
85+
if(geographyChanged) this.fetchLeis()
86+
if(geographyChanged || leisReloaded) this.filterLeis()
87+
if(leisReloaded)
88+
this.setState({ isLargeFile: this.checkIfLargeFile(this.state.category, this.state.items) })
89+
}
90+
6791
updateSearch() {
6892
this.props.history.replace({search: makeSearchFromState(this.state)})
6993
}
@@ -115,9 +139,21 @@ class Geography extends Component {
115139
}
116140

117141
checkIfLargeFile(category, items) {
118-
if(isNationwide(category) && !items.length) return true
119-
if(isNationwide(category) || category === 'leis')
120-
return this.checkIfLargeCount(items, LEI_COUNTS)
142+
const leisSelected = this.state && this.state.leis.length
143+
const leisFetched = this.state && !this.state.leiDetails.loading
144+
145+
if(category === 'leis'){
146+
if(items.length && leisFetched)
147+
return this.checkIfLargeCount(items, this.state.leiDetails.counts)
148+
149+
if(!items.length)
150+
return this.checkIfLargeFile(this.state.category, this.state.items)
151+
}
152+
153+
if(leisSelected && leisFetched)
154+
return this.checkIfLargeCount(this.state.leis, this.state.leiDetails.counts)
155+
156+
if(isNationwide(category)) return true
121157
if(category === 'states') return this.checkIfLargeCount(items, STATE_COUNTS)
122158
if(category === 'msamds') return this.checkIfLargeCount(items, MSAMD_COUNTS)
123159
return false
@@ -133,7 +169,6 @@ class Geography extends Component {
133169
this.setStateAndRoute({
134170
category: catObj.value,
135171
items: [],
136-
leis: [],
137172
details: {},
138173
isLargeFile: this.checkIfLargeFile(catObj.value, []),
139174
})
@@ -147,18 +182,17 @@ class Geography extends Component {
147182
return this.setStateAndRoute({
148183
items,
149184
details: {},
150-
isLargeFile: this.checkIfLargeFile(this.state.category, items)
151185
})
152186
}
153187

154188
onInstitutionChange(selectedLEIs = []){
155189
let leis = selectedLEIs.map(l => l.value)
156190
if(leis.includes('all')) leis = []
157191

158-
return this.setStateAndRoute({
159-
leis,
160-
details: {},
161-
isLargeFile: this.checkIfLargeFile(this.state.category, leis)
192+
return this.setState({leis, details: {}}, () => {
193+
return this.setStateAndRoute({
194+
isLargeFile: this.checkIfLargeFile('leis', this.state.leis)
195+
})
162196
})
163197
}
164198

@@ -219,7 +253,7 @@ class Geography extends Component {
219253
const total = formatWithCommas(this.makeTotal(details))
220254
return (
221255
<>
222-
<Aggregations ref={this.tableRef} details={details} orderedVariables={orderedVariables} year={this.props.match.params.year}/>
256+
<Aggregations ref={this.tableRef} details={details} orderedVariables={orderedVariables} year={this.props.match.params.year} leis={this.state.leiDetails} />
223257
<div className="CSVButtonContainer">
224258
{this.renderTotal(total)}
225259
</div>
@@ -228,7 +262,8 @@ class Geography extends Component {
228262
}
229263

230264
render() {
231-
const { category, items, leis, isLargeFile, variables, orderedVariables, details, loadingDetails, error } = this.state
265+
const { category, details, error, isLargeFile, items, leiDetails, leis,
266+
loadingDetails, orderedVariables, variables } = this.state
232267

233268
const nationwide = isNationwide(category)
234269
const enabled = nationwide || items.length
@@ -269,8 +304,7 @@ class Geography extends Component {
269304
<InstitutionSelect
270305
items={leis}
271306
onChange={this.onInstitutionChange}
272-
options={this.itemOptions}
273-
nationwide={nationwide}
307+
leiDetails={leiDetails}
274308
/>
275309
<VariableSelect
276310
options={this.variableOptions}
@@ -286,7 +320,7 @@ class Geography extends Component {
286320
<ActionsWarningsErrors
287321
downloadEnabled={enabled}
288322
downloadCallback={checksExist ? this.requestSubsetCSV : this.requestItemCSV}
289-
showSummaryButton={!details.aggregations || !this.makeTotal(details)}
323+
showSummaryButton={!details.aggregations}
290324
summaryEnabled={enabled && checksExist}
291325
loadingDetails={loadingDetails}
292326
requestSubset={this.requestSubset}

0 commit comments

Comments
 (0)