API & Client: add an activity w/o gpx
This commit is contained in:
		@@ -207,7 +207,7 @@ def post_activity_no_gpx(auth_user_id):
 | 
			
		||||
            user_id=auth_user_id,
 | 
			
		||||
            sport_id=activity_data.get('sport_id'),
 | 
			
		||||
            activity_date=datetime.strptime(
 | 
			
		||||
                activity_data.get('activity_date'), '%d/%m/%Y'),
 | 
			
		||||
                activity_data.get('activity_date'), '%Y-%m-%d'),
 | 
			
		||||
            duration=timedelta(seconds=activity_data.get('duration'))
 | 
			
		||||
        )
 | 
			
		||||
        new_activity.moving = new_activity.duration
 | 
			
		||||
 
 | 
			
		||||
@@ -76,5 +76,6 @@ class Activity(db.Model):
 | 
			
		||||
            "descent": float(self.descent) if self.descent else None,
 | 
			
		||||
            "ascent": float(self.ascent) if self.ascent else None,
 | 
			
		||||
            "max_speed": float(self.max_speed) if self.max_speed else None,
 | 
			
		||||
            "ave_speed": float(self.ave_speed) if self.ave_speed else None
 | 
			
		||||
            "ave_speed": float(self.ave_speed) if self.ave_speed else None,
 | 
			
		||||
            "with_gpx": self.gpx is not None
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -55,12 +55,14 @@ def test_get_all_activities_for_authenticated_user(app):
 | 
			
		||||
    assert 1 == data['data']['activities'][0]['user_id']
 | 
			
		||||
    assert 2 == data['data']['activities'][0]['sport_id']
 | 
			
		||||
    assert '0:17:04' == data['data']['activities'][0]['duration']
 | 
			
		||||
    assert data['data']['activities'][0]['with_gpx'] is False
 | 
			
		||||
 | 
			
		||||
    assert 'creation_date' in data['data']['activities'][1]
 | 
			
		||||
    assert 'Sun, 01 Apr 2018 00:00:00 GMT' == data['data']['activities'][1]['activity_date']  # noqa
 | 
			
		||||
    assert 1 == data['data']['activities'][1]['user_id']
 | 
			
		||||
    assert 1 == data['data']['activities'][1]['sport_id']
 | 
			
		||||
    assert '1:40:00' == data['data']['activities'][1]['duration']
 | 
			
		||||
    assert data['data']['activities'][0]['with_gpx'] is False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_activities_for_authenticated_user_no_activity(app):
 | 
			
		||||
@@ -133,6 +135,22 @@ def test_add_an_activity_gpx(app):
 | 
			
		||||
 | 
			
		||||
    assert response.status_code == 201
 | 
			
		||||
    assert 'created' in data['status']
 | 
			
		||||
    assert len(data['data']['activities']) == 1
 | 
			
		||||
    assert 'creation_date' in data['data']['activities'][0]
 | 
			
		||||
    assert 'Tue, 13 Mar 2018 12:44:45 GMT' == data['data']['activities'][0]['activity_date']  # noqa
 | 
			
		||||
    assert 1 == data['data']['activities'][0]['user_id']
 | 
			
		||||
    assert 1 == data['data']['activities'][0]['sport_id']
 | 
			
		||||
    assert '0:04:10' == data['data']['activities'][0]['duration']
 | 
			
		||||
    assert data['data']['activities'][0]['ascent'] == 0.4
 | 
			
		||||
    assert data['data']['activities'][0]['ave_speed'] == 4.6
 | 
			
		||||
    assert data['data']['activities'][0]['descent'] == 23.4
 | 
			
		||||
    assert data['data']['activities'][0]['distance'] == 0.32
 | 
			
		||||
    assert data['data']['activities'][0]['max_alt'] == 998.0
 | 
			
		||||
    assert data['data']['activities'][0]['max_speed'] == 5.09
 | 
			
		||||
    assert data['data']['activities'][0]['min_alt'] == 975.0
 | 
			
		||||
    assert data['data']['activities'][0]['moving'] == '0:04:10'
 | 
			
		||||
    assert data['data']['activities'][0]['pauses'] is None
 | 
			
		||||
    assert data['data']['activities'][0]['with_gpx'] is True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_add_an_activity_gpx_invalid_file(app):
 | 
			
		||||
