Skip to content

Commit

Permalink
Merge pull request #2 from AdamZarger/narrative_filter
Browse files Browse the repository at this point in the history
Narrative Filter
  • Loading branch information
AdamZarger authored Aug 11, 2017
2 parents b756bf4 + 7482dc3 commit c8bb91e
Show file tree
Hide file tree
Showing 24 changed files with 767 additions and 163 deletions.
27 changes: 14 additions & 13 deletions ccdb5_ui/static/ccdb5.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ccdb5_ui/static/ccdb5.min.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ccdb5-ui",
"version": "0.3.0",
"version": "0.5.0",
"private": false,
"description": "Consumer Complaint Database UI",
"homepage": "https://www.consumerfinance.gov/",
Expand Down
9 changes: 3 additions & 6 deletions src/FilterPanel.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import './FilterPanel.less'
import Aggregation from './Filters/Aggregation'
import CollapsibleFilter from './Filters/CollapsibleFilter'
import CompanyName from './Filters/CompanyName'
import { connect } from 'react-redux'
import DateFilter from './Filters/DateFilter'
import FederalState from './Filters/FederalState'
import Issue from './Filters/Issue'
import Product from './Filters/Product'
import React from 'react'
import SimpleFilter from './Filters/SimpleFilter'
import SingleCheckbox from './Filters/SingleCheckbox'
Expand All @@ -17,16 +17,13 @@ export class FilterPanel extends React.Component {
<section className="filter-panel">
<h3>Filter results by...</h3>
<SingleCheckbox title="Only show complaints with narratives?"
label="Yes" />
fieldName="has_narrative" />
<hr />
<DateFilter fieldName="date_received" />
<hr />
<CompanyName />
<hr />
<Aggregation title="Product / sub-product"
desc="The type of product and sub-product the consumer identified in the complaint"
fieldName="product"
/>
<Product />
<hr />
<Issue />
<hr />
Expand Down
32 changes: 0 additions & 32 deletions src/Filters/Aggregation.jsx

This file was deleted.

66 changes: 66 additions & 0 deletions src/Filters/Product.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import AggregationBranch from './AggregationBranch'
import CollapsibleFilter from './CollapsibleFilter'
import { connect } from 'react-redux'
import MoreOrLess from './MoreOrLess'
import React from 'react'
import { SLUG_SEPARATOR } from '../constants'
import { sortSelThenCount } from './utils'

export class Product extends React.Component {
constructor( props ) {
super( props )

this._onBucket = this._onBucket.bind( this )
}

render() {
const listComponentProps = {
fieldName: 'product'
}

return (
<CollapsibleFilter title="Product / sub-product"
desc="The type of product and sub-product the consumer identified in the complaint"
showChildren={this.props.showChildren}
className="aggregation">
<a href="http://files.consumerfinance.gov/f/documents/201704_cfpb_Summary_of_Product_and_Sub-product_Changes.pdf" target="_blank">Recent changes to products and sub-products</a>
<MoreOrLess listComponent={AggregationBranch}
listComponentProps={listComponentProps}
options={this.props.options}
perBucketProps={this._onBucket} />
</CollapsibleFilter>
)
}

// --------------------------------------------------------------------------
// MoreOrLess Helpers

_onBucket( bucket, props ) {
props.subitems = bucket['sub_product.raw'].buckets
return props
}
}

export const mapStateToProps = state => {
// See if there are an active product filters
const allProducts = state.query.product || []
const selections = []

// Reduce the products to the parent keys (and dedup)
allProducts.forEach( x => {
const idx = x.indexOf( SLUG_SEPARATOR )
const key = idx === -1 ? x : x.substr( 0, idx )
if ( selections.indexOf( key ) === -1 ) {
selections.push( key )
}
} )

// Make a cloned, sorted version of the aggs
const options = sortSelThenCount( state.aggs.product, selections )

return {
options
}
}

export default connect( mapStateToProps )( Product )
73 changes: 68 additions & 5 deletions src/Filters/SingleCheckbox.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,78 @@
import React from 'react';
import { changeFlagFilter } from '../actions/filter'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import React from 'react'

export class SingleCheckbox extends React.Component {
constructor( props ) {
super( props )
this.state = { isChecked: this.props.isChecked }
}

componentWillReceiveProps( nextProps ) {
const newState = {
isChecked: nextProps.isChecked
}
this.setState( newState )
}

componentDidUpdate() {
this.props.changeFlagFilter( this.props.fieldName, this.state.isChecked )
}

export default class SingleCheckbox extends React.Component {
render() {
return (
<section className="single-checkbox">
<h5>{this.props.title}</h5>
<div className="m-form-field m-form-field__checkbox">
<input className="a-checkbox" type="checkbox" id="theCheckbox" />
<label className="a-label" htmlFor="theCheckbox">{this.props.label}</label>
<input className="a-checkbox"
id="theCheckbox"
type="checkbox"
onClick={ this._changeFlag.bind( this ) }
checked={ this.state.isChecked }
value={ this.props.fieldName } />
<label className="a-label" htmlFor="theCheckbox">Yes</label>
</div>
</section>
);
)
}

// --------------------------------------------------------------------------
// Helper Methods

_changeFlag( ) {
const newState = {
isChecked: !this.state.isChecked
}
this.setState( newState )
}
}

// ----------------------------------------------------------------------------
// Meta

SingleCheckbox.propTypes = {
fieldName: PropTypes.string.isRequired,
isChecked: PropTypes.bool
}

SingleCheckbox.defaultProps = {
isChecked: false
}

export const mapStateToProps = state => {
var queryValue = state.query.has_narrative
return {
isChecked: typeof queryValue !== 'undefined' &&
( queryValue.toString() === 'yes' ||
queryValue.toString() === 'true' )
}
}

export const mapDispatchToProps = dispatch => ( {
changeFlagFilter: ( fieldName, isChecked ) => {
dispatch( changeFlagFilter( fieldName, isChecked ) )
}
} )

export default connect( mapStateToProps, mapDispatchToProps )( SingleCheckbox )
37 changes: 0 additions & 37 deletions src/Filters/__tests__/Aggregation.spec.jsx

This file was deleted.

136 changes: 136 additions & 0 deletions src/Filters/__tests__/Product.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import React from 'react'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import renderer from 'react-test-renderer';
import { IntlProvider } from 'react-intl';
import { Provider } from 'react-redux'
import { shallow } from 'enzyme';
import ReduxProduct, {Product, mapStateToProps} from '../Product'
import { slugify } from '../utils'

const fixture = [
{
"sub_product.raw": {
"buckets": [
{"key": "Credit reporting","doc_count": 3200},
{"key": "Other personal consumer report","doc_count": 67},
{"key": "Credit repair services","doc_count": 10}
],
},
"key": "Credit reporting, credit repair services, or other personal consumer reports",
"doc_count": 3277
},
{
"sub_product.raw": {
"buckets": [
{"key": "Conventional home mortgage","doc_count": 652},
{"key": "Conventional fixed mortgage","doc_count": 612}
],
},
"key": "Mortgage",
"doc_count": 2299
},
{
"sub_product.raw": {
"buckets": [],
},
"key": "Credit reporting",
"doc_count": 1052
},
{
"sub_product.raw": {
"buckets": [],
},
"key": "Student loan",
"doc_count": 959
},
{
"sub_product.raw": {
"buckets": [],
},
"key": "Credit card or prepaid card",
"doc_count": 836
},
{
"sub_product.raw": {
"buckets": [],
},
"key": "Credit card",
"doc_count": 652
}
]

function setupEnzyme(initial) {
const props = {
options: initial
}

const target = shallow(<Product {...props} />);

return {
props,
target
}
}

function setupSnapshot(initial) {
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
const store = mockStore({
query: {},
aggs: {
product: initial
}
})

return renderer.create(
<Provider store={store}>
<IntlProvider locale="en">
<ReduxProduct />
</IntlProvider>
</Provider>
)
}

describe('component:Product', () => {
describe('snapshots', () => {
it('renders without crashing', () => {
const target = setupSnapshot([])
let tree = target.toJSON()
expect(tree).toMatchSnapshot()
})

it('only shows the first five items', () => {
const target = setupSnapshot(fixture)
let tree = target.toJSON()
expect(tree).toMatchSnapshot()
})
})

describe('sorting', () => {
it('places selections ahead of unselected', () => {
const selected = [
'Credit reporting, credit repair services, or other personal consumer reports',
slugify('Credit reporting, credit repair services, or other personal consumer reports',
'Other personal consumer report'),
'Credit card'
]
const actual = mapStateToProps({
query: {product: selected},
aggs: {product: fixture}
})
expect(actual.options[1]).toEqual(fixture[5])
})

it('treats child selections as parent selections', () => {
const selected = [
slugify("Mortgage", 'Conventional home mortgage')
]
const actual = mapStateToProps({
query: {product: selected},
aggs: {product: fixture}
})
expect(actual.options[0]).toEqual(fixture[1])
})
})
})
Loading

0 comments on commit c8bb91e

Please sign in to comment.