Skip to content

Commit

Permalink
fix(manager): update validation errors to use graphql
Browse files Browse the repository at this point in the history
  • Loading branch information
landonreed committed Nov 8, 2017
1 parent e7a7aa6 commit f95fd57
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 31 deletions.
76 changes: 72 additions & 4 deletions lib/manager/actions/versions.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,16 +188,84 @@ export function receiveValidationResult (feedVersion, validationResult) {
validationResult
}
}

const fetchingValidationErrors = createAction('FETCHING_VALIDATION_ERRORS')

const receiveValidationErrors = createAction('RECEIVE_VALIDATION_ERRORS')

export function fetchValidationErrors ({feedVersion, errorType, limit, offset}) {
return function (dispatch, getState) {
dispatch(fetchingValidationErrors)
// FIXME: why does namespace need to appear twice?
const query = `
query errorsQuery($namespace: String, $errorType: [String], $limit: Int, $offset: Int) {
feed(namespace: $namespace) {
feed_id
feed_version
filename
errors (error_type: $errorType, limit: $limit, offset: $offset) {
error_type
entity_type
entity_id
line_number
bad_value
}
}
}
`
const {namespace} = feedVersion
const method = 'post'
const body = JSON.stringify({
query,
variables: JSON.stringify({namespace, errorType: [errorType], limit, offset})
})
return fetch(GTFS_GRAPHQL_PREFIX, {method, body})
.then(response => response.json())
.then(result => {
if (result.feed) {
const {errors} = result.feed
dispatch(receiveValidationErrors({feedVersion, errorType, limit, offset, errors}))
}
})
.catch(err => console.log(err))
}
}

export function fetchValidationResult (feedVersion, isPublic) {
return function (dispatch, getState) {
dispatch(requestingValidationResult(feedVersion))
const route = isPublic ? 'public' : 'secure'
const url = `/api/manager/${route}/feedversion/${feedVersion.id}/validation`
return dispatch(secureFetch(url))
const {namespace} = feedVersion
const query = `
query countsQuery($namespace: String) {
feed(namespace: $namespace) {
feed_id
feed_version
filename
row_counts {
stops
trips
calendar_dates
errors
}
error_counts {
type, count
}
}
}
`
const method = 'post'
const body = JSON.stringify({
query,
variables: JSON.stringify({namespace})
})
return fetch(GTFS_GRAPHQL_PREFIX, {method, body})
.then(response => response.json())
.then(result => {
dispatch(receiveValidationResult(feedVersion, result))
if (result.feed) {
dispatch(receiveValidationResult(feedVersion, result.feed))
}
})
.catch(err => console.log(err))
}
}