@@ -252,7 +270,7 @@ def test_add_an_activity_no_gpx(app):
 | 
			
		||||
        data=json.dumps(dict(
 | 
			
		||||
            sport_id=1,
 | 
			
		||||
            duration=3600,
 | 
			
		||||
            activity_date='15/05/2018',
 | 
			
		||||
            activity_date='2018-05-15',
 | 
			
		||||
            distance=10
 | 
			
		||||
        )),
 | 
			
		||||
        headers=dict(
 | 
			
		||||
@@ -280,6 +298,7 @@ def test_add_an_activity_no_gpx(app):
 | 
			
		||||
    assert data['data']['activities'][0]['min_alt'] is None
 | 
			
		||||
    assert data['data']['activities'][0]['moving'] == '1:00:00'
 | 
			
		||||
    assert data['data']['activities'][0]['pauses'] is None
 | 
			
		||||
    assert data['data']['activities'][0]['with_gpx'] is False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_add_an_activity_no_gpx_invalid_payload(app):
 | 
			
		||||
@@ -396,6 +415,7 @@ def test_get_an_activity_without_gpx(app):
 | 
			
		||||
    assert data['data']['activities'][0]['min_alt'] is None
 | 
			
		||||
    assert data['data']['activities'][0]['moving'] is None
 | 
			
		||||
    assert data['data']['activities'][0]['pauses'] is None
 | 
			
		||||
    assert data['data']['activities'][0]['with_gpx'] is False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_an_activity_with_gpx(app):
 | 
			
		||||
@@ -452,3 +472,4 @@ def test_get_an_activity_with_gpx(app):
 | 
			
		||||
    assert data['data']['activities'][0]['min_alt'] == 975.0
 | 
			
		||||
    assert data['data']['activities'][0]['moving'] == '0:04:10'
 | 
			
		||||
    assert data['data']['activities'][0]['pauses'] is None
 | 
			
		||||
    assert data['data']['activities'][0]['with_gpx'] is True
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,18 @@ export const addActivity = form => dispatch => mpwoApi
 | 
			
		||||
  .catch(error => dispatch(setError(`activities: ${error}`)))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export const addActivityWithoutGpx = form => dispatch => mpwoApi
 | 
			
		||||
  .addActivityWithoutGpx(form)
 | 
			
		||||
  .then(ret => {
 | 
			
		||||
    if (ret.status === 'created') {
 | 
			
		||||
      history.push('/')
 | 
			
		||||
    } else {
 | 
			
		||||
      dispatch(setError(`activities: ${ret.message}`))
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  .catch(error => dispatch(setError(`activities: ${error}`)))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export const getActivityGpx = activityId => dispatch => {
 | 
			
		||||
  if (activityId) {
 | 
			
		||||
    return mpwoApi
 | 
			
		||||
 
 | 
			
		||||
@@ -3,16 +3,33 @@ import { Helmet } from 'react-helmet'
 | 
			
		||||
import { connect } from 'react-redux'
 | 
			
		||||
 | 
			
		||||
import FormWithGpx from './ActivityForms/FormWithGpx'
 | 
			
		||||
import FormWithoutGpx from './ActivityForms/FormWithoutGpx'
 | 
			
		||||
import { getData } from '../../actions/index'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AddActivity extends React.Component {
 | 
			
		||||
  constructor(props, context) {
 | 
			
		||||
    super(props, context)
 | 
			
		||||
    this.state = {
 | 
			
		||||
      withGpx: true,
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentDidMount() {
 | 
			
		||||
      this.props.loadSports()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleRadioChange (changeEvent) {
 | 
			
		||||
    this.setState({
 | 
			
		||||
      withGpx:
 | 
			
		||||
        changeEvent.target.name === 'withGpx'
 | 
			
		||||
          ? changeEvent.target.value : !changeEvent.target.value
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    const { message, sports } = this.props
 | 
			
		||||
    const { withGpx } = this.state
 | 
			
		||||
    return (
 | 
			
		||||
      <div>
 | 
			
		||||
        <Helmet>
 | 
			
		||||
@@ -32,7 +49,37 @@ class AddActivity extends React.Component {
 | 
			
		||||
                  Add a sport
 | 
			
		||||
                </h2>
 | 
			
		||||
                <div className="card-body">
 | 
			
		||||
                  <FormWithGpx sports={sports} />
 | 
			
		||||
                  <form>
 | 
			
		||||
                    <div className="form-group row">
 | 
			
		||||
                      <div className="col">
 | 
			
		||||
                        <label className="radioLabel">
 | 
			
		||||
                        <input
 | 
			
		||||
                          type="radio"
 | 
			
		||||
                          name="withGpx"
 | 
			
		||||
                          checked={withGpx}
 | 
			
		||||
                          onChange={event => this.handleRadioChange(event)}
 | 
			
		||||
                        />
 | 
			
		||||
                          with gpx file
 | 
			
		||||
                        </label>
 | 
			
		||||
                      </div>
 | 
			
		||||
                      <div className="col">
 | 
			
		||||
                        <label className="radioLabel">
 | 
			
		||||
                        <input
 | 
			
		||||
                          type="radio"
 | 
			
		||||
                          name="withoutGpx"
 | 
			
		||||
                          checked={!withGpx}
 | 
			
		||||
                          onChange={event => this.handleRadioChange(event)}
 | 
			
		||||
                        />
 | 
			
		||||
                          without gpx file
 | 
			
		||||
                        </label>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </form>
 | 
			
		||||
                  {withGpx ? (
 | 
			
		||||
                    <FormWithGpx sports={sports} />
 | 
			
		||||
                  ) : (
 | 
			
		||||
                    <FormWithoutGpx sports={sports} />
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,8 @@ class ActivityDisplay extends React.Component {
 | 
			
		||||
                  <p>
 | 
			
		||||
                    <i className="fa fa-clock-o" aria-hidden="true" />{' '}
 | 
			
		||||
                    Duration: {activity.duration} {' '}
 | 
			
		||||
                    {activity.pauses !== '0:00:00' && (
 | 
			
		||||
                    {activity.pauses !== '0:00:00' &&
 | 
			
		||||
                     activity.pauses !== null && (
 | 
			
		||||
                        `(pauses: ${activity.pauses})`
 | 
			
		||||
                    )}
 | 
			
		||||
                  </p>
 | 
			
		||||
@@ -56,14 +57,18 @@ class ActivityDisplay extends React.Component {
 | 
			
		||||
                    Average speed: {activity.ave_speed} km/h -{' '}
 | 
			
		||||
                    Max speed : {activity.max_speed} km/h
 | 
			
		||||
                  </p>
 | 
			
		||||
                  {activity.min_alt && activity.max_alt && (
 | 
			
		||||
                  <p><i className="fi-mountains" />{' '}
 | 
			
		||||
                    Min altitude: {activity.min_alt}m -{' '}
 | 
			
		||||
                    Max altitude: {activity.max_alt}m
 | 
			
		||||
                  </p>
 | 
			
		||||
                  )}
 | 
			
		||||
                  {activity.ascent && activity.descent && (
 | 
			
		||||
                  <p><i className="fa fa-location-arrow" />{' '}
 | 
			
		||||
                    Ascent: {activity.ascent}m -{' '}
 | 
			
		||||
                    Descent: {activity.descent}m
 | 
			
		||||
                  </p>
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
@@ -73,7 +78,11 @@ class ActivityDisplay extends React.Component {
 | 
			
		||||
                  Map
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="card-body">
 | 
			
		||||
                  <ActivityMap activity={activity} />
 | 
			
		||||
                  {activity.with_gpx ? (
 | 
			
		||||
                    <ActivityMap activity={activity} />
 | 
			
		||||
                  ) : (
 | 
			
		||||
                    'No map'
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,96 @@
 | 
			
		||||
import React from 'react'
 | 
			
		||||
import { connect } from 'react-redux'
 | 
			
		||||
 | 
			
		||||
import { addActivityWithoutGpx } from '../../../actions/activities'
 | 
			
		||||
import { history } from '../../../index'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function FormWithoutGpx (props) {
 | 
			
		||||
  const { onAddSport, sports } = props
 | 
			
		||||
  return (
 | 
			
		||||
    <form
 | 
			
		||||
      onSubmit={event => event.preventDefault()}
 | 
			
		||||
    >
 | 
			
		||||
      <div className="form-group">
 | 
			
		||||
        <label>
 | 
			
		||||
          Sport:
 | 
			
		||||
          <select
 | 
			
		||||
            className="form-control input-lg"
 | 
			
		||||
            name="sport_id"
 | 
			
		||||
            required
 | 
			
		||||
          >
 | 
			
		||||
            <option value="" />
 | 
			
		||||
            {sports.map(sport => (
 | 
			
		||||
              <option key={sport.id} value={sport.id}>
 | 
			
		||||
                {sport.label}
 | 
			
		||||
              </option>
 | 
			
		||||
            ))}
 | 
			
		||||
          </select>
 | 
			
		||||
        </label>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div className="form-group">
 | 
			
		||||
        <label>
 | 
			
		||||
          Activity Date:
 | 
			
		||||
          <input
 | 
			
		||||
            name="activity_date"
 | 
			
		||||
            className="form-control input-lg"
 | 
			
		||||
            type="date"
 | 
			
		||||
          />
 | 
			
		||||
        </label>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div className="form-group">
 | 
			
		||||
        <label>
 | 
			
		||||
          Duration:
 | 
			
		||||
          <input
 | 
			
		||||
            name="duration"
 | 
			
		||||
            className="form-control input-lg"
 | 
			
		||||
            type="text"
 | 
			
		||||
          />
 | 
			
		||||
        </label>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div className="form-group">
 | 
			
		||||
        <label>
 | 
			
		||||
          Distance (km):
 | 
			
		||||
          <input
 | 
			
		||||
            name="distance"
 | 
			
		||||
            className="form-control input-lg"
 | 
			
		||||
            type="number"
 | 
			
		||||
          />
 | 
			
		||||
        </label>
 | 
			
		||||
      </div>
 | 
			
		||||
      <input
 | 
			
		||||
        type="submit"
 | 
			
		||||
        className="btn btn-primary btn-lg btn-block"
 | 
			
		||||
        onClick={event => onAddSport(event)}
 | 
			
		||||
        value="Submit"
 | 
			
		||||
      />
 | 
			
		||||
      <input
 | 
			
		||||
        type="submit"
 | 
			
		||||
        className="btn btn-secondary btn-lg btn-block"
 | 
			
		||||
        onClick={() => history.go(-1)}
 | 
			
		||||
        value="Cancel"
 | 
			
		||||
      />
 | 
			
		||||
    </form>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default connect(
 | 
			
		||||
  () => ({ }),
 | 
			
		||||
  dispatch => ({
 | 
			
		||||
    onAddSport: e => {
 | 
			
		||||
      const data = [].slice
 | 
			
		||||
        .call(e.target.form.elements)
 | 
			
		||||
        .reduce(function(map, obj) {
 | 
			
		||||
          if (obj.name) {
 | 
			
		||||
            if (obj.name === 'duration' || obj.name === 'distance') {
 | 
			
		||||
              map[obj.name] = +obj.value
 | 
			
		||||
            } else {
 | 
			
		||||
              map[obj.name] = obj.value
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          return map
 | 
			
		||||
        }, {})
 | 
			
		||||
      dispatch(addActivityWithoutGpx(data))
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
)(FormWithoutGpx)
 | 
			
		||||
@@ -64,3 +64,7 @@ input, textarea {
 | 
			
		||||
.leaflet-container {
 | 
			
		||||
  height: 240px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.radioLabel {
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,20 @@ export default class MpwoApi {
 | 
			
		||||
      .catch(error => error)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static addActivityWithoutGpx(data) {
 | 
			
		||||
    const request = new Request(`${apiUrl}activities/no_gpx`, {
 | 
			
		||||
      method: 'POST',
 | 
			
		||||
      headers: new Headers({
 | 
			
		||||
        'Content-Type': 'application/json',
 | 
			
		||||
        Authorization: `Bearer ${window.localStorage.getItem('authToken')}`,
 | 
			
		||||
      }),
 | 
			
		||||
      body: JSON.stringify(data)
 | 
			
		||||
    })
 | 
			
		||||
    return fetch(request)
 | 
			
		||||
      .then(response => response.json())
 | 
			
		||||
      .catch(error => error)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static getActivityGpx(activityId) {
 | 
			
		||||
    const request = new Request(`${apiUrl}activities/${activityId}/gpx`, {
 | 
			
		||||
      method: 'GET',
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user