Skip to content

Commit

Permalink
fix(timetable): add pattern/route/calendar grouped trip counts
Browse files Browse the repository at this point in the history
fixes #110
  • Loading branch information
landonreed committed May 3, 2018
1 parent c55c88e commit 38a2151
Show file tree
Hide file tree
Showing 15 changed files with 307 additions and 85 deletions.
14 changes: 14 additions & 0 deletions lib/editor/actions/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,20 @@ export function fetchBaseGtfs ({namespace, component, newId, activeEntityId, fee
const query = `
query ($namespace: String) {
feed(namespace: $namespace) {
trip_counts {
service_id {
type
count
}
pattern_id {
type
count
}
route_id {
type
count
}
}
feed_info {
id
feed_publisher_name
Expand Down
66 changes: 65 additions & 1 deletion lib/editor/actions/trip.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {createAction} from 'redux-actions'
import snakeCaseKeys from 'snakecase-keys'
import {fetchGraphQL, secureFetch} from '../../common/actions'
import { setErrorMessage } from '../../manager/actions/status'
import {setErrorMessage} from '../../manager/actions/status'
import {entityIsNew} from '../util/objects'
import {getEditorNamespace} from '../util/gtfs'

Expand All @@ -10,6 +10,8 @@ const deletingTrips = createAction('DELETING_TRIPS_FOR_CALENDAR')
const requestingTripsForCalendar = createAction('REQUESTING_TRIPS_FOR_CALENDAR')
const receiveTripsForCalendar = createAction('RECEIVE_TRIPS_FOR_CALENDAR')
const savingTrips = createAction('SAVING_TRIPS')
const receiveTripCountsForPattern = createAction('RECEIVE_TRIP_COUNTS_FOR_PATTERN')
const receiveTripCounts = createAction('RECEIVE_TRIP_COUNTS')

// Actions used in UI
export const offsetRows = createAction('OFFSET_ROWS')
Expand Down Expand Up @@ -193,3 +195,65 @@ export function deleteTripsForCalendar (feedId, pattern, calendarId, trips) {
})
}
}

/**
* Fetch trip counts per calendar filtered by a specific pattern ID.
*/
export function fetchCalendarTripCountsForPattern (feedId, patternId) {
return function (dispatch, getState) {
const namespace = getEditorNamespace(feedId, getState())
// This fetches patterns on the pattern_id field (rather than ID) because
// pattern_id is needed to join on the nested trips table
const query = `query ($namespace: String, $pattern_id: String) {
feed(namespace: $namespace) {
trip_counts {
service_id (pattern_id: $pattern_id) {
type
count
}
}
}
}`
return dispatch(fetchGraphQL({
query,
variables: {namespace, pattern_id: patternId},
errorMessage: 'Could not fetch trip counts for pattern'
}))
.then(data => dispatch(receiveTripCountsForPattern({tripCounts: data.feed.trip_counts, patternId})))
}
}

/**
* Fetch all trip count categories (for each pattern, calendar, and route).
*/
export function fetchTripCounts (feedId) {
return function (dispatch, getState) {
const namespace = getEditorNamespace(feedId, getState())
// This fetches patterns on the pattern_id field (rather than ID) because
// pattern_id is needed to join on the nested trips table
const query = `query ($namespace: String) {
feed(namespace: $namespace) {
trip_counts {
service_id {
type
count
}
pattern_id {
type
count
}
route_id {
type
count
}
}
}
}`
return dispatch(fetchGraphQL({
query,
variables: {namespace},
errorMessage: 'Could not fetch trip counts.'
}))
.then(data => dispatch(receiveTripCounts({tripCounts: data.feed.trip_counts})))
}
}
2 changes: 2 additions & 0 deletions lib/editor/actions/tripPattern.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import snakeCaseKeys from 'snakecase-keys'
import {secureFetch} from '../../common/actions'
import {fetchGTFSEntities} from '../../manager/actions/versions'
import {resetActiveGtfsEntity, savedGtfsEntity, updateEditSetting} from './active'
import {fetchTripCounts} from './trip'
import {entityIsNew} from '../util/objects'
import {getEditorNamespace} from '../util/gtfs'

Expand Down Expand Up @@ -83,5 +84,6 @@ export function deleteAllTripsForPattern (feedId, patternId) {
return dispatch(secureFetch(url, 'delete'))
.then(res => res.json())
.then(json => json && json.result === 'OK')
.then(() => dispatch(fetchTripCounts(feedId)))
}
}
5 changes: 2 additions & 3 deletions lib/editor/components/pattern/EditSchedulePanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,12 @@ export default class EditSchedulePanel extends Component {
}

render () {
const {activePattern, activePatternId} = this.props
const {activePattern, activePatternId, activePatternTripCount} = this.props
if (!activePattern) return null
const {
directionId,
name,
patternStops,
tripCount,
useFrequency
} = activePattern
const timetableOptions = [
Expand All @@ -82,7 +81,7 @@ export default class EditSchedulePanel extends Component {
return (
<div>
<h4 className='line'>
Schedules {`(${tripCount} trip${tripCount !== 1 ? 's' : ''})`}
Schedules {`(${activePatternTripCount} trip${activePatternTripCount !== 1 ? 's' : ''})`}
</h4>
<FormGroup style={{marginTop: '5px'}}>
<ButtonGroup className='pull-right'>
Expand Down
91 changes: 56 additions & 35 deletions lib/editor/components/timetable/CalendarSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,31 @@ export default class CalendarSelect extends Component {
}

_optionRenderer = (option) => {
// const {activePattern} = this.props
// FIXME: undefined trip count field in graphql
const patternTrips = 0 // (activePattern && activePattern.tripCountByCalendar[option.value]) || 0
const routeCount = 0 // Object.keys(option.calendar.routes).length
const {
label,
service_id: serviceId,
patternTrips,
totalTrips
} = option
// FIXME: Add back route count and route trips for calendar?
return (
<span title={`${option.label} (${option.service_id})`}>
<Icon type='calendar-o' /> {option.label}
<span title={`${label} (${serviceId})`}>
<Icon type='calendar-o' /> {label}
{' '}
<Label
bsStyle={patternTrips ? 'success' : 'default'}
title={`Calendar has ${patternTrips} trips for pattern and ${option.routeTrips} for route`}>
<Icon type='bars' /> {patternTrips}/{option.routeTrips}
title={`Calendar has ${patternTrips} trips for pattern`}>
<Icon type='bars' /> {patternTrips}
</Label>
{' '}
{/** {' '}
<Label
title={`Calendar has trips for ${routeCount} routes`}>
<Icon type='bus' /> {routeCount}
</Label>
</Label> **/}
{' '}
<Label
title={`Calendar has ${option.totalTrips} trips for feed`}>
<Icon type='building-o' /> {option.totalTrips}
title={`Calendar has ${totalTrips} trips for feed`}>
<Icon type='building-o' /> {totalTrips}
</Label>
</span>
)
Expand All @@ -50,38 +53,56 @@ export default class CalendarSelect extends Component {
setActiveEntity(feedSource.id, 'route', route, 'trippattern', activePattern, 'timetable', calendar)
}

render () {
const { activePattern, route, activeCalendar, calendars, trips } = this.props
console.log(activeCalendar)
const options = calendars && route
_getTripCount = (tripCounts, id) => {
const item = tripCounts && tripCounts.service_id.find(item => item.type === id)
return item ? item.count : 0
}

_getPatternTripCount = (tripCounts, serviceId, patternId) => {
const item = tripCounts && tripCounts[`pattern:${patternId}`].service_id
.find(item => item.type === serviceId)
return item ? item.count : 0
}

_getOptions = () => {
const {activePattern, calendars, tripCounts, trips} = this.props
const patternId = activePattern && activePattern.patternId
const calendarOptions = calendars && activePattern
? calendars
// FIXME: add sort back in
// .sort((a, b) => {
// if (route.id in a.routes && !(route.id in b.routes)) return -1
// else if (route.id in b.routes && !(route.id in a.routes)) return 1
// else return b.numberOfTrips - a.numberOfTrips
// })
.map(calendar => {
return {
label: calendar.description,
value: calendar.service_id,
service_id: calendar.service_id,
calendar,
totalTrips: calendar.numberOfTrips,
// FIXME: argh, IDs should not be integers...
routeTrips: 0, // calendar.routes[route.id] || 0,
calendarTrips: trips.length
}
})
.map(calendar => ({
label: calendar.description,
value: calendar.service_id,
service_id: calendar.service_id,
calendar,
patternTrips: this._getTripCount(tripCounts[`pattern:${patternId}`], calendar.service_id),
totalTrips: this._getTripCount(tripCounts, calendar.service_id),
// FIXME: argh, IDs should not be integers...
// routeTrips: 0, // calendar.routes[route.id] || 0,
calendarTrips: trips.length
}))
: []
return calendarOptions
.sort((a, b) => {
return b.patternTrips - a.patternTrips
// if (route.id in a.routes && !(route.id in b.routes)) return -1
// else if (route.id in b.routes && !(route.id in a.routes)) return 1
// else return b.numberOfTrips - a.numberOfTrips
})
}

render () {
const {
activePattern,
activeCalendar
} = this.props
return (
<Select
value={activeCalendar && activeCalendar.service_id}
placeholder={<span><Icon type='calendar-o' /> Select calendar...</span>}
valueRenderer={this._optionRenderer}
optionRenderer={this._optionRenderer}
disabled={!activePattern || entityIsNew(activePattern)}
options={options}
options={this._getOptions()}
onChange={this._onChange}
filterOptions />
)
Expand Down
70 changes: 54 additions & 16 deletions lib/editor/components/timetable/PatternSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,54 @@ export default class PatternSelect extends Component {
route: PropTypes.object,
feedSource: PropTypes.object,
calendars: PropTypes.array,
setActiveEntity: PropTypes.func
setActiveEntity: PropTypes.func,
fetchCalendarTripCountsForPattern: PropTypes.func
}
/**
* Fetch active pattern's trip counts when component mounts.
*/
componentDidMount () {
const {
activePattern,
feedSource,
fetchCalendarTripCountsForPattern
} = this.props
if (activePattern) {
fetchCalendarTripCountsForPattern(feedSource.id, activePattern.patternId)
}
}

/**
* Fetch new pattern's trip counts whenever the pattern is changed.
*/
componentWillReceiveProps (nextProps) {
const {
activePattern: newPattern,
feedSource,
fetchCalendarTripCountsForPattern
} = nextProps
const currentPatternId = this.props.activePattern && this.props.activePattern.id
if (newPattern && newPattern.id !== currentPatternId) {
fetchCalendarTripCountsForPattern(feedSource.id, newPattern.patternId)
}
}

_optionRenderer = (option) => {
// FIXME: need trip counts in graphql response
const calendarCount = 2 // Object.keys(option.pattern.tripCountByCalendar).length
// FIXME: Add number of calendars for which pattern has trips?
// const calendarCount = Object.keys(option.pattern.tripCountByCalendar).length
return (
<span title={option.label}>
<Icon type='code-fork' /> {option.label}
{' '}
<Label
title={`Pattern has ${option.pattern.tripCount} trips`}>
<Icon type='bars' /> {option.pattern.tripCount}
title={`Pattern has ${option.tripCount} trips`}>
<Icon type='bars' /> {option.tripCount}
</Label>
{' '}
{/** {' '}
<Label
title={`Pattern has trips for ${calendarCount} calendars`}>
<Icon type='calendar-o' /> {calendarCount}
</Label>
</Label> **/}
</span>
)
}
Expand All @@ -41,23 +70,32 @@ export default class PatternSelect extends Component {
setActiveEntity(feedSource.id, 'route', route, 'trippattern', pattern, 'timetable', null)
}

render () {
const {activePattern, route} = this.props
_getTripCount = (tripCounts, id) => {
const item = tripCounts && tripCounts.pattern_id.find(item => item.type === id)
return item ? item.count : 0
}

_getOptions = () => {
const {route, tripCounts} = this.props
const patterns = route && route.tripPatterns ? route.tripPatterns : []
return patterns.map(pattern => ({
value: pattern.id,
label: `${getEntityName(pattern)}` || '[Unnamed]',
pattern,
tripCount: this._getTripCount(tripCounts, pattern.patternId)
}))
}

render () {
const {activePattern} = this.props
return (
<Select
value={activePattern && activePattern.id}
component={'pattern'}
valueRenderer={this._optionRenderer}
optionRenderer={this._optionRenderer}
placeholder={<span><Icon type='code-fork' /> Select pattern...</span>}
options={patterns.map(pattern => (
{
value: pattern.id,
label: `${getEntityName(pattern)}` || '[Unnamed]',
pattern
}))
}
options={this._getOptions()}
onChange={this._onChange} />
)
}
Expand Down
Loading

0 comments on commit 38a2151

Please sign in to comment.