Expand Down
4 changes: 2 additions & 2 deletions lib/manager/components/validation/GtfsValidationSummary.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class ValidationSummaryTable extends Component {
const {
version
} = this.props
if (version && version.validationResult && version.validationResult.errors.length === 0) {
if (version && version.validationResult && version.validationResult.error_counts.length === 0) {
return <div className='lead text-center '>No validation issues found.</div>
} else if (!version || !version.validationResult) {
return <div className='lead text-center '>Feed has not yet been validated.</div>
Expand All @@ -65,7 +65,7 @@ export class ValidationSummaryTable extends Component {
overflowWrap: 'break-word'
}
const problemMap = {}
version.validationResult.errors && version.validationResult.errors.map(val => {
version.validationResult.error_counts && version.validationResult.error_counts.map(val => {
if (!problemMap[val.errorType]) {
problemMap[val.errorType] = {
count: 0,
Expand Down
99 changes: 82 additions & 17 deletions lib/manager/components/validation/GtfsValidationViewer.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import Icon from '@conveyal/woonerf/components/icon'
import moment from 'moment'
import React, {Component, PropTypes} from 'react'
import {
// Badge,
// Button
} from 'react-bootstrap'
import {ListGroup, ListGroupItem, Panel} from 'react-bootstrap'
import BootstrapTable from 'react-bootstrap-table/lib/BootstrapTable'
import TableHeaderColumn from 'react-bootstrap-table/lib/TableHeaderColumn'

import {ValidationSummaryTable} from './GtfsValidationSummary'
import OptionButton from '../../../common/components/OptionButton'

const DEFAULT_LIMIT = 10

export default class GtfsValidationViewer extends Component {
static propTypes = {
fetchValidationResult: PropTypes.func,
validationResult: PropTypes.object,
version: PropTypes.object
}
state = {
offset: 0,
limit: DEFAULT_LIMIT
}
componentWillMount () {
this.props.fetchValidationResult()
}
Expand All @@ -40,11 +44,35 @@ export default class GtfsValidationViewer extends Component {
formatter (cell, row) {
return <span title={cell}>{cell}</span>
}

_onClickErrorType = errorType => {
const {version: feedVersion, fetchValidationErrors} = this.props
const {active} = this.state
if (active === errorType) {
this.setState({active: null})
} else {
const offset = 0
const limit = DEFAULT_LIMIT
// reset active error type, limit, and offset
this.setState({active: errorType, limit, offset})
fetchValidationErrors({feedVersion, errorType, offset, limit})
}
}

_onClickLoadMoreErrors = errorType => {
const {version: feedVersion, fetchValidationErrors} = this.props
const {limit} = this.state
const offset = this.state.offset * limit + 1
this.setState({offset})
fetchValidationErrors({feedVersion, errorType, offset, limit})
}

render () {
const {
validationResult: result,
version
} = this.props
const {active} = this.state
const dateFormat = 'MMM. DD, YYYY'
const timeFormat = 'h:MMa'
// const messages = getComponentMessages('GtfsValidationViewer')
Expand All @@ -61,21 +89,58 @@ export default class GtfsValidationViewer extends Component {
sizePerPageList: [10, 20, 50, 100]
}
}
// let report = null
const files = ['routes', 'stops', 'trips', 'shapes', 'stop_times']
const errors = {}
result && result.errors.map((error, i) => {
error.index = i
const key = files.indexOf(error.file) !== -1 ? error.file : 'other'
if (!errors[error.file]) {
errors[key] = []
}
errors[key].push(error)
})
const hasValidation = result && result.error_counts
const hasErrors = hasValidation && result.error_counts.length
const listGroupItemStyle = { fontSize: '18px', textAlign: 'center' }
return (
<div>
<h2 style={{marginTop: '0px'}}>{version.name} <small>{moment(version.updated).format(dateFormat + ', ' + timeFormat)}</small></h2>
<ValidationSummaryTable version={version} />
<Panel header={<h2>Validation errors</h2>}>
{hasErrors
? result.error_counts.map((category, index) => {
const activeWithErrors = category.errors && active === category.type
return (
<ListGroup key={index} fill>
<ListGroupItem style={listGroupItemStyle}>
{category.type}: {category.count}
{' '}
<OptionButton
value={category.type}
onClick={this._onClickErrorType}>
<Icon type={`caret-${active === category.type ? 'up' : 'down'}`} />
</OptionButton>
</ListGroupItem>
{activeWithErrors
? category.errors.map((error, index) => (
<ListGroupItem key={index} style={listGroupItemStyle}>
line: {error.line_number}{' '}
entity_type: {error.entity_type}{' '}
entity_id: {error.entity_id}{' '}
bad_value: {error.bad_value}
</ListGroupItem>
))
: null
}
{activeWithErrors && category.errors.length < category.count
? <ListGroupItem style={listGroupItemStyle}>
<OptionButton
value={category.type}
onClick={this._onClickLoadMoreErrors}>
Load more
</OptionButton>
</ListGroupItem>
: activeWithErrors
? <ListGroupItem style={listGroupItemStyle}>
No more errors of this type
</ListGroupItem>
: null
}
</ListGroup>
)
})
: null
}
</Panel>
<BootstrapTable
data={result && result.errors ? result.errors : []}
{...tableOptions}
Expand Down
10 changes: 4 additions & 6 deletions lib/manager/components/version/FeedVersionNavigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default class FeedVersionNavigator extends Component {
fetchValidationResult: PropTypes.func,
setVersionIndex: PropTypes.func,
sortedVersions: PropTypes.array,
user: PropTypes.object,
validationJob: PropTypes.object,
version: PropTypes.object
}
Expand Down Expand Up @@ -105,10 +106,9 @@ export default class FeedVersionNavigator extends Component {
isPublic,
sortedVersions,
user,
version,
versionSection
} = this.props
const versions = feedSource.feedVersions
const {feedVersions: versions} = feedSource
const messages = getComponentMessages('FeedVersionNavigator')

if (typeof feedVersionIndex === 'undefined') return null
Expand Down Expand Up @@ -216,9 +216,8 @@ export default class FeedVersionNavigator extends Component {
<Row>
<Col xs={12}>
<FeedVersionViewer
{...this.props}
isPublic={isPublic}
feedSource={feedSource}
version={version}
feedVersionIndex={feedVersionIndex}
versionSection={versionSection || null}
versions={versions}
Expand All @@ -228,8 +227,7 @@ export default class FeedVersionNavigator extends Component {
gtfsPlusDataRequested={this._onRequestGtfsPlusData}
notesRequested={this._onVersionNotesRequested}
newNotePosted={this._onVersionNotePosted}
user={user}
{...this.props} />
user={user} />
</Col>
</Row>
</div>
Expand Down
2 changes: 1 addition & 1 deletion lib/manager/components/version/FeedVersionViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ export default class FeedVersionViewer extends Component {
? <FeedVersionReport {...this.props} />
: versionSection === 'issues'
? <GtfsValidationViewer
{...this.props}
validationResult={version.validationResult}
version={version}
fetchValidationResult={this._onFetchValidation} />
: versionSection === 'gtfsplus' && isModuleEnabled('gtfsplus')
? <ActiveGtfsPlusVersionSummary
Expand Down
4 changes: 3 additions & 1 deletion lib/manager/containers/ActiveFeedVersionNavigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import FeedVersionNavigator from '../components/version/FeedVersionNavigator'
import {
deleteFeedVersion,
downloadFeedViaToken,
fetchValidationErrors,
fetchValidationResult,
renameFeedVersion,
setActiveVersion,
Expand Down Expand Up @@ -40,7 +41,7 @@ const mapStateToProps = (state, ownProps) => {
}
const { jobs } = state.status.jobMonitor
const validationJob = feedVersionIndex >= 1
? jobs.find(j => j.type === 'VALIDATE_FEED' && j.feedVersion.id === feedVersions[feedVersionIndex - 1].id)
? jobs.find(j => j.type === 'VALIDATE_FEED' && j.feedVersionId === feedVersions[feedVersionIndex - 1].id)
: null

const hasVersions = feedVersions && feedVersions.length > 0
Expand Down Expand Up @@ -89,6 +90,7 @@ const mapDispatchToProps = (dispatch, ownProps) => {
newNotePostedForVersion: (feedVersion, note) => dispatch(postNoteForFeedVersion(feedVersion, note)),
notesRequestedForVersion: (feedVersion) => dispatch(fetchNotesForFeedVersion(feedVersion)),
fetchValidationResult: (feedVersion, isPublic) => dispatch(fetchValidationResult(feedVersion, isPublic)),
fetchValidationErrors: payload => dispatch(fetchValidationErrors(payload)),
publishFeedVersion: (feedVersion) => dispatch(publishFeedVersion(feedVersion))
}
}
Expand Down

0 comments on commit f95fd57

Please sign in to comment.