From b2997454b4decd49643ef7916534fc1c0845bc7f Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Thu, 6 May 2021 16:50:55 +0100 Subject: [PATCH 01/36] feat(gtfs.yml and validation.js): Updated GTFS Spec for stops.txt and agency.txt #663 --- gtfs.yml | 10 ++++++++-- lib/editor/util/validation.js | 36 +++++++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/gtfs.yml b/gtfs.yml index 4170346d5..caf511556 100644 --- a/gtfs.yml +++ b/gtfs.yml @@ -151,12 +151,12 @@ columnWidth: 12 helpContent: "The stop_desc field contains a description of a stop. Please provide useful, quality information. Do not simply duplicate the name of the stop." - name: "stop_lat" - required: true + required: false inputType: LATITUDE columnWidth: 6 helpContent: "The stop_lat field contains the latitude of a stop or station. The field value must be a valid WGS 84 latitude." - name: "stop_lon" - required: true + required: false inputType: LONGITUDE columnWidth: 6 helpContent: "The stop_lon field contains the longitude of a stop or station. The field value must be a valid WGS 84 longitude value from -180 to 180." @@ -180,6 +180,12 @@ text: Stop (0) - value: '1' text: Station (1) + - value: '2' + text: Entrance/Exit (2) + - value: '3' + text: Generic Node (3) + - value: '4' + text: Boarding Area (4) columnWidth: 12 helpContent: "The location_type field identifies whether this stop ID represents a stop or station. If no location type is specified, or the location_type is blank, stop IDs are treated as stops. Stations may have different properties from stops when they are represented on a map or used in trip planning." - name: "parent_station" diff --git a/lib/editor/util/validation.js b/lib/editor/util/validation.js index f6451d01c..0fc91b493 100644 --- a/lib/editor/util/validation.js +++ b/lib/editor/util/validation.js @@ -25,6 +25,7 @@ export function doesNotExist (value: any): boolean { * Returns false if the value is ok. * Returns an EditorValidationIssue object if the value is not ok. */ +// eslint-disable-next-line complexity export function validate ( field: GtfsSpecField, value: any, @@ -36,8 +37,8 @@ export function validate ( const valueDoesNotExist = doesNotExist(value) const isRequiredButEmpty = required && valueDoesNotExist const isOptionalAndEmpty = !required && valueDoesNotExist - let reason = 'Required field must not be empty' const agencies = getTableById(tableData, 'agency') + let reason = 'Required field must not be empty' // setting as a variable here because of eslint bug type CheckPositiveOutput = { @@ -100,12 +101,19 @@ export function validate ( (indices.length > 1 || (indices.length > 0 && entities[indices[0]].id !== entity.id)) ) + console.log(idList.length) + console.log({entity}) + if (agencies.length > 1 && entity.agency_id === null) { + reason = 'Identifier is required if more than one agency exists' + return {field: name, invalid: isRequiredButEmpty, reason} + } if (isRequiredButEmpty || isNotUnique) { if (isNotUnique) { reason = 'Identifier must be unique' } return {field: name, invalid: isRequiredButEmpty || isNotUnique, reason} - } else { + } + else { return false } case 'TEXT': @@ -175,21 +183,29 @@ export function validate ( } case 'LATITUDE': const isNotLat = value > 90 || value < -90 - if (isRequiredButEmpty || isNotLat) { - if (isNotLat) { - reason = 'Field must be valid latitude.' + if (isNotLat) { + reason = 'Field must be valid latitude.' + return {field: name, invalid: isOptionalAndEmpty || isNotLat, reason} + } + if (entity && entity.location_type >= 2) { + if (isOptionalAndEmpty) { + reason = '1 Latitude and Longitude are required for your current location type' + return {field: name, invalid: isOptionalAndEmpty || isNotLat, reason} } - return {field: name, invalid: isRequiredButEmpty || isNotLat, reason} } else { return false } case 'LONGITUDE': const isNotLng = value > 180 || value < -180 - if (isRequiredButEmpty || isNotLng) { - if (isNotLng) { - reason = 'Field must be valid longitude.' + if (isNotLng) { + reason = 'Field must be valid longitude.' + return {field: name, invalid: isOptionalAndEmpty || isNotLng, reason} + } + if (entity && entity.location_type >= 2) { + if (isOptionalAndEmpty) { + reason = '1 Latitude and Longitude are required for your current location type' + return {field: name, invalid: isOptionalAndEmpty || isNotLng, reason} } - return {field: name, invalid: isRequiredButEmpty || isNotLng, reason} } else { return false } From 976b5c7af0b3b6fab3ce7848c690f2ef3d70ffab Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Thu, 6 May 2021 17:14:57 +0100 Subject: [PATCH 02/36] style(validation.js): removed space #663 --- lib/editor/util/validation.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/editor/util/validation.js b/lib/editor/util/validation.js index 0fc91b493..d4ed659ab 100644 --- a/lib/editor/util/validation.js +++ b/lib/editor/util/validation.js @@ -112,8 +112,7 @@ export function validate ( reason = 'Identifier must be unique' } return {field: name, invalid: isRequiredButEmpty || isNotUnique, reason} - } - else { + } else { return false } case 'TEXT': From b1341cf75094c3d8445c96f8cb0ce7715e831ea5 Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Mon, 10 May 2021 11:44:31 +0100 Subject: [PATCH 03/36] refactor(validation.js): SOrted typing issues and removed some code #668 --- lib/editor/util/validation.js | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/lib/editor/util/validation.js b/lib/editor/util/validation.js index d4ed659ab..2f0d125c9 100644 --- a/lib/editor/util/validation.js +++ b/lib/editor/util/validation.js @@ -38,8 +38,14 @@ export function validate ( const isRequiredButEmpty = required && valueDoesNotExist const isOptionalAndEmpty = !required && valueDoesNotExist const agencies = getTableById(tableData, 'agency') + let locationType: ?number = 0 let reason = 'Required field must not be empty' + // entity.locationtype is a string. Convert to number for conditinals later on. + if (entity && entity.location_type) { + locationType = parseInt(entity.location_type) + } + // setting as a variable here because of eslint bug type CheckPositiveOutput = { num?: number, @@ -101,9 +107,11 @@ export function validate ( (indices.length > 1 || (indices.length > 0 && entities[indices[0]].id !== entity.id)) ) - console.log(idList.length) - console.log({entity}) - if (agencies.length > 1 && entity.agency_id === null) { + if ( + name === 'agency_id' && + idList.length > 1 && + valueDoesNotExist + ) { reason = 'Identifier is required if more than one agency exists' return {field: name, invalid: isRequiredButEmpty, reason} } @@ -184,30 +192,28 @@ export function validate ( const isNotLat = value > 90 || value < -90 if (isNotLat) { reason = 'Field must be valid latitude.' - return {field: name, invalid: isOptionalAndEmpty || isNotLat, reason} + return {field: name, invalid: isNotLat, reason} } - if (entity && entity.location_type >= 2) { + if (!locationType || locationType < 2) { if (isOptionalAndEmpty) { - reason = '1 Latitude and Longitude are required for your current location type' - return {field: name, invalid: isOptionalAndEmpty || isNotLat, reason} + reason = 'Latitude and Longitude are required for your current location type' + return {field: name, invalid: isOptionalAndEmpty, reason} } - } else { - return false } + return false case 'LONGITUDE': const isNotLng = value > 180 || value < -180 if (isNotLng) { reason = 'Field must be valid longitude.' return {field: name, invalid: isOptionalAndEmpty || isNotLng, reason} } - if (entity && entity.location_type >= 2) { + if (!locationType || locationType < 2) { if (isOptionalAndEmpty) { - reason = '1 Latitude and Longitude are required for your current location type' + reason = 'Latitude and Longitude are required for your current location type' return {field: name, invalid: isOptionalAndEmpty || isNotLng, reason} } - } else { - return false } + return false case 'TIME': case 'NUMBER': const isNotANumber = isNaN(value) From 8a9c3834686e24436e51184b752236612b765ab6 Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Tue, 11 May 2021 15:10:06 +0100 Subject: [PATCH 04/36] improvement(gtfs.yml, end-to-end.js, index.js,validation.js): WIP: Various GTFS changes and updates gtfs.yml --- __tests__/end-to-end.js | 2 +- gtfs.yml | 9 +++++++-- lib/editor/util/validation.js | 10 ++++++++-- lib/types/index.js | 2 +- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/__tests__/end-to-end.js b/__tests__/end-to-end.js index dd083ca9a..81c6efe2c 100644 --- a/__tests__/end-to-end.js +++ b/__tests__/end-to-end.js @@ -418,7 +418,7 @@ async function createStop ({ option: number }, url: string, - wheelchairBoarding?: string, // make optional due to https://github.com/facebook/flow/issues/183 + wheelchairBoarding?: number, // make optional due to https://github.com/facebook/flow/issues/183 zoneId?: string // make optional due to https://github.com/facebook/flow/issues/183 }) { log.info(`creating stop with name: ${name}`) diff --git a/gtfs.yml b/gtfs.yml index caf511556..248178dd0 100644 --- a/gtfs.yml +++ b/gtfs.yml @@ -139,7 +139,7 @@ columnWidth: 6 helpContent: "The stop_code field contains short text or a number that uniquely identifies the stop for passengers. Stop codes are often used in phone-based transit information systems or printed on stop signage to make it easier for riders to get a stop schedule or real-time arrival information for a particular stop." - name: "stop_name" - required: true + required: false inputType: TEXT bulkEditEnabled: true columnWidth: 12 @@ -186,8 +186,13 @@ text: Generic Node (3) - value: '4' text: Boarding Area (4) - columnWidth: 12 + columnWidth: 7 helpContent: "The location_type field identifies whether this stop ID represents a stop or station. If no location type is specified, or the location_type is blank, stop IDs are treated as stops. Stations may have different properties from stops when they are represented on a map or used in trip planning." + - name: "platform_code" + required: false + inputType: TEXT + columnWidth: 5 + helpContent: "Platform identifier for a platform stop (a stop belonging to a station). This should be just the platform identifier (eg. G or 3)." - name: "parent_station" required: false inputType: GTFS_STOP diff --git a/lib/editor/util/validation.js b/lib/editor/util/validation.js index 2f0d125c9..05f577523 100644 --- a/lib/editor/util/validation.js +++ b/lib/editor/util/validation.js @@ -124,6 +124,12 @@ export function validate ( return false } case 'TEXT': + if (name === 'stop_name' && isOptionalAndEmpty) { + if (!locationType || locationType <= 2) { + reason = 'Stop name is required for your current location type' + return {field: name, invalid: isOptionalAndEmpty, reason} + } + } if (name === 'route_short_name' && !value && entity && entity.route_long_name) { return false } else if ( @@ -194,7 +200,7 @@ export function validate ( reason = 'Field must be valid latitude.' return {field: name, invalid: isNotLat, reason} } - if (!locationType || locationType < 2) { + if (!locationType || locationType <= 2) { if (isOptionalAndEmpty) { reason = 'Latitude and Longitude are required for your current location type' return {field: name, invalid: isOptionalAndEmpty, reason} @@ -207,7 +213,7 @@ export function validate ( reason = 'Field must be valid longitude.' return {field: name, invalid: isOptionalAndEmpty || isNotLng, reason} } - if (!locationType || locationType < 2) { + if (!locationType || locationType <= 2) { if (isOptionalAndEmpty) { reason = 'Latitude and Longitude are required for your current location type' return {field: name, invalid: isOptionalAndEmpty || isNotLng, reason} diff --git a/lib/types/index.js b/lib/types/index.js index 538430db8..0f3c411c2 100644 --- a/lib/types/index.js +++ b/lib/types/index.js @@ -89,7 +89,7 @@ export type Route = { routeTextColor: string, routeUrl: string, status: string, - wheelchairBoarding: string + wheelchairBoarding: number } export type Calendar = {| From baaba8b5342008d446db9f39cde2f5bf0eb69363 Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Tue, 11 May 2021 15:17:48 +0100 Subject: [PATCH 05/36] improvement(gtfs.yml): Added continuous_pickup and drop off fields to routes.txt --- gtfs.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/gtfs.yml b/gtfs.yml index 248178dd0..ffbdfe6cb 100644 --- a/gtfs.yml +++ b/gtfs.yml @@ -308,6 +308,36 @@ inputType: POSITIVE_INT columnWidth: 6 helpContent: The route_sort_order field can be used to order the routes in a way which is ideal for presentation to customers. It must be a non-negative integer. Routes with smaller route_sort_order values should be displayed before routes with larger route_sort_order values. + - name: continuous_pickup + required: false + inputType: DROPDOWN + bulkEditEnabled: true + options: + - value: 0 + text: Continuous stopping pickup (0) + - value: 1 + text: No continuous stopping pickup (1) + - value: 2 + text: Must phone an agency to arrange continuous stopping pickup (2) + - value: 3 + text: Must coordinate with a driver to arrange continuous stopping pickup (3) + columnWidth: 12 + helpContent: Indicates whether a rider can board the transit vehicle anywhere along the vehicle’s travel path. + - name: continuous_drop_off + required: false + inputType: DROPDOWN + bulkEditEnabled: true + options: + - value: 0 + text: Continuous stopping drop-off (0) + - value: 1 + text: No continuous stopping drop-off (1) + - value: 2 + text: Must phone an agency to arrange continuous stopping drop-off (2) + - value: 3 + text: Must coordinate with a driver to arrange continuous stopping drop-off (3) + columnWidth: 12 + helpContent: Indicates whether a rider can alight from the transit vehicle at any point along the vehicle’s travel path. - name: route_url required: false inputType: URL From acd651a59c1ef0ae9318cecd0f084f0f9e4a6024 Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Tue, 11 May 2021 16:54:29 +0100 Subject: [PATCH 06/36] feat(gtfs.yml): Added fields to feed_info.txt Added defult_lang, feed_contact_email, feed_contact_url #663 --- gtfs.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/gtfs.yml b/gtfs.yml index ffbdfe6cb..b708b20b1 100644 --- a/gtfs.yml +++ b/gtfs.yml @@ -17,12 +17,16 @@ required: true inputType: URL columnWidth: 12 - helpContent: "The feed_publisher_url field contains the URL of the feed publishing organization's website. (This may be the same as one of the agency_url values in agency.txt.) The value must be a fully qualified URL that includes http:// or https://, and any special characters in the URL must be correctly escaped. See http://www.w3.org/Addressing/URL/4_URI_Recommentations.html for a description of how to create fully qualified URL values." - name: "feed_lang" required: true inputType: LANGUAGE columnWidth: 12 helpContent: "The feed_lang field contains a IETF BCP 47 language code specifying the default language used for the text in this feed. This setting helps GTFS consumers choose capitalization rules and other language-specific settings for the feed. For an introduction to IETF BCP 47, please refer to http://www.rfc-editor.org/rfc/bcp/bcp47.txt and http://www.w3.org/International/articles/language-tags/." + - name: "default_lang" + required: false + inputType: LANGUAGE + columnWidth: 12 + helpContent: "Defines the language used when the data consumer doesn’t know the language of the rider. It's often defined as en, English." - name: "feed_start_date" required: false inputType: DATE @@ -67,6 +71,16 @@ inputType: TEXT columnWidth: 12 helpContent: "The feed publisher can specify a string here that indicates the current version of their GTFS feed. GTFS-consuming applications can display this value to help feed publishers determine whether the latest version of their feed has been incorporated." + - name: "feed_contact_email" + required: false + inputType: EMAIL + columnWidth: 12 + helpContent: "Email address for communication regarding the GTFS dataset and data publishing practices." + - name: "feed_contact_url" + required: false + inputType: URL + columnWidth: 12 + helpContent: "URL for contact information, a web-form, support desk, or other tools for communication regarding the GTFS dataset and data publishing practices." - id: agency name: agency.txt From 93b1f538244205cadc912686a4cfb23d3c26e2e3 Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Tue, 1 Jun 2021 16:47:17 +0100 Subject: [PATCH 07/36] improvement(OatternStopCard.js lib/gtfs/util/index.js lib/types/index.js): Update to the GTFS Spec U Added the changes where applicable to the gtfs.yml file. Added the new types for continuous drop off/pick up and added fields as well as basic validation in the patternStopCard file. BREAKING CHANGE: There have been added fields that need to coincide with changes on the backend as the api will be looking for the new fields and those fields need to be present on the Front end #663 --- .../components/pattern/PatternStopCard.js | 156 +++++++++++++++++- lib/gtfs/util/index.js | 2 + lib/types/index.js | 2 + 3 files changed, 158 insertions(+), 2 deletions(-) diff --git a/lib/editor/components/pattern/PatternStopCard.js b/lib/editor/components/pattern/PatternStopCard.js index 4ae24cdf5..ee3ba637b 100644 --- a/lib/editor/components/pattern/PatternStopCard.js +++ b/lib/editor/components/pattern/PatternStopCard.js @@ -3,7 +3,7 @@ import Icon from '@conveyal/woonerf/components/icon' import React, {Component} from 'react' import { DragSource, DropTarget } from 'react-dnd' -import { Row, Col, Collapse, FormGroup, ControlLabel, Checkbox } from 'react-bootstrap' +import { Row, Col, Collapse, FormGroup, ControlLabel, Checkbox, FormControl } from 'react-bootstrap' import * as activeActions from '../../actions/active' import * as stopStrategiesActions from '../../actions/map/stopStrategies' @@ -246,12 +246,90 @@ class PatternStopContents extends Component { updatePatternStops(activePattern, patternStops) } + _onPickupChange = (evt: SyntheticInputEvent) => { + const newPickup: number = parseInt(evt.target.value) + const {activePattern, index, updatePatternStops} = this.props + const patternStops = [...activePattern.patternStops] + + patternStops[index].pickupType = newPickup + this.setState({update: true}) + updatePatternStops(activePattern, patternStops) + } + + _onDropOffChange = (evt: SyntheticInputEvent) => { + const newDropOff: number = parseInt(evt.target.value) + const {activePattern, index, updatePatternStops} = this.props + const patternStops = [...activePattern.patternStops] + + patternStops[index].dropOffType = newDropOff + this.setState({update: true}) + updatePatternStops(activePattern, patternStops) + } + + _onContinuousPickupChange = (evt: SyntheticInputEvent) => { + const newPickup: number = parseInt(evt.target.value) + const {activePattern, index, updatePatternStops} = this.props + const patternStops = [...activePattern.patternStops] + + patternStops[index].continuousPickup = newPickup + this.setState({update: true}) + updatePatternStops(activePattern, patternStops) + } + + _onContinuousDropOffChange = (evt: SyntheticInputEvent) => { + const newPickup: number = parseInt(evt.target.value) + const {activePattern, index, updatePatternStops} = this.props + const patternStops = [...activePattern.patternStops] + + patternStops[index].continuousDropOff = newPickup + this.setState({update: true}) + updatePatternStops(activePattern, patternStops) + } + render () { - const {active, patternEdited, patternStop} = this.props + const {active, activePattern, patternEdited, patternStop} = this.props // This component has a special shouldComponentUpdate to ensure that state // is not overwritten with new props, so use state.update to check edited // state. const isEdited = patternEdited || this.state.update + const hasShapeId = activePattern.shapeId !== null ? false : true + const pickupDropoffOptions = [ + { + value: 0, + text: 'Regularly scheduled (0)' + }, + { + value: 1, + text: 'Not available (1)' + }, + { + value: 2, + text: 'Must phone agency to arrange (2)' + }, + { + value: 3, + text: 'Must coordinate with driver to arrange (3)' + } + ] + const continuousPickupDropoffOptions = [ + { + value: 0, + text: 'Continuous stopping pickup (0)' + }, + { + value: 1, + text: 'Not available (1)' + }, + { + value: 2, + text: 'Must phone agency to arrange (2)' + }, + { + value: 3, + text: 'Must coordinate with driver to arrange (3)' + } + ] + let innerDiv if (active) { innerDiv =
@@ -301,6 +379,80 @@ class PatternStopContents extends Component { + {/* Pickup and drop off type selectors */} + + + + + Pickup type + + + {pickupDropoffOptions.map(o => ( + + ))} + + + + + + + Drop-off type + + + {pickupDropoffOptions.map(o => ( + + ))} + + + + + {/* Continuous Stopping Pickup and drop off type selectors */} + + + + + Continuous Pickup + + + {continuousPickupDropoffOptions.map(o => ( + + ))} + + + + + + + Continuous Drop off + + + {continuousPickupDropoffOptions.map(o => ( + + ))} + + + +
} diff --git a/lib/gtfs/util/index.js b/lib/gtfs/util/index.js index 20fd5d4ab..a65575e91 100644 --- a/lib/gtfs/util/index.js +++ b/lib/gtfs/util/index.js @@ -79,6 +79,8 @@ export function getGraphQLFieldsForEntity (type: string, editor: boolean = false pickup_type drop_off_type timepoint + continuous_pickup + continuous_drop_off }` switch (type.toLowerCase()) { case 'stoptime': diff --git a/lib/types/index.js b/lib/types/index.js index 0f3c411c2..1fefb02fb 100644 --- a/lib/types/index.js +++ b/lib/types/index.js @@ -492,6 +492,8 @@ export type GeoJsonFeatureCollection = { } export type PatternStop = { + continuousDropOff: number, + continuousPickup: number, defaultDwellTime: number, defaultTravelTime: number, dropOffType: number, From 5397f4ac998d19ccb009e1fde609b6e3895b4691 Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Tue, 1 Jun 2021 17:00:16 +0100 Subject: [PATCH 08/36] refactor(patternStopCard.js): Refactored code from github feedback Removed spaces. Improved boolean ternary #663 --- lib/editor/components/pattern/PatternStopCard.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/editor/components/pattern/PatternStopCard.js b/lib/editor/components/pattern/PatternStopCard.js index ee3ba637b..f5dc23c7c 100644 --- a/lib/editor/components/pattern/PatternStopCard.js +++ b/lib/editor/components/pattern/PatternStopCard.js @@ -275,7 +275,7 @@ class PatternStopContents extends Component { this.setState({update: true}) updatePatternStops(activePattern, patternStops) } - + _onContinuousDropOffChange = (evt: SyntheticInputEvent) => { const newPickup: number = parseInt(evt.target.value) const {activePattern, index, updatePatternStops} = this.props @@ -292,7 +292,7 @@ class PatternStopContents extends Component { // is not overwritten with new props, so use state.update to check edited // state. const isEdited = patternEdited || this.state.update - const hasShapeId = activePattern.shapeId !== null ? false : true + const hasShapeId = activePattern.shapeId === null const pickupDropoffOptions = [ { value: 0, From ad6bfe69c73fc43f332d9f42a0a728d95d280b61 Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Wed, 2 Jun 2021 15:17:22 +0100 Subject: [PATCH 09/36] refactor(end-to-end.js map.js index.js): Fix errors Changed wheelchairBoarding to a string. Added continuous fields to the map.js. #663 --- __tests__/end-to-end.js | 2 +- lib/editor/util/map.js | 2 ++ lib/types/index.js | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/__tests__/end-to-end.js b/__tests__/end-to-end.js index 81c6efe2c..dd083ca9a 100644 --- a/__tests__/end-to-end.js +++ b/__tests__/end-to-end.js @@ -418,7 +418,7 @@ async function createStop ({ option: number }, url: string, - wheelchairBoarding?: number, // make optional due to https://github.com/facebook/flow/issues/183 + wheelchairBoarding?: string, // make optional due to https://github.com/facebook/flow/issues/183 zoneId?: string // make optional due to https://github.com/facebook/flow/issues/183 }) { log.info(`creating stop with name: ${name}`) diff --git a/lib/editor/util/map.js b/lib/editor/util/map.js index 6dd7e3636..3abb91bfe 100644 --- a/lib/editor/util/map.js +++ b/lib/editor/util/map.js @@ -754,6 +754,8 @@ export function stopToPatternStop ( id: generateUID(), stopSequence, stopId: stop.stop_id, + continuousDropOff: 1, + continuousPickup: 1, defaultDwellTime: 0, defaultTravelTime: 0, dropOffType: 0, diff --git a/lib/types/index.js b/lib/types/index.js index 1fefb02fb..b27c7bc69 100644 --- a/lib/types/index.js +++ b/lib/types/index.js @@ -89,7 +89,7 @@ export type Route = { routeTextColor: string, routeUrl: string, status: string, - wheelchairBoarding: number + wheelchairBoarding: string } export type Calendar = {| From 6a9e2ed6403c6e130e599ff100c657e8bea77347 Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Mon, 7 Jun 2021 17:41:19 +0100 Subject: [PATCH 10/36] refactor(PatternStopCard.js): Refactored based on PR Feedback. --- .../components/pattern/PatternStopCard.js | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/lib/editor/components/pattern/PatternStopCard.js b/lib/editor/components/pattern/PatternStopCard.js index f5dc23c7c..7efad9373 100644 --- a/lib/editor/components/pattern/PatternStopCard.js +++ b/lib/editor/components/pattern/PatternStopCard.js @@ -3,7 +3,15 @@ import Icon from '@conveyal/woonerf/components/icon' import React, {Component} from 'react' import { DragSource, DropTarget } from 'react-dnd' -import { Row, Col, Collapse, FormGroup, ControlLabel, Checkbox, FormControl } from 'react-bootstrap' +import { + Checkbox, + Col, + Collapse, + ControlLabel, + FormControl, + FormGroup, + Row +} from 'react-bootstrap' import * as activeActions from '../../actions/active' import * as stopStrategiesActions from '../../actions/map/stopStrategies' @@ -277,11 +285,11 @@ class PatternStopContents extends Component { } _onContinuousDropOffChange = (evt: SyntheticInputEvent) => { - const newPickup: number = parseInt(evt.target.value) + const newDropOff: number = parseInt(evt.target.value) const {activePattern, index, updatePatternStops} = this.props const patternStops = [...activePattern.patternStops] - patternStops[index].continuousDropOff = newPickup + patternStops[index].continuousDropOff = newDropOff this.setState({update: true}) updatePatternStops(activePattern, patternStops) } @@ -296,25 +304,7 @@ class PatternStopContents extends Component { const pickupDropoffOptions = [ { value: 0, - text: 'Regularly scheduled (0)' - }, - { - value: 1, - text: 'Not available (1)' - }, - { - value: 2, - text: 'Must phone agency to arrange (2)' - }, - { - value: 3, - text: 'Must coordinate with driver to arrange (3)' - } - ] - const continuousPickupDropoffOptions = [ - { - value: 0, - text: 'Continuous stopping pickup (0)' + text: 'Available (0)' }, { value: 1, @@ -428,8 +418,15 @@ class PatternStopContents extends Component { > Continuous Pickup - - {continuousPickupDropoffOptions.map(o => ( + + {pickupDropoffOptions.map(o => ( ))} @@ -445,8 +442,15 @@ class PatternStopContents extends Component { > Continuous Drop off - - {continuousPickupDropoffOptions.map(o => ( + + {pickupDropoffOptions.map(o => ( ))} From b30a941e97828d9c5dd447725a2aaad0eb3e9980 Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Tue, 8 Jun 2021 17:57:11 +0100 Subject: [PATCH 11/36] refactor(PatternStopCard.js): REfactor Consolidated duplicate methods into a facade type pattern #663 --- .../components/pattern/PatternStopCard.js | 83 +++++++++---------- 1 file changed, 38 insertions(+), 45 deletions(-) diff --git a/lib/editor/components/pattern/PatternStopCard.js b/lib/editor/components/pattern/PatternStopCard.js index 7efad9373..00b7cc99f 100644 --- a/lib/editor/components/pattern/PatternStopCard.js +++ b/lib/editor/components/pattern/PatternStopCard.js @@ -254,42 +254,29 @@ class PatternStopContents extends Component { updatePatternStops(activePattern, patternStops) } - _onPickupChange = (evt: SyntheticInputEvent) => { - const newPickup: number = parseInt(evt.target.value) - const {activePattern, index, updatePatternStops} = this.props - const patternStops = [...activePattern.patternStops] - - patternStops[index].pickupType = newPickup - this.setState({update: true}) - updatePatternStops(activePattern, patternStops) - } + _onPickupOrDropOffChange = (evt: SyntheticInputEvent) => { + const selectOptionType: string = evt.target.id + const selectedOptionValue: number = parseInt(evt.target.value) - _onDropOffChange = (evt: SyntheticInputEvent) => { - const newDropOff: number = parseInt(evt.target.value) const {activePattern, index, updatePatternStops} = this.props const patternStops = [...activePattern.patternStops] - patternStops[index].dropOffType = newDropOff - this.setState({update: true}) - updatePatternStops(activePattern, patternStops) - } - - _onContinuousPickupChange = (evt: SyntheticInputEvent) => { - const newPickup: number = parseInt(evt.target.value) - const {activePattern, index, updatePatternStops} = this.props - const patternStops = [...activePattern.patternStops] - - patternStops[index].continuousPickup = newPickup - this.setState({update: true}) - updatePatternStops(activePattern, patternStops) - } - - _onContinuousDropOffChange = (evt: SyntheticInputEvent) => { - const newDropOff: number = parseInt(evt.target.value) - const {activePattern, index, updatePatternStops} = this.props - const patternStops = [...activePattern.patternStops] - - patternStops[index].continuousDropOff = newDropOff + switch (selectOptionType) { + case 'continuous_pick-up': + patternStops[index].continuousPickup = selectedOptionValue + break + case 'continuous_drop_off': + patternStops[index].continuousDropOff = selectedOptionValue + break + case 'pickup_type': + patternStops[index].pickupType = selectedOptionValue + break + case 'drop_off_type': + patternStops[index].dropOffType = selectedOptionValue + break + default: + return null + } this.setState({update: true}) updatePatternStops(activePattern, patternStops) } @@ -373,15 +360,19 @@ class PatternStopContents extends Component { - Pickup type + Pickup - + {pickupDropoffOptions.map(o => ( ))} @@ -390,15 +381,19 @@ class PatternStopContents extends Component { - Drop-off type + Drop-off - + {pickupDropoffOptions.map(o => ( ))} @@ -416,15 +411,14 @@ class PatternStopContents extends Component { className='small' title='Indicates whether a rider can board the transit vehicle anywhere along the vehicle’s travel path.' > - Continuous Pickup + Continuous pickup {pickupDropoffOptions.map(o => ( @@ -440,15 +434,14 @@ class PatternStopContents extends Component { className='small' title='Indicates whether a rider can alight from the transit vehicle at any point along the vehicle’s travel path.' > - Continuous Drop off + Continuous drop-off {pickupDropoffOptions.map(o => ( From c4cf1153b19c1ea363c9f11db906d60459e94388 Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Wed, 9 Jun 2021 16:03:03 +0100 Subject: [PATCH 12/36] refactor(PatternStopCard.js): Refactored drop down select method The select IDs were changed to matcth the api patternstop model and then there was no need for the switch block #371 --- .../components/pattern/PatternStopCard.js | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/editor/components/pattern/PatternStopCard.js b/lib/editor/components/pattern/PatternStopCard.js index 00b7cc99f..c17562c87 100644 --- a/lib/editor/components/pattern/PatternStopCard.js +++ b/lib/editor/components/pattern/PatternStopCard.js @@ -255,28 +255,28 @@ class PatternStopContents extends Component { } _onPickupOrDropOffChange = (evt: SyntheticInputEvent) => { - const selectOptionType: string = evt.target.id const selectedOptionValue: number = parseInt(evt.target.value) const {activePattern, index, updatePatternStops} = this.props const patternStops = [...activePattern.patternStops] - switch (selectOptionType) { - case 'continuous_pick-up': - patternStops[index].continuousPickup = selectedOptionValue - break - case 'continuous_drop_off': - patternStops[index].continuousDropOff = selectedOptionValue - break - case 'pickup_type': - patternStops[index].pickupType = selectedOptionValue - break - case 'drop_off_type': - patternStops[index].dropOffType = selectedOptionValue - break - default: - return null - } + patternStops[index][evt.target.id] = selectedOptionValue + + // switch (selectOptionType) { + // case 'continuous_pick-up': + // break + // case 'continuousDropOff': + // patternStops[index].continuousDropOff = selectedOptionValue + // break + // case 'pickup_type': + // patternStops[index].pickupType = selectedOptionValue + // break + // case 'drop_off_type': + // patternStops[index].dropOffType = selectedOptionValue + // break + // default: + // return null + // } this.setState({update: true}) updatePatternStops(activePattern, patternStops) } @@ -360,7 +360,7 @@ class PatternStopContents extends Component { { { { Date: Wed, 9 Jun 2021 16:40:54 +0100 Subject: [PATCH 13/36] refactor(PatternStopCard.js): Fix lint errors --- .../components/pattern/PatternStopCard.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/lib/editor/components/pattern/PatternStopCard.js b/lib/editor/components/pattern/PatternStopCard.js index c17562c87..db1f4f7f9 100644 --- a/lib/editor/components/pattern/PatternStopCard.js +++ b/lib/editor/components/pattern/PatternStopCard.js @@ -256,27 +256,9 @@ class PatternStopContents extends Component { _onPickupOrDropOffChange = (evt: SyntheticInputEvent) => { const selectedOptionValue: number = parseInt(evt.target.value) - const {activePattern, index, updatePatternStops} = this.props const patternStops = [...activePattern.patternStops] - patternStops[index][evt.target.id] = selectedOptionValue - - // switch (selectOptionType) { - // case 'continuous_pick-up': - // break - // case 'continuousDropOff': - // patternStops[index].continuousDropOff = selectedOptionValue - // break - // case 'pickup_type': - // patternStops[index].pickupType = selectedOptionValue - // break - // case 'drop_off_type': - // patternStops[index].dropOffType = selectedOptionValue - // break - // default: - // return null - // } this.setState({update: true}) updatePatternStops(activePattern, patternStops) } From 79073f74a0a5bce5acf8a25ae87e09bc84de8f11 Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Mon, 28 Jun 2021 16:03:31 +0100 Subject: [PATCH 14/36] refactor(PatternStopCard.js validation.js): Refactor the form control and switch case block #663 --- .../components/pattern/PatternStopCard.js | 204 +++++++++--------- lib/editor/util/validation.js | 31 ++- 2 files changed, 108 insertions(+), 127 deletions(-) diff --git a/lib/editor/components/pattern/PatternStopCard.js b/lib/editor/components/pattern/PatternStopCard.js index db1f4f7f9..9c223c616 100644 --- a/lib/editor/components/pattern/PatternStopCard.js +++ b/lib/editor/components/pattern/PatternStopCard.js @@ -29,6 +29,7 @@ type Props = { addStopToPattern: typeof stopStrategiesActions.addStopToPattern, // property is available through react dnd? connectDragSource: any, + controlLabel: string, cumulativeTravelTime: number, feedSource: Feed, findCard: string => { card: PatternStop, index: number }, @@ -41,15 +42,71 @@ type Props = { removeStopFromPattern: typeof stopStrategiesActions.removeStopFromPattern, rowStyle: {[string]: number | string}, saveActiveGtfsEntity: typeof activeActions.saveActiveGtfsEntity, + selectType: any, setActiveEntity: typeof activeActions.setActiveEntity, setActiveStop: typeof tripPatternActions.setActiveStop, + shouldHaveDisabledOption: boolean, status: any, stop: any, stopIsActive: boolean, + title: string, updateActiveGtfsEntity: typeof activeActions.updateActiveGtfsEntity, updatePatternStops: typeof tripPatternActions.updatePatternStops } +type State = { + initialDwellTime: number, + initialTravelTime: number, + update: boolean +} + +const pickupDropoffOptions = [ + { + value: 0, + text: 'Available (0)' + }, + { + value: 1, + text: 'Not available (1)' + }, + { + value: 2, + text: 'Must phone agency to arrange (2)' + }, + { + value: 3, + text: 'Must coordinate with driver to arrange (3)' + } +] + +// renders the form control drop downs for dropOff/Pick up and also continuous +const PickupDropoffSelect = (props: Props) => { + const {activePattern, controlLabel, selectType, shouldHaveDisabledOption, title, onChange, value} = props + const hasShapeId = activePattern.shapeId === null + return ( + + + {controlLabel} + + + {pickupDropoffOptions.map(o => ( + + ))} + + + ) +} + const cardSource = { beginDrag (props: Props) { return { @@ -182,12 +239,6 @@ class PatternStopCard extends Component { } } -type State = { - initialDwellTime: number, - initialTravelTime: number, - update: boolean -} - class PatternStopContents extends Component { componentWillMount () { this.setState({ @@ -255,39 +306,21 @@ class PatternStopContents extends Component { } _onPickupOrDropOffChange = (evt: SyntheticInputEvent) => { - const selectedOptionValue: number = parseInt(evt.target.value) + const selectedOptionValue: number = parseInt(evt.target.value, 10) const {activePattern, index, updatePatternStops} = this.props const patternStops = [...activePattern.patternStops] + patternStops[index][evt.target.id] = selectedOptionValue this.setState({update: true}) updatePatternStops(activePattern, patternStops) } render () { - const {active, activePattern, patternEdited, patternStop} = this.props + const {active, patternEdited, patternStop} = this.props // This component has a special shouldComponentUpdate to ensure that state // is not overwritten with new props, so use state.update to check edited // state. const isEdited = patternEdited || this.state.update - const hasShapeId = activePattern.shapeId === null - const pickupDropoffOptions = [ - { - value: 0, - text: 'Available (0)' - }, - { - value: 1, - text: 'Not available (1)' - }, - { - value: 2, - text: 'Must phone agency to arrange (2)' - }, - { - value: 3, - text: 'Must coordinate with driver to arrange (3)' - } - ] let innerDiv if (active) { @@ -341,95 +374,50 @@ class PatternStopContents extends Component { {/* Pickup and drop off type selectors */} - - - Pickup - - - {pickupDropoffOptions.map(o => ( - - ))} - - + - - - Drop-off - - - {pickupDropoffOptions.map(o => ( - - ))} - - + - {/* Continuous Stopping Pickup and drop off type selectors */} - - - Continuous pickup - - - {pickupDropoffOptions.map(o => ( - - ))} - - + - - - Continuous drop-off - - - {pickupDropoffOptions.map(o => ( - - ))} - - + diff --git a/lib/editor/util/validation.js b/lib/editor/util/validation.js index 05f577523..cf15641ca 100644 --- a/lib/editor/util/validation.js +++ b/lib/editor/util/validation.js @@ -25,7 +25,6 @@ export function doesNotExist (value: any): boolean { * Returns false if the value is ok. * Returns an EditorValidationIssue object if the value is not ok. */ -// eslint-disable-next-line complexity export function validate ( field: GtfsSpecField, value: any, @@ -38,12 +37,12 @@ export function validate ( const isRequiredButEmpty = required && valueDoesNotExist const isOptionalAndEmpty = !required && valueDoesNotExist const agencies = getTableById(tableData, 'agency') - let locationType: ?number = 0 + let locationType: ?number = null let reason = 'Required field must not be empty' // entity.locationtype is a string. Convert to number for conditinals later on. - if (entity && entity.location_type) { - locationType = parseInt(entity.location_type) + if (entity && entity.location_type !== null) { + locationType = parseInt(entity.location_type, 10) } // setting as a variable here because of eslint bug @@ -124,11 +123,9 @@ export function validate ( return false } case 'TEXT': - if (name === 'stop_name' && isOptionalAndEmpty) { - if (!locationType || locationType <= 2) { - reason = 'Stop name is required for your current location type' - return {field: name, invalid: isOptionalAndEmpty, reason} - } + if (name === 'stop_name' && locationType !== null && locationType <= 2) { + reason = 'Stop name is required for your current location type' + return {field: name, invalid: isOptionalAndEmpty, reason} } if (name === 'route_short_name' && !value && entity && entity.route_long_name) { return false @@ -200,11 +197,9 @@ export function validate ( reason = 'Field must be valid latitude.' return {field: name, invalid: isNotLat, reason} } - if (!locationType || locationType <= 2) { - if (isOptionalAndEmpty) { - reason = 'Latitude and Longitude are required for your current location type' - return {field: name, invalid: isOptionalAndEmpty, reason} - } + if (isOptionalAndEmpty && locationType !== null && locationType <= 2) { + reason = 'Latitude and Longitude are required for your current location type' + return {field: name, invalid: isOptionalAndEmpty, reason} } return false case 'LONGITUDE': @@ -213,11 +208,9 @@ export function validate ( reason = 'Field must be valid longitude.' return {field: name, invalid: isOptionalAndEmpty || isNotLng, reason} } - if (!locationType || locationType <= 2) { - if (isOptionalAndEmpty) { - reason = 'Latitude and Longitude are required for your current location type' - return {field: name, invalid: isOptionalAndEmpty || isNotLng, reason} - } + if (isOptionalAndEmpty && locationType !== null && locationType <= 2) { + reason = 'Latitude and Longitude are required for your current location type' + return {field: name, invalid: isOptionalAndEmpty || isNotLng, reason} } return false case 'TIME': From 094efc7a74b5805e224ed6b93ad0c3d5dfecb78d Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Mon, 28 Jun 2021 16:59:55 +0100 Subject: [PATCH 15/36] refactor(validation.js PatternStopCard.js): Fix flow issues #663 --- lib/editor/components/pattern/PatternStopCard.js | 11 +++++++---- lib/editor/util/validation.js | 7 ++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/editor/components/pattern/PatternStopCard.js b/lib/editor/components/pattern/PatternStopCard.js index 9c223c616..ac77806e4 100644 --- a/lib/editor/components/pattern/PatternStopCard.js +++ b/lib/editor/components/pattern/PatternStopCard.js @@ -22,8 +22,10 @@ import NormalizeStopTimesTip from './NormalizeStopTimesTip' import PatternStopButtons from './PatternStopButtons' import type {Feed, Pattern, PatternStop} from '../../../types' +import { string } from 'jszip/lib/support' type Props = { + _onPickupOrDropOffChange: () => void, active: boolean, activePattern: Pattern, addStopToPattern: typeof stopStrategiesActions.addStopToPattern, @@ -42,7 +44,8 @@ type Props = { removeStopFromPattern: typeof stopStrategiesActions.removeStopFromPattern, rowStyle: {[string]: number | string}, saveActiveGtfsEntity: typeof activeActions.saveActiveGtfsEntity, - selectType: any, + selectType: string, + selectValue: string, setActiveEntity: typeof activeActions.setActiveEntity, setActiveStop: typeof tripPatternActions.setActiveStop, shouldHaveDisabledOption: boolean, @@ -81,7 +84,7 @@ const pickupDropoffOptions = [ // renders the form control drop downs for dropOff/Pick up and also continuous const PickupDropoffSelect = (props: Props) => { - const {activePattern, controlLabel, selectType, shouldHaveDisabledOption, title, onChange, value} = props + const {activePattern, controlLabel, selectType, shouldHaveDisabledOption, title, _onPickupOrDropOffChange, selectValue} = props const hasShapeId = activePattern.shapeId === null return ( { {pickupDropoffOptions.map(o => ( diff --git a/lib/editor/util/validation.js b/lib/editor/util/validation.js index cf15641ca..43fa8006d 100644 --- a/lib/editor/util/validation.js +++ b/lib/editor/util/validation.js @@ -25,6 +25,7 @@ export function doesNotExist (value: any): boolean { * Returns false if the value is ok. * Returns an EditorValidationIssue object if the value is not ok. */ +// eslint-disable-next-line complexity export function validate ( field: GtfsSpecField, value: any, @@ -123,7 +124,7 @@ export function validate ( return false } case 'TEXT': - if (name === 'stop_name' && locationType !== null && locationType <= 2) { + if (name === 'stop_name' && locationType !== null && (typeof locationType === 'number' && locationType <= 2)) { reason = 'Stop name is required for your current location type' return {field: name, invalid: isOptionalAndEmpty, reason} } @@ -197,7 +198,7 @@ export function validate ( reason = 'Field must be valid latitude.' return {field: name, invalid: isNotLat, reason} } - if (isOptionalAndEmpty && locationType !== null && locationType <= 2) { + if (isOptionalAndEmpty && locationType !== null && (typeof locationType === 'number' && locationType <= 2)) { reason = 'Latitude and Longitude are required for your current location type' return {field: name, invalid: isOptionalAndEmpty, reason} } @@ -208,7 +209,7 @@ export function validate ( reason = 'Field must be valid longitude.' return {field: name, invalid: isOptionalAndEmpty || isNotLng, reason} } - if (isOptionalAndEmpty && locationType !== null && locationType <= 2) { + if (isOptionalAndEmpty && locationType !== null && (typeof locationType === 'number' && locationType <= 2)) { reason = 'Latitude and Longitude are required for your current location type' return {field: name, invalid: isOptionalAndEmpty || isNotLng, reason} } From 7717c28996c349ae2862d698805cd8bdde877a08 Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Mon, 28 Jun 2021 17:06:08 +0100 Subject: [PATCH 16/36] fix(PatternStopCard.js): Fix flow issue #663 --- lib/editor/components/pattern/PatternStopCard.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/editor/components/pattern/PatternStopCard.js b/lib/editor/components/pattern/PatternStopCard.js index ac77806e4..d592e70a8 100644 --- a/lib/editor/components/pattern/PatternStopCard.js +++ b/lib/editor/components/pattern/PatternStopCard.js @@ -22,7 +22,6 @@ import NormalizeStopTimesTip from './NormalizeStopTimesTip' import PatternStopButtons from './PatternStopButtons' import type {Feed, Pattern, PatternStop} from '../../../types' -import { string } from 'jszip/lib/support' type Props = { _onPickupOrDropOffChange: () => void, From f1cb21e1edc578434b66382ec07e4791d7c64656 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Thu, 1 Jul 2021 14:33:50 -0400 Subject: [PATCH 17/36] refactor(validation.js): add validationIssue method --- lib/editor/util/validation.js | 87 +++++++++++++++++------------------ 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/lib/editor/util/validation.js b/lib/editor/util/validation.js index 43fa8006d..cdca6cb36 100644 --- a/lib/editor/util/validation.js +++ b/lib/editor/util/validation.js @@ -15,6 +15,8 @@ export type EditorValidationIssue = { reason: string } +const EMPTY_FIELD_REASON = 'Required field must not be empty' + export function doesNotExist (value: any): boolean { return value === '' || value === null || typeof value === 'undefined' } @@ -39,7 +41,6 @@ export function validate ( const isOptionalAndEmpty = !required && valueDoesNotExist const agencies = getTableById(tableData, 'agency') let locationType: ?number = null - let reason = 'Required field must not be empty' // entity.locationtype is a string. Convert to number for conditinals later on. if (entity && entity.location_type !== null) { @@ -52,13 +53,21 @@ export function validate ( result: false | EditorValidationIssue } + /** + * Construct an EditorValidationIssue for the field name and reason (defaults to + * empty field message). + */ + function validationIssue (reason = EMPTY_FIELD_REASON, field = name) { + return {field, invalid: true, reason} + } + /** * Checks whether value is a positive number */ function checkPositiveNumber (): CheckPositiveOutput { if (isRequiredButEmpty) { return { - result: {field: name, invalid: isRequiredButEmpty, reason} + result: validationIssue() } } else if (isOptionalAndEmpty) { return { @@ -70,14 +79,14 @@ export function validate ( // make sure value is parseable to a number if (isNaN(num)) { return { - result: {field: name, invalid: true, reason: 'Field must be a valid number'} + result: validationIssue('Field must be a valid number') } } // make sure value is positive if (num < 0) { return { - result: {field: name, invalid: true, reason: 'Field must be a positive number'} + result: validationIssue('Field must be a positive number') } } @@ -112,21 +121,20 @@ export function validate ( idList.length > 1 && valueDoesNotExist ) { - reason = 'Identifier is required if more than one agency exists' - return {field: name, invalid: isRequiredButEmpty, reason} + return validationIssue('Identifier is required if more than one agency exists') } if (isRequiredButEmpty || isNotUnique) { + let reason if (isNotUnique) { reason = 'Identifier must be unique' } - return {field: name, invalid: isRequiredButEmpty || isNotUnique, reason} + return validationIssue(reason) } else { return false } case 'TEXT': if (name === 'stop_name' && locationType !== null && (typeof locationType === 'number' && locationType <= 2)) { - reason = 'Stop name is required for your current location type' - return {field: name, invalid: isOptionalAndEmpty, reason} + return validationIssue('Stop name is required for your current location type') } if (name === 'route_short_name' && !value && entity && entity.route_long_name) { return false @@ -139,7 +147,7 @@ export function validate ( return false } else { if (isRequiredButEmpty) { - return {field: name, invalid: isRequiredButEmpty, reason} + return validationIssue() } else { return false } @@ -150,82 +158,77 @@ export function validate ( case 'GTFS_FARE': case 'GTFS_SERVICE': if (isRequiredButEmpty) { - return {field: name, invalid: isRequiredButEmpty, reason} + return validationIssue() } else { return false } case 'URL': const isNotUrl = value && !validator.isURL(value) if (isRequiredButEmpty || isNotUrl) { + let reason if (isNotUrl) { reason = 'Field must contain valid URL.' } - return {field: name, invalid: isRequiredButEmpty || isNotUrl, reason} + return validationIssue(reason) } else { return false } case 'EMAIL': const isNotEmail = value && !validator.isEmail(value) if (isRequiredButEmpty || isNotEmail) { + let reason if (isNotEmail) { reason = 'Field must contain valid email address.' } - return {field: name, invalid: isRequiredButEmpty || isNotEmail, reason} + return validationIssue(reason) } else { return false } case 'GTFS_ZONE': if (isRequiredButEmpty) { - return {field: name, invalid: isRequiredButEmpty, reason} + return validationIssue() } else { return false } case 'TIMEZONE': if (isRequiredButEmpty) { - return {field: name, invalid: isRequiredButEmpty, reason} + return validationIssue() } else { return false } case 'LANGUAGE': if (isRequiredButEmpty) { - return {field: name, invalid: isRequiredButEmpty, reason} + return validationIssue() } else { return false } case 'LATITUDE': const isNotLat = value > 90 || value < -90 if (isNotLat) { - reason = 'Field must be valid latitude.' - return {field: name, invalid: isNotLat, reason} + return validationIssue(name, 'Field must be valid latitude.') } if (isOptionalAndEmpty && locationType !== null && (typeof locationType === 'number' && locationType <= 2)) { - reason = 'Latitude and Longitude are required for your current location type' - return {field: name, invalid: isOptionalAndEmpty, reason} + return validationIssue('Latitude and Longitude are required for your current location type') } return false case 'LONGITUDE': const isNotLng = value > 180 || value < -180 if (isNotLng) { - reason = 'Field must be valid longitude.' - return {field: name, invalid: isOptionalAndEmpty || isNotLng, reason} + return validationIssue('Field must be valid longitude.') } - if (isOptionalAndEmpty && locationType !== null && (typeof locationType === 'number' && locationType <= 2)) { - reason = 'Latitude and Longitude are required for your current location type' - return {field: name, invalid: isOptionalAndEmpty || isNotLng, reason} + if (isOptionalAndEmpty && typeof locationType === 'number' && locationType <= 2) { + return validationIssue('Latitude and Longitude are required for your current location type') } return false case 'TIME': case 'NUMBER': const isNotANumber = isNaN(value) if (isRequiredButEmpty || isNotANumber) { + let reason if (isNotANumber) { reason = 'Field must be valid number' } - return { - field: name, - invalid: isRequiredButEmpty || isNotANumber, - reason - } + return validationIssue(reason) } else { return false } @@ -247,8 +250,7 @@ export function validate ( } if (!hasService && name === 'monday') { // only add validation issue for one day of week (monday) - reason = 'Calendar must have service for at least one day' - return {field: name, invalid: isRequiredButEmpty, reason} + return validationIssue('Calendar must have service for at least one day') } return false case 'DROPDOWN': @@ -257,7 +259,7 @@ export function validate ( field.options && field.options.findIndex(o => o.value === '') === -1 ) { - return {field: name, invalid: isRequiredButEmpty, reason} + return validationIssue() } else { return false } @@ -267,11 +269,7 @@ export function validate ( agencies.length > 1 ) { if (valueDoesNotExist) { - return { - field: name, - invalid: true, - reason: 'Field must be populated for feeds with more than one agency.' - } + return validationIssue('Field must be populated for feeds with more than one agency.') } } return false @@ -300,18 +298,19 @@ export function validate ( } } if (!value || value.length === 0) { - return {field: `dates`, invalid: true, reason} + return validationIssue(EMPTY_FIELD_REASON, 'dates') } // check if date already exists in this or other exceptions for (let i = 0; i < value.length; i++) { + const dateItemName = `dates-${i}` if (dateMap[value[i]] && dateMap[value[i]].length > 1) { // eslint-disable-next-line standard/computed-property-even-spacing - reason = `Date (${value[ + const reason = `Date (${value[ i ]}) cannot appear more than once for all exceptions` - return {field: `dates-${i}`, invalid: true, reason} + return validationIssue(reason, dateItemName) } else if (!moment(value[i], 'YYYYMMDD', true).isValid()) { - return {field: `dates-${i}`, invalid: true, reason} + return validationIssue(EMPTY_FIELD_REASON, dateItemName) } } return false @@ -331,7 +330,7 @@ export function validate ( ) ) ) { - return {field: name, invalid: true, reason: 'Field must be a positive integer'} + return validationIssue(name, 'Field must be a positive integer') } return false case 'POSITIVE_NUM': @@ -342,7 +341,7 @@ export function validate ( case 'COLOR': default: if (isRequiredButEmpty) { - return {field: name, invalid: isRequiredButEmpty, reason} + return validationIssue() } return false } From ff05ac795644c9b5adece49619174fb643fde3c4 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Thu, 1 Jul 2021 14:39:16 -0400 Subject: [PATCH 18/36] refactor(validation): fix stop name message --- lib/editor/util/validation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/editor/util/validation.js b/lib/editor/util/validation.js index cdca6cb36..6c8eb0dea 100644 --- a/lib/editor/util/validation.js +++ b/lib/editor/util/validation.js @@ -134,7 +134,7 @@ export function validate ( } case 'TEXT': if (name === 'stop_name' && locationType !== null && (typeof locationType === 'number' && locationType <= 2)) { - return validationIssue('Stop name is required for your current location type') + return validationIssue('Stop name is required for stop, station, and entrance location types.') } if (name === 'route_short_name' && !value && entity && entity.route_long_name) { return false From 5d17fbb376c24fce3e203f353f069b55302acdb8 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Thu, 1 Jul 2021 14:45:47 -0400 Subject: [PATCH 19/36] refactor(validation): fix bad invocation of validationIssue --- lib/editor/util/validation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/editor/util/validation.js b/lib/editor/util/validation.js index 6c8eb0dea..b34b3a7cc 100644 --- a/lib/editor/util/validation.js +++ b/lib/editor/util/validation.js @@ -205,7 +205,7 @@ export function validate ( case 'LATITUDE': const isNotLat = value > 90 || value < -90 if (isNotLat) { - return validationIssue(name, 'Field must be valid latitude.') + return validationIssue('Field must be valid latitude.') } if (isOptionalAndEmpty && locationType !== null && (typeof locationType === 'number' && locationType <= 2)) { return validationIssue('Latitude and Longitude are required for your current location type') @@ -330,7 +330,7 @@ export function validate ( ) ) ) { - return validationIssue(name, 'Field must be a positive integer') + return validationIssue('Field must be a positive integer') } return false case 'POSITIVE_NUM': From 212cf37efc4ce7a7ee7381b065f0d43bb566458e Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Fri, 2 Jul 2021 17:23:56 +0100 Subject: [PATCH 20/36] refactor(PatternStopCard.js): Changed a method name and added a deleted line back in #668 --- gtfs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/gtfs.yml b/gtfs.yml index b708b20b1..0674ed8f1 100644 --- a/gtfs.yml +++ b/gtfs.yml @@ -17,6 +17,7 @@ required: true inputType: URL columnWidth: 12 + helpContent: "The feed_publisher_url field contains the URL of the feed publishing organization's website. (This may be the same as one of the agency_url values in agency.txt.) The value must be a fully qualified URL that includes http:// or https://, and any special characters in the URL must be correctly escaped. See http://www.w3.org/Addressing/URL/4_URI_Recommentations.html for a description of how to create fully qualified URL values." - name: "feed_lang" required: true inputType: LANGUAGE From 3b9771960b4990eff0d2ecb7ecffe738ce4314d1 Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Fri, 2 Jul 2021 17:26:55 +0100 Subject: [PATCH 21/36] refactor(PatternStopCard.js): Changed method name #668 --- lib/editor/components/pattern/PatternStopCard.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/editor/components/pattern/PatternStopCard.js b/lib/editor/components/pattern/PatternStopCard.js index d592e70a8..99084ca0f 100644 --- a/lib/editor/components/pattern/PatternStopCard.js +++ b/lib/editor/components/pattern/PatternStopCard.js @@ -24,7 +24,6 @@ import PatternStopButtons from './PatternStopButtons' import type {Feed, Pattern, PatternStop} from '../../../types' type Props = { - _onPickupOrDropOffChange: () => void, active: boolean, activePattern: Pattern, addStopToPattern: typeof stopStrategiesActions.addStopToPattern, @@ -38,6 +37,7 @@ type Props = { index: number, isDragging: boolean, moveCard: (string, number) => void, + onChange: () => void, patternEdited: boolean, patternStop: PatternStop, removeStopFromPattern: typeof stopStrategiesActions.removeStopFromPattern, @@ -83,7 +83,7 @@ const pickupDropoffOptions = [ // renders the form control drop downs for dropOff/Pick up and also continuous const PickupDropoffSelect = (props: Props) => { - const {activePattern, controlLabel, selectType, shouldHaveDisabledOption, title, _onPickupOrDropOffChange, selectValue} = props + const {activePattern, controlLabel, selectType, shouldHaveDisabledOption, title, onChange, selectValue} = props const hasShapeId = activePattern.shapeId === null return ( { {pickupDropoffOptions.map(o => ( @@ -307,7 +307,7 @@ class PatternStopContents extends Component { updatePatternStops(activePattern, patternStops) } - _onPickupOrDropOffChange = (evt: SyntheticInputEvent) => { + onChange = (evt: SyntheticInputEvent) => { const selectedOptionValue: number = parseInt(evt.target.value, 10) const {activePattern, index, updatePatternStops} = this.props const patternStops = [...activePattern.patternStops] @@ -383,7 +383,7 @@ class PatternStopContents extends Component { title='Define the pickup method/availability at this stop.' controlLabel='Pickup' value={patternStop.pickupType || ''} - onChange={this._onPickupOrDropOffChange} + onChange={this.onChange} /> @@ -394,7 +394,7 @@ class PatternStopContents extends Component { title='Define the dropff method/availability at this stop.' controlLabel='Drop-off' value={patternStop.dropOffType || ''} - onChange={this._onPickupOrDropOffChange} + onChange={this.onChange} /> @@ -407,7 +407,7 @@ class PatternStopContents extends Component { title='Indicates whether a rider can board the transit vehicle anywhere along the vehicle’s travel path.' controlLabel='Continuous pickup' value={patternStop.continuousPickup || ''} - onChange={this._onPickupOrDropOffChange} + onChange={this.onChange} /> @@ -418,7 +418,7 @@ class PatternStopContents extends Component { title='Indicates whether a rider can alight from the transit vehicle at any point along the vehicle’s travel path.' controlLabel='Continuous drop-off' value={patternStop.continuousDropOff || ''} - onChange={this._onPickupOrDropOffChange} + onChange={this.onChange} /> From 4039af7c2a0cf731ddb23ce3c02b4f61b136c21d Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Fri, 2 Jul 2021 18:01:09 +0100 Subject: [PATCH 22/36] fix(PatternSTopCard.js): Fixed prop bug #668 --- .../components/pattern/PatternStopCard.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/editor/components/pattern/PatternStopCard.js b/lib/editor/components/pattern/PatternStopCard.js index 99084ca0f..b05920adc 100644 --- a/lib/editor/components/pattern/PatternStopCard.js +++ b/lib/editor/components/pattern/PatternStopCard.js @@ -44,7 +44,6 @@ type Props = { rowStyle: {[string]: number | string}, saveActiveGtfsEntity: typeof activeActions.saveActiveGtfsEntity, selectType: string, - selectValue: string, setActiveEntity: typeof activeActions.setActiveEntity, setActiveStop: typeof tripPatternActions.setActiveStop, shouldHaveDisabledOption: boolean, @@ -53,7 +52,8 @@ type Props = { stopIsActive: boolean, title: string, updateActiveGtfsEntity: typeof activeActions.updateActiveGtfsEntity, - updatePatternStops: typeof tripPatternActions.updatePatternStops + updatePatternStops: typeof tripPatternActions.updatePatternStops, + value: string } type State = { @@ -83,7 +83,7 @@ const pickupDropoffOptions = [ // renders the form control drop downs for dropOff/Pick up and also continuous const PickupDropoffSelect = (props: Props) => { - const {activePattern, controlLabel, selectType, shouldHaveDisabledOption, title, onChange, selectValue} = props + const {activePattern, controlLabel, selectType, shouldHaveDisabledOption, title, onChange, value} = props const hasShapeId = activePattern.shapeId === null return ( { disabled={shouldHaveDisabledOption && hasShapeId} componentClass='select' onChange={onChange} - value={selectValue} + value={value} placeholder='select'> {pickupDropoffOptions.map(o => ( @@ -307,7 +307,7 @@ class PatternStopContents extends Component { updatePatternStops(activePattern, patternStops) } - onChange = (evt: SyntheticInputEvent) => { + _onPickupOrDropOffChange = (evt: SyntheticInputEvent) => { const selectedOptionValue: number = parseInt(evt.target.value, 10) const {activePattern, index, updatePatternStops} = this.props const patternStops = [...activePattern.patternStops] @@ -315,6 +315,7 @@ class PatternStopContents extends Component { patternStops[index][evt.target.id] = selectedOptionValue this.setState({update: true}) updatePatternStops(activePattern, patternStops) + console.log('made it to here') } render () { @@ -383,7 +384,7 @@ class PatternStopContents extends Component { title='Define the pickup method/availability at this stop.' controlLabel='Pickup' value={patternStop.pickupType || ''} - onChange={this.onChange} + onChange={this._onPickupOrDropOffChange} /> @@ -394,7 +395,7 @@ class PatternStopContents extends Component { title='Define the dropff method/availability at this stop.' controlLabel='Drop-off' value={patternStop.dropOffType || ''} - onChange={this.onChange} + onChange={this._onPickupOrDropOffChange} /> @@ -407,7 +408,7 @@ class PatternStopContents extends Component { title='Indicates whether a rider can board the transit vehicle anywhere along the vehicle’s travel path.' controlLabel='Continuous pickup' value={patternStop.continuousPickup || ''} - onChange={this.onChange} + onChange={this._onPickupOrDropOffChange} /> @@ -418,7 +419,7 @@ class PatternStopContents extends Component { title='Indicates whether a rider can alight from the transit vehicle at any point along the vehicle’s travel path.' controlLabel='Continuous drop-off' value={patternStop.continuousDropOff || ''} - onChange={this.onChange} + onChange={this._onPickupOrDropOffChange} /> From 025502962c8397a1e5d9860f7d0b8fb20b07d51d Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Sat, 3 Jul 2021 13:57:25 +0100 Subject: [PATCH 23/36] fix(PatternStopCard.js): Attempt to fix flow error #668 --- lib/editor/components/pattern/PatternStopCard.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/editor/components/pattern/PatternStopCard.js b/lib/editor/components/pattern/PatternStopCard.js index b05920adc..6a38ae4b6 100644 --- a/lib/editor/components/pattern/PatternStopCard.js +++ b/lib/editor/components/pattern/PatternStopCard.js @@ -37,7 +37,6 @@ type Props = { index: number, isDragging: boolean, moveCard: (string, number) => void, - onChange: () => void, patternEdited: boolean, patternStop: PatternStop, removeStopFromPattern: typeof stopStrategiesActions.removeStopFromPattern, @@ -53,7 +52,7 @@ type Props = { title: string, updateActiveGtfsEntity: typeof activeActions.updateActiveGtfsEntity, updatePatternStops: typeof tripPatternActions.updatePatternStops, - value: string + value: string | number } type State = { @@ -315,7 +314,6 @@ class PatternStopContents extends Component { patternStops[index][evt.target.id] = selectedOptionValue this.setState({update: true}) updatePatternStops(activePattern, patternStops) - console.log('made it to here') } render () { From ae5eaaefe8a46e81b4b560c332f10adf574e600d Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Sat, 3 Jul 2021 14:02:02 +0100 Subject: [PATCH 24/36] fix(PatternStopCard.js): Attempt to fix flow issue again #668 --- lib/editor/components/pattern/PatternStopCard.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/editor/components/pattern/PatternStopCard.js b/lib/editor/components/pattern/PatternStopCard.js index 6a38ae4b6..ed0c1988f 100644 --- a/lib/editor/components/pattern/PatternStopCard.js +++ b/lib/editor/components/pattern/PatternStopCard.js @@ -37,6 +37,7 @@ type Props = { index: number, isDragging: boolean, moveCard: (string, number) => void, + onChange: () => void, patternEdited: boolean, patternStop: PatternStop, removeStopFromPattern: typeof stopStrategiesActions.removeStopFromPattern, From cb588bed71d3620b1465d65453ecd4275aa9b91a Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Sat, 3 Jul 2021 14:09:04 +0100 Subject: [PATCH 25/36] fix(PatternStopCard.js): Attempt to fox flow issue 3 #668 --- lib/editor/components/pattern/PatternStopCard.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/editor/components/pattern/PatternStopCard.js b/lib/editor/components/pattern/PatternStopCard.js index ed0c1988f..6af561ad8 100644 --- a/lib/editor/components/pattern/PatternStopCard.js +++ b/lib/editor/components/pattern/PatternStopCard.js @@ -37,7 +37,7 @@ type Props = { index: number, isDragging: boolean, moveCard: (string, number) => void, - onChange: () => void, + onChange: (evt: SyntheticInputEvent) => void, patternEdited: boolean, patternStop: PatternStop, removeStopFromPattern: typeof stopStrategiesActions.removeStopFromPattern, @@ -307,7 +307,7 @@ class PatternStopContents extends Component { updatePatternStops(activePattern, patternStops) } - _onPickupOrDropOffChange = (evt: SyntheticInputEvent) => { + _onPickupOrDropOffChange = (evt) => { const selectedOptionValue: number = parseInt(evt.target.value, 10) const {activePattern, index, updatePatternStops} = this.props const patternStops = [...activePattern.patternStops] From a95a02c0b51a8b19a8b554133e9fa3c5ca5b4d27 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 6 Jul 2021 11:15:12 -0400 Subject: [PATCH 26/36] refactor(PatternStopCard): Separate props for --- .../components/pattern/PatternStopCard.js | 70 +++++++++++-------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/lib/editor/components/pattern/PatternStopCard.js b/lib/editor/components/pattern/PatternStopCard.js index 6af561ad8..7638b9a89 100644 --- a/lib/editor/components/pattern/PatternStopCard.js +++ b/lib/editor/components/pattern/PatternStopCard.js @@ -29,7 +29,6 @@ type Props = { addStopToPattern: typeof stopStrategiesActions.addStopToPattern, // property is available through react dnd? connectDragSource: any, - controlLabel: string, cumulativeTravelTime: number, feedSource: Feed, findCard: string => { card: PatternStop, index: number }, @@ -37,22 +36,27 @@ type Props = { index: number, isDragging: boolean, moveCard: (string, number) => void, - onChange: (evt: SyntheticInputEvent) => void, patternEdited: boolean, patternStop: PatternStop, removeStopFromPattern: typeof stopStrategiesActions.removeStopFromPattern, rowStyle: {[string]: number | string}, saveActiveGtfsEntity: typeof activeActions.saveActiveGtfsEntity, - selectType: string, setActiveEntity: typeof activeActions.setActiveEntity, setActiveStop: typeof tripPatternActions.setActiveStop, - shouldHaveDisabledOption: boolean, status: any, stop: any, stopIsActive: boolean, - title: string, updateActiveGtfsEntity: typeof activeActions.updateActiveGtfsEntity, - updatePatternStops: typeof tripPatternActions.updatePatternStops, + updatePatternStops: typeof tripPatternActions.updatePatternStops +} + +type PickupDropoffSelectProps = { + activePattern: Pattern, + controlLabel: string, + onChange: (evt: SyntheticInputEvent) => void, + selectType: string, + shouldHaveDisabledOption: boolean, + title: string, value: string | number } @@ -82,13 +86,22 @@ const pickupDropoffOptions = [ ] // renders the form control drop downs for dropOff/Pick up and also continuous -const PickupDropoffSelect = (props: Props) => { - const {activePattern, controlLabel, selectType, shouldHaveDisabledOption, title, onChange, value} = props +const PickupDropoffSelect = (props: PickupDropoffSelectProps) => { + const { + activePattern, + controlLabel, + onChange, + selectType, + shouldHaveDisabledOption, + title, + value + } = props const hasShapeId = activePattern.shapeId === null return ( + > { {controlLabel} + > {pickupDropoffOptions.map(o => ( ))} @@ -318,7 +332,7 @@ class PatternStopContents extends Component { } render () { - const {active, patternEdited, patternStop} = this.props + const {active, activePattern, patternEdited, patternStop} = this.props // This component has a special shouldComponentUpdate to ensure that state // is not overwritten with new props, so use state.update to check edited // state. @@ -377,48 +391,48 @@ class PatternStopContents extends Component { From 5696ff19fd40e6d48e7fb8ce807e4c02b71bf0bd Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 6 Jul 2021 11:16:32 -0400 Subject: [PATCH 27/36] refactor(editor/util/validation): Add emptyFieldValidationIssue. --- lib/editor/util/validation.js | 66 +++++++++++++++++------------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/lib/editor/util/validation.js b/lib/editor/util/validation.js index b34b3a7cc..3e7c94922 100644 --- a/lib/editor/util/validation.js +++ b/lib/editor/util/validation.js @@ -57,17 +57,25 @@ export function validate ( * Construct an EditorValidationIssue for the field name and reason (defaults to * empty field message). */ - function validationIssue (reason = EMPTY_FIELD_REASON, field = name) { + function validationIssue (reason: string, field = name) { return {field, invalid: true, reason} } + /** + * Construct an EditorValidationIssue for this field, used if it is required + * and has an empty value. + */ + function emptyFieldValidationIssue () { + return {field: name, invalid: true, reason: EMPTY_FIELD_REASON} + } + /** * Checks whether value is a positive number */ function checkPositiveNumber (): CheckPositiveOutput { if (isRequiredButEmpty) { return { - result: validationIssue() + result: emptyFieldValidationIssue() } } else if (isOptionalAndEmpty) { return { @@ -123,12 +131,10 @@ export function validate ( ) { return validationIssue('Identifier is required if more than one agency exists') } - if (isRequiredButEmpty || isNotUnique) { - let reason - if (isNotUnique) { - reason = 'Identifier must be unique' - } - return validationIssue(reason) + if (isRequiredButEmpty) { + return emptyFieldValidationIssue() + } else if (isNotUnique) { + return validationIssue('Identifier must be unique') } else { return false } @@ -147,7 +153,7 @@ export function validate ( return false } else { if (isRequiredButEmpty) { - return validationIssue() + return emptyFieldValidationIssue() } else { return false } @@ -158,47 +164,43 @@ export function validate ( case 'GTFS_FARE': case 'GTFS_SERVICE': if (isRequiredButEmpty) { - return validationIssue() + return emptyFieldValidationIssue() } else { return false } case 'URL': const isNotUrl = value && !validator.isURL(value) - if (isRequiredButEmpty || isNotUrl) { - let reason - if (isNotUrl) { - reason = 'Field must contain valid URL.' - } - return validationIssue(reason) + if (isRequiredButEmpty) { + return emptyFieldValidationIssue() + } else if (isNotUrl) { + return validationIssue('Field must contain valid URL.') } else { return false } case 'EMAIL': const isNotEmail = value && !validator.isEmail(value) - if (isRequiredButEmpty || isNotEmail) { - let reason - if (isNotEmail) { - reason = 'Field must contain valid email address.' - } - return validationIssue(reason) + if (isRequiredButEmpty) { + return emptyFieldValidationIssue() + } else if (isNotEmail) { + return validationIssue('Field must contain valid email address.') } else { return false } case 'GTFS_ZONE': if (isRequiredButEmpty) { - return validationIssue() + return emptyFieldValidationIssue() } else { return false } case 'TIMEZONE': if (isRequiredButEmpty) { - return validationIssue() + return emptyFieldValidationIssue() } else { return false } case 'LANGUAGE': if (isRequiredButEmpty) { - return validationIssue() + return emptyFieldValidationIssue() } else { return false } @@ -223,12 +225,10 @@ export function validate ( case 'TIME': case 'NUMBER': const isNotANumber = isNaN(value) - if (isRequiredButEmpty || isNotANumber) { - let reason - if (isNotANumber) { - reason = 'Field must be valid number' - } - return validationIssue(reason) + if (isRequiredButEmpty) { + return emptyFieldValidationIssue() + } else if (isNotANumber) { + return validationIssue('Field must be valid number') } else { return false } @@ -259,7 +259,7 @@ export function validate ( field.options && field.options.findIndex(o => o.value === '') === -1 ) { - return validationIssue() + return emptyFieldValidationIssue() } else { return false } @@ -341,7 +341,7 @@ export function validate ( case 'COLOR': default: if (isRequiredButEmpty) { - return validationIssue() + return emptyFieldValidationIssue() } return false } From e692199a8824774af57441185a527f5cd9a49835 Mon Sep 17 00:00:00 2001 From: Robert Gregg Date: Wed, 7 Jul 2021 15:40:30 +0100 Subject: [PATCH 28/36] refactor(PatternStopCard.js validation.js): Refactor Create separate prop types for the pickUpDropOffSelect method. Reorder the props alphabetically. Add additional placeholder prop. Refactor validation to use separate methods for validation errors and empty errors #668 --- lib/editor/components/pattern/PatternStopCard.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/editor/components/pattern/PatternStopCard.js b/lib/editor/components/pattern/PatternStopCard.js index 7638b9a89..fbdb4cee7 100644 --- a/lib/editor/components/pattern/PatternStopCard.js +++ b/lib/editor/components/pattern/PatternStopCard.js @@ -85,7 +85,7 @@ const pickupDropoffOptions = [ } ] -// renders the form control drop downs for dropOff/Pick up and also continuous +/** renders the form control drop downs for dropOff/Pick up and also continuous */ const PickupDropoffSelect = (props: PickupDropoffSelectProps) => { const { activePattern, @@ -321,7 +321,7 @@ class PatternStopContents extends Component { updatePatternStops(activePattern, patternStops) } - _onPickupOrDropOffChange = (evt) => { + _onPickupOrDropOffChange = (evt: SyntheticInputEvent) => { const selectedOptionValue: number = parseInt(evt.target.value, 10) const {activePattern, index, updatePatternStops} = this.props const patternStops = [...activePattern.patternStops] From f56707782a994c438978beab6e269e856375eb3b Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Wed, 7 Jul 2021 11:06:22 -0400 Subject: [PATCH 29/36] refactor(validation): reuse validation method for 'empty' method --- lib/editor/util/validation.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/editor/util/validation.js b/lib/editor/util/validation.js index 3e7c94922..f977a86a2 100644 --- a/lib/editor/util/validation.js +++ b/lib/editor/util/validation.js @@ -15,8 +15,6 @@ export type EditorValidationIssue = { reason: string } -const EMPTY_FIELD_REASON = 'Required field must not be empty' - export function doesNotExist (value: any): boolean { return value === '' || value === null || typeof value === 'undefined' } @@ -65,8 +63,8 @@ export function validate ( * Construct an EditorValidationIssue for this field, used if it is required * and has an empty value. */ - function emptyFieldValidationIssue () { - return {field: name, invalid: true, reason: EMPTY_FIELD_REASON} + function emptyFieldValidationIssue (field = name) { + return validationIssue('Required field must not be empty', field) } /** @@ -298,7 +296,7 @@ export function validate ( } } if (!value || value.length === 0) { - return validationIssue(EMPTY_FIELD_REASON, 'dates') + return emptyFieldValidationIssue('dates') } // check if date already exists in this or other exceptions for (let i = 0; i < value.length; i++) { @@ -310,7 +308,7 @@ export function validate ( ]}) cannot appear more than once for all exceptions` return validationIssue(reason, dateItemName) } else if (!moment(value[i], 'YYYYMMDD', true).isValid()) { - return validationIssue(EMPTY_FIELD_REASON, dateItemName) + return emptyFieldValidationIssue(dateItemName) } } return false From 589b6c65d35e1067e86d2fdb90f31ba3fb42fa00 Mon Sep 17 00:00:00 2001 From: Robert Gregg Date: Wed, 7 Jul 2021 16:59:40 +0100 Subject: [PATCH 30/36] fix(validation.js): Remove ES lint waiver #668 --- lib/editor/util/validation.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/editor/util/validation.js b/lib/editor/util/validation.js index f977a86a2..00b810164 100644 --- a/lib/editor/util/validation.js +++ b/lib/editor/util/validation.js @@ -25,7 +25,6 @@ export function doesNotExist (value: any): boolean { * Returns false if the value is ok. * Returns an EditorValidationIssue object if the value is not ok. */ -// eslint-disable-next-line complexity export function validate ( field: GtfsSpecField, value: any, From ef69da36f4a448b098dca8e5c8008888aad04074 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 19 Jul 2021 17:03:07 -0400 Subject: [PATCH 31/36] fix(editor/util/validation): Fix stop name validation. --- lib/editor/util/validation.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/editor/util/validation.js b/lib/editor/util/validation.js index 00b810164..ec4ce6cf2 100644 --- a/lib/editor/util/validation.js +++ b/lib/editor/util/validation.js @@ -136,7 +136,11 @@ export function validate ( return false } case 'TEXT': - if (name === 'stop_name' && locationType !== null && (typeof locationType === 'number' && locationType <= 2)) { + if (name === 'stop_name' && + !value && + locationType !== null && + (typeof locationType === 'number' && locationType <= 2) + ) { return validationIssue('Stop name is required for stop, station, and entrance location types.') } if (name === 'route_short_name' && !value && entity && entity.route_long_name) { From 00ea4d41acd8a3123183174dc1b9cbc859b6a0f7 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 20 Jul 2021 17:18:52 -0400 Subject: [PATCH 32/36] style(editor/util/validation): Reformat stop_name validation condition --- lib/editor/util/validation.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/editor/util/validation.js b/lib/editor/util/validation.js index ec4ce6cf2..d6ddd7601 100644 --- a/lib/editor/util/validation.js +++ b/lib/editor/util/validation.js @@ -136,10 +136,11 @@ export function validate ( return false } case 'TEXT': - if (name === 'stop_name' && - !value && - locationType !== null && - (typeof locationType === 'number' && locationType <= 2) + if ( + name === 'stop_name' && + !value && + locationType !== null && + (typeof locationType === 'number' && locationType <= 2) ) { return validationIssue('Stop name is required for stop, station, and entrance location types.') } From 59d429a741b40aa19232203bcba3a83b14658b55 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 21 Sep 2021 10:16:09 -0400 Subject: [PATCH 33/36] refactor(PatternStopCard): Clone before updating pickup/dropoff stop attribute. --- lib/editor/components/pattern/PatternStopCard.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/editor/components/pattern/PatternStopCard.js b/lib/editor/components/pattern/PatternStopCard.js index fbdb4cee7..e5aa5d053 100644 --- a/lib/editor/components/pattern/PatternStopCard.js +++ b/lib/editor/components/pattern/PatternStopCard.js @@ -1,7 +1,8 @@ // @flow import Icon from '@conveyal/woonerf/components/icon' -import React, {Component} from 'react' +import clone from 'lodash/cloneDeep' +import React, { Component } from 'react' import { DragSource, DropTarget } from 'react-dnd' import { Checkbox, @@ -18,11 +19,11 @@ import * as stopStrategiesActions from '../../actions/map/stopStrategies' import * as tripPatternActions from '../../actions/tripPattern' import {getEntityName, getAbbreviatedStopName} from '../../util/gtfs' import MinuteSecondInput from '../MinuteSecondInput' +import type {Feed, Pattern, PatternStop} from '../../../types' + import NormalizeStopTimesTip from './NormalizeStopTimesTip' import PatternStopButtons from './PatternStopButtons' -import type {Feed, Pattern, PatternStop} from '../../../types' - type Props = { active: boolean, activePattern: Pattern, @@ -326,7 +327,9 @@ class PatternStopContents extends Component { const {activePattern, index, updatePatternStops} = this.props const patternStops = [...activePattern.patternStops] - patternStops[index][evt.target.id] = selectedOptionValue + const newPatternStop = clone(patternStops[index]) + newPatternStop[evt.target.id] = selectedOptionValue + patternStops[index] = newPatternStop this.setState({update: true}) updatePatternStops(activePattern, patternStops) } From 548fb245d0622af183781add229e57fc97e99e20 Mon Sep 17 00:00:00 2001 From: Phil Cline Date: Wed, 22 Sep 2021 09:54:50 -0400 Subject: [PATCH 34/36] fix(saveEntity, saveTripsForCalendar): Resolve missing defaults with GTFS spec change fix #716, #717 --- lib/editor/actions/active.js | 23 +++++++++++++++++------ lib/editor/actions/trip.js | 10 +++++++++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/lib/editor/actions/active.js b/lib/editor/actions/active.js index 49b74d68a..f7bf8fc37 100644 --- a/lib/editor/actions/active.js +++ b/lib/editor/actions/active.js @@ -6,10 +6,8 @@ import {createAction, type ActionType} from 'redux-actions' import {createVoidPayloadAction, secureFetch} from '../../common/actions' import {ENTITY} from '../constants' -import {newGtfsEntity, fetchBaseGtfs} from './editor' import {fetchFeedSourceAndProject} from '../../manager/actions/feeds' import {fetchGTFSEntities} from '../../manager/actions/versions' -import {saveTripPattern} from './tripPattern' import { getEditorNamespace, getTableById, @@ -18,10 +16,12 @@ import { subSubComponentList } from '../util/gtfs' import {getMapFromGtfsStrategy, entityIsNew} from '../util/objects' - import type {Entity, Feed} from '../../types' import type {dispatchFn, getStateFn, AppState} from '../../types/reducers' +import {saveTripPattern} from './tripPattern' +import {newGtfsEntity, fetchBaseGtfs} from './editor' + export const clearGtfsContent = createVoidPayloadAction('CLEAR_GTFSEDITOR_CONTENT') const receivedNewEntity = createAction( 'RECEIVE_NEW_ENTITY', @@ -331,14 +331,25 @@ export function saveEntity ( return } dispatch(savingActiveGtfsEntity()) - const notNew = !entityIsNew(entity) + // Add default vals for component + const defaults = {} + if (component === 'route') { + defaults.continuous_pickup = 1 // Default value for no continuous pickup + defaults.continuous_drop_off = 1 // Default value for no continuous drop off + } else if (component === 'feedinfo') { + defaults.default_lang = '' + defaults.feed_contact_url = '' + defaults.feed_contact_email = '' + } + const entityWithDefaults = {...defaults, ...(entity: any)} // add defaults, if any. + const notNew = !entityIsNew(entityWithDefaults) const method = notNew ? 'put' : 'post' - const idParam = notNew ? `/${entity.id || ''}` : '' + const idParam = notNew ? `/${entityWithDefaults.id || ''}` : '' const {sessionId} = getState().editor.data.lock const route = component === 'fare' ? 'fareattribute' : component const url = `/api/editor/secure/${route}${idParam}?feedId=${feedId}&sessionId=${sessionId || ''}` const mappingStrategy = getMapFromGtfsStrategy(component) - const data = mappingStrategy(entity) + const data = mappingStrategy(entityWithDefaults) return dispatch(secureFetch(url, method, data)) .then(res => res.json()) .then(savedEntity => { diff --git a/lib/editor/actions/trip.js b/lib/editor/actions/trip.js index 8b25515c6..6f65a0cb9 100644 --- a/lib/editor/actions/trip.js +++ b/lib/editor/actions/trip.js @@ -7,7 +7,6 @@ import {createVoidPayloadAction, fetchGraphQL, secureFetch} from '../../common/a import {setErrorMessage} from '../../manager/actions/status' import {entityIsNew} from '../util/objects' import {getEditorNamespace} from '../util/gtfs' - import type {Pattern, TimetableColumn, Trip} from '../../types' import type {dispatchFn, getStateFn, TripCounts} from '../../types/reducers' @@ -159,6 +158,15 @@ export function saveTripsForCalendar ( trips = trips.map(snakeCaseKeys) return Promise.all(trips.filter(t => t).map((trip, index) => { const tripExists = !entityIsNew(trip) && trip.id !== null + const tripCopy: any = (trip: any) + // Add default value to continuous pickup if not provided + const defaults = { + continuous_pickup: 1, + continuous_drop_off: 1 + } + tripCopy.stop_times = tripCopy.stop_times.map((stopTime, index) => { + return {...defaults, ...(stopTime: any)} + }) const method = tripExists ? 'put' : 'post' const url = tripExists && trip.id ? `/api/editor/secure/trip/${trip.id}?feedId=${feedId}&sessionId=${sessionId}` From c48bb1826bdeb075763a93734a394a61ed9ba3af Mon Sep 17 00:00:00 2001 From: Phil Cline Date: Wed, 22 Sep 2021 12:26:58 -0400 Subject: [PATCH 35/36] refactor(fetchBaseGtfs, saveTripsForCalendar): Fix fetch of new spec fields, address comments --- lib/editor/actions/editor.js | 7 +++++-- lib/editor/actions/trip.js | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/editor/actions/editor.js b/lib/editor/actions/editor.js index 62f2f6a75..6d1d97bde 100644 --- a/lib/editor/actions/editor.js +++ b/lib/editor/actions/editor.js @@ -8,7 +8,6 @@ import {createAction, type ActionType} from 'redux-actions' import {createVoidPayloadAction, fetchGraphQL, secureFetch} from '../../common/actions' import {generateUID} from '../../common/util/util' -import {clearGtfsContent, saveActiveGtfsEntity, setActiveGtfsEntity} from './active' import {ENTITY} from '../constants' import { generateNullProps, @@ -17,7 +16,6 @@ import { getTableById } from '../util/gtfs' import {fetchGTFSEntities} from '../../manager/actions/versions' - import type { dispatchFn, getStateFn, @@ -26,6 +24,8 @@ import type { LockState } from '../../types/reducers' +import {clearGtfsContent, saveActiveGtfsEntity, setActiveGtfsEntity} from './active' + export const updateEntitySort = createAction('UPDATE_ENTITY_SORT') const createGtfsEntity = createAction( @@ -384,6 +384,9 @@ export function fetchBaseGtfs ({ feed_version default_route_color default_route_type + default_lang + feed_contact_url + feed_contact_email } agency (limit: -1) { id diff --git a/lib/editor/actions/trip.js b/lib/editor/actions/trip.js index 6f65a0cb9..752001496 100644 --- a/lib/editor/actions/trip.js +++ b/lib/editor/actions/trip.js @@ -1,5 +1,5 @@ // @flow - +import clone from 'lodash/cloneDeep' import {createAction, type ActionType} from 'redux-actions' import {snakeCaseKeys} from '../../common/util/map-keys' @@ -158,8 +158,9 @@ export function saveTripsForCalendar ( trips = trips.map(snakeCaseKeys) return Promise.all(trips.filter(t => t).map((trip, index) => { const tripExists = !entityIsNew(trip) && trip.id !== null - const tripCopy: any = (trip: any) + const tripCopy: any = clone((trip: any)) // Add default value to continuous pickup if not provided + // Editing continuous pickup/drop off is not currently supported in the schedule editor const defaults = { continuous_pickup: 1, continuous_drop_off: 1 From cda4f65a8dc80f555e1e0aaf167621ffa0cbac0a Mon Sep 17 00:00:00 2001 From: Phil Cline Date: Wed, 22 Sep 2021 14:45:38 -0400 Subject: [PATCH 36/36] refactor(saveTripsForCalendar): Fix missing tripCopy ref --- lib/editor/actions/trip.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/editor/actions/trip.js b/lib/editor/actions/trip.js index 752001496..ddef25d49 100644 --- a/lib/editor/actions/trip.js +++ b/lib/editor/actions/trip.js @@ -172,7 +172,7 @@ export function saveTripsForCalendar ( const url = tripExists && trip.id ? `/api/editor/secure/trip/${trip.id}?feedId=${feedId}&sessionId=${sessionId}` : `/api/editor/secure/trip?feedId=${feedId}&sessionId=${sessionId}` - return dispatch(secureFetch(url, method, trip)) + return dispatch(secureFetch(url, method, tripCopy)) .then(res => res.json()) .catch(err => { console.warn(err)