diff --git a/lib/manager/components/UserHomePage.js b/lib/manager/components/UserHomePage.js
index 951177f27..e09afac1b 100644
--- a/lib/manager/components/UserHomePage.js
+++ b/lib/manager/components/UserHomePage.js
@@ -3,14 +3,14 @@
import { Auth0ContextInterface } from '@auth0/auth0-react'
import Icon from '@conveyal/woonerf/components/icon'
import React, {Component} from 'react'
-import {Grid, Row, Col, Button, ButtonToolbar, Jumbotron} from 'react-bootstrap'
+import { Alert, Button, ButtonToolbar, Col, Grid, Jumbotron, Row } from 'react-bootstrap'
import objectPath from 'object-path'
import * as feedsActions from '../actions/feeds'
import * as userActions from '../actions/user'
import * as visibilityFilterActions from '../actions/visibilityFilter'
import ManagerPage from '../../common/components/ManagerPage'
-import { DEFAULT_DESCRIPTION, DEFAULT_TITLE } from '../../common/constants'
+import { AUTH0_DISABLED, DEFAULT_DESCRIPTION, DEFAULT_TITLE } from '../../common/constants'
import {getConfigProperty, getComponentMessages} from '../../common/util/config'
import {defaultSorter} from '../../common/util/util'
import type {Props as ContainerProps} from '../containers/ActiveUserHomePage'
@@ -85,6 +85,7 @@ export default class UserHomePage extends Component
{
} = this.props
const visibleProjects = projects.sort(defaultSorter)
const activeProject = project
+ const appTitle = getConfigProperty('application.title') || 'datatools'
return (
{
{/* Top Welcome Box */}
- {this.messages('welcomeTo')} {getConfigProperty('application.title') || DEFAULT_TITLE}!
+ {this.messages('welcomeTo')} {appTitle || DEFAULT_TITLE}!
{getConfigProperty('application.description') || DEFAULT_DESCRIPTION}
{
+ {/* Info banner shown if auth is disabled. */}
+ {AUTH0_DISABLED && (
+
+
+ {this.messages('authDisabledInfo').replace('%appTitle%', appTitle)}
+
+ )}
{/* Recent Activity List */}
{this.messages('recentActivity')}
diff --git a/lib/manager/components/deployment/CurrentDeploymentPanel.js b/lib/manager/components/deployment/CurrentDeploymentPanel.js
index ca9487b41..efd5088d9 100644
--- a/lib/manager/components/deployment/CurrentDeploymentPanel.js
+++ b/lib/manager/components/deployment/CurrentDeploymentPanel.js
@@ -278,7 +278,7 @@ class DeployJobSummary extends Component<{
onClick={this._onClickDownloadLogs}>
Build log
- {/* TODO: otp-runner uploads these?
Graph build report
@@ -287,7 +287,7 @@ class DeployJobSummary extends Component<{
bsSize='xsmall'
onClick={this._onClickDownloadBundle}>
Bundle
- */}
+
{
const stateUpdate = {}
set(stateUpdate,
`routerConfig.updaters.$${this.state.routerConfig.updaters ? 'push' : 'set'}`,
- [{type: '', url: '', frequencySec: 30, sourceType: '', defaultAgencyId: ''}]
+ [{type: '', url: '', frequencySec: 30, sourceType: '', feedId: ''}]
)
this.setState(update(this.state, stateUpdate))
}
diff --git a/lib/manager/components/deployment/PeliasPanel.js b/lib/manager/components/deployment/PeliasPanel.js
index 1877be369..1ec679c04 100644
--- a/lib/manager/components/deployment/PeliasPanel.js
+++ b/lib/manager/components/deployment/PeliasPanel.js
@@ -17,6 +17,9 @@ import ConfirmModal from '../../../common/components/ConfirmModal'
import { updateDeployment, uploadPeliasWebhookCsvFile, updatePelias } from '../../actions/deployments'
import { setErrorMessage } from '../../actions/status'
import type { Deployment, Project } from '../../../types'
+import {
+ formatTimestamp
+} from '../../../common/util/date-time'
type Props = {
deployment: Deployment,
@@ -29,13 +32,55 @@ type Props = {
type State = {
fileToDeleteOnSuccesfulUpload: string | null,
+ peliasCsvUploadsDates: Array
}
class PeliasPanel extends Component {
state = {
- fileToDeleteOnSuccesfulUpload: null
+ fileToDeleteOnSuccesfulUpload: null,
+ peliasCsvUploadsDates: []
};
+componentDidUpdate = (newProps) => {
+ const { deployment } = newProps
+ if (!deployment.peliasCsvFiles) {
+ return
+ }
+
+ // Only update if the deployment has changed
+ if (
+ this.props.deployment.peliasCsvFiles &&
+ this.props.deployment.peliasCsvFiles.every(
+ // $FlowFixMe flow is wrong
+ (file) => deployment.peliasCsvFiles.indexOf(file) > -1
+ ) &&
+ // $FlowFixMe flow is wrong
+ deployment.peliasCsvFiles.every(
+ // $FlowFixMe flow is wrong
+ (file) => this.props.deployment.peliasCsvFiles.indexOf(file) > -1
+ // And if there are no upload dates yet
+ ) && this.state.peliasCsvUploadsDates.length > 0
+ ) {
+ return
+ }
+
+ // $FlowFixMe flow is wrong
+ deployment.peliasCsvFiles.forEach(
+ (file, index) => this.fetchAndUpdatePeliasDate(file, index)
+ )
+}
+
+ fetchAndUpdatePeliasDate = (url, index) => {
+ const { peliasCsvUploadsDates } = this.state
+ fetch(url, {method: 'HEAD'}).then(data => {
+ const lastModified = data.headers.get('last-modified')
+ if (!lastModified) return
+
+ peliasCsvUploadsDates[index] = lastModified
+ this.setState({peliasCsvUploadsDates})
+ })
+ }
+
/**
* Method fired when Pelias *Update* button is pressed
*/
@@ -94,6 +139,16 @@ class PeliasPanel extends Component {
}
}
+ _onCustomNameCancel = () => {
+ this.refs.confirm.open({
+ title: `Are you sure you want to remove this file?`,
+ body: 'This is irreversible',
+ onConfirm: () => {
+ this._updateDeployment({ peliasSynonymsBase64: null })
+ }
+ })
+ }
+
/**
* Takes files submitted via the file uploader modal and sends them to datatools server where they are
* uploaded to S3
@@ -115,6 +170,39 @@ class PeliasPanel extends Component {
return uploadSuccess
}
+ /**
+ * Takes files submitted via the file uploader modal and encodes it to base64, then
+ * adds it to the deployment object
+ * @param files Array of files returned by the file upload henmodal
+ * @returns True or false depending on if the upload was a success
+ */
+ _onConfirmSynonymsUpload = async (files: Array) => {
+ const file = files[0]
+
+ if (!file) return false
+ if (file.type !== 'text/plain') {
+ return false
+ }
+
+ // Attempt base64 encode
+ const toBase64 = file => new Promise((resolve, reject) => {
+ const reader = new FileReader()
+ reader.readAsDataURL(file)
+ reader.onload = () => resolve(reader.result)
+ reader.onerror = reject
+ })
+
+ try {
+ const encoded = await toBase64(file)
+ this._updateDeployment({
+ peliasSynonymsBase64: encoded
+ })
+ return true
+ } catch {
+ return false
+ }
+ }
+
/**
* Removes a csv file from the list of Pelias csv files associated with a deployment
* WARNING: DOES NOT REMOVE THE FILE FROM S3!
@@ -141,9 +229,10 @@ class PeliasPanel extends Component {
* buttons to replace or remove the file
* @param {*} url The URL to add to the list of csv files associated with the deployment
* @param {*} enabled Whether the buttons should be enabled
+ * @param {*} label An optional label to render next to the buttons
* @returns JSX including the file name and buttons
*/
- renderCsvUrl = (url: string, enabled: boolean) => {
+ renderCsvUrl = (url: string, enabled: boolean, label?: string) => {
// Usually, files will be rendered by https://github.com/ibi-group/datatools-server/blob/dev/src/main/java/com/conveyal/datatools/manager/controllers/api/DeploymentController.java
// so we can take advantage of a predictable filename
// As a fallback, render the full url
@@ -167,6 +256,15 @@ class PeliasPanel extends Component {
>
Delete
+ { window.location = url }}
+ >
+ Download
+
+ {label && {label} }
)
}
@@ -183,14 +281,22 @@ class PeliasPanel extends Component {
return (
-
- Local Places Index Settings
-
+
+
+ Local Places Index Settings
+
+
-
+
Local Places Index
-
+
{/* If there are no deployments don't allow user to update Pelias yet */}
{
this.refs.reInitPeliasModal.open()}
- >Reset
+ >
+ Reset
+
{
title='Are you sure you want to rebuild the Local Places Index database?'
/>
-
+
Custom Point Of Interest CSV Files
- These files are sent to the Local Places Index when it is updated or reset
+
+ These files are sent to the Local Places Index when it is updated
+ or reset
+
{
/>
{deployment.peliasCsvFiles &&
- deployment.peliasCsvFiles.map((url) =>
- this.renderCsvUrl(url, !peliasButtonsDisabled)
+ deployment.peliasCsvFiles.map((url, index) => {
+ const uploadDate = this.state.peliasCsvUploadsDates[index]
+ const label = uploadDate ? formatTimestamp(uploadDate) : ''
+ return this.renderCsvUrl(url, !peliasButtonsDisabled, label)
+ }
)}
{
Upload New CSV File
+
+ Synonyms File
+ Upload your synonyms file here
+ this._onConfirmSynonymsUpload(files)}
+ ref='uploadSynonymsModal'
+ title='Upload Synonyms File'
+ />
+
+ this.refs.uploadSynonymsModal.open()}
+ style={{ marginTop: 15 }}
+ >
+ {deployment.peliasSynonymsBase64 ? 'Replace' : 'Upload'} Synonyms File
+
+ {deployment.peliasSynonymsBase64 &&
+ Synonyms file currently loaded }
+
+
+ Remove Synonyms File
+
+
+
)
diff --git a/lib/manager/components/transform/AddCustomFile.js b/lib/manager/components/transform/AddCustomFile.js
new file mode 100644
index 000000000..d334d9664
--- /dev/null
+++ b/lib/manager/components/transform/AddCustomFile.js
@@ -0,0 +1,101 @@
+// @flow
+
+import React, { Component } from 'react'
+
+import type { AddCustomFileProps, TransformProps } from '../../../types'
+import CSV_VALIDATION_ERRORS from '../../util/enums/transform'
+import { getComponentMessages } from '../../../common/util/config'
+
+import CustomCSVForm from './CustomCSVForm'
+
+/**
+ * Component that renders fields for AddCustomFile. This transformation shares csvData props with the ReplaceFileFromString transformation.
+ * TODO: adapt this transformation to include a file upload for larger custom files?
+ */
+export default class AddCustomFile extends Component, AddCustomFileProps> {
+ // Messages are for the child CSV Form component but since messages are shared across transformation types,
+ // the messages are being grouped under that component.
+ messages = getComponentMessages('AddCustomFile')
+ constructor (props: TransformProps) {
+ super(props)
+ this.state = {csvData: props.transformation.csvData, table: props.transformation.table}
+ }
+
+ componentDidMount () {
+ this._updateErrors()
+ }
+
+ componentDidUpdate (prevProps: TransformProps, prevState: AddCustomFileProps) {
+ if (prevState !== this.state) {
+ this._updateErrors()
+ }
+ }
+
+ _handleChange = (evt: SyntheticInputEvent) => {
+ const newState = {...this.state, table: evt.target.value}
+ this.setState(newState)
+ }
+
+ _onSaveCsvData = () => {
+ const csvData = this.state.csvData || null
+ const table = this.state.table
+ this.props.onSave({csvData, table}, this.props.index)
+ }
+
+ _onChangeCsvData = (evt: SyntheticInputEvent) => {
+ const newState = {...this.state, csvData: evt.target.value}
+ this.setState(newState)
+ }
+
+ _getValidationErrors (fields: AddCustomFileProps): Array {
+ const issues = []
+ const { csvData, table } = fields
+
+ // CSV data must be defined.
+ if (!csvData || csvData.length === 0) {
+ issues.push(CSV_VALIDATION_ERRORS.UNDEFINED_CSV_DATA)
+ }
+ if (!table) {
+ issues.push(CSV_VALIDATION_ERRORS.CSV_MUST_HAVE_NAME)
+ }
+ if (table && table.endsWith('.txt')) {
+ issues.push(CSV_VALIDATION_ERRORS.CSV_NAME_CONTAINS_TXT)
+ }
+ return issues
+ }
+
+ /**
+ * Notify containing component of the resulting validation errors if any.
+ * @param fields: The updated state. If not set, the component state will be used.
+ */
+ _updateErrors = (fields?: AddCustomFileProps) => {
+ const { onValidationErrors } = this.props
+ onValidationErrors(this._getValidationErrors(fields || this.state))
+ }
+
+ render () {
+ const {transformation} = this.props
+ const {csvData, table} = this.state
+ const inputIsSame = csvData === transformation.csvData && table === transformation.table
+ return (
+
+ )
+ }
+}
diff --git a/lib/manager/components/transform/CustomCSVForm.js b/lib/manager/components/transform/CustomCSVForm.js
new file mode 100644
index 000000000..e2144e492
--- /dev/null
+++ b/lib/manager/components/transform/CustomCSVForm.js
@@ -0,0 +1,64 @@
+// @flow
+
+import React from 'react'
+import { Button } from 'react-bootstrap'
+
+import { getComponentMessages } from '../../../common/util/config'
+
+type Props = {
+ buttonText: string,
+ csvData: ?string,
+ headerText: string,
+ inputIsSame: boolean,
+ onChangeCsvData: (SyntheticInputEvent) => void,
+ onSaveCsvData: () => void,
+}
+const CustomCSVForm = (props: Props) => {
+ const { buttonText, csvData, headerText, inputIsSame, onChangeCsvData, onSaveCsvData } = props
+
+ const numLines = !csvData ? 0 : csvData.split(/\r*\n/).length
+ const messages = getComponentMessages('CustomCSVForm')
+
+ return (
+
+
+ {headerText}
+
+
+
+
+ {buttonText}
+
+ {messages('numLines').replace('%numLines%', numLines.toString())}
+
+
+ )
+}
+
+export default CustomCSVForm
diff --git a/lib/manager/components/transform/FeedTransformRules.js b/lib/manager/components/transform/FeedTransformRules.js
index e0df8b562..689bf691e 100644
--- a/lib/manager/components/transform/FeedTransformRules.js
+++ b/lib/manager/components/transform/FeedTransformRules.js
@@ -33,7 +33,9 @@ function newFeedTransformation (type: string = 'ReplaceFileFromVersionTransforma
const feedTransformationTypes = [
'ReplaceFileFromVersionTransformation',
'ReplaceFileFromStringTransformation',
- 'NormalizeFieldTransformation'
+ 'NormalizeFieldTransformation',
+ 'PreserveCustomFieldsTransformation',
+ 'AddCustomFileTransformation'
]
type TransformRulesProps = {
diff --git a/lib/manager/components/transform/FeedTransformation.js b/lib/manager/components/transform/FeedTransformation.js
index 78b523c0b..9a6b709d7 100644
--- a/lib/manager/components/transform/FeedTransformation.js
+++ b/lib/manager/components/transform/FeedTransformation.js
@@ -5,8 +5,9 @@ import React, {Component} from 'react'
import {Button} from 'react-bootstrap'
import Select from 'react-select'
-import {getGtfsSpec, getGtfsPlusSpec, isModuleEnabled} from '../../../common/util/config'
+import {getComponentMessages, getGtfsSpec, getGtfsPlusSpec, isModuleEnabled} from '../../../common/util/config'
import {getTransformationName, getTransformationPlaceholder} from '../../util/transform'
+import CSV_VALIDATION_ERRORS from '../../util/enums/transform'
import type {
Feed,
FeedTransformation as FeedTransformationType,
@@ -16,6 +17,8 @@ import type {
import NormalizeField from './NormalizeField'
import ReplaceFileFromString from './ReplaceFileFromString'
import ReplaceFileFromVersion from './ReplaceFileFromVersion'
+import AddCustomFile from './AddCustomFile'
+import PreserveCustomFields from './PreserveCustomFields'
type Props = {
feedSource: Feed,
@@ -37,11 +40,15 @@ const transformationTypes = {
},
ReplaceFileFromVersionTransformation: {
component: ReplaceFileFromVersion
+ },
+ PreserveCustomFieldsTransformation: {
+ component: PreserveCustomFields
+ },
+ AddCustomFileTransformation: {
+ component: AddCustomFile
}
}
-const TABLE_MUST_BE_DEFINED_ERROR = 'Table must be defined'
-
/**
* Component that renders fields for one feed transformation
* (e.g., ReplaceFileFromStringTransformation).
@@ -56,7 +63,7 @@ export default class FeedTransformation extends Component e !== TABLE_MUST_BE_DEFINED_ERROR) })
+ this.setState({ errors: this.state.errors.filter(e => e !== CSV_VALIDATION_ERRORS.TABLE_MUST_BE_DEFINED) })
}
}
@@ -76,8 +83,9 @@ export default class FeedTransformation extends Component) => {
const issues: Array = []
- if (!this.props.transformation.table) {
- issues.push(TABLE_MUST_BE_DEFINED_ERROR)
+ const { transformation } = this.props
+ if (!transformation.table && transformation['@type'] !== 'AddCustomFileTransformation') {
+ issues.push(CSV_VALIDATION_ERRORS.TABLE_MUST_BE_DEFINED)
}
this.setState({ errors: issues.concat(errors) })
}
@@ -95,6 +103,7 @@ export default class FeedTransformation extends Component !t.datatools)
+ const errorMessages = getComponentMessages('FeedTransformationErrors')
if (isModuleEnabled('gtfsplus')) tables.push(...getGtfsPlusSpec())
const transformationType = transformation['@type']
const {errors: validationIssues} = this.state
@@ -120,13 +129,16 @@ export default class FeedTransformation extends Component
-
({value: table.name.split('.txt')[0], label: table.name}))}
- value={transformation.table}
- onChange={this._onSelectTable} />
+ {transformationType !== 'AddCustomFileTransformation' && (
+ ({value: table.name.split('.txt')[0], label: table.name}))}
+ value={transformation.table}
+ onChange={this._onSelectTable}
+ />
+ )}
{this._getFieldsForType(transformationType)}
{validationIssues.length > 0
?
@@ -134,7 +146,7 @@ export default class FeedTransformation extends Component {
return (
- {issue}
+ {errorMessages(issue)}
)
})}
diff --git a/lib/manager/components/transform/PreserveCustomFields.js b/lib/manager/components/transform/PreserveCustomFields.js
new file mode 100644
index 000000000..8bcfd55c4
--- /dev/null
+++ b/lib/manager/components/transform/PreserveCustomFields.js
@@ -0,0 +1,28 @@
+import React from 'react'
+
+import { getComponentMessages } from '../../../common/util/config'
+
+import ReplaceFileFromString from './ReplaceFileFromString'
+import CustomCSVForm from './CustomCSVForm'
+
+export default class PreserveCustomFields extends ReplaceFileFromString {
+ // Messages are for the child CSV Form component but since messages are shared across transformation types,
+ // the messages are being grouped under that component.
+ messages = getComponentMessages('PreserveCustomFields')
+ render () {
+ const {transformation} = this.props
+ const {csvData} = this.state
+ const inputIsSame = csvData === transformation.csvData
+
+ return (
+
+ )
+ }
+}
diff --git a/lib/manager/components/transform/ReplaceFileFromString.js b/lib/manager/components/transform/ReplaceFileFromString.js
index d7f9bbd92..f2f858046 100644
--- a/lib/manager/components/transform/ReplaceFileFromString.js
+++ b/lib/manager/components/transform/ReplaceFileFromString.js
@@ -1,14 +1,20 @@
// @flow
import React, { Component } from 'react'
-import { Button } from 'react-bootstrap'
import type { ReplaceFileFromStringFields, TransformProps } from '../../../types'
+import CSV_VALIDATION_ERRORS from '../../util/enums/transform'
+import { getComponentMessages } from '../../../common/util/config'
+
+import CustomCSVForm from './CustomCSVForm'
/**
* Component that renders fields for ReplaceFileFromStringTransformation.
*/
export default class ReplaceFileFromString extends Component, ReplaceFileFromStringFields> {
+ // Messages are for the child CSV Form component but since messages are shared across transformation types,
+ // the messages are being grouped under that component.
+ messages = getComponentMessages('ReplaceFileFromString')
constructor (props: TransformProps) {
super(props)
this.state = {csvData: props.transformation.csvData}
@@ -35,7 +41,7 @@ export default class ReplaceFileFromString extends Component
-
- Add the CSV data to add to/replace in the incoming GTFS:
-
-
-
-
- Save CSV
-
- {numLines} lines
-
-
+
)
}
}
diff --git a/lib/manager/components/validation/GtfsValidationViewer.js b/lib/manager/components/validation/GtfsValidationViewer.js
index ffbf19c60..6c6503e59 100644
--- a/lib/manager/components/validation/GtfsValidationViewer.js
+++ b/lib/manager/components/validation/GtfsValidationViewer.js
@@ -17,7 +17,7 @@ import {
import Loading from '../../../common/components/Loading'
import OptionButton from '../../../common/components/OptionButton'
import {getComponentMessages} from '../../../common/util/config'
-import toSentenceCase from '../../../common/util/to-sentence-case'
+import toSentenceCase from '../../../common/util/text'
import {
BLOCKING_ERROR_TYPES,
getTableFatalExceptions,
@@ -30,6 +30,7 @@ import type {Props as FeedVersionViewerProps} from '../version/FeedVersionViewer
import type {ValidationResult} from '../../../types'
import ValidationErrorItem from './ValidationErrorItem'
+import MobilityDataValidationResult from './MobilityDataValidationResult'
const DEFAULT_LIMIT = 10
@@ -266,9 +267,21 @@ export default class GtfsValidationViewer extends Component {
}
- {this.messages('title')}
+
+ {this.messages('title')}
+
{validationContent}
+ Mobility Data Validation Issues
+
+ {version.mobilityDataResult && version.mobilityDataResult.notices.map(notice => (
+
+ ))}
+ {(!version.mobilityDataResult || version.mobilityDataResult.notices.length === 0) &&
+ The MobilityData validator has not produced any errors or warnings. This may be because the validator is still running.
+ }
+
+
)
}
diff --git a/lib/manager/components/validation/MobilityDataValidationResult.js b/lib/manager/components/validation/MobilityDataValidationResult.js
new file mode 100644
index 000000000..de81e0d5e
--- /dev/null
+++ b/lib/manager/components/validation/MobilityDataValidationResult.js
@@ -0,0 +1,156 @@
+/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
+/* eslint-disable no-fallthrough */
+import React, { useState } from 'react'
+import Icon from '@conveyal/woonerf/components/icon'
+import { ListGroupItem, Table } from 'react-bootstrap'
+import Markdown from 'markdown-to-jsx'
+
+import toSentenceCase, { spaceOutNumbers } from '../../../common/util/text'
+import {
+ mobilityDataValidationErrorMapping,
+ validationErrorIconLookup
+} from '../../util/version'
+
+import rules from './rules.json'
+
+// from https://stackoverflow.com/a/4149671
+function unCamelCase (s) {
+ return s
+ .split(/(?=[A-Z])/)
+ .join(' ')
+ .toLowerCase()
+}
+
+const NoticeTable = ({ headerOverides = {
+ 'stopSequence1': 'Stop seq-uence 1',
+ 'stopSequence2': 'Stop seq-uence 2'
+}, notices }) => {
+ if (notices.length === 0) return null
+
+ const headers = Object.keys(notices[0])
+
+ return (
+
+
+
+ {headers.map((header) => (
+
+ {headerOverides[header] || toSentenceCase(unCamelCase(header))}
+
+ ))}
+
+
+
+ {notices.map((notice) => (
+
+ {headers.map((header, index) => {
+ const FieldWrapper =
+ (header === 'fieldValue' || header === 'message') ? 'pre' : React.Fragment
+
+ let field = notice[header]
+ if (header.endsWith('Km') || header.endsWith('Kph')) {
+ field = Math.round(field)
+ }
+
+ return (
+
+ {field}
+
+ )
+ })}
+
+ ))}
+
+
+ )
+}
+
+// eslint-disable-next-line complexity
+const renderNoticeDetail = (notice) => {
+ switch (notice.code) {
+ case 'too_many_rows':
+ notice.csvRowNumber = notice.rowNumber
+ case 'fare_transfer_rule_duration_limit_type_without_duration_limit':
+ case 'fare_transfer_rule_duration_limit_without_type':
+ case 'fare_transfer_rule_missing_transfer_count':
+ case 'fare_transfer_rule_with_forbidden_transfer_count':
+ notice.filename = 'fare_transfer_rules.txt'
+ case 'empty_file':
+ case 'emtpy_row':
+ case 'missing_timepoint_column':
+ case 'missing_required_file':
+ case 'missing_recommended_file':
+ case 'unknown_file':
+ return (
+
+ {notice.sampleNotices.map((notice) => (
+
+ {notice.filename}
+ {notice.csvRowNumber && `: row ${notice.csvRowNumber}`}
+
+ ))}
+
+ )
+ default:
+ return (
+
+ )
+ }
+}
+
+const MobilityDataValidationResult = ({notice}) => {
+ const rule = rules.find((rd) => rd.rule === notice.code)
+ if (!rule) return null
+
+ const errorClass = `gtfs-error-${mobilityDataValidationErrorMapping[notice.severity]}`
+ const [expanded, setExpanded] = useState(notice.totalNotices < 2)
+
+ const onRowSelect = () => setExpanded(!expanded)
+
+ return (
+
+
+
+
+
+
+ {toSentenceCase(spaceOutNumbers(notice.code))}
+
+ {' '}
+ — {notice.totalNotices} case
+ {notice.totalNotices > 1 ? 's' : ''} found
+
+
+
+
+
+ {expanded && (
+ <>
+
{rule.description}
+
+
+ More details
+
+
+ >
+ )}
+
+ {expanded && renderNoticeDetail(notice)}
+
+ )
+}
+
+export default MobilityDataValidationResult
diff --git a/lib/manager/components/validation/ServicePerModeChart.js b/lib/manager/components/validation/ServicePerModeChart.js
index 5e7e3a6f9..bc8e86dd9 100644
--- a/lib/manager/components/validation/ServicePerModeChart.js
+++ b/lib/manager/components/validation/ServicePerModeChart.js
@@ -4,7 +4,7 @@ import React, { Component } from 'react'
import moment from 'moment'
import Loading from '../../../common/components/Loading'
-import toSentenceCase from '../../../common/util/to-sentence-case'
+import toSentenceCase from '../../../common/util/text'
import {getChartMax, getChartPeriod} from '../../util'
import type {ValidationResult} from '../../../types'
diff --git a/lib/manager/components/validation/TripsChart.js b/lib/manager/components/validation/TripsChart.js
index 8e29beb2d..4875d7cab 100644
--- a/lib/manager/components/validation/TripsChart.js
+++ b/lib/manager/components/validation/TripsChart.js
@@ -4,7 +4,7 @@ import React, { Component } from 'react'
import moment from 'moment'
import Loading from '../../../common/components/Loading'
-import toSentenceCase from '../../../common/util/to-sentence-case'
+import toSentenceCase from '../../../common/util/text'
import {getChartMax, getChartPeriod} from '../../util'
import type {ValidationResult} from '../../../types'
diff --git a/lib/manager/components/validation/rules.json b/lib/manager/components/validation/rules.json
new file mode 100644
index 000000000..bd270746c
--- /dev/null
+++ b/lib/manager/components/validation/rules.json
@@ -0,0 +1 @@
+[{"rule":"block_trips_with_overlapping_stop_times","description":"Trips with the same block id have overlapping stop times."},{"rule":"csv_parsing_failed","description":"Parsing of a CSV file failed. One common case of the problem is when a cell value contains more than 4096 characters."},{"rule":"decreasing_shape_distance","description":"When sorted by `shape.shape_pt_sequence`, two consecutive shape points must not have decreasing values for `shape_dist_traveled`. "},{"rule":"decreasing_or_equal_stop_time_distance","description":"When sorted by `stop_times.stop_sequence`, two consecutive entries in `stop_times.txt` should have increasing distance, based on the field `shape_dist_traveled`. If the values are equal, this is considered as an error. "},{"rule":"duplicated_column","description":"The input file CSV header has the same column name repeated."},{"rule":"duplicate_key","description":"The values of the given key and rows are duplicates."},{"rule":"empty_column_name","description":"A column name has not been provided. Such columns are skipped by the validator."},{"rule":"empty_file","description":"Empty csv file found in the archive: file does not have any headers, or is a required file and does not have any data. The GTFS specification requires the first line of each file to contain field names and required files must have data."},{"rule":"equal_shape_distance_diff_coordinates","description":"When sorted by `shape.shape_pt_sequence`, the values for `shape_dist_traveled` must increase along a shape. Two consecutive points with equal values for `shape_dist_traveled` and different coordinates indicate an error."},{"rule":"fare_transfer_rule_duration_limit_type_without_duration_limit","description":"A row from GTFS file `fare_transfer_rules.txt` has a defined `duration_limit_type` field but no `duration_limit` specified."},{"rule":"fare_transfer_rule_duration_limit_without_type","description":"A row from GTFS file `fare_transfer_rules.txt` has a defined `duration_limit` field but no `duration_limit_type` specified."},{"rule":"fare_transfer_rule_invalid_transfer_count","description":"A row from GTFS file `fare_transfer_rules.txt` has a defined `transfer_count` with an invalid value."},{"rule":"fare_transfer_rule_missing_transfer_count","description":"A row from GTFS file `fare_transfer_rules.txt` has `from_leg_group_id` equal to `to_leg_group_id`, but has no `transfer_count` specified. Per the spec, `transfer_count` is required if the two leg group ids are equal."},{"rule":"fare_transfer_rule_with_forbidden_transfer_count","description":"A row from GTFS file `fare_transfer_rules.txt` has `from_leg_group_id` not equal to `to_leg_group_id`, but has `transfer_count` specified. Per the spec, `transfer_count` is forbidden if the two leg group ids are not equal."},{"rule":"foreign_key_violation","description":"A foreign key references the primary key of another file. A foreign key violation means that the foreign key referenced from a given row (the child file) cannot be found in the corresponding file (the parent file). The Foreign keys are defined in the specification under \"Type\" for each file."},{"rule":"inconsistent_agency_timezone","description":"Agencies from GTFS `agency.txt` have been found to have different timezones."},{"rule":"invalid_color","description":"Value of field with type `color` is not valid. A color must be encoded as a six-digit hexadecimal number. The leading \"#\" is not included."},{"rule":"invalid_currency","description":"Value of field with type `currency` is not valid. Currency code must follow ISO 4217 "},{"rule":"invalid_currency_amount","description":"A currency amount field has a value that does not match the format (e.g. expected number of decimal places) of its corresponding currency code field. The number of decimal places is specified by ISO 4217 ."},{"rule":"invalid_date","description":"Value of field with type `date` is not valid. Dates must have the YYYYMMDD format."},{"rule":"invalid_email","description":"Value of field with type `email` is not valid. Definitions for valid emails are quite vague. We perform strict validation using the Apache Commons EmailValidator."},{"rule":"invalid_float","description":"Value of field with type `float` is not valid. "},{"rule":"invalid_integer","description":"Value of field with type `integer` is not valid. "},{"rule":"invalid_language_code","description":"Value of field with type `language` is not valid. Language codes must follow IETF BCP 47 ."},{"rule":"invalid_phone_number","description":"Value of field with type `phone number` is not valid. This rule uses the [PhoneNumberUtil](https://www.javadoc.io/doc/com.googlecode.libphonenumber/libphonenumber/8.4.1/com/google/i18n/phonenumbers/PhoneNumberUtil.html) class to validate a phone number based on a country code. If no country code is provided in the parameters used to run the validator, this notice won't be emitted. "},{"rule":"invalid_row_length","description":"A row in the input file has a different number of values than specified by the CSV header."},{"rule":"invalid_time","description":"Value of field with type `time` is not valid. Time must be in the `H:MM:SS`, `HH:MM:SS` or `HHH:MM:SS` format."},{"rule":"invalid_timezone","description":"Value of field with type `timezone` is not valid.Timezones are defined at www.iana.org . Timezone names never contain the space character but may contain an underscore. Refer to Wikipedia for a list of valid values."},{"rule":"invalid_url","description":"Value of field with type `url` is not valid. Definitions for valid URLs are quite vague. We perform strict validation using the Apache Commons UrlValidator."},{"rule":"location_without_parent_station","description":"A location that must have `parent_station` field does not have it. The following location types must have `parent_station`: entrance, generic node, boarding_area."},{"rule":"location_with_unexpected_stop_time","description":"Referenced locations (using `stop_times.stop_id`) must be stops/platforms, i.e. their `stops.location_type` value must be 0 or empty."},{"rule":"missing_calendar_and_calendar_date_files","description":"Both files calendar_dates.txt and calendar.txt are missing from the GTFS archive. At least one of the files must be provided."},{"rule":"missing_level_id","description":"GTFS file `levels.txt` is required for elevator (`pathway_mode=5`). A row from `stops.txt` linked to an elevator pathway has no value for `stops.level_id`."},{"rule":"missing_required_column","description":"A required column is missing in the input file."},{"rule":"missing_required_field","description":"The given field has no value in some input row, even though values are required."},{"rule":"missing_required_file","description":"A required file is missing. If this notice is triggered for every core file, it might be a problem with the input. To create a zip file from the GTFS `.txt` files: select all the `.txt` files, right-click, and compress. Do not compress the folder containing the files. "},{"rule":"missing_stop_name","description":"`stops.stop_name` is required for locations that are stops (`location_type=0`), stations (`location_type=1`) or entrances/exits (`location_type=2`)."},{"rule":"missing_trip_edge","description":"First and last stop of a trip must define both `arrival_time` and `departure_time` fields."},{"rule":"new_line_in_value","description":"A value in CSV file has a new line or carriage return."},{"rule":"number_out_of_range","description":"The values in the given column of the input rows are out of range."},{"rule":"overlapping_frequency","description":"Trip frequencies must not overlap in time"},{"rule":"pathway_to_platform_with_boarding_areas","description":"A pathway has an endpoint that is a platform which has boarding areas. A platform that has boarding"},{"rule":"pathway_to_wrong_location_type","description":"A pathway has an endpoint that is a station. Pathways endpoints must be platforms (stops),"},{"rule":"pathway_unreachable_location","description":"A location belongs to a station that has pathways and is not reachable at least in one direction:"},{"rule":"point_near_origin","description":"A point is too close to origin `(0, 0)`."},{"rule":"point_near_pole","description":"A point is too close to the North or South Pole."},{"rule":"route_both_short_and_long_name_missing","description":"Both `route_short_name` and `route_long_name` are missing for a route."},{"rule":"start_and_end_range_equal","description":"The fields `frequencies.start_date` and `frequencies.end_date` have been found equal in `frequencies.txt`. The GTFS spec is currently unclear how this case should be handled (e.g., is it a trip that circulates once?). It is recommended to use a trip not defined via frequencies.txt for this case."},{"rule":"start_and_end_range_out_of_order","description":"Date or time fields have been found out of order in `calendar.txt`, `feed_info.txt` and `stop_times.txt`."},{"rule":"station_with_parent_station","description":"Field `parent_station` must be empty when `location_type` is 1."},{"rule":"stop_time_timepoint_without_times","description":"Any records with `stop_times.timepoint` set to 1 must define a value for `stop_times.arrival_time` and `stop_times.departure_time` fields."},{"rule":"stop_time_with_arrival_before_previous_departure_time","description":"For a given `trip_id`, the `arrival_time` of (n+1)-th stoptime in sequence must not precede the `departure_time` of n-th stoptime in sequence in `stop_times.txt`."},{"rule":"stop_time_with_only_arrival_or_departure_time","description":"Missing `stop_time.arrival_time` or `stop_time.departure_time`"},{"rule":"stop_without_location","description":"`stop_lat` and/or `stop_lon` are required for locations that are stops (`location_type=0`), stations (`location_type=1`) or entrances/exits (`location_type=2`)."},{"rule":"stop_without_zone_id","description":"If `fare_rules.txt` is provided, and `fare_rules.txt` uses at least one column among `origin_id`, `destination_id`, and `contains_id`, then all stops and platforms (location_type = 0) must have `stops.zone_id` assigned. "},{"rule":"too_many_rows","description":"A CSV file has too many rows. Feeds with too large files cannot be processed in a reasonable time by GTFS consumers."},{"rule":"transfer_with_invalid_stop_location_type","description":"A `from_stop_id` or `to_stop_id` field from GTFS file `transfers.txt` references a stop that has a `location_type` other than 0 or 1 (aka Stop/Platform or Station)."},{"rule":"transfer_with_invalid_trip_and_route","description":"A `from_trip_id` or `to_trip_id` field from GTFS file `transfers.txt` references a route that does not match its `trips.txt` `route_id`."},{"rule":"transfer_with_invalid_trip_and_stop","description":"A `from_trip_id` or `to_trip_id` field from GTFS file `transfers.txt` references a stop that is not included in the referenced trip's stop-times."},{"rule":"transfer_with_suspicious_mid_trip_in_seat","description":"A `from_trip_id` or `to_trip_id` field from GTFS file `transfers.txt` with an in-seat transfer type references a stop that is not in the expected position in the trip's stop-times. For in-seat transfers, we expect the stop to be the last stop-time in the trip sequence for `from_stop_id` and the first stop-time for `to_stop_id`. If you are intentionally using this feature to model mid-trip transfers, you can ignore this warning, but be aware that this functionality is still considered to be partially experimental in some interpretations of the spec."},{"rule":"translation_foreign_key_violation","description":"An entity with the given `record_id` and `record_sub_id` cannot be found in the referenced table."},{"rule":"translation_unexpected_value","description":"A field in a translations row has value but must be empty."},{"rule":"wrong_parent_location_type","description":"Value of field `location_type` of parent found in field `parent_station` is invalid."},{"rule":"attribution_without_role","description":"At least one of the fields `is_producer`, `is_operator`, or `is_authority` should be set to 1."},{"rule":"duplicate_route_name","description":"All routes of the same `route_type` with the same `agency_id` should have unique combinations of `route_short_name` and `route_long_name`."},{"rule":"empty_row","description":"A row in the input file has only spaces."},{"rule":"equal_shape_distance_same_coordinates","description":"When sorted by `shape.shape_pt_sequence`, the values for `shape_dist_traveled` must increase along a shape. Two consecutive points with equal values for `shape_dist_traveled` and the same coordinates indicate a duplicative shape point."},{"rule":"fast_travel_between_consecutive_stops","description":"A transit vehicle moves too fast between two consecutive stops. The speed threshold depends on route type."},{"rule":"fast_travel_between_far_stops","description":"A transit vehicle moves too fast between far consecutive stops (more than in 10 km apart). "},{"rule":"feed_expiration_date7_days","description":"The dataset expiration date defined in `feed_info.txt` is in seven days or less. At any time, the published GTFS dataset should be valid for at least the next 7 days."},{"rule":"feed_expiration_date30_days","description":"At any time, the GTFS dataset should cover at least the next 30 days of service, and ideally for as long as the operator is confident that the schedule will continue to be operated."},{"rule":"feed_info_lang_and_agency_lang_mismatch","description":" The default language may be multilingual for datasets with the original text in multiple languages. In such cases, the feed_lang field should contain the language code mul defined by the norm ISO 639-2."},{"rule":"inconsistent_agency_lang","description":"Agencies from GTFS `agency.txt` have been found to have different languages."},{"rule":"leading_or_trailing_whitespaces","description":"The value in CSV file has leading or trailing whitespaces."},{"rule":"missing_feed_info_date","description":"Even though `feed_info.start_date` and `feed_info.end_date` are optional, if one field is provided the second one should also be provided."},{"rule":"missing_recommended_file","description":"A recommended file is missing."},{"rule":"missing_recommended_field","description":"The given field has no value in some input row, even though values are recommended."},{"rule":"missing_timepoint_column","description":"The `timepoint` column should be provided."},{"rule":"missing_timepoint_value","description":"Even though the column `timepoint` is optional in `stop_times.txt` according to the specification, `stop_times.timepoint` should not be empty when provided. "},{"rule":"mixed_case_recommended_field","description":"This field contains customer-facing text and should use Mixed Case (upper and lower case letters) to ensure good readability when displayed to riders. Avoid the use of abbreviations throughout the feed (e.g. St. for Street) unless a location is called by its abbreviated name (e.g. âJFK Airportâ). Abbreviations may be problematic for accessibility by screen reader software and voice user interfaces."},{"rule":"more_than_one_entity","description":"The file is expected to have a single entity but has more (e.g., \"feed_info.txt\")."},{"rule":"non_ascii_or_non_printable_char","description":"A value of a field with type `id` contains non ASCII or non printable characters. This is not recommended."},{"rule":"pathway_dangling_generic_node","description":"A generic node has only one incident location in a pathway graph. Such generic node is useless"},{"rule":"pathway_loop","description":"A pathway should not have same values for `from_stop_id` and `to_stop_id`."},{"rule":"platform_without_parent_station","description":"A platform has no `parent_station` field set."},{"rule":"route_color_contrast","description":"A route's color and `route_text_color` should be contrasting."},{"rule":"route_long_name_contains_short_name","description":"In routes.txt, `route_long_name` should not contain the value for `route_short_name`, because when both are provided, they are often combined by transit applications. Note that only one of the two fields is required. If there is no short name used for a route, use `route_long_name` only."},{"rule":"route_short_name_too_long","description":"Short name of a route is too long (more than 12 characters)."},{"rule":"same_name_and_description_for_route","description":"The GTFS spec defines `routes.txt` [route_desc](https://gtfs.org/reference/static/#routestxt) as:"},{"rule":"same_name_and_description_for_stop","description":"The GTFS spec defines `stops.txt` [stop_description](https://gtfs.org/reference/static/#stopstxt) as:\n\n> Description of the location that provides useful, quality information. Do not simply duplicate the name of the location."},{"rule":"same_route_and_agency_url","description":"A route should not have the same `routes.route_url` as a record from `agency.txt`."},{"rule":"same_stop_and_agency_url","description":"A stop should not have the same `stops.stop_url` as a record from `agency.txt`."},{"rule":"same_stop_and_route_url","description":"A stop should not have the same `stop.stop_url` as a record from `routes.txt`."},{"rule":"stop_has_too_many_matches_for_shape","description":"A stop entry that has many potential matches to the trip's path of travel, as defined by the shape entry in `shapes.txt`."},{"rule":"stops_match_shape_out_of_order","description":"Two stop entries in `stop_times.txt` are different than their arrival-departure order as defined by the shape in the `shapes.txt` file."},{"rule":"stop_too_far_from_shape","description":"Per GTFS Best Practices, route alignments (in `shapes.txt`) should be within 100 meters of stop locations which a trip serves."},{"rule":"stop_too_far_from_shape_using_user_distance","description":"A stop time entry that is a large distance away from the location of the shape in `shapes.txt` as defined by `shape_dist_traveled` values."},{"rule":"stop_without_stop_time","description":"A stop in `stops.txt` is not referenced by any `stop_times.stop_id`, so it is not used by any trip."},{"rule":"translation_unknown_table_name","description":"A translation references an unknown or missing GTFS table."},{"rule":"unexpected_enum_value","description":"An enum has an unexpected value."},{"rule":"unusable_trip","description":"A trip must visit more than one stop in stop_times.txt to be usable by passengers for boarding and alighting."},{"rule":"unused_shape","description":"All records defined by GTFS `shapes.txt` should be used in `trips.txt`."},{"rule":"unused_trip","description":"Trips should be referred to at least once in `stop_times.txt`."},{"rule":"unknown_column","description":"A column is unknown."},{"rule":"unknown_file","description":"A file is unknown."},{"rule":"i_o_error","description":"Error in IO operation."},{"rule":"runtime_exception_in_loader_error","description":"A [RuntimeException](https://docs.oracle.com/javase/8/docs/api/java/lang/RuntimeException.html) occurred while loading a table. This normally indicates a bug in validator."},{"rule":"runtime_exception_in_validator_error","description":"A [RuntimeException](https://docs.oracle.com/javase/8/docs/api/java/lang/RuntimeException.html) occurred during validation. This normally indicates a bug in validator code, e.g., in a custom validator class."},{"rule":"thread_execution_error","description":"An [ExecutionException](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutionException.html) occurred during multithreaded validation."},{"rule":"u_r_i_syntax_error","description":"A string could not be parsed as a URI reference."}]
\ No newline at end of file
diff --git a/lib/manager/components/version/FeedVersionViewer.js b/lib/manager/components/version/FeedVersionViewer.js
index 0bf9555ff..e079806b8 100644
--- a/lib/manager/components/version/FeedVersionViewer.js
+++ b/lib/manager/components/version/FeedVersionViewer.js
@@ -23,7 +23,7 @@ import * as plusActions from '../../../gtfsplus/actions/gtfsplus'
import ActiveGtfsPlusVersionSummary from '../../../gtfsplus/containers/ActiveGtfsPlusVersionSummary'
import NotesViewer from '../NotesViewer'
import TransformationsViewer from '../TransformationsViewer'
-import { errorPriorityLevels, getTableFatalExceptions } from '../../util/version'
+import { errorPriorityLevels, getTableFatalExceptions, mobilityDataValidationErrorMapping } from '../../util/version'
import GtfsValidationViewer from '../validation/GtfsValidationViewer'
import type { Feed, FeedVersion, GtfsPlusValidation, Note, Project } from '../../../types'
import type { GtfsState, ManagerUserState } from '../../../types/reducers'
@@ -32,6 +32,7 @@ import VersionButtonToolbar from './VersionButtonToolbar'
import VersionDateLabel from './VersionDateLabel'
import FeedVersionReport from './FeedVersionReport'
import DeltaStat from './DeltaStat'
+import numeral from 'numeral'
export type Props = {
comparedVersion?: FeedVersion,
@@ -150,6 +151,7 @@ type SelectorProps = {
}
class VersionSectionSelector extends Component {
+ // eslint-disable-next-line complexity
_renderIssuesLabel = () => {
const { comparedVersion, version } = this.props
const { feedLoadResult, validationResult, validationSummary } = version
@@ -171,9 +173,29 @@ class VersionSectionSelector extends Component {
})
}
- const text = hasCriticalError
+ // Do the same for the mobility data results
+ let highestMBPriority = 'UNKNOWN'
+ if (version && version.mobilityDataResult) {
+ version.mobilityDataResult.notices.forEach(notice => {
+ const level = errorPriorityLevels[mobilityDataValidationErrorMapping[notice.severity]]
+ if (level < errorPriorityLevels[highestMBPriority]) {
+ highestMBPriority = mobilityDataValidationErrorMapping[notice.severity]
+ }
+ })
+ }
+
+ const dtValidationCount = hasCriticalError
? 'critical error'
- : validationSummary.errorCount
+ : numeral(validationSummary.errorCount).format('0a')
+
+ const mbValidationCount =
+ version.mobilityDataResult &&
+ version.mobilityDataResult.notices &&
+ numeral(
+ version.mobilityDataResult.notices.reduce((prev, cur) => {
+ return prev + cur.totalNotices
+ }, 0)
+ ).format('0a')
let diffLabel
if (comparedVersion) {
@@ -190,8 +212,11 @@ class VersionSectionSelector extends Component {
return (
- {text}
+ DT {dtValidationCount}
+ {mbValidationCount &&
+ MD {mbValidationCount}
+ }
{diffLabel}
)
diff --git a/lib/manager/components/version/VersionRetrievalBadge.js b/lib/manager/components/version/VersionRetrievalBadge.js
index cdba8b31c..094b95c7b 100644
--- a/lib/manager/components/version/VersionRetrievalBadge.js
+++ b/lib/manager/components/version/VersionRetrievalBadge.js
@@ -3,7 +3,7 @@
import Icon from '@conveyal/woonerf/components/icon'
import React from 'react'
-import toSentenceCase from '../../../common/util/to-sentence-case'
+import toSentenceCase from '../../../common/util/text'
import type { FeedVersionSummary, RetrievalMethod } from '../../../types'
type Props = {
diff --git a/lib/manager/containers/FeedSourceTable.js b/lib/manager/containers/FeedSourceTable.js
index b796346cd..872c515f7 100644
--- a/lib/manager/containers/FeedSourceTable.js
+++ b/lib/manager/containers/FeedSourceTable.js
@@ -4,7 +4,6 @@ import {connect} from 'react-redux'
import FeedSourceTable from '../components/FeedSourceTable'
import {getFilteredFeeds} from '../util'
-
import type {Project} from '../../types'
import type {AppState} from '../../types/reducers'
diff --git a/lib/manager/containers/__tests__/__snapshots__/ActiveProjectViewer.js.snap b/lib/manager/containers/__tests__/__snapshots__/ActiveProjectViewer.js.snap
index c6ad6dd04..9c054a6a9 100644
--- a/lib/manager/containers/__tests__/__snapshots__/ActiveProjectViewer.js.snap
+++ b/lib/manager/containers/__tests__/__snapshots__/ActiveProjectViewer.js.snap
@@ -75,8 +75,10 @@ exports[`lib > manager > ActiveProjectViewer should render with newly created pr
"peliasWebhookUrl": null,
"pinnedDeploymentId": null,
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
@@ -2024,8 +2026,10 @@ exports[`lib > manager > ActiveProjectViewer should render with newly created pr
"peliasWebhookUrl": null,
"pinnedDeploymentId": null,
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
@@ -2071,8 +2075,10 @@ exports[`lib > manager > ActiveProjectViewer should render with newly created pr
"peliasWebhookUrl": null,
"pinnedDeploymentId": null,
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
@@ -2208,8 +2214,10 @@ exports[`lib > manager > ActiveProjectViewer should render with newly created pr
"peliasWebhookUrl": null,
"pinnedDeploymentId": null,
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
@@ -2277,8 +2285,10 @@ exports[`lib > manager > ActiveProjectViewer should render with newly created pr
"peliasWebhookUrl": null,
"pinnedDeploymentId": null,
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
@@ -4165,7 +4175,7 @@ exports[`lib > manager > ActiveProjectViewer should render with newly created pr
Note:
- Public feeds page can be viewed
+ Public feeds page can be viewed
manager > ActiveProjectViewer should render with newly created pr
"peliasWebhookUrl": null,
"pinnedDeploymentId": null,
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
@@ -4329,8 +4341,10 @@ exports[`lib > manager > ActiveProjectViewer should render with newly created pr
"peliasWebhookUrl": null,
"pinnedDeploymentId": null,
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
@@ -4412,8 +4426,10 @@ exports[`lib > manager > ActiveProjectViewer should render with newly created pr
"peliasWebhookUrl": null,
"pinnedDeploymentId": null,
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
diff --git a/lib/manager/containers/__tests__/__snapshots__/DeploymentsPanel.js.snap b/lib/manager/containers/__tests__/__snapshots__/DeploymentsPanel.js.snap
index 5f4ab1a06..03678351c 100644
--- a/lib/manager/containers/__tests__/__snapshots__/DeploymentsPanel.js.snap
+++ b/lib/manager/containers/__tests__/__snapshots__/DeploymentsPanel.js.snap
@@ -108,6 +108,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"feedVersionId": "mock-feed-version-id",
"loadFailureReason": null,
"loadStatus": "SUCCESS",
+ "mobilityDataResult": Object {},
"routeCount": 10,
"startDate": "20180801",
"stopCount": 237,
@@ -129,6 +130,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -164,6 +166,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -235,8 +238,10 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"peliasWebhookUrl": null,
"pinnedDeploymentId": "mock-deployment-id-0",
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
@@ -344,6 +349,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"feedVersionId": "mock-feed-version-id",
"loadFailureReason": null,
"loadStatus": "SUCCESS",
+ "mobilityDataResult": Object {},
"routeCount": 10,
"startDate": "20180801",
"stopCount": 237,
@@ -365,6 +371,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -400,6 +407,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -471,8 +479,10 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"peliasWebhookUrl": null,
"pinnedDeploymentId": "mock-deployment-id-0",
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
@@ -637,6 +647,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"feedVersionId": "mock-feed-version-id",
"loadFailureReason": null,
"loadStatus": "SUCCESS",
+ "mobilityDataResult": Object {},
"routeCount": 10,
"startDate": "20180801",
"stopCount": 237,
@@ -658,6 +669,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -693,6 +705,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -801,6 +814,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"feedVersionId": "mock-feed-version-id",
"loadFailureReason": null,
"loadStatus": "SUCCESS",
+ "mobilityDataResult": Object {},
"routeCount": 10,
"startDate": "20180801",
"stopCount": 237,
@@ -822,6 +836,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -857,6 +872,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -928,8 +944,10 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"peliasWebhookUrl": null,
"pinnedDeploymentId": "mock-deployment-id-0",
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
@@ -1153,6 +1171,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"feedVersionId": "mock-feed-version-id",
"loadFailureReason": null,
"loadStatus": "SUCCESS",
+ "mobilityDataResult": Object {},
"routeCount": 10,
"startDate": "20180801",
"stopCount": 237,
@@ -1174,6 +1193,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -1282,6 +1302,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"feedVersionId": "mock-feed-version-id",
"loadFailureReason": null,
"loadStatus": "SUCCESS",
+ "mobilityDataResult": Object {},
"routeCount": 10,
"startDate": "20180801",
"stopCount": 237,
@@ -1303,6 +1324,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -1338,6 +1360,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -1409,8 +1432,10 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"peliasWebhookUrl": null,
"pinnedDeploymentId": "mock-deployment-id-0",
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
@@ -1582,6 +1607,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -1690,6 +1716,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"feedVersionId": "mock-feed-version-id",
"loadFailureReason": null,
"loadStatus": "SUCCESS",
+ "mobilityDataResult": Object {},
"routeCount": 10,
"startDate": "20180801",
"stopCount": 237,
@@ -1711,6 +1738,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -1746,6 +1774,7 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -1817,8 +1846,10 @@ exports[`lib > manager > DeploymentsPanel should render with the list of deploym
"peliasWebhookUrl": null,
"pinnedDeploymentId": "mock-deployment-id-0",
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
diff --git a/lib/manager/containers/__tests__/__snapshots__/FeedSourceTable.js.snap b/lib/manager/containers/__tests__/__snapshots__/FeedSourceTable.js.snap
index b73c61a2c..e2cd12990 100644
--- a/lib/manager/containers/__tests__/__snapshots__/FeedSourceTable.js.snap
+++ b/lib/manager/containers/__tests__/__snapshots__/FeedSourceTable.js.snap
@@ -107,6 +107,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"feedVersionId": "mock-feed-version-id",
"loadFailureReason": null,
"loadStatus": "SUCCESS",
+ "mobilityDataResult": Object {},
"routeCount": 10,
"startDate": "20180801",
"stopCount": 237,
@@ -128,6 +129,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -199,8 +201,10 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"peliasWebhookUrl": null,
"pinnedDeploymentId": null,
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
@@ -406,6 +410,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"feedVersionId": "mock-feed-version-id",
"loadFailureReason": null,
"loadStatus": "SUCCESS",
+ "mobilityDataResult": Object {},
"routeCount": 10,
"startDate": "20180801",
"stopCount": 237,
@@ -427,6 +432,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -498,8 +504,10 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"peliasWebhookUrl": null,
"pinnedDeploymentId": null,
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
@@ -699,6 +707,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"feedVersionId": "mock-feed-version-id",
"loadFailureReason": null,
"loadStatus": "SUCCESS",
+ "mobilityDataResult": Object {},
"routeCount": 10,
"startDate": "20180801",
"stopCount": 237,
@@ -720,6 +729,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -791,8 +801,10 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"peliasWebhookUrl": null,
"pinnedDeploymentId": null,
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
@@ -925,6 +937,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"feedVersionId": "mock-feed-version-id",
"loadFailureReason": null,
"loadStatus": "SUCCESS",
+ "mobilityDataResult": Object {},
"routeCount": 10,
"startDate": "20180801",
"stopCount": 237,
@@ -946,6 +959,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -1017,8 +1031,10 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"peliasWebhookUrl": null,
"pinnedDeploymentId": null,
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
@@ -3050,6 +3066,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"feedVersionId": "mock-feed-version-id",
"loadFailureReason": null,
"loadStatus": "SUCCESS",
+ "mobilityDataResult": Object {},
"routeCount": 10,
"startDate": "20180801",
"stopCount": 237,
@@ -3071,6 +3088,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -3142,8 +3160,10 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"peliasWebhookUrl": null,
"pinnedDeploymentId": null,
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
@@ -3172,6 +3192,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"feedVersionId": "mock-feed-version-id",
"loadFailureReason": null,
"loadStatus": "SUCCESS",
+ "mobilityDataResult": Object {},
"routeCount": 10,
"startDate": "20180801",
"stopCount": 237,
@@ -3321,6 +3342,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"feedVersionId": "mock-feed-version-id",
"loadFailureReason": null,
"loadStatus": "SUCCESS",
+ "mobilityDataResult": Object {},
"routeCount": 10,
"startDate": "20180801",
"stopCount": 237,
@@ -3342,6 +3364,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -3413,8 +3436,10 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"peliasWebhookUrl": null,
"pinnedDeploymentId": null,
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
@@ -3646,6 +3671,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"feedVersionId": "mock-feed-version-id",
"loadFailureReason": null,
"loadStatus": "SUCCESS",
+ "mobilityDataResult": Object {},
"routeCount": 10,
"startDate": "20180801",
"stopCount": 237,
@@ -3667,6 +3693,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -3738,8 +3765,10 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"peliasWebhookUrl": null,
"pinnedDeploymentId": null,
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
@@ -4045,6 +4074,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"feedVersionId": "mock-feed-version-id",
"loadFailureReason": null,
"loadStatus": "SUCCESS",
+ "mobilityDataResult": Object {},
"routeCount": 10,
"startDate": "20180801",
"stopCount": 237,
@@ -4066,6 +4096,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -4137,8 +4168,10 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"peliasWebhookUrl": null,
"pinnedDeploymentId": null,
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
@@ -4312,6 +4345,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"feedVersionId": "mock-feed-version-id",
"loadFailureReason": null,
"loadStatus": "SUCCESS",
+ "mobilityDataResult": Object {},
"routeCount": 10,
"startDate": "20180801",
"stopCount": 237,
@@ -4461,6 +4495,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"feedVersionId": "mock-feed-version-id",
"loadFailureReason": null,
"loadStatus": "SUCCESS",
+ "mobilityDataResult": Object {},
"routeCount": 10,
"startDate": "20180801",
"stopCount": 237,
@@ -4482,6 +4517,7 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"otpVersion": null,
"peliasCsvFiles": Array [],
"peliasResetDb": null,
+ "peliasSynonymsBase64": null,
"peliasUpdate": null,
"pinnedfeedVersionIds": Array [],
"projectBounds": Object {
@@ -4553,8 +4589,10 @@ exports[`lib > manager > FeedSourceTable should render with a project with feeds
"peliasWebhookUrl": null,
"pinnedDeploymentId": null,
"routerConfig": Object {
- "carDropoffTime": null,
- "numItineraries": null,
+ "driveDistanceReluctance": null,
+ "itineraryFilters": Object {
+ "nonTransitGeneralizedCostLimit": null,
+ },
"requestLogFile": null,
"stairsReluctance": null,
"updaters": null,
diff --git a/lib/manager/reducers/status.js b/lib/manager/reducers/status.js
index f56c920cc..e693a609c 100644
--- a/lib/manager/reducers/status.js
+++ b/lib/manager/reducers/status.js
@@ -31,7 +31,6 @@ const config = (state: StatusState = defaultState, action: Action): StatusState
case 'REQUESTING_PROJECT':
case 'REQUESTING_FEEDSOURCES':
case 'REQUESTING_FEEDSOURCE':
- return {...state, saving: false, message: messages(action.type)}
case 'SAVING_PROJECT':
case 'SAVING_FEEDSOURCE':
case 'DELETING_FEEDSOURCE':
@@ -73,7 +72,7 @@ const config = (state: StatusState = defaultState, action: Action): StatusState
case 'UPDATING_SERVER':
case 'FETCHING_ALL_JOBS':
case 'REQUEST_RTD_ALERTS':
- return {...state, saving: true, message: messages(action.type)}
+ return {...state, message: messages(action.type)}
// STATUS MODAL UPDATES (TYPICALLY SET ON JOB COMPLETION)
case 'SET_ERROR_MESSAGE':
diff --git a/lib/manager/util/deployment.js b/lib/manager/util/deployment.js
index aabb9a9d4..ed15f8cbf 100644
--- a/lib/manager/util/deployment.js
+++ b/lib/manager/util/deployment.js
@@ -34,24 +34,22 @@ export const FIELDS: Array = [
placeholder: 'pdx',
type: 'text',
width: 12
- }, {
- name: 'routerConfig.numItineraries',
- type: 'number',
- step: '1', // integer
- placeholder: '6'
- }, {
- name: 'routerConfig.walkSpeed',
- type: 'number',
- placeholder: '3.0'
- }, {
- name: 'routerConfig.stairsReluctance',
- type: 'number',
- placeholder: '2.0'
- }, {
- name: 'routerConfig.carDropoffTime',
- type: 'number',
- placeholder: '240 (sec)'
- }, {
+ },
+ // {
+ // name: 'routerConfig.driveDistanceReluctance',
+ // type: 'number',
+ // placeholder: '1.5'
+ // }, {
+ // name: 'routerConfig.stairsReluctance',
+ // type: 'number',
+ // placeholder: '2.0'
+ // },
+ // {
+ // name: 'routerConfig.itineraryFilters.nonTransitGeneralizedCostLimit',
+ // type: 'text',
+ // placeholder: '0 + 1.0x'
+ // },
+ {
name: 'routerConfig.requestLogFile',
type: 'text',
placeholder: '/var/otp/request.log',
@@ -66,11 +64,13 @@ export const UPDATER_FIELDS: Array = [
componentClass: 'select',
children: [
{disabled: true, value: '', children: '[select value]'},
+ {value: 'real-time-alerts', children: 'real-time-alerts'},
+ {value: 'stop-time-updater', children: 'stop-time-updater'},
+ {value: 'vehicle-positions', children: 'vehicle-positions'},
+ {value: 'vehicle-rental', children: 'vehicle-rental'},
{value: 'bike-rental', children: 'bike-rental'},
{value: 'bike-park', children: 'bike-park'},
- {value: 'stop-time-updater', children: 'stop-time-updater'},
{value: 'websocket-gtfs-rt-updater', children: 'websocket-gtfs-rt-updater'},
- {value: 'real-time-alerts', children: 'real-time-alerts'},
{value: 'example-updater', children: 'example-updater'},
{value: 'example-polling-updater', children: 'example-polling-updater'},
{value: 'winkki-polling-updater', children: 'winkki-polling-updater'},
@@ -88,7 +88,8 @@ export const UPDATER_FIELDS: Array = [
type: 'select',
componentClass: 'select',
children: [
- {value: '', children: '[optional]'},
+ {value: 'gtfs-http', children: 'gtfs-http'},
+ {value: 'gbfs', children: 'gbfs'},
{value: 'jcdecaux', children: 'jcdecaux'},
{value: 'b-cycle', children: 'b-cycle'},
{value: 'bixi', children: 'bixi'},
@@ -102,17 +103,15 @@ export const UPDATER_FIELDS: Array = [
{value: 'sf-bay-area', children: 'sf-bay-area'},
{value: 'share-bike', children: 'share-bike'},
{value: 'kml-placemarks', children: 'kml-placemarks'},
- {value: 'gbfs', children: 'gbfs'},
- {value: 'gtfs-file', children: 'gtfs-file'},
- {value: 'gtfs-http', children: 'gtfs-http'}
+ {value: 'gtfs-file', children: 'gtfs-file'}
]
}, {
name: 'routerConfig.updaters.$index.url',
placeholder: 'https://agency.com/realtime/tripupdates',
type: 'text'
}, {
- name: 'routerConfig.updaters.$index.defaultAgencyId',
- placeholder: 'TriMet',
+ name: 'routerConfig.updaters.$index.feedId',
+ placeholder: '[optional]',
type: 'text'
}
]
diff --git a/lib/manager/util/enums/transform.js b/lib/manager/util/enums/transform.js
new file mode 100644
index 000000000..90e66abe0
--- /dev/null
+++ b/lib/manager/util/enums/transform.js
@@ -0,0 +1,10 @@
+// CSV validation errors for transformations.
+// Values are keys to YAML error messages.
+const CSV_VALIDATION_ERRORS = {
+ CSV_MUST_HAVE_NAME: 'csvMissingName',
+ CSV_NAME_CONTAINS_TXT: 'csvNameContainsTxt',
+ TABLE_MUST_BE_DEFINED: 'undefinedTable',
+ UNDEFINED_CSV_DATA: 'undefinedCSVData'
+}
+
+export default CSV_VALIDATION_ERRORS
diff --git a/lib/manager/util/index.js b/lib/manager/util/index.js
index 28c1309a5..f43b1ca22 100644
--- a/lib/manager/util/index.js
+++ b/lib/manager/util/index.js
@@ -245,12 +245,12 @@ export function isEditingDisabled (
feedSource: Feed,
project: ?Project
): boolean {
- // If any of the args or null,
return (
+ // If any of the args are null...
!user ||
!feedSource ||
!project ||
- // or the user does not have permission, editing is disabled.
+ // ...or the user does not have permission, editing is disabled.
!user.permissions ||
!user.permissions.hasFeedPermission(
project.organizationId,
diff --git a/lib/manager/util/version.js b/lib/manager/util/version.js
index ffa856eb6..0abfa7f95 100644
--- a/lib/manager/util/version.js
+++ b/lib/manager/util/version.js
@@ -145,6 +145,13 @@ export const validationErrorIconLookup = {
UNKNOWN: 'question-circle'
}
+export const mobilityDataValidationErrorMapping = {
+ ERROR: 'HIGH',
+ WARNING: 'MEDIUM',
+ INFO: 'LOW',
+ SYSTEM_ERROR: 'UNKNOWN'
+}
+
/**
* Get the appropriate comparison feed version or summarized feed version
* summary given a project, the feed source within the project and a comparison
diff --git a/lib/public/components/PublicHeader.js b/lib/public/components/PublicHeader.js
index d4474e13b..92605155c 100644
--- a/lib/public/components/PublicHeader.js
+++ b/lib/public/components/PublicHeader.js
@@ -2,12 +2,13 @@
import type { Auth0ContextInterface } from '@auth0/auth0-react'
import Icon from '@conveyal/woonerf/components/icon'
-import React, {Component} from 'react'
-import {Grid, Row, Col, Button, Glyphicon, ButtonToolbar} from 'react-bootstrap'
-import {LinkContainer} from 'react-router-bootstrap'
+import React, { Component } from 'react'
+import { Button, ButtonToolbar, Col, Glyphicon, Grid, Row } from 'react-bootstrap'
+import { LinkContainer } from 'react-router-bootstrap'
import * as userActions from '../../manager/actions/user'
-import type {Props as ContainerProps} from '../containers/ActivePublicHeader'
+import type { Props as ContainerProps } from '../containers/ActivePublicHeader'
+import { AUTH0_DISABLED } from '../../common/constants'
type Props = ContainerProps & {
auth0: Auth0ContextInterface,
@@ -17,21 +18,7 @@ type Props = ContainerProps & {
username: ?string
}
-type State = {
- showLogin: boolean
-}
-
-export default class PublicHeader extends Component {
- state = { showLogin: false }
-
- _onLoginClick = () => {
- this.setState({ showLogin: true })
- }
-
- _onLoginHide = () => {
- this.setState({ showLogin: false })
- }
-
+export default class PublicHeader extends Component {
_onLogoutClick = () => {
this.props.logout(this.props.auth0)
}
@@ -73,14 +60,12 @@ export default class PublicHeader extends Component {
)
}
- {/* "Log out" Button */}
- {username
- ? (
-
- Log out
-
- )
- : null}
+ {/* "Log out" Button (unless auth is disabled) */}
+ {username && !AUTH0_DISABLED && (
+
+ Log out
+
+ )}
diff --git a/lib/public/components/UserAccount.js b/lib/public/components/UserAccount.js
index b43127ddd..8cd472350 100644
--- a/lib/public/components/UserAccount.js
+++ b/lib/public/components/UserAccount.js
@@ -7,6 +7,7 @@ import {browserHistory, Link} from 'react-router'
import {LinkContainer} from 'react-router-bootstrap'
import ManagerPage from '../../common/components/ManagerPage'
+import { AUTH0_DISABLED } from '../../common/constants'
import {getComponentMessages, getConfigProperty, isModuleEnabled} from '../../common/util/config'
import * as deploymentsActions from '../../manager/actions/deployments'
import * as feedsActions from '../../manager/actions/feeds'
@@ -68,6 +69,7 @@ export default class UserAccount extends Component {
_onResetPassword = () => this.props.sendPasswordReset(this.props.user)
+ // eslint-disable-next-line complexity
render () {
const {accountTypes = {}, activeComponent, projectId, projects, user} = this.props
const {profile} = user
@@ -87,6 +89,10 @@ export default class UserAccount extends Component {
: defaultAccountType && defaultAccountType.name)
const accountTermsUrl = accountTypeObject && accountTypeObject.terms_url
+ const userProjects = user && projects.filter(p => {
+ return user && user.permissions && user.permissions.hasProject(p.id, p.organizationId)
+ })
+
const subscriptions: ?Array = userSettings && userSettings.subscriptions
const ACCOUNT_SECTIONS = [
{
@@ -95,48 +101,52 @@ export default class UserAccount extends Component {
Profile Information
-
-
- Email address
- {profile.email}
-
- {/* Display account type if two or more are configured and if the relevant text is available.
- (or the account type from the user profile does not match the one(s) configured). */}
- {((Object.keys(accountTypes).length > 1 && displayedAccountType) || accountTypeIsUnknown) && (
+ {!AUTH0_DISABLED && (
+ // If auth is disabled, simply show nothing under the Profile pane,
+ // but keep the Profile pane visible because it is the default one for Settings.
+
- Account type
-
- {displayedAccountType}
- {/* Show account terms URL only if one has been configured. */}
- {accountTermsUrl && (
-
- {' - '}
- Terms and conditions
-
- )}
-
+ Email address
+ {profile.email}
- )}
-
- Avatar
-
-
- Change on gravatar.com
-
-
-
- Password
-
- Change password
-
-
-
+ {/* Display account type if two or more are configured and if the relevant text is available
+ (or the account type from the user profile does not match the one(s) configured). */}
+ {((Object.keys(accountTypes).length > 1 && displayedAccountType) || accountTypeIsUnknown) && (
+
+ Account type
+
+ {displayedAccountType}
+ {/* Show account terms URL only if one has been configured. */}
+ {accountTermsUrl && (
+
+ {' - '}
+ Terms and conditions
+
+ )}
+
+
+ )}
+
+ Avatar
+
+
+ Change on gravatar.com
+
+
+
+ Password
+
+ Change password
+
+
+
+ )}
)
@@ -235,7 +245,7 @@ export default class UserAccount extends Component {
{this.messages('organizationSettings')}
- {projects && projects.map(project => {
+ {userProjects && userProjects.map(project => {
return (
diff --git a/lib/types/index.js b/lib/types/index.js
index 7ef9b4ca1..209a91d6c 100644
--- a/lib/types/index.js
+++ b/lib/types/index.js
@@ -149,6 +149,12 @@ export type ControlPoint = {
stopId?: string
}
+// StopControlPoints are created when we filter the full list of ControlPoints for stops
+// and maintain a cpIndex reference to the original ControlPoints array.
+export type StopControlPoint = ControlPoint & {
+ cpIndex: number
+}
+
export type DatatoolsSettings = {
account_terms_accepted?: boolean,
account_type?: string,
@@ -258,6 +264,7 @@ export type Deployment = {
otpVersion: ?string,
peliasCsvFiles: ?Array,
peliasResetDb: ?boolean,
+ peliasSynonymsBase64: ?string,
peliasUpdate: ?boolean,
pinnedfeedVersionIds: Array,
projectBounds: Bounds,
@@ -335,6 +342,8 @@ export type ReplaceFileFromStringFields = {
csvData?: ?string
}
+export type AddCustomFileProps = ReplaceFileFromStringFields & {table: ?string}
+
export type FeedTransformationBase = {
'@type': $Values,
active: boolean,
@@ -501,6 +510,7 @@ export type SummarizedFeedVersion = {
export type TableTransformResult = {
addedCount: number,
+ customColumnsAdded: number,
deletedCount: number,
tableName: string,
transformType: 'TABLE_ADDED' | 'TABLE_REPLACED' | 'TABLE_DELETED' | 'TABLE_MODIFIED',
@@ -539,6 +549,7 @@ export type FeedVersion = FeedVersionSummary & {
fileTimestamp: number,
isCreating?: boolean,
isochrones?: any,
+ mobilityDataResult?: any,
nextVersionId: ?string,
noteCount: number,
notes: Array,
@@ -788,8 +799,8 @@ export type BuildConfig = {
}
export type RouterConfig = {
- carDropoffTime?: ?any, // TODO: define more specific type
- numItineraries?: ?any, // TODO: define more specific type
+ driveDistanceReluctance?: ?any, // TODO: define more specific type
+ itineraryFilters?: ?any, // TODO: define more specific type
requestLogFile?: ?any, // TODO: define more specific type
stairsReluctance?: ?any, // TODO: define more specific type
updaters?: ?any, // TODO: define more specific type
diff --git a/lib/types/reducers.js b/lib/types/reducers.js
index de232a4cd..83d0c8ad0 100644
--- a/lib/types/reducers.js
+++ b/lib/types/reducers.js
@@ -572,8 +572,7 @@ export type StatusState = {
applicationRequests: Array,
jobMonitor: JobStatusState,
message: ?string,
- modal: ?ModalStatus,
- saving: ?boolean
+ modal: ?ModalStatus
}
export type UiState = {
diff --git a/package.json b/package.json
index b2bdf6e2b..5250d8019 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"cover-end-to-end": "cross-env COLLECT_COVERAGE=true npm run test-end-to-end",
"deploy": "mastarm deploy --minify --env production",
"flow": "mastarm flow",
+ "import-mobility-data": "node scripts/import-mobility-data-rules.mjs",
"lint": "mastarm lint __tests__ lib scripts --quiet",
"lint-messages": "node scripts/lint-messages.js",
"postinstall": "patch-package && husky install",
@@ -60,6 +61,7 @@
"leaflet": "^1.7.1",
"leaflet-textpath": "^1.2.3",
"lodash": "^4.17.10",
+ "markdown-to-jsx": "^7.1.9",
"moment": "^2.29.4",
"numeral": "2.0.4",
"object-path": "^0.11.5",
diff --git a/scripts/import-mobility-data-rules.mjs b/scripts/import-mobility-data-rules.mjs
new file mode 100644
index 000000000..2afab0017
--- /dev/null
+++ b/scripts/import-mobility-data-rules.mjs
@@ -0,0 +1,50 @@
+import { writeFileSync } from 'fs'
+
+import fetch from 'isomorphic-fetch'
+
+const logger = (msg) => {
+ console.log(`[MOBILITY DATA RULES IMPORTER]: ${msg}`)
+}
+
+const findDescription = (text, header) => {
+ // Split the rules.MD file into lines
+ let lines = text.split('\n')
+ // Remove the distracting top part of the file
+ const moreDetails = lines.findIndex(l => l.includes('More details'))
+ lines = lines.splice(moreDetails)
+
+ const description = lines.findIndex(l => l.includes(header))
+
+ // Edge cases? Something is strange about the file
+ if (header === 'equal_shape_distance_diff_coordinates') {
+ return lines[description + 4]
+ }
+ if (header === 'same_name_and_description_for_stop' || header === 'same_name_and_description_for_route') {
+ return lines[description + 2] + '\n\n' + lines[description + 4]
+ }
+
+ return lines[description + 2]
+}
+
+logger('Writing gtfs-validator rules.MD to JSON')
+fetch(
+ 'https://raw.githubusercontent.com/MobilityData/gtfs-validator/master/RULES.md'
+)
+ .then((raw) => raw.text())
+ .then((text) => {
+ logger('rules.MD downloaded!')
+ // Match all rule headings
+ const rules = text
+ .match(/### .*/g)
+ .filter((rule) => rule.includes('_'))
+ .map((rule) => rule.split('### ')[1])
+
+ // Extract descriptions
+ const rulesAndDescriptions = rules.map((rule) => ({
+ rule,
+ description: findDescription(text, rule)
+ }))
+ logger('rules.MD data extracted successfully!')
+ writeFileSync('lib/manager/components/validation/rules.json', JSON.stringify(rulesAndDescriptions))
+ logger('Wrote gtfs-validator rules.MD to JSON')
+ })
diff --git a/yarn.lock b/yarn.lock
index d91b16d50..8d2a47ed3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8709,6 +8709,11 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
+markdown-to-jsx@^7.1.9:
+ version "7.1.9"
+ resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-7.1.9.tgz#1ffae0cda07c189163d273bd57a5b8f8f8745586"
+ integrity sha512-x4STVIKIJR0mGgZIZ5RyAeQD7FEZd5tS8m/htbcVGlex32J+hlSLj+ExrHCxP6nRKF1EKbcO7i6WhC1GtOpBlA==
+
marked-terminal@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/marked-terminal/-/marked-terminal-4.1.0.tgz#01087372d3636dc7cb286475a1d6147187f500e0"