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

incorporate more agency info to explorer page #745

Merged
merged 2 commits into from
May 10, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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 { agency, 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={agency}
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/agency.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 agency from './agency'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this part of the state object might end up holding more than a single agency, I think we should call it 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({
agency,
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