aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTeddy Wing2018-03-15 16:19:24 +0100
committerTeddy Wing2018-03-15 17:44:22 +0100
commitfa0fdbe92d441e1a226a56c792c014a032fd46e9 (patch)
tree07b90e265f2fe6c6f3308f379a3a311297cba16f
parentd43365e243a8cf9bef3a856e2bf65c9b401e78e5 (diff)
downloadchouette-core-fa0fdbe92d441e1a226a56c792c014a032fd46e9.tar.bz2
JourneyPatternsCollection#show: Fallback to route costs
When editing a `JourneyPattern`, you can edit the distance & time costs between stops. We want to pre-fill these cost values (in the input fields) if they haven't already been set by a user. This way, they get an existing estimate of the cost and don't have to enter a value manually unless the default doesn't work. The pre-filled values come from `Route#costs`, which get calculated ahead of time via the TomTom API. Add a new `fetchRouteCosts` function that will fetch the costs for the current route from the API. This function also caches the value on `actions` so we don't keep making requests since the data isn't going to change. Put the cached fetch in a `requestAnimationFrame` as a sort of timeout to prevent a warning from React complaining about doing this during a `render()` call. Update `getTimeAndDistanceBetweenStops` to use the cost value from the route costs instead of the journey pattern costs if it doesn't exist. The `totalDistance` and `totalTime` we moved into `componentWillUpdate` instead of `render()` because we thought that might be the cause of the `render()` warning I mentioned above. Decided to leave this part in even though it doesn't have anything to do with the goal of the changes here, because it seemed like an okay change. The `RECEIVE_ROUTE_COSTS` reducer will update the state with route costs. We need the default distance:0 time:0 to avoid infinitely fetching the costs API if a cost with the given key can't be found. Put the route cost API URL in `window.routeCostsUrl` to allow us to get it from the Rails URL helper. Huge thanks to Johan for pairing with me on this code and walking me through the setup for integrating the route costs JSON response into the frontend interface. Refs #6203
-rw-r--r--app/javascript/journey_patterns/actions/index.js30
-rw-r--r--app/javascript/journey_patterns/components/JourneyPattern.js26
-rw-r--r--app/javascript/journey_patterns/components/JourneyPatterns.js4
-rw-r--r--app/javascript/journey_patterns/containers/JourneyPatternList.js3
-rw-r--r--app/javascript/journey_patterns/reducers/journeyPatterns.js11
-rw-r--r--app/views/journey_patterns_collections/show.html.slim1
-rw-r--r--spec/javascript/journey_patterns/components/JourneyPattern_spec.js4
-rw-r--r--spec/javascript/journey_patterns/components/JourneyPatterns_spec.js4
-rw-r--r--spec/javascript/journey_patterns/reducers/journey_patterns_spec.js64
9 files changed, 139 insertions, 8 deletions
diff --git a/app/javascript/journey_patterns/actions/index.js b/app/javascript/journey_patterns/actions/index.js
index a70a2e6f2..b90908264 100644
--- a/app/javascript/journey_patterns/actions/index.js
+++ b/app/javascript/journey_patterns/actions/index.js
@@ -20,6 +20,12 @@ const actions = {
type: "RECEIVE_ERRORS",
json
}),
+ receiveRouteCosts: (costs, key, index) => ({
+ type: "RECEIVE_ROUTE_COSTS",
+ costs,
+ key,
+ index
+ }),
unavailableServer : () => ({
type: 'UNAVAILABLE_SERVER'
}),
@@ -213,6 +219,30 @@ const actions = {
}
})
},
+ fetchRouteCosts: (dispatch, key, index) => {
+ if (actions.routeCostsCache) {
+ // Dispatch asynchronously to prevent warning when
+ // this executes during `render()`
+ requestAnimationFrame(() => {
+ dispatch(actions.receiveRouteCosts(
+ actions.routeCostsCache,
+ key,
+ index
+ ))
+ })
+ } else {
+ fetch(window.routeCostsUrl, {
+ credentials: 'same-origin',
+ }).then(response => {
+ return response.json()
+ }).then(json => {
+ let costs = json.costs ? json.costs : {}
+ actions.routeCostsCache = costs
+
+ dispatch(actions.receiveRouteCosts(costs, key, index))
+ })
+ }
+ },
getChecked : (jp) => {
return jp.filter((obj) => {
return obj.checked
diff --git a/app/javascript/journey_patterns/components/JourneyPattern.js b/app/javascript/journey_patterns/components/JourneyPattern.js
index d20294594..15d8b6db4 100644
--- a/app/javascript/journey_patterns/components/JourneyPattern.js
+++ b/app/javascript/journey_patterns/components/JourneyPattern.js
@@ -98,12 +98,24 @@ export default class JourneyPattern extends Component{
getTimeAndDistanceBetweenStops(from, to){
let costsKey = from + "-" + to
- let costs = this.props.value.costs[costsKey] || {distance: 0, time: 0}
+ let costs = this.getCosts(costsKey)
let time = costs['time'] || 0
let distance = costs['distance'] || 0
return [costsKey, costs, time, distance]
}
+ getCosts(costsKey) {
+ let cost = this.props.value.costs[costsKey]
+
+ if (cost) {
+ return cost
+ }
+
+ this.props.fetchRouteCosts(costsKey)
+
+ return { distance: 0, time: 0 }
+ }
+
formatDistance(distance){
return parseFloat(Math.round(distance * 100) / 100).toFixed(2) + " km"
}
@@ -119,9 +131,12 @@ export default class JourneyPattern extends Component{
}
}
+ componentWillUpdate() {
+ [this.totalTime, this.totalDistance] = this.totals(false)
+ }
+
render() {
this.previousSpId = undefined
- let [totalTime, totalDistance] = this.totals(false)
let [commercialTotalTime, commercialTotalDistance] = this.totals(true)
return (
<div className={'t2e-item' + (this.props.value.deletable ? ' disabled' : '') + (this.props.value.object_id ? '' : ' to_record') + (this.props.value.errors ? ' has-error': '') + (this.hasFeature('costs_in_journey_patterns') ? ' with-costs' : '')}>
@@ -131,8 +146,8 @@ export default class JourneyPattern extends Component{
<div>{actions.getChecked(this.props.value.stop_points).length} arrĂȘt(s)</div>
{this.hasFeature('costs_in_journey_patterns') &&
<div className="small row totals">
- <span className="col-md-6"><i className="fa fa-arrows-h"></i>{totalDistance}</span>
- <span className="col-md-6"><i className="fa fa-clock-o"></i>{totalTime}</span>
+ <span className="col-md-6"><i className="fa fa-arrows-h"></i>{this.totalDistance}</span>
+ <span className="col-md-6"><i className="fa fa-clock-o"></i>{this.totalTime}</span>
</div>
}
{this.hasFeature('costs_in_journey_patterns') &&
@@ -228,5 +243,6 @@ JourneyPattern.propTypes = {
onCheckboxChange: PropTypes.func.isRequired,
onOpenEditModal: PropTypes.func.isRequired,
onDeleteJourneyPattern: PropTypes.func.isRequired,
- journeyPatterns: PropTypes.object.isRequired
+ journeyPatterns: PropTypes.object.isRequired,
+ fetchRouteCosts: PropTypes.func.isRequired
}
diff --git a/app/javascript/journey_patterns/components/JourneyPatterns.js b/app/javascript/journey_patterns/components/JourneyPatterns.js
index e8b6bf143..930acb390 100644
--- a/app/javascript/journey_patterns/components/JourneyPatterns.js
+++ b/app/javascript/journey_patterns/components/JourneyPatterns.js
@@ -138,6 +138,7 @@ export default class JourneyPatterns extends Component {
status= {this.props.status}
editMode= {this.props.editMode}
journeyPatterns= {this}
+ fetchRouteCosts={(costsKey) => this.props.fetchRouteCosts(costsKey, index)}
/>
)}
</div>
@@ -156,5 +157,6 @@ JourneyPatterns.propTypes = {
status: PropTypes.object.isRequired,
onCheckboxChange: PropTypes.func.isRequired,
onLoadFirstPage: PropTypes.func.isRequired,
- onOpenEditModal: PropTypes.func.isRequired
+ onOpenEditModal: PropTypes.func.isRequired,
+ fetchRouteCosts: PropTypes.func.isRequired
}
diff --git a/app/javascript/journey_patterns/containers/JourneyPatternList.js b/app/javascript/journey_patterns/containers/JourneyPatternList.js
index d338345f2..539b6b54c 100644
--- a/app/javascript/journey_patterns/containers/JourneyPatternList.js
+++ b/app/javascript/journey_patterns/containers/JourneyPatternList.js
@@ -29,6 +29,9 @@ const mapDispatchToProps = (dispatch) => {
onUpdateJourneyPatternCosts: (index, costs) =>{
dispatch(actions.updateJourneyPatternCosts(index, costs))
},
+ fetchRouteCosts: (key, index) => {
+ actions.fetchRouteCosts(dispatch, key, index)
+ },
}
}
diff --git a/app/javascript/journey_patterns/reducers/journeyPatterns.js b/app/javascript/journey_patterns/reducers/journeyPatterns.js
index 1ce069522..6c38e9288 100644
--- a/app/javascript/journey_patterns/reducers/journeyPatterns.js
+++ b/app/javascript/journey_patterns/reducers/journeyPatterns.js
@@ -40,6 +40,17 @@ export default function journeyPatterns (state = [], action) {
return [...action.json]
case 'RECEIVE_ERRORS':
return [...action.json]
+ case 'RECEIVE_ROUTE_COSTS':
+ return state.map((j, i) =>{
+ if(i == action.index) {
+ const new_costs = Object.assign({}, j.costs)
+ new_costs[action.key] = action.costs[action.key] ||
+ {distance: 0, time: 0}
+ return _.assign({}, j, {costs: new_costs})
+ } else {
+ return j
+ }
+ })
case 'GO_TO_PREVIOUS_PAGE':
$('#ConfirmModal').modal('hide')
if(action.pagination.page > 1){
diff --git a/app/views/journey_patterns_collections/show.html.slim b/app/views/journey_patterns_collections/show.html.slim
index 8a7b0c47c..b389a1da7 100644
--- a/app/views/journey_patterns_collections/show.html.slim
+++ b/app/views/journey_patterns_collections/show.html.slim
@@ -19,5 +19,6 @@
| window.journeyPatternsPerPage = #{@ppage};
| window.perms = #{raw @perms};
| window.features = #{raw @features};
+ | window.routeCostsUrl = "#{costs_referential_line_route_url(@referential, @route.line, @route, format: :json).html_safe}";
= javascript_pack_tag 'journey_patterns/index.js'
diff --git a/spec/javascript/journey_patterns/components/JourneyPattern_spec.js b/spec/javascript/journey_patterns/components/JourneyPattern_spec.js
index 0da75ad47..82c996424 100644
--- a/spec/javascript/journey_patterns/components/JourneyPattern_spec.js
+++ b/spec/javascript/journey_patterns/components/JourneyPattern_spec.js
@@ -26,7 +26,8 @@ describe('the edit button', () => {
stop_points: []
},
index: 0,
- editMode: editMode
+ editMode: editMode,
+ fetchRouteCosts: () => {}
}
let list = renderer.create(
<JourneyPattern
@@ -38,6 +39,7 @@ describe('the edit button', () => {
onDeleteJourneyPattern={props.onDeleteJourneyPattern}
onOpenEditModal={props.onOpenEditModal}
editMode={props.editMode}
+ fetchRouteCosts={props.fetchRouteCosts}
/>
)
diff --git a/spec/javascript/journey_patterns/components/JourneyPatterns_spec.js b/spec/javascript/journey_patterns/components/JourneyPatterns_spec.js
index 0c852deff..b6f83963b 100644
--- a/spec/javascript/journey_patterns/components/JourneyPatterns_spec.js
+++ b/spec/javascript/journey_patterns/components/JourneyPatterns_spec.js
@@ -15,7 +15,8 @@ describe('stopPointHeader', () => {
onLoadFirstPage: ()=>{},
onOpenEditModal: ()=>{},
stopPointsList: [stop_point, same_city_stop_point, other_country_stop_point],
- journeyPatterns: []
+ journeyPatterns: [],
+ fetchRouteCosts: () => {}
}
let list = renderer.create(
<JourneyPatterns
@@ -25,6 +26,7 @@ describe('stopPointHeader', () => {
onCheckboxChange={props.onCheckboxChange}
onLoadFirstPage={props.onLoadFirstPage}
onOpenEditModal={props.onOpenEditModal}
+ fetchRouteCosts={props.fetchRouteCosts}
/>
).toJSON()
diff --git a/spec/javascript/journey_patterns/reducers/journey_patterns_spec.js b/spec/javascript/journey_patterns/reducers/journey_patterns_spec.js
index bfa87d24a..ce3bdc055 100644
--- a/spec/javascript/journey_patterns/reducers/journey_patterns_spec.js
+++ b/spec/javascript/journey_patterns/reducers/journey_patterns_spec.js
@@ -132,6 +132,70 @@ describe('journeyPatterns reducer', () => {
).toEqual([state[0], new_state])
})
+ it('should handle RECEIVE_ROUTE_COSTS', () => {
+ const costs = {
+ "1-2": {
+ distance: 1,
+ time: 9,
+ },
+ "2-3": {
+ distance: 23,
+ time: 10
+ }
+ }
+ const new_costs = {
+ "1-2": {
+ distance: 0,
+ time: 10,
+ },
+ "2-3": {
+ distance: 23,
+ time: 10
+ }
+ }
+ const new_state = Object.assign({}, state[1], {costs: new_costs})
+ expect(
+ jpReducer(state, {
+ type: 'RECEIVE_ROUTE_COSTS',
+ costs,
+ key: '2-3',
+ index: 1
+ })
+ ).toEqual([state[0], new_state])
+ })
+
+ it('should handle RECEIVE_ROUTE_COSTS when cost key is missing', () => {
+ const costs = {
+ "1-2": {
+ distance: 1,
+ time: 9,
+ },
+ "2-3": {
+ distance: 23,
+ time: 10
+ }
+ }
+ const new_costs = {
+ "1-2": {
+ distance: 0,
+ time: 10,
+ },
+ "3-4": {
+ distance: 0,
+ time: 0
+ }
+ }
+ const new_state = Object.assign({}, state[1], {costs: new_costs})
+ expect(
+ jpReducer(state, {
+ type: 'RECEIVE_ROUTE_COSTS',
+ costs,
+ key: '3-4',
+ index: 1
+ })
+ ).toEqual([state[0], new_state])
+ })
+
it('should handle DELETE_JOURNEYPATTERN', () => {
expect(
jpReducer(state, {