Skip to content

Commit

Permalink
Merge pull request #745 from 18F/bjs-get-agency-info
Browse files Browse the repository at this point in the history
incorporate more agency info to explorer page
  • Loading branch information
jeremiak authored May 10, 2017
2 parents bf5b00d + b346fc5 commit afc6d67
Show file tree
Hide file tree
Showing 17 changed files with 213 additions and 65 deletions.
20 changes: 20 additions & 0 deletions src/actions/agency.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { AGENCY_FETCHING, AGENCY_RECEIVED } from './constants'
import api from '../util/api'

export const fetchingAgency = () => ({
type: AGENCY_FETCHING,
})

export const receivedAgency = agency => ({
type: AGENCY_RECEIVED,
agency,
})

export const fetchAgency = params => dispatch => {
dispatch(fetchingAgency())

return api.getAgency(params.place).then(data => {
dispatch(receivedAgency(data))
})
}

3 changes: 3 additions & 0 deletions src/actions/composite.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable import/prefer-default-export */
import { updateFilters } from './filters'
import { fetchNibrs } from './nibrs'
import { fetchAgency } from '../actions/agency'
import { fetchSummaries } from '../actions/summary'
import { fetchUcrParticipation } from '../actions/ucr'
import history, { createNewLocation } from '../util/history'
Expand All @@ -12,7 +13,9 @@ import {

const fetchData = () => (dispatch, getState) => {
const { filters } = getState()
const { placeType } = filters

if (placeType === 'agency') dispatch(fetchAgency(filters))
if (shouldFetchUcr(filters)) dispatch(fetchUcrParticipation(filters))
if (shouldFetchSummaries(filters)) dispatch(fetchSummaries(filters))
if (shouldFetchNibrs(filters)) dispatch(fetchNibrs(filters))
Expand Down
3 changes: 3 additions & 0 deletions src/actions/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
by nouns will show up near each other in an alphabetized list
*/

export const AGENCY_FETCHING = 'AGENCY_FETCHING'
export const AGENCY_RECEIVED = 'AGENCY_RECEIVED'

export const FEEDBACK_HIDE = 'FEEDBACK_HIDE'
export const FEEDBACK_SHOW = 'FEEDBACK_SHOW'

Expand Down
5 changes: 1 addition & 4 deletions src/actions/summary.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ export const fetchSummaries = params => dispatch => {
const requests = api.getSummaryRequests(params)

return Promise.all(requests).then(data => {
const summaries = Object.assign(
...data.map(d => ({ [d.place]: d.results })),
)

const summaries = Object.assign(...data.map(d => ({ [d.key]: d.results })))
dispatch(receivedSummary(summaries))
})
}
3 changes: 2 additions & 1 deletion src/components/BetaBanner.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import PropTypes from 'prop-types'
import React from 'react'

const BetaBanner = ({ onClick }) => (
Expand All @@ -22,7 +23,7 @@ const BetaBanner = ({ onClick }) => (
)

BetaBanner.propTypes = {
onClick: React.PropTypes.func,
onClick: PropTypes.func,
}

export default BetaBanner
20 changes: 12 additions & 8 deletions src/components/Explorer.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import startCase from 'lodash.startcase'
import React from 'react'

import AboutTheData from './AboutTheData'
import AgencyChartContainer from './AgencyChartContainer'
import ExplorerHeader from './ExplorerHeader'
import NibrsContainer from './NibrsContainer'
import NotFound from './NotFound'
import Sidebar from './Sidebar'
import Sparklines from './Sparklines'
import TrendContainer from './TrendContainer'
import UcrParticipationInformation from './UcrParticipationInformation'
import { updateApp } from '../actions/composite'
Expand Down Expand Up @@ -73,11 +74,12 @@ class Explorer extends React.Component {
return <NotFound />
}

const { filters, nibrs, sidebar, summaries, ucr } = appState
const { agencies, filters, nibrs, sidebar, summaries, ucr } = appState
const { since, until } = filters
const noNibrs = ['violent-crime', 'property-crime']
const participation = ucrParticipation(place)
const showNibrs = !noNibrs.includes(crime) && participation.nibrs
const isAgency = placeType === 'agency'

return (
<div className="site-wrapper">
Expand Down Expand Up @@ -106,11 +108,12 @@ class Explorer extends React.Component {
/>
<div className="site-content">
<div className="container-main mx-auto px2 md-py3 lg-px8">
<div className="items-baseline my4 border-bottom border-blue-lighter">
<h1 className="flex-auto mt0 mb1 fs-22 sm-fs-32">
{startCase(place)}, {startCase(crime)}
</h1>
</div>
<ExplorerHeader
agency={agencies}
crime={crime}
place={place}
placeType={placeType}
/>
<UcrParticipationInformation
dispatch={dispatch}
place={place}
Expand All @@ -119,7 +122,8 @@ class Explorer extends React.Component {
ucr={ucr}
/>
<hr className="mt0 mb3" />
{placeType === 'agency'
{isAgency && <Sparklines place={place} summaries={summaries} />}
{isAgency
? <AgencyChartContainer
crime={crime}
place={place}
Expand Down
18 changes: 18 additions & 0 deletions src/components/ExplorerHeader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import startCase from 'lodash.startcase'
import React from 'react'

const getTitle = ({ agency, crime, place, placeType }) => {
const info = agency[place]

if (placeType !== 'agency') return `${startCase(place)}, ${startCase(crime)}`
if (agency.loading || !info) return 'Agency loading...'
return `${info.agency_name} Police Department, ${startCase(crime)}`
}

const ExplorerHeader = params => (
<div className="items-baseline my4 border-bottom border-blue-lighter">
<h1 className="flex-auto mt0 mb1 fs-22 sm-fs-32">{getTitle(params)}</h1>
</div>
)

export default ExplorerHeader
13 changes: 7 additions & 6 deletions src/components/Feedback.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import http from 'axios' // use axios to skip localStorage
import PropTypes from 'prop-types'
import React from 'react'

import OnEscape from './OnEscape'
Expand Down Expand Up @@ -184,14 +185,14 @@ class Feedback extends React.Component {
}

Feedback.propTypes = {
fields: React.PropTypes.arrayOf(
React.PropTypes.shape({
id: React.PropTypes.string,
label: React.PropTypes.string,
fields: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
label: PropTypes.string,
}),
),
onClose: React.PropTypes.func.isRequired,
isOpen: React.PropTypes.bool,
onClose: PropTypes.func.isRequired,
isOpen: PropTypes.bool,
}

Feedback.defaultProps = {
Expand Down
30 changes: 30 additions & 0 deletions src/components/Sparklines.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react'

import { nationalKey } from '../util/usa'

const Sparklines = ({ place, summaries }) => {
const { data, loading } = summaries
const state = place.slice(0, 2)

if (loading || !data) return null

return (
<div className="mb4">
<h3>State and national crime rates</h3>
<div className="clearfix mxn1 fs-10">
<div className="sm-col sm-col-6 px1">
<div className="p2 bg-white">
<pre>{JSON.stringify(data[state], null, 2)}</pre>
</div>
</div>
<div className="sm-col sm-col-6 px1">
<div className="p2 bg-white">
<pre>{JSON.stringify(data[nationalKey], null, 2)}</pre>
</div>
</div>
</div>
</div>
)
}

export default Sparklines
2 changes: 1 addition & 1 deletion src/components/TrendContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ TrendContainer.propTypes = {
data: PropTypes.object,
loading: PropTypes.boolean,
}).isRequired,
until: React.PropTypes.number.isRequired,
until: PropTypes.number.isRequired,
}

export default TrendContainer
23 changes: 23 additions & 0 deletions src/reducers/agencies.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { AGENCY_FETCHING, AGENCY_RECEIVED } from '../actions/constants'

const initialState = {
loading: false,
}

export default (state = initialState, action) => {
switch (action.type) {
case AGENCY_FETCHING:
return {
...state,
loading: true,
}
case AGENCY_RECEIVED:
return {
...state,
...action.agency,
loading: false,
}
default:
return state
}
}
2 changes: 2 additions & 0 deletions src/reducers/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable import/no-unresolved */
import { combineReducers } from 'redux'

import agencies from './agencies'
import feedback from './feedback'
import filters from './filters'
import glossary from './glossary'
Expand All @@ -11,6 +12,7 @@ import summaries from './summary'
import ucr from './ucr'

export default combineReducers({
agencies,
feedback,
filters,
glossary,
Expand Down
6 changes: 5 additions & 1 deletion src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ const getEnvVar = name => {
const { HTTP_BASIC_USERNAME, HTTP_BASIC_PASSWORD } = credService.credentials
const API = process.env.CDE_API
const apiKey = getEnvVar('API_KEY')
const initState = { ucr: { loading: true }, summaries: { loading: true } }
const initState = {
agency: { loading: true },
ucr: { loading: true },
summaries: { loading: true },
}
const repoOwner = getEnvVar('GITHUB_ISSUE_REPO_OWNER')
const repoName = getEnvVar('GITHUB_ISSUE_REPO_NAME')
const repoToken = getEnvVar('GITHUB_ISSUE_BOT_TOKEN')
Expand Down
61 changes: 37 additions & 24 deletions src/util/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ const dimensionEndpoints = {
sexCode: 'sex_code',
}

const getAgency = ori =>
get(`${API}/agencies/${ori}`).then(response => ({
[ori]: response,
}))

const getNibrs = ({ crime, dim, place, type }) => {
const field = dimensionEndpoints[dim]
const fieldPath = `${field}/offenses`
Expand Down Expand Up @@ -51,37 +56,42 @@ const getNibrsRequests = params => {
return slices.map(s => getNibrs({ ...s, crime, place }))
}

const getSummaryEndpoint = ({ place, placeType }) => {
if (placeType === 'agency') {
const state = place.slice(0, 2)
return `${API}/agencies/count/states/offenses/${state}/${place}`
}
const fetchResults = (key, path) =>
get(`${API}/${path}?per_page=200`).then(response => ({
key,
results: response.results,
}))

return place === nationalKey
? `${API}/estimates/national`
: `${API}/estimates/states/${lookupUsa(place).toUpperCase()}`
}
const fetchNationalEstimates = () =>
fetchResults(nationalKey, 'estimates/national')

const getSummary = params => {
const { place } = params
const endpoint = getSummaryEndpoint(params)
const fetchStateEstimates = state => {
const stateFmt = state.length === 2 ? state : lookupUsa(state)
const path = `estimates/states/${stateFmt.toUpperCase()}`
return fetchResults(state, path)
}

return get(`${endpoint}?per_page=200`).then(response => ({
place,
results: response.results,
}))
const fetchAgencyAggregates = (ori, state) => {
const path = `agencies/count/states/offenses/${state}/${ori}`
return fetchResults(ori, path)
}

const getSummaryRequests = params => {
const { place, placeType } = params
const requests = [getSummary({ place, placeType })]
const getSummaryRequests = ({ place, placeType }) => {
if (placeType === 'agency') {
const state = place.slice(0, 2)

// add national summary request (unless you already did)
if (place !== nationalKey && placeType !== 'agency') {
requests.push(getSummary({ place: nationalKey }))
return [
fetchAgencyAggregates(place, state),
fetchStateEstimates(state),
fetchNationalEstimates(),
]
}

return requests
if (placeType === 'state') {
return [fetchStateEstimates(place), fetchNationalEstimates()]
}

return [fetchNationalEstimates()]
}

const getUcrParticipation = place => {
Expand Down Expand Up @@ -109,9 +119,12 @@ const getUcrParticipationRequests = params => {
}

export default {
fetchNationalEstimates,
fetchStateEstimates,
fetchAgencyAggregates,
getAgency,
getNibrs,
getNibrsRequests,
getSummary,
getSummaryRequests,
getUcrParticipation,
getUcrParticipationRequests,
Expand Down
6 changes: 3 additions & 3 deletions test/actions/summary.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const createPromise = (res, err) => {
const success = {
results: [
{
place: 'montana',
key: 'montana',
results: ['fake'],
},
],
Expand Down Expand Up @@ -80,11 +80,11 @@ describe('summary action', () => {
const dispatch = sandbox.spy()
sandbox.stub(api, 'getSummaryRequests', () => [
createPromise({
place: 'montana',
key: 'montana',
results: ['fake-one'],
}),
createPromise({
place: nationalKey,
key: nationalKey,
results: ['fake-two'],
}),
])
Expand Down
Loading

0 comments on commit afc6d67

Please sign in to comment.