Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion __tests__/end-to-end.js
Original file line number Diff line number Diff line change
Expand Up @@ -915,7 +915,8 @@ describe('end-to-end', () => {
await waitForAndClick(`[data-test-id="edit-user-${testUserSlug}"]`)
// $FlowFixMe cryptic error that is hard to resolve :(
const adminCheckbox = await page.$(`[data-test-id="app-admin-checkbox-${testUserSlug}"]`)
expect(await (await adminCheckbox.getProperty('checked')).jsonValue()).toBe(true)
const isAdmin = await (await adminCheckbox.getProperty('checked')).jsonValue()
expect(isAdmin).toBe(true)
}, defaultTestTimeout, 'should allow admin user to create another user')

makeTestPostLogin('should delete a user', async () => {
Expand Down
2 changes: 2 additions & 0 deletions __tests__/test-utils/mock-data/manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export const mockFeedWithVersion = {
retrievalMethod: 'MANUALLY_UPLOADED',
s3Url: null,
snapshotVersion: null,
transformRules: [],
url: 'http://mdtrip.org/googletransit/AnnapolisTransit/google_transit.zip',
user: null
}
Expand All @@ -160,6 +161,7 @@ export const mockFeedWithoutVersion = {
retrievalMethod: 'FETCHED_AUTOMATICALLY',
s3Url: null,
snapshotVersion: null,
transformRules: [],
url: null,
user: null
}
Expand Down
1 change: 1 addition & 0 deletions gtfs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,7 @@

- id: scheduleexception
name: (none)
datatools: true
helpContent: Conveyal-specific table for classifying schedule exceptions.
fields:
- name: name
Expand Down
17 changes: 17 additions & 0 deletions i18n/english.yml
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,23 @@ components:
upload: Upload
versions: Versions
viewPublic: View public page
FeedTransformationDescriptions:
general:
fileDefined: below text
filePlaceholder: '[choose file]'
tablePlaceholder: '[choose table]'
table: table
version: version
versionPlaceholder: '[choose version]'
DeleteRecordsTransformation:
label: Delete records from %tablePlaceholder%
name: Delete records transformation
ReplaceFileFromStringTransformation:
label: Replace %tablePlaceholder% from %filePlaceholder%
name: Replace file from string transformation
ReplaceFileFromVersionTransformation:
label: Replace %tablePlaceholder% from %versionPlaceholder%
name: Replace file from version transformation
FeedVersionNavigator:
confirmDelete: Are you sure you want to delete this version? This cannot be undone.
confirmLoad: 'This will override all active GTFS Editor data for this Feed Source with the data from this version. If there is unsaved work in the Editor you want to keep, you must snapshot the current Editor data first. Are you sure you want to continue?'
Expand Down
16 changes: 16 additions & 0 deletions lib/common/constants/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @flow

const SECURE: string = 'secure/'
export const API_PREFIX: string = `/api/manager/`
export const SECURE_API_PREFIX: string = `${API_PREFIX}${SECURE}`
Expand All @@ -9,3 +10,18 @@ export const DEFAULT_DESCRIPTION = 'A command center for managing, editing, vali
export const DEFAULT_LOGO = 'https://d2tyb7byn1fef9.cloudfront.net/ibi_group_black-512x512.png'
export const DEFAULT_LOGO_SMALL = 'https://d2tyb7byn1fef9.cloudfront.net/ibi_group-128x128.png'
export const DEFAULT_TITLE = 'Data Tools'

export const RETRIEVAL_METHODS = Object.freeze({
MANUALLY_UPLOADED: 'MANUALLY_UPLOADED',
FETCHED_AUTOMATICALLY: 'FETCHED_AUTOMATICALLY',
PRODUCED_IN_HOUSE: 'PRODUCED_IN_HOUSE',
SERVICE_PERIOD_MERGE: 'SERVICE_PERIOD_MERGE',
REGIONAL_MERGE: 'REGIONAL_MERGE',
VERSION_CLONE: 'VERSION_CLONE'
})

export const FEED_TRANSFORMATION_TYPES = Object.freeze({
DELETE_RECORDS: 'DeleteRecordsTransformation',
REPLACE_FILE_FROM_VERSION: 'ReplaceFileFromVersionTransformation',
REPLACE_FILE_FROM_STRING: 'ReplaceFileFromStringTransformation'
})
2 changes: 1 addition & 1 deletion lib/common/util/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function getComponentMessages (
return message
} else {
if (logWarning) console.warn(`Couldn't find message entry for ${componentName}.${path}`)
return `{${path}}`
return message || `{${path}}`
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions lib/manager/actions/__tests__/__snapshots__/projects.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Object {
"retrievalMethod": "MANUALLY_UPLOADED",
"s3Url": null,
"snapshotVersion": null,
"transformRules": Array [],
"url": "http://mdtrip.org/googletransit/AnnapolisTransit/google_transit.zip",
"user": null,
},
Expand Down Expand Up @@ -162,6 +163,7 @@ Object {
"retrievalMethod": "MANUALLY_UPLOADED",
"s3Url": null,
"snapshotVersion": null,
"transformRules": Array [],
"url": "http://mdtrip.org/googletransit/AnnapolisTransit/google_transit.zip",
"user": null,
},
Expand Down
2 changes: 1 addition & 1 deletion lib/manager/actions/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ export function updateUserData (user: any, userData: any) {
const url = `/api/manager/secure/user/${user.user_id}`
const payload = {
user_id: profile.user_id,
app_metadata: metadata.datatools
data: metadata.datatools
}
let status
// Make request to server
Expand Down
219 changes: 43 additions & 176 deletions lib/manager/components/FeedSourceSettings.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
// @flow

import Icon from '@conveyal/woonerf/components/icon'
import React, {Component} from 'react'
import {Col, Row, ListGroup, ListGroupItem, Button, Panel, FormControl, InputGroup, ControlLabel, FormGroup, Checkbox} from 'react-bootstrap'
import {
Col,
ListGroup,
ListGroupItem,
Panel,
Row
} from 'react-bootstrap'
import {LinkContainer} from 'react-router-bootstrap'

import * as feedsActions from '../actions/feeds'
import toSentenceCase from '../../common/util/to-sentence-case'
import ExternalPropertiesTable from './ExternalPropertiesTable'
import FeedTransformationSettings from './transform/FeedTransformationSettings'
import GeneralSettings from './GeneralSettings'

import type {Feed, Project} from '../../types'
import type {ManagerUserState} from '../../types/reducers'
Expand All @@ -23,85 +30,24 @@ type Props = {
user: ManagerUserState
}

type State = {
name?: ?string,
url?: ?string
}

export default class FeedSourceSettings extends Component<Props, State> {
state = {}

_onChange = ({target}: SyntheticInputEvent<HTMLInputElement>) => {
// Change empty string to null to avoid setting URL to empty string value.
const value = target.value === '' ? null : target.value.trim()
this.setState({[target.name]: value})
}

_onToggleDeployable = () => {
const {feedSource, updateFeedSource} = this.props
updateFeedSource(feedSource, {deployable: !feedSource.deployable})
}

_getFormValue = (key: 'name' | 'url') => {
// If state value does not exist (i.e., form is unedited), revert to value
// from props.
const value = typeof this.state[key] === 'undefined'
? this.props.feedSource[key]
: this.state[key]
// Revert to empty string to avoid console error with null value for form.
return value || ''
}

_onToggleAutoFetch = () => {
const {feedSource, updateFeedSource} = this.props
const value = feedSource.retrievalMethod === 'FETCHED_AUTOMATICALLY'
? 'MANUALLY_UPLOADED'
: 'FETCHED_AUTOMATICALLY'
updateFeedSource(feedSource, {retrievalMethod: value})
}

_onTogglePublic = () => {
const {feedSource, updateFeedSource} = this.props
updateFeedSource(feedSource, {isPublic: !feedSource.isPublic})
}

_onNameChanged = (evt: SyntheticInputEvent<HTMLInputElement>) => {
this.setState({name: evt.target.value})
}

_onNameSaved = () => {
const {feedSource, updateFeedSource} = this.props
updateFeedSource(feedSource, {name: this.state.name})
}

_onSaveUrl = () => {
const {feedSource, updateFeedSource} = this.props
updateFeedSource(feedSource, {url: this.state.url})
}

export default class FeedSourceSettings extends Component<Props> {
render () {
const {
activeComponent,
activeSubComponent,
confirmDeleteFeedSource,
updateExternalFeedResource,
feedSource,
project,
user
} = this.props
const {
name,
url
} = this.state
const disabled = user.permissions && !user.permissions.hasFeedPermission(
project.organizationId, project.id, feedSource.id, 'manage-feed'
)
const isProjectAdmin = user.permissions && user.permissions.isProjectAdmin(
project.id, project.organizationId
)
// const editGtfsDisabled = !user.permissions.hasFeedPermission(project.organizationId, project.id, feedSource.id, 'edit-gtfs')
const autoFetchFeed = feedSource.retrievalMethod === 'FETCHED_AUTOMATICALLY'
const resourceType = activeComponent === 'settings' && activeSubComponent && activeSubComponent.toUpperCase()
const showTransformationsTab = resourceType === 'TRANSFORMATIONS'
if (disabled) {
return (
<Row>
Expand All @@ -122,6 +68,11 @@ export default class FeedSourceSettings extends Component<Props, State> {
active={!activeSubComponent}>
<ListGroupItem>General</ListGroupItem>
</LinkContainer>
<LinkContainer
to={`/feed/${feedSource.id}/settings/transformations`}
active={showTransformationsTab}>
<ListGroupItem>Feed Transformations</ListGroupItem>
</LinkContainer>
{Object.keys(feedSource.externalProperties || {}).map(resourceType => {
const resourceLowerCase = resourceType.toLowerCase()
return (
Expand All @@ -138,118 +89,34 @@ export default class FeedSourceSettings extends Component<Props, State> {
</Col>
<Col xs={6} />
{!resourceType
? <Col xs={7}>
{/* Settings */}
<Panel header={<h3>Settings</h3>}>
<ListGroup fill>
<ListGroupItem>
<FormGroup>
<ControlLabel>Feed source name</ControlLabel>
<InputGroup>
<FormControl
value={this._getFormValue('name')}
name={'name'}
disabled={disabled}
onChange={this._onChange} />
<InputGroup.Button>
<Button
// disable if no change or no value (name is required).
disabled={disabled || !name || name === feedSource.name}
onClick={this._onNameSaved}>
Rename
</Button>
</InputGroup.Button>
</InputGroup>
</FormGroup>
</ListGroupItem>
<ListGroupItem>
<FormGroup>
<Checkbox
checked={feedSource.deployable}
data-test-id='make-feed-source-deployable-button'
onChange={this._onToggleDeployable}>
<strong>Make feed source deployable</strong>
</Checkbox>
<small>Enable this feed source to be deployed to an OpenTripPlanner (OTP) instance (defined in organization settings) as part of a collection of feed sources or individually.</small>
</FormGroup>
</ListGroupItem>
</ListGroup>
</Panel>
<Panel header={<h3>Automatic fetch</h3>}>
<ListGroup fill>
<ListGroupItem>
<FormGroup>
<ControlLabel>Feed source fetch URL</ControlLabel>
<InputGroup data-test-id='feed-source-url-input-group'>
<FormControl
disabled={disabled}
name={'url'}
onChange={this._onChange}
value={this._getFormValue('url')}
/>
<InputGroup.Button>
<Button
// Disable if no change or field has not been edited.
disabled={disabled || typeof url === 'undefined' || url === feedSource.url}
onClick={this._onSaveUrl}>
Change URL
</Button>
</InputGroup.Button>
</InputGroup>
</FormGroup>
</ListGroupItem>
<ListGroupItem>
<FormGroup>
<Checkbox
checked={autoFetchFeed}
disabled={disabled}
onChange={this._onToggleAutoFetch}
bsStyle='danger'>
<strong>Auto fetch feed source</strong>
</Checkbox>
<small>Set this feed source to fetch automatically. (Feed source URL must be specified and project auto fetch must be enabled.)</small>
</FormGroup>
</ListGroupItem>
</ListGroup>
</Panel>
<Panel bsStyle='danger' header={<h3>Danger zone</h3>}>
<ListGroup fill>
<ListGroupItem>
<Button
onClick={this._onTogglePublic}
disabled={disabled}
className='pull-right'>
Make {feedSource.isPublic ? 'private' : 'public'}
</Button>
<h4>Make this feed source {feedSource.isPublic ? 'private' : 'public'}.</h4>
<p>This feed source is currently {feedSource.isPublic ? 'public' : 'private'}.</p>
</ListGroupItem>
<ListGroupItem>
<Button
onClick={confirmDeleteFeedSource}
className='pull-right'
disabled={disabled}
bsStyle='danger'>
<Icon type='trash' /> Delete feed source
</Button>
<h4>Delete this feed source.</h4>
<p>Once you delete a feed source, it cannot be recovered.</p>
</ListGroupItem>
</ListGroup>
</Panel>
</Col>
: <Col xs={7}>
<ExternalPropertiesTable
resourceType={resourceType}
? <GeneralSettings
confirmDeleteFeedSource={this.props.confirmDeleteFeedSource}
disabled={disabled}
feedSource={feedSource}
project={project}
updateFeedSource={this.props.updateFeedSource}
user={user}
/>
: showTransformationsTab
? <FeedTransformationSettings
disabled={disabled}
feedSource={feedSource}
editingIsDisabled={disabled}
isProjectAdmin={isProjectAdmin}
resourceProps={feedSource.externalProperties
? feedSource.externalProperties[resourceType]
: {}
}
updateExternalFeedResource={updateExternalFeedResource} />
</Col>
project={project}
updateFeedSource={this.props.updateFeedSource}
user={user}
/>
: <Col xs={7}>
<ExternalPropertiesTable
resourceType={resourceType}
feedSource={feedSource}
editingIsDisabled={disabled}
isProjectAdmin={isProjectAdmin}
resourceProps={feedSource.externalProperties
? feedSource.externalProperties[resourceType]
: {}
}
updateExternalFeedResource={updateExternalFeedResource} />
</Col>
}
</Row>
)
Expand Down
Loading