Skip to content

Commit 7effe17

Browse files
committed
fix(editor): handle bad entity and pattern IDs in editor URL
fixes #99
1 parent b09fe6c commit 7effe17

File tree

5 files changed

+67
-34
lines changed

5 files changed

+67
-34
lines changed

lib/editor/actions/active.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,15 @@ export function setActiveEntity (feedSourceId, component, entity, subComponent,
7373
*
7474
* FIXME: There is too much going on in this action.
7575
*/
76-
export function setActiveGtfsEntity (feedSourceId, component, entityId, subComponent, subEntityId, subSubComponent, subSubEntityId) {
76+
export function setActiveGtfsEntity (
77+
feedSourceId,
78+
component,
79+
entityId,
80+
subComponent,
81+
subEntityId,
82+
subSubComponent,
83+
subSubEntityId
84+
) {
7785
return function (dispatch, getState) {
7886
// TODO: Figure out a good way to handle route changes without confirm window
7987
// when there are unsaved changes to the active entity (or subentity).
@@ -93,6 +101,7 @@ export function setActiveGtfsEntity (feedSourceId, component, entityId, subCompo
93101
// Feed source has changed. Clear GTFS content
94102
// FIXME: Do we need to clear GTFS content if namespace changes?
95103
// FIXME: This should be covered by GtfsEditor#componentWillReceiveProps
104+
console.warn(`Feed source ID has changed ${prevFeedSourceId} to ${feedSourceId}. Clearing GTFS content.`)
96105
dispatch(clearGtfsContent())
97106
}
98107
if (editSettings.editGeometry) {

lib/editor/actions/editor.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,10 @@ export function newGtfsEntity (feedSourceId, component, props, save, refetch = t
212212
} else {
213213
dispatch(createGtfsEntity(feedSourceId, component, props))
214214
if (props && 'routeId' in props) {
215-
// console.log('setting after create')
215+
// Set active entity for trip pattern
216216
dispatch(setActiveGtfsEntity(feedSourceId, 'route', props.routeId, component, ENTITY.NEW_ID))
217217
} else {
218-
console.log('setting after create', feedSourceId, component, ENTITY.NEW_ID)
218+
// Set active entity for all other basic types (e.g., routes, stops, etc.).
219219
dispatch(setActiveGtfsEntity(feedSourceId, component, ENTITY.NEW_ID))
220220
}
221221
}

lib/editor/containers/ActiveGtfsEditor.js

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {connect} from 'react-redux'
22

33
import GtfsEditor from '../components/GtfsEditor'
4-
import {ENTITY} from '../constants'
54
import {fetchFeedSourceAndProject} from '../../manager/actions/feeds'
65
import {
76
fetchTripPatternsForRoute,
@@ -45,37 +44,32 @@ import {updateUserMetadata} from '../../manager/actions/user'
4544
import {findProjectByFeedSource} from '../../manager/util'
4645
import {setTutorialHidden} from '../../manager/actions/ui'
4746
import {getControlPoints, getValidationErrors} from '../selectors'
48-
import {getTableById} from '../util/gtfs'
47+
import {getTableById, getIdsFromParams} from '../util/gtfs'
4948

5049
const mapStateToProps = (state, ownProps) => {
5150
const {
5251
feedSourceId,
5352
activeComponent,
5453
subComponent,
5554
subSubComponent,
56-
// activeEntityId,
57-
// subEntityId,
5855
activeSubSubEntity
5956
} = ownProps.routeParams
60-
// TODO: Figure out a better way to parse an integer from react-router route
61-
// params
6257
// Cast IDs to integers to match data type of fields returned by the SQL database
63-
const activeEntityId = typeof ownProps.routeParams.activeEntityId !== 'undefined' ? +ownProps.routeParams.activeEntityId : undefined
64-
const subEntityId = typeof ownProps.routeParams.subEntityId !== 'undefined' ? +ownProps.routeParams.subEntityId : undefined
58+
const {activeEntityId, subEntityId} = getIdsFromParams(ownProps.routeParams)
6559
const {data, editSettings: editSettingsState, mapState} = state.editor
6660
const {present: editSettings} = editSettingsState
6761
const {active, tables, tripPatterns, status} = data
6862
// FIXME: entityId is now a non-string line number and somewhere the number is
6963
// being cast to a string.
70-
const activeEntity =
71-
active.entity && active.entity.id === activeEntityId
64+
const activeEntity = active.entity && active.entity.id === activeEntityId
65+
? active.entity
66+
: active.entity && activeComponent === 'feedinfo'
7267
? active.entity
73-
: active.entity && activeComponent === 'feedinfo' ? active.entity : null
68+
: null
7469
const activePattern = active.subEntity
7570
const entityEdited = active.edited
7671
const patternEdited = Boolean(active.patternEdited)
7772
const {controlPoints, patternSegments: patternCoordinates} = getControlPoints(state)
78-
// console.log('pattern segments', patternCoordinates)
7973
const tableView = ownProps.location.query && ownProps.location.query.table === 'true'
8074
// Active set of entities (e.g., stops, routes, agency)
8175
const entities = activeComponent && getTableById(tables, activeComponent)
@@ -86,6 +80,7 @@ const mapStateToProps = (state, ownProps) => {
8680
// find the containing project
8781
const project = findProjectByFeedSource(state.projects.all, feedSourceId)
8882
const feedSource = project && project.feedSources.find(fs => fs.id === feedSourceId)
83+
const namespace = feedSource && feedSource.editorNamespace
8984

9085
const feedInfo = getTableById(tables, 'feedinfo')[0]
9186
const patternStop = active.patternStop || {}
@@ -100,6 +95,7 @@ const mapStateToProps = (state, ownProps) => {
10095
feedSource,
10196
entities,
10297
feedSourceId,
98+
namespace,
10399
feedInfo,
104100
entityEdited,
105101
tableView,
@@ -133,16 +129,10 @@ const mapDispatchToProps = (dispatch, ownProps) => {
133129
activeComponent,
134130
subComponent,
135131
subSubComponent,
136-
// activeEntityId,
137-
// subEntityId,
138132
activeSubSubEntity
139133
} = ownProps.routeParams
140-
// console.log(ownProps.routeParams)
141-
// TODO: Figure out a better way to parse an integer from react-router route
142-
// params
143-
// FIXME: handle "new" entityId route
144-
const activeEntityId = typeof ownProps.routeParams.activeEntityId !== 'undefined' ? +ownProps.routeParams.activeEntityId : undefined
145-
const subEntityId = typeof ownProps.routeParams.subEntityId !== 'undefined' ? +ownProps.routeParams.subEntityId : undefined
134+
// Parse IDs from route params
135+
const {activeEntityId, subEntityId} = getIdsFromParams(ownProps.routeParams)
146136
const props = {
147137
component: activeComponent,
148138
newId: activeEntityId,
@@ -171,18 +161,15 @@ const mapDispatchToProps = (dispatch, ownProps) => {
171161
}
172162
},
173163
onComponentUpdate: (prevProps, newProps) => {
174-
// handle back button presses by re-setting active gtfs entity
175-
if (prevProps.activeEntityId !== ENTITY.NEW_ID &&
176-
(prevProps.activeComponent !== newProps.activeComponent ||
164+
// Detect push changes to URL (e.g., back button or direct link) and update
165+
// active table/entity accordingly.
166+
if (prevProps.activeComponent !== newProps.activeComponent ||
177167
prevProps.activeEntityId !== newProps.activeEntityId ||
178168
prevProps.subComponent !== newProps.subComponent ||
179169
prevProps.subEntityId !== newProps.subEntityId ||
180170
prevProps.subSubComponent !== newProps.subSubComponent ||
181-
prevProps.activeSubSubEntity !== newProps.activeSubSubEntity)
171+
prevProps.activeSubSubEntity !== newProps.activeSubSubEntity
182172
) {
183-
console.log('handling back button', activeEntityId, subEntityId)
184-
// FIXME: handle infinite loop case when, for example, entity ID is bad
185-
// or is not found.
186173
dispatch(setActiveGtfsEntity(feedSourceId, activeComponent, activeEntityId, subComponent, subEntityId, subSubComponent, activeSubSubEntity))
187174
}
188175
},

lib/editor/util/gtfs.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,21 @@ export function getTableById (tableData: any, id: string, emptyArrayOnNull: bool
5757
const table = tableData[tableName]
5858
// TODO: use Immutable List?
5959
// If table is null, return empty array if boolean specifies
60-
// return emptyArrayOnNull
61-
// ? List(table) || []
62-
// : List(table)
6360
return emptyArrayOnNull
6461
? table || []
6562
: table
6663
}
6764

65+
export function getIdsFromParams (params: any) {
66+
const activeEntityId = typeof params.activeEntityId !== 'undefined' && !isNaN(params.activeEntityId)
67+
? +params.activeEntityId
68+
: undefined
69+
const subEntityId = typeof params.subEntityId !== 'undefined' && !isNaN(params.subEntityId)
70+
? +params.subEntityId
71+
: undefined
72+
return {activeEntityId, subEntityId}
73+
}
74+
6875
export function getKeyForId (id: string, key: string): ?string {
6976
const component = COMPONENT_LIST.find(c => c.id === id)
7077
return typeof component !== 'undefined' ? component[key] : null

lib/manager/actions/versions.js

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {SECURE_API_PREFIX} from '../../common/constants'
88
import {getConfigProperty} from '../../common/util/config'
99
import {uploadFile} from '../../common/util/upload-file'
1010
import fileDownload from '../../common/util/file-download'
11+
import {getKeyForId} from '../../editor/util/gtfs'
1112
import {getEntityGraphQLRoot, getEntityIdField, getGraphQLFieldsForEntity} from '../../gtfs/util'
1213
import {handleJobResponse, setErrorMessage, startJobMonitor} from './status'
1314
import {fetchFeedSource} from './feeds'
@@ -207,14 +208,16 @@ export function fetchGTFSEntities ({namespace, id, type, editor = false, replace
207208
}
208209
}
209210
`
210-
dispatch(fetchingGTFSEntities({namespace, id, type, editor, query, patternId}))
211+
dispatch(fetchingGTFSEntities({namespace, id, type, editor, query}))
211212
// If fetching for the editor, cast id to int for csv_line field
212213
return dispatch(fetchGraphQL({
213214
query,
214215
variables: {namespace, [entityIdField]: editor ? +id : id}
215216
}))
216217
.then(data => {
217218
dispatch(receiveGTFSEntities({namespace, id, component: type, data, editor, replaceNew}))
219+
const patternId = getState().editor.data.active.subEntityId
220+
checkEntityIdValidity(data, type, id, patternId)
218221
if (replaceNew) {
219222
// FIXME Verify this is working properly.
220223
const {feedSourceId} = getState().editor.data.active
@@ -231,6 +234,33 @@ export function fetchGTFSEntities ({namespace, id, type, editor = false, replace
231234
}
232235
}
233236

237+
/**
238+
* Check that entity and pattern IDs are valid and update URL if not.
239+
*/
240+
function checkEntityIdValidity (data, type, id, patternId) {
241+
const tableName = getKeyForId(type, 'tableName')
242+
const entity = data.feed[tableName][0]
243+
const pathParts = window.location.pathname.split('/')
244+
let entityNotFound = false
245+
if (!entity) {
246+
console.warn(`No ${type} entity found for id=${id}. Removing id from URL.`)
247+
entityNotFound = true
248+
while (pathParts[pathParts.length - 1] !== type) pathParts.pop()
249+
} else if (type === 'route' && patternId) {
250+
const pattern = entity.tripPatterns.find(p => p.id === patternId)
251+
if (!pattern) {
252+
console.warn(`No pattern entity found for id=${patternId}. Removing id from URL.`)
253+
entityNotFound = true
254+
pathParts.pop()
255+
}
256+
}
257+
if (entityNotFound) {
258+
// If there was no entity found for the requested ID, remove the ID
259+
// from the URL
260+
browserHistory.push(pathParts.join('/'))
261+
}
262+
}
263+
234264
export function fetchValidationErrors ({feedVersion, errorType, limit, offset}) {
235265
return function (dispatch, getState) {
236266
dispatch(fetchingValidationErrors({feedVersion, errorType, limit, offset}))

0 commit comments

Comments
 (0)