diff options
36 files changed, 676 insertions, 127 deletions
| diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..8259981f8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +FROM debian:stable-slim + +ENV RAILS_ENV=production RAILS_SERVE_STATIC_FILES=true RAILS_LOG_TO_STDOUT=true + +RUN apt-get update && \ +    apt-get install -y --no-install-recommends ruby2.3 && \ +    apt-get install -y --no-install-recommends libpq5 libxml2 zlib1g imagemagick libproj12 && \ +    apt-get install -y --no-install-recommends cron && \ +    apt-get clean && rm -rf /var/lib/apt/lists/* && \ +    gem2.3 install bundler + + +COPY stif-boiv-release.tar.gz / +RUN mkdir /app && apt-get update &&\ +    apt-get -y install --no-install-recommends build-essential ruby2.3-dev libpq-dev libxml2-dev zlib1g-dev libproj-dev&& \ +    tar -C /app -zxf stif-boiv-release.tar.gz && \ +    cd /app && bundle install --local && \ +    apt-get -y remove build-essential ruby2.3-dev libpq-dev libxml2-dev zlib1g-dev && \ +    apt-get clean && apt-get -y autoremove && rm -rf /var/lib/apt/lists/* && \ +    cd /app && rm config/database.yml && mv config/database.yml.docker config/database.yml && \ +    cd /app && rm config/secrets.yml && mv config/secrets.yml.docker config/secrets.yml && \ +    mv script/launch-cron /app && \ +    bundle exec whenever --output '/proc/1/fd/1' --update-crontab stif-boiv --set 'environment=production&bundle_command=bundle exec' --roles=app,db,web + +WORKDIR /app +VOLUME /app/public/uploads + +EXPOSE 3000 + +CMD ["sh", "-c", "bundle exec rake db:migrate && bundle exec rails server -b 0.0.0.0"] @@ -60,7 +60,7 @@ gem 'faraday', '~> 0.9.1'  platforms :ruby do    gem 'therubyracer', '~> 0.12'    gem 'pg' -  gem 'sqlite3' +  #gem 'sqlite3'  end  gem 'activerecord-postgis-adapter', "~> 3.0.0" @@ -139,6 +139,8 @@ gem 'rake'  gem 'devise-async'  gem 'apartment', '~> 1.0.0'  gem 'aasm' +gem 'activerecord-nulldb-adapter' +gem 'puma', '~> 3.10.0'  gem 'newrelic_rpm'  gem 'letter_opener' @@ -158,6 +160,7 @@ group :development do    gem 'bundler-audit'    gem 'spring-commands-rspec'    gem 'dbshell-rails' +  gem 'rack-livereload'    platforms :ruby_20, :ruby_21, :ruby_22 do      gem 'better_errors' diff --git a/Gemfile.lock b/Gemfile.lock index ade052d8a..ba1a90a5a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -356,6 +356,7 @@ GEM        pry (~> 0.10)      pry-rails (0.3.6)        pry (>= 0.10.4) +    puma (3.10.0)      pundit (1.1.0)        activesupport (>= 3.0.0)      quiet_assets (1.1.0) @@ -363,6 +364,8 @@ GEM      rabl (0.13.1)        activesupport (>= 2.3.14)      rack (1.6.8) +    rack-livereload (0.3.16) +      rack      rack-protection (1.5.3)        rack      rack-proxy (0.6.2) @@ -522,7 +525,6 @@ GEM        actionpack (>= 3.0)        activesupport (>= 3.0)        sprockets (>= 2.8, < 4.0) -    sqlite3 (1.3.13)      teaspoon (1.1.5)        railties (>= 3.2.5, < 6)      teaspoon-jasmine (2.3.4) @@ -648,9 +650,11 @@ DEPENDENCIES    polylines    pry-byebug    pry-rails +  puma (~> 3.10.0)    pundit    quiet_assets    rabl +  rack-livereload    rails (~> 4.2.8)    rails-assets-bootstrap-sass-official (~> 3.3.0)!    rails-assets-footable (~> 2.0.3)! @@ -686,7 +690,6 @@ DEPENDENCIES    slim-rails (~> 3.1)    spring    spring-commands-rspec -  sqlite3    teaspoon-jasmine    therubyracer (~> 0.12)    timecop diff --git a/app/assets/stylesheets/base/_config.sass b/app/assets/stylesheets/base/_config.sass index ec1c43e7f..2c226357f 100644 --- a/app/assets/stylesheets/base/_config.sass +++ b/app/assets/stylesheets/base/_config.sass @@ -16,7 +16,7 @@ $blue: #007fbb  $darkgrey: #4b4b4b  $grey: #a4a4a4 -$lightgrey: rgba($grey, 0.15) +$lightgrey: lighten($grey, 25)  $green: #70b12b  $red: #da2f36 diff --git a/app/assets/stylesheets/components/_forms.sass b/app/assets/stylesheets/components/_forms.sass index 47faf19b1..5143d2ba4 100644 --- a/app/assets/stylesheets/components/_forms.sass +++ b/app/assets/stylesheets/components/_forms.sass @@ -284,6 +284,7 @@ table, .table    height: $cbx-size    width: $cbx-size    margin: 0 auto +  transition: transform 0.2s, background-color 0.2s    > input[type='checkbox']      &:not(:checked), &:checked diff --git a/app/assets/stylesheets/modules/_jp_collection.sass b/app/assets/stylesheets/modules/_jp_collection.sass index f579cf87b..14a6b9205 100644 --- a/app/assets/stylesheets/modules/_jp_collection.sass +++ b/app/assets/stylesheets/modules/_jp_collection.sass @@ -5,6 +5,7 @@  #journey_patterns    .table-2entries      .t2e-head +        > .td          position: relative          padding-left: 25px @@ -99,32 +100,195 @@          top: 50%          margin-top: -8px -  // Errors -  .table-2entries .t2e-item-list -    .t2e-item -      position: relative +  .table-2entries +    .t2e-item-list +      & > div +        overflow: visible +      .td +        overflow: hidden +         +      .t2e-item +        position: relative -      .th .vj_tt -        display: inline-block -        vertical-align: top +        .th .vj_tt +          display: inline-block +          vertical-align: top -        + .vj_tt -          margin-left: 5px +          + .vj_tt +            margin-left: 5px -      &.has-error -        &:before -          content: '' -          position: absolute -          top: 0 -          left: 0 -          right: 0 -          bottom: 0 -          border: 2px solid $red - -        > .th -          > div:first-child, > div:first-child + div -            color: $red - -        // Reset default behaviour -        .form-control -          border-color: #ccc +        &.with-costs +          .td +            padding: 15px 8px + +          $link-size: 10px +          .link +            position: absolute +            left: 50px +            width: 10px +            top: -15px +            bottom: -15px +            background: $blue +            z-index: 3 +            opacity: 0.5 +            &:after +              content: "" +              width: $link-size +              height: $link-size +              position: absolute +              top: 50% +              bottom: 50% +              margin-top: -$link-size/2 +              border-top: $link-size/2 solid transparent +              border-left: $link-size/2 solid transparent +              border-right: $link-size/2 solid $blue +              border-bottom: $link-size/2 solid $blue +              transform: rotate(135deg) +              left: 0% +              opacity: 0 +              transition: left 0.2s, opacity 0.2s + +          .headlined .link +            top: 0 +            bottom: -15px + +            &:after +              top: 75% +              margin-top: -$link-size/2 - 1px + +          .activated .link +            &:after +              left: -50% +              opacity: 1 + +          & > div +            position: relative + +          .link +            left: 35px + +          .has_radio +            margin-right: 150px + +          .costs +            background: $blue +            opacity: 0.5 +            padding: 5px +            color: white +            position: absolute +            cursor: not-allowed +            left: 75px +            top: -1px +            transform: translateY(-50%) +            font-size: 0.75em +            transition: background 0.1s +            border: 1px solid white + +            &:hover +              opacity: 1 +              &:after +                opacity: 1 + +            &:after +              opacity: 0.5 +              content: "" +              height: 2px +              position: absolute +              left: -23px +              background: $blue +              right: 100% +              top: 50% +              transition: background 0.1s + +            p +              display: block +              border: none +              margin-bottom: 0 +              i +                margin-right: 3px +                width: 12px +              & + p +                position: relative +                z-index: 2 +                padding-right: 0 +                margin: 0 +                border-right: none + +              input +                display: inline-block +                width: 40px +                border: none +                margin-right: 5px +                color: black + +        .edit-mode +          .costs +            cursor: pointer +            p +              margin-bottom: 5px +              & + p +                margin-bottom: 0 + +            opacity: 1 +            &:after +              opacity: 1 + +          .link +            opacity: 1 + +        .with-headline + .costs +          top: 25% + +        .deactivated .costs +          display: none + +        &.has-error +          &:before +            content: '' +            position: absolute +            top: 0 +            left: 0 +            right: 0 +            bottom: 0 +            border: 2px solid $red + +          > .th +            > div:first-child, > div:first-child + div +              color: $red + +          // Reset default behaviour +          .form-control +            border-color: #ccc + +    .t2e-head +      .td.with-costs +        & > div +          &:not(.headlined) +            height: calc(100% + 6px) +          & > span +            &:after +              top: -15px +              bottom: -9px + +        div.headlined +          &:before +            margin-bottom: 0 +          & > span +            height: calc(100% - (1.4em + 15px)) +            &:after +              top: calc((1.4em + 30px) * -1) +              bottom: 0 + +    .td.with-costs, .with-costs .td +      padding-top: 15px +      padding-bottom: 15px + +      & > div +        height: calc(100% + 15px) +        &.headlined +          &:before +            padding-top: 15px +            padding-bottom: 15px +            height: calc(1.4em + 30px) +            margin-top: -15px +            margin-bottom: 15px diff --git a/app/controllers/journey_patterns_collections_controller.rb b/app/controllers/journey_patterns_collections_controller.rb index 5fe78766c..4c698bb89 100644 --- a/app/controllers/journey_patterns_collections_controller.rb +++ b/app/controllers/journey_patterns_collections_controller.rb @@ -17,35 +17,46 @@ class JourneyPatternsCollectionsController < ChouetteController    alias_method :vehicle_journey, :resource    def show -    @q = route.journey_patterns.search(params[:q]).result(distinct: true).includes(:stop_points) +    @q = route.journey_patterns +    if params[:q].present? +      ids = @q.search(params[:q]).result(distinct: true).pluck(:id) +      @q = @q.where(id: ids) +    end +    @q = @q.includes(:stop_points)      @ppage = 10      @journey_patterns ||= @q.paginate(page: params[:page], per_page: @ppage).order(:name) - -    @stop_points_list = [] -    route.stop_points.each do |sp| -      @stop_points_list << { -        :id => sp.stop_area.id, -        :route_id => sp.try(:route_id), -        :object_id => sp.try(:objectid), -        :position => sp.try(:position), -        :for_boarding => sp.try(:for_boarding), -        :for_alighting => sp.try(:for_alighting), -        :name => sp.stop_area.try(:name), -        :zip_code => sp.stop_area.try(:zip_code), -        :city_name => sp.stop_area.try(:city_name), -        :comment => sp.stop_area.try(:comment), -        :area_type => sp.stop_area.try(:area_type), -        :registration_number => sp.stop_area.try(:registration_number), -        :nearest_topic_name => sp.stop_area.try(:nearest_topic_name), -        :fare_code => sp.stop_area.try(:fare_code), -        :longitude => sp.stop_area.try(:longitude), -        :latitude => sp.stop_area.try(:latitude), -        :long_lat_type => sp.stop_area.try(:long_lat_type), -        :country_code => sp.stop_area.try(:country_code), -        :street_name => sp.stop_area.try(:street_name) -      } +    respond_to do |format| +      format.json do +        @journey_patterns = @journey_patterns.includes(stop_points: {stop_area: :stop_area_referential}) +      end +      format.html do +        @stop_points_list = [] +        route.stop_points.includes(:stop_area).each do |sp| +          @stop_points_list << { +            :id => sp.stop_area.id, +            :route_id => sp.try(:route_id), +            :object_id => sp.try(:objectid), +            :position => sp.try(:position), +            :for_boarding => sp.try(:for_boarding), +            :for_alighting => sp.try(:for_alighting), +            :name => sp.stop_area.try(:name), +            :zip_code => sp.stop_area.try(:zip_code), +            :city_name => sp.stop_area.try(:city_name), +            :comment => sp.stop_area.try(:comment), +            :area_type => sp.stop_area.try(:area_type), +            :registration_number => sp.stop_area.try(:registration_number), +            :nearest_topic_name => sp.stop_area.try(:nearest_topic_name), +            :fare_code => sp.stop_area.try(:fare_code), +            :longitude => sp.stop_area.try(:longitude), +            :latitude => sp.stop_area.try(:latitude), +            :long_lat_type => sp.stop_area.try(:long_lat_type), +            :country_code => sp.stop_area.try(:country_code), +            :street_name => sp.stop_area.try(:street_name) +          } +        end +        @stop_points_list = @stop_points_list.sort_by {|a| a[:position] } +      end      end -    @stop_points_list = @stop_points_list.sort_by {|a| a[:position] }    end    def user_permissions diff --git a/app/controllers/referential_vehicle_journeys_controller.rb b/app/controllers/referential_vehicle_journeys_controller.rb index ad08699a5..8445fc423 100644 --- a/app/controllers/referential_vehicle_journeys_controller.rb +++ b/app/controllers/referential_vehicle_journeys_controller.rb @@ -12,6 +12,7 @@ class ReferentialVehicleJourneysController < ChouetteController    def collection      @q ||= end_of_association_chain.ransack(params[:q])      @vehicle_journeys ||= @q.result.includes(:vehicle_journey_at_stops).paginate page: params[:page], per_page: 10 +    @all_companies = Chouette::Company.where("id IN (#{@referential.vehicle_journeys.select(:company_id).to_sql})").distinct    end  end diff --git a/app/javascript/journey_patterns/actions/index.js b/app/javascript/journey_patterns/actions/index.js index 1c2eb68b2..a70a2e6f2 100644 --- a/app/javascript/journey_patterns/actions/index.js +++ b/app/javascript/journey_patterns/actions/index.js @@ -64,6 +64,11 @@ const actions = {      type : 'DELETE_JOURNEYPATTERN',      index,    }), +  updateJourneyPatternCosts : (index, costs) => ({ +    type : 'UPDATE_JOURNEYPATTERN_COSTS', +    index, +    costs +  }),    closeModal : () => ({      type : 'CLOSE_MODAL'    }), @@ -194,16 +199,13 @@ const actions = {                    }                  })                } -              journeyPatterns.push({ -                name: val.name, -                object_id: val.object_id, -                short_id: val.short_id, -                checksum: val.checksum, -                published_name: val.published_name, -                registration_number: val.registration_number, -                stop_points: val.route_short_description.stop_points, -                deletable: false -              }) +              journeyPatterns.push( +                _.assign({}, val, { +                  stop_points: val.route_short_description.stop_points, +                  costs: val.costs || {}, +                  deletable: false +                }) +              )              }            }            window.currentItemsLength = journeyPatterns.length diff --git a/app/javascript/journey_patterns/components/JourneyPattern.js b/app/javascript/journey_patterns/components/JourneyPattern.js index d4c9816ec..69eff978e 100644 --- a/app/javascript/journey_patterns/components/JourneyPattern.js +++ b/app/javascript/journey_patterns/components/JourneyPattern.js @@ -5,6 +5,17 @@ export default class JourneyPattern extends Component{    constructor(props){      super(props)      this.previousCity = undefined +    this.previousSpId = undefined +    this.updateCosts = this.updateCosts.bind(this) +  } + +  updateCosts(e) { +    let costs = { +      [e.target.dataset.costsKey]: { +        [e.target.name]: parseFloat(e.target.value) +      } +    } +    this.props.onUpdateJourneyPatternCosts(costs)    }    vehicleJourneyURL(jpOid) { @@ -16,16 +27,26 @@ export default class JourneyPattern extends Component{      )    } +  hasFeature(key) { +    return this.props.status.features[key] +  } +    cityNameChecker(sp) {      let bool = false +      if(sp.city_name != this.previousCity){        bool = true        this.previousCity = sp.city_name      } +    return bool +  } + +  spNode(sp, headlined){      return (        <div -        className={(bool) ? 'headlined' : ''} +        className={(headlined) ? 'headlined' : ''}        > +        <div className={'link '}></div>          <span className='has_radio'>            <input              onChange = {(e) => this.props.onCheckboxChange(e)} @@ -61,9 +82,9 @@ export default class JourneyPattern extends Component{    render() {      this.previousCity = undefined - +    this.previousSpId = undefined      return ( -      <div className={'t2e-item' + (this.props.value.deletable ? ' disabled' : '') + (this.props.value.object_id ? '' : ' to_record') + (this.props.value.errors ? ' has-error': '')}> +      <div className={'t2e-item' + (this.props.value.deletable ? ' disabled' : '') + (this.props.value.object_id ? '' : ' to_record') + (this.props.value.errors ? ' has-error': '') + (this.hasFeature('costs_in_journey_patterns') ? ' with-costs' : '')}>          {/* Errors */}          {/* this.props.value.errors ? this.getErrors(this.props.value.errors) : '' */} @@ -112,9 +133,49 @@ export default class JourneyPattern extends Component{            </div>            {this.props.value.stop_points.map((stopPoint, i) =>{ +            let costs = null +            let costsKey = null +            let time = null +            let distance = null +            let time_in_words = null +            if(this.previousSpId && stopPoint.checked){ +              costsKey = this.previousSpId + "-" + stopPoint.id +              costs = this.props.value.costs[costsKey] || {distance: 0, time: 0} +              time = costs['time'] || 0 +              distance = costs['distance'] || 0 +              if(time < 60){ +                time_in_words = time + " min" +              } +              else{ +                let hours = parseInt(time/60) +                time_in_words = hours + " h " + (time - 60*hours) + " min" +              } +            } +            if(stopPoint.checked){ +              this.previousSpId = stopPoint.id +            } +            let headlined = this.cityNameChecker(stopPoint)              return ( -              <div key={i} className='td'> -                {this.cityNameChecker(stopPoint)} +              <div key={i} className={(stopPoint.checked ? 'activated' : 'deactivated') + (this.props.editMode ? ' edit-mode' : '')}> +                <div className={'td' + (headlined ? ' with-headline' : '')}> +                  {this.spNode(stopPoint, headlined)} +                </div> +                {this.hasFeature('costs_in_journey_patterns') && costs && <div className='costs' id={'costs-' + this.props.value.id + '-' + costsKey }> +                  {this.props.editMode && <div> +                    <p> +                      <input type="number" value={costs['distance'] || 0} min='0' name="distance" step="0.01" onChange={this.updateCosts} data-costs-key={costsKey}/> +                      <span>km</span> +                    </p> +                    <p> +                      <input type="number" value={costs['time'] || 0} min='0' name="time" onChange={this.updateCosts} data-costs-key={costsKey}/> +                      <span>min</span> +                    </p> +                  </div>} +                  {!this.props.editMode && <div> +                    <p><i className="fa fa-arrows-h"></i>{(costs['distance'] || 0) + " km"}</p> +                    <p><i className="fa fa-clock-o"></i>{time_in_words}</p> +                  </div>} +                </div>}                </div>              )            })} @@ -129,4 +190,4 @@ JourneyPattern.propTypes = {    onCheckboxChange: PropTypes.func.isRequired,    onOpenEditModal: PropTypes.func.isRequired,    onDeleteJourneyPattern: PropTypes.func.isRequired -}
\ No newline at end of file +} diff --git a/app/javascript/journey_patterns/components/JourneyPatterns.js b/app/javascript/journey_patterns/components/JourneyPatterns.js index 4b2badabb..1e391b0c2 100644 --- a/app/javascript/journey_patterns/components/JourneyPatterns.js +++ b/app/javascript/journey_patterns/components/JourneyPatterns.js @@ -54,6 +54,10 @@ export default class JourneyPatterns extends Component {      }    } +  hasFeature(key) { +    return this.props.status.features[key] +  } +    cityNameChecker(sp) {      let bool = false      if(sp.city_name != this.previousCity){ @@ -115,7 +119,7 @@ export default class JourneyPatterns extends Component {                  </div>                  {this.props.stopPointsList.map((sp, i) =>{                    return ( -                    <div key={i} className='td'> +                    <div key={i} className={'td' + (this.hasFeature('costs_in_journey_patterns') ? ' with-costs' : '')}>                        {this.cityNameChecker(sp)}                      </div>                    ) @@ -131,6 +135,7 @@ export default class JourneyPatterns extends Component {                        onCheckboxChange= {(e) => this.props.onCheckboxChange(e, index)}                        onOpenEditModal= {() => this.props.onOpenEditModal(index, journeyPattern)}                        onDeleteJourneyPattern={() => this.props.onDeleteJourneyPattern(index)} +                      onUpdateJourneyPatternCosts={(costs) => this.props.onUpdateJourneyPatternCosts(index, costs)}                        status= {this.props.status}                        editMode= {this.props.editMode}                        /> @@ -152,4 +157,4 @@ JourneyPatterns.propTypes = {    onCheckboxChange: PropTypes.func.isRequired,    onLoadFirstPage: PropTypes.func.isRequired,    onOpenEditModal: PropTypes.func.isRequired -}
\ No newline at end of file +} diff --git a/app/javascript/journey_patterns/containers/JourneyPatternList.js b/app/javascript/journey_patterns/containers/JourneyPatternList.js index d98734407..d338345f2 100644 --- a/app/javascript/journey_patterns/containers/JourneyPatternList.js +++ b/app/javascript/journey_patterns/containers/JourneyPatternList.js @@ -25,7 +25,10 @@ const mapDispatchToProps = (dispatch) => {      },      onDeleteJourneyPattern: (index) =>{        dispatch(actions.deleteJourneyPattern(index)) -    } +    }, +    onUpdateJourneyPatternCosts: (index, costs) =>{ +      dispatch(actions.updateJourneyPatternCosts(index, costs)) +    },    }  } diff --git a/app/javascript/journey_patterns/reducers/journeyPatterns.js b/app/javascript/journey_patterns/reducers/journeyPatterns.js index 0bbcba976..1ce069522 100644 --- a/app/javascript/journey_patterns/reducers/journeyPatterns.js +++ b/app/javascript/journey_patterns/reducers/journeyPatterns.js @@ -17,6 +17,7 @@ const journeyPattern = (state = {}, action) =>{          published_name: action.data.published_name.value,          registration_number: action.data.registration_number.value,          stop_points: stopPoints, +        costs: {},          deletable: false        }      case 'UPDATE_CHECKBOX_VALUE': @@ -67,6 +68,19 @@ export default function journeyPatterns (state = [], action)  {            return j          }        }) +    case 'UPDATE_JOURNEYPATTERN_COSTS': +      return state.map((j, i) =>{ +        if(i == action.index) { +          const new_costs = Object.assign({}, j.costs) +          Object.keys(action.costs).map((key) => { +            let new_costs_for_key = Object.assign({}, j.costs[key] || {}, action.costs[key]) +            new_costs[key] = new_costs_for_key +          }) +          return _.assign({}, j, {costs: new_costs}) +        } else { +          return j +        } +      })      case 'ADD_JOURNEYPATTERN':        return [          journeyPattern(state, action), @@ -87,4 +101,4 @@ export default function journeyPatterns (state = [], action)  {      default:        return state    } -}
\ No newline at end of file +} diff --git a/app/javascript/packs/journey_patterns/index.js b/app/javascript/packs/journey_patterns/index.js index fde28b45d..367a8830f 100644 --- a/app/javascript/packs/journey_patterns/index.js +++ b/app/javascript/packs/journey_patterns/index.js @@ -16,6 +16,7 @@ var initialState = {    editMode: false,    status: {      policy: window.perms, +    features: window.features,      fetchSuccess: true,      isFetching: false    }, diff --git a/app/models/chouette/journey_pattern.rb b/app/models/chouette/journey_pattern.rb index 366fde188..1ddf7c9fb 100644 --- a/app/models/chouette/journey_pattern.rb +++ b/app/models/chouette/journey_pattern.rb @@ -58,14 +58,14 @@ module Chouette        {          name: item['name'],          published_name: item['published_name'], -        registration_number: item['registration_number'] +        registration_number: item['registration_number'], +        costs: item['costs']        }      end      def self.state_create_instance route, item        # Flag new record, so we can unset object_id if transaction rollback        jp = route.journey_patterns.create(state_permited_attributes(item)) -        # FIXME        # DefaultAttributesSupport will trigger some weird validation on after save        # wich will call to valid?, wich will populate errors @@ -146,5 +146,9 @@ module Chouette          vjas.destroy        end      end + +    def costs +      read_attribute(:costs) || {} +    end    end  end diff --git a/app/models/referential.rb b/app/models/referential.rb index a5d5acbf9..4cddd502e 100644 --- a/app/models/referential.rb +++ b/app/models/referential.rb @@ -156,7 +156,7 @@ class Referential < ActiveRecord::Base    def stop_points      Chouette::StopPoint.all    end -   +    def compliance_check_sets      ComplianceCheckSet.all    end @@ -356,7 +356,13 @@ class Referential < ActiveRecord::Base        end        Rails.logger.info("Schema create benchmark: '#{slug}'\t#{report}") -      Rails.logger.error( "Schema migrations count for Referential #{slug} " + Referential.connection.select_value("select count(*) from #{slug}.schema_migrations;").to_s ) +      Rails.logger.info("Schema migrations count for Referential #{slug}: #{migration_count || '-'}") +    end +  end + +  def migration_count +    if self.class.connection.table_exists?("#{slug}.schema_migrations") +      self.class.connection.select_value("select count(*) from #{slug}.schema_migrations;")      end    end diff --git a/app/views/api/v1/journey_patterns/show.rabl b/app/views/api/v1/journey_patterns/show.rabl index 86876f3fb..815b1cf0b 100644 --- a/app/views/api/v1/journey_patterns/show.rabl +++ b/app/views/api/v1/journey_patterns/show.rabl @@ -5,16 +5,21 @@ extends "api/v1/trident_objects/show"    attributes attr, :unless => lambda { |m| m.send( attr).nil?}  end +if has_feature? :costs_in_journey_patterns +  attribute :costs +end +  node(:route_short_description) do |journey_pattern|    partial("api/v1/routes/short_description", :object => journey_pattern.route)  end  node(:vehicle_journey_object_ids) do |journey_pattern| -  journey_pattern.vehicle_journeys.map(&:objectid) +  journey_pattern.vehicle_journeys.pluck(:objectid)  end unless root_object.vehicle_journeys.empty?  child :stop_points => :stop_area_short_descriptions do |stop_points|    node do |stop_point| +    cache stop_point.stop_area_id      partial("api/v1/stop_areas/short_description", :object => stop_point.stop_area)    end  end diff --git a/app/views/referential_vehicle_journeys/_filters.html.slim b/app/views/referential_vehicle_journeys/_filters.html.slim index 963da8cea..6d22e1378 100644 --- a/app/views/referential_vehicle_journeys/_filters.html.slim +++ b/app/views/referential_vehicle_journeys/_filters.html.slim @@ -5,6 +5,10 @@        span.input-group-btn          button.btn.btn-default#search-btn type='submit'            span.fa.fa-search +  .ffg-row +    .form-group.togglable +      = f.label Chouette::VehicleJourney.human_attribute_name(:company_id), required: false, class: 'control-label' +      = f.input :company_id_eq_any, collection: @all_companies.select(:id, :name).order(name: :asc), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + l.name + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}    .actions      = link_to 'Effacer', referential_vehicle_journeys_path(@referential), class: 'btn btn-link' diff --git a/config/database.yml b/config/database.yml index 2a3ddf5d0..60f1d032f 100644 --- a/config/database.yml +++ b/config/database.yml @@ -21,6 +21,7 @@ test: &test  production:    <<: *default +  adapter: <%= ENV.fetch 'RAILS_DB_ADAPTER', 'postgis' %>    database: chouette2  cucumber: diff --git a/config/database.yml.docker b/config/database.yml.docker new file mode 100644 index 000000000..f49b22b87 --- /dev/null +++ b/config/database.yml.docker @@ -0,0 +1,11 @@ +<%= ENV.fetch 'RAILS_ENV', 'production' %>: +  adapter: <%= ENV.fetch 'RAILS_DB_ADAPTER', 'postgis' %> +  encoding: unicode +  pool: <%= ENV.fetch 'RAILS_DB_POOLSIZE', '5' %> +  host: <%= ENV.fetch 'RAILS_DB_HOST', 'db' %> +  port: <%= ENV.fetch 'RAILS_DB_PORT', '5432' %> +  schema_search_path: 'public,shared_extensions' +  postgis_schema: 'shared_extensions' +  database: <%= ENV.fetch 'RAILS_DB_NAME', 'chouette' %> +  username: <%= ENV.fetch 'RAILS_DB_USER', 'chouette' %> +  password: <%= ENV.fetch 'RAILS_DB_PASSWORD' %> diff --git a/config/environments/development.rb b/config/environments/development.rb index e9cd16c34..24a4ed6b8 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -93,4 +93,6 @@ Rails.application.configure do    config.validation_spec = "http://www.chouette.mobi/neptune-validation/v21/"    config.i18n.available_locales = [:fr, :en] + +  config.middleware.insert_after(ActionDispatch::Static, Rack::LiveReload) if ENV['LIVERELOAD']  end diff --git a/config/environments/production.rb b/config/environments/production.rb index 8e21f0919..cb50cd145 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -19,7 +19,9 @@ Rails.application.configure do    # config.action_dispatch.rack_cache = true    # Disable Rails's static asset server (Apache or nginx will already do this). -  config.serve_static_files = false +  # config.serve_static_files = false +  config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? +  # config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?    # Compress JavaScripts and CSS.    config.assets.js_compressor = :uglifier @@ -53,9 +55,10 @@ Rails.application.configure do    #if ENV['OS'] == 'Windows_NT'    #  # args = log_path,number of files,file sizes    #  config.logger = Logger.new("C:/chouette/logs/chouette2.log", 5, 10.megabytes) -  config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new("rails/chouette2").tap do |syslog| -                                                     syslog.level = Logger::INFO -                                                   end) +  config.logger = Logger.new(STDOUT) +  #config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new("rails/chouette2").tap do |syslog| +  #                                                   syslog.level = Logger::INFO +  #                                                 end)    # Use a different cache store in production.    # config.cache_store = :mem_cache_store @@ -84,42 +87,31 @@ Rails.application.configure do    config.active_record.dump_schema_after_migration = false -  config.action_mailer.default_url_options = { :host => 'my-domain-name.com' } +  config.action_mailer.default_url_options = { :host => ENV.fetch('MAIL_HOST','iboo.stif.info') }    # Configure the e-mail address which will be shown in Devise::Maile -  config.mailer_sender = "chouette-production@my-domain-name.com" - -  ActionMailer::Base.smtp_settings = { -    :address        => "smtp.sample.com", -    :port           => 25, -    :domain         => "sample.com", -    :user_name      => "smtp_user", -    :password       => "smtp_password", -    :authentication => :login -  } +  config.mailer_sender = ENV.fetch('MAIL_FROM', 'STIF Iboo <noreply@stif.info>') +  config.action_mailer.default_options = { from: ENV.fetch('MAIL_FROM', 'STIF Iboo <noreply@stif.info>') } +  config.action_mailer.smtp_settings = { address: ENV.fetch('SMTP_HOST', 'mail.stif.info') } +  config.action_mailer.asset_host          = ENV.fetch('MAIL_ASSETS_URL_BASE','http://iboo.stif.info')    # Specific theme for each company    # AFIMB -  config.company_name = "afimb" -  config.company_theme = "#61970b" # AFIMB color -  config.company_contact = "http://www.chouette.mobi/club-utilisateurs/contact-support/" -  config.accept_user_creation = true - -  # CITYWAY -  # config.company_name = "cityway" -  # config.company_theme = "#32adb0" -  # config.company_contact = "http://www.cityway.fr/contact/?rub_code=14" -  # config.accept_user_creation = false - -  config.chouette_authentication_settings = { -    type: "cas", -    cas_server: "https://portail-server/sessions", -    cas_validate_url: "http://portail-server/sessions/proxyValidate" -  } -  config.stif_portail_api = { -    key: "api_token_for_portail_goes_here", -    url: "http://portail-server" -  } +  config.company_name = ENV.fetch('COMPANY_NAME',"STIF") +  config.company_theme = ENV.fetch('COMPANY_THEME',"#61970b") # AFIMB color +  config.company_contact = ENV.fetch('COMPANY_CONTACT',"http://www.chouette.mobi/club-utilisateurs/contact-support/") +  config.accept_user_creation = ENV.fetch('ACCEPT_USER_CREATION','0')=='1'?true:false + +  config.chouette_authentication_settings = JSON.parse(ENV.fetch('AUTH_SETTINGS','{ +    "type": "cas", +    "cas_server": "https://portail.stif.info/sessions", +    "cas_validate_url": "http://portail.stif.info/sessions/proxyValidate" +  }'),{symbolize_names: true}) + +  config.stif_portail_api = JSON.parse(ENV.fetch('SESAME_API_SETTINGS','{ +    "key": "xxxxxxxxxxx", +    "url": "http://portail.stif.info" +  }'),{symbolize_names: true})    # file to data for demo    # config.demo_data = "/path/to/demo.zip" @@ -135,13 +127,13 @@ Rails.application.configure do    config.i18n.available_locales = [:fr, :en]    # REFLEX api url -  config.reflex_api_url = "https://pprod.reflex.stif.info/ws/reflex/V1/service=getData" +  config.reflex_api_url = ENV.fetch('REFLEX_API_URL',"https://pprod.reflex.stif.info/ws/reflex/V1/service=getData")    # CODIFLIGNE api url -  config.codifligne_api_url = "https://pprod.codifligne.stif.info/rest/v1/lc/getlist" +  config.codifligne_api_url = ENV.fetch('CODIFLIGNE_API_URL',"https://pprod.codifligne.stif.info/rest/v1/lc/getlist")    # IEV -  config.iev_url = "http://worker-server:8080" -  config.rails_host = ENV.fetch('RAILS_HOST') +  config.iev_url = ENV.fetch('IEV_URL',"http://iev:8080") +  config.rails_host = ENV.fetch('RAILS_HOST','http://front')    # Set node env for browserify-rails    # config.browserify_rails.node_env = "production" diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index e44d8df52..bc60dbe20 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -7,4 +7,9 @@ Sidekiq.configure_server do |config|        pendings.map { |sync| sync.failed({error: 'Failed by Sidekiq reboot', processing_time: 0}) }      end    end +  config.redis = { url: ENV.fetch('SIDEKIQ_REDIS_URL', 'redis://redis:6379/12') } +end + +Sidekiq.configure_client do |config| +  config.redis = { url: ENV.fetch('SIDEKIQ_REDIS_URL', 'redis://redis:6379/12') }  end diff --git a/config/schedule.rb b/config/schedule.rb index 8aa21076f..08488c255 100644 --- a/config/schedule.rb +++ b/config/schedule.rb @@ -42,3 +42,7 @@ end  every 5.minutes do    rake "import:notify_parent"  end + +every 1.minute do +  command "/bin/echo HeartBeat" +end diff --git a/config/secrets.yml.docker b/config/secrets.yml.docker new file mode 100644 index 000000000..1bef794a8 --- /dev/null +++ b/config/secrets.yml.docker @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key is used for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! + +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +# You can use `rake secret` to generate a secure secret key. + +# Make sure the secrets in this file are kept private +# if you're sharing your code publicly. + +<%= ENV.fetch 'RAILS_ENV', 'production' %>: +  secret_key_base: <%= ENV.fetch 'SECRET_KEY_BASE', 'change_this_string_for_something_more_secure' %> +  api_endpoint: <%= ENV.fetch 'IEV_API_ENDPOINT', 'http://iev:8080/chouette_iev/' %> +  api_token: <%= ENV.fetch 'IEV_API_TOKEN', 'change this according to IEV configuration' %> diff --git a/db/migrate/20180103084612_add_costs_to_journey_patterns.rb b/db/migrate/20180103084612_add_costs_to_journey_patterns.rb new file mode 100644 index 000000000..6795e3671 --- /dev/null +++ b/db/migrate/20180103084612_add_costs_to_journey_patterns.rb @@ -0,0 +1,5 @@ +class AddCostsToJourneyPatterns < ActiveRecord::Migration +  def change +    add_column :journey_patterns, :costs, :json +  end +end diff --git a/db/schema.rb b/db/schema.rb index 667b95c84..fa48a5643 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,12 +11,12 @@  #  # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171227113809) do +ActiveRecord::Schema.define(version: 20180105102012) do    # These are extensions that must be enabled in order to support this database    enable_extension "plpgsql" -  enable_extension "postgis"    enable_extension "hstore" +  enable_extension "postgis"    enable_extension "unaccent"    create_table "access_links", id: :bigserial, force: :cascade do |t| @@ -442,6 +442,7 @@ ActiveRecord::Schema.define(version: 20171227113809) do      t.string   "checksum"      t.text     "checksum_source"      t.string   "data_source_ref" +    t.json     "costs"    end    add_index "journey_patterns", ["objectid"], name: "journey_patterns_objectid_key", unique: true, using: :btree diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..3c3367a01 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,73 @@ +version: "3.0" +services: +  front: +    image: registry.af83.io/stif-iboo:latest +    environment: +      RAILS_DB_HOST: "192.168.15.98" +      RAILS_DB_USER: "chouette" +      RAILS_DB_NAME: "chouette2" +      RAILS_DB_PASSWORD: "chouette" +      SECRET_KEY_BASE: "KSKSJDHF0QDKJDSfkSJDFKSJDfh98SDF" +      SIDEKIQ_REDIS_URL: "redis://redis:6379/12" +      MAIL_HOST: "iboo-rec.af83.priv" +      MAIL_ASSETS_URL_BASE: "http://iboo-rec.af83.priv" +      MAIL_FROM: "docker <infra+docker@af83.com>" +      SMTP_HOST: "mail.af83.priv" +      REDIS_CACHE_STORE: "redis://redis:6379/0/cache" +      SESAME_API_URL: '{"key":"1234567890azertyuiop","url":"http://172.28.100.18:3001/"}' +      AUTH_SETTINGS: '{"type":"cas","cas_server":"http://172.28.100.18:3001/sessions","cas_validate_url":"http://172.28.100.18:3001/sessions/proxyValidate"}' +    volumes: +      - /data/iboo/uploads:/app/public/uploads +    ports: +      - "3004:3000" +    restart: always +    depends_on: +      - redis +  async: +    image: registry.af83.io/stif-iboo:latest +    environment: +      RAILS_DB_HOST: "192.168.15.98" +      RAILS_DB_USER: "chouette" +      RAILS_DB_NAME: "chouette2" +      RAILS_DB_PASSWORD: "chouette" +      SECRET_KEY_BASE: "KSKSJDHF0QDKJDSfkSJDFKSJDfh98SDF" +      SIDEKIQ_REDIS_URL: "redis://redis:6379/12" +      MAIL_HOST: "iboo-rec.af83.priv" +      MAIL_ASSETS_URL_BASE: "http://iboo-rec.af83.priv" +      MAIL_FROM: "docker <infra+docker@af83.com>" +      SMTP_HOST: "mail.af83.priv" +      SESAME_API_URL: '{"key":"1234567890azertyuiop","url":"http://172.28.100.18:3001/"}' +      AUTH_SETTINGS: '{"type":"cas","cas_server":"http://172.28.100.18:3001/sessions","cas_validate_url":"http://172.28.100.18:3001/sessions/proxyValidate"}' +    command: bundle exec sidekiq -e production +    restart: always +    depends_on: +      - redis +  sync: +    image: registry.af83.io/stif-iboo:latest +    environment: +      RAILS_DB_HOST: "192.168.15.98" +      RAILS_DB_USER: "chouette" +      RAILS_DB_NAME: "chouette2" +      RAILS_DB_PASSWORD: "chouette" +      SECRET_KEY_BASE: "KSKSJDHF0QDKJDSfkSJDFKSJDfh98SDF" +      SIDEKIQ_REDIS_URL: "redis://redis:6379/12" +      MAIL_HOST: "iboo-rec.af83.priv" +      MAIL_ASSETS_URL_BASE: "http://iboo-rec.af83.priv" +      MAIL_FROM: "docker <infra+docker@af83.com>" +      SMTP_HOST: "mail.af83.priv" +      SESAME_API_URL: '{"key":"1234567890azertyuiop","url":"http://172.28.100.18:3001/"}' +      AUTH_SETTINGS: '{"type":"cas","cas_server":"http://172.28.100.18:3001/sessions","cas_validate_url":"http://172.28.100.18:3001/sessions/proxyValidate"}' +    command: bash launch-cron +    restart: always +    depends_on: +      - redis +  redis: +    image: redis:latest +#  db: +#    image: mdillon/postgis +#    environment: +#      POSTGRES_USER: iboo +#      POSTGRES_PASSWORD: stif_iboo_db +#    volumes: +#     - /data/iboo/postgresql:/var/lib/postgresql/data +#    restart: always diff --git a/lib/tasks/install.rake b/lib/tasks/install.rake index ccc6f2450..1150825b2 100644 --- a/lib/tasks/install.rake +++ b/lib/tasks/install.rake @@ -11,6 +11,7 @@ task :package do    sh "bundle exec rake assets:precompile RAILS_ENV=production"    sh "tar -rf tmp/package/stif-boiv-release-#{release_name}.tar vendor/cache"    sh "tar -rf tmp/package/stif-boiv-release-#{release_name}.tar public/assets" +  sh "tar -rf tmp/package/stif-boiv-release-#{release_name}.tar public/packs"    %w{deploy-helper.sh README sidekiq-stif-boiv.service stif-boiv.conf stif-boiv-setup.sh template-stif-boiv.sql}.each do |f|      cp "install/#{f}", "tmp/package/#{f}" @@ -21,3 +22,26 @@ task :package do    sh "tar -czf stif-boiv-#{release_name}.tar.gz -C tmp/package ."    sh "rm -rf tmp/package vendor/cache"  end + +desc "generate all-in-1 tar.gz package for docker" +task :pkg4docker do +  release_name = Time.now.strftime('%Y%m%d%H%M%S') + +  rm_rf "tmp/package" +  mkdir_p "tmp/package" + +  sh "git archive --format=tar --output=tmp/package/stif-boiv-release-#{release_name}.tar HEAD" + +  sh "bundle package --all" +#  sh "RAILS_DB_ADAPTER=nulldb bundle exec rake assets:clobber RAILS_ENV=production" +#  sh "RAILS_DB_ADAPTER=nulldb bundle exec rake assets:precompile RAILS_ENV=production" +  sh "bundle exec rake assets:clobber RAILS_ENV=production" +  sh "bundle exec rake assets:precompile RAILS_ENV=production" +  sh "tar -rf tmp/package/stif-boiv-release-#{release_name}.tar vendor/cache" +  sh "tar -rf tmp/package/stif-boiv-release-#{release_name}.tar public/assets" +  sh "tar -rf tmp/package/stif-boiv-release-#{release_name}.tar public/packs" + +  sh "gzip -c tmp/package/stif-boiv-release-#{release_name}.tar > tmp/stif-boiv-release.tar.gz" + +  sh "rm -rf tmp/package vendor/cache" +end diff --git a/script/launch-cron b/script/launch-cron new file mode 100644 index 000000000..183e5a331 --- /dev/null +++ b/script/launch-cron @@ -0,0 +1,20 @@ +#!/bin/bash + +function append_var_if_defined +{ +    VAR_NAME=$1 +    OUTPUT=$2 +    grep -qE "^${VAR_NAME}=" ${OUTPUT}||env|grep -E "^${VAR_NAME}=">>${OUTPUT} +} +VAR_LIST="RAILS_DB_HOST RAILS_DB_PORT RAILS_DB_USER RAILS_DB_PASSWORD RAILS_DB_NAME MAIL_HOST MAIL_ASSETS_URL_BASE MAIL_FROM SMTP_HOST SECRET_BASE SIDEKIQ_REDIS_URL CODIFLIGNE_API_URL REDIS_CACHE_STORE_URL RAILS_LOG_TO_STDOUT PATH" + +TMPF=$(tempfile) +for v in $VAR_LIST; do +    append_var_if_defined $v $TMPF    +done + +crontab -l >> $TMPF +cat $TMPF |crontab - +rm $TMPF + +exec cron -f diff --git a/spec/controllers/journey_patterns_collections_controller_spec.rb b/spec/controllers/journey_patterns_collections_controller_spec.rb index a3efbc23f..e2adc59f4 100644 --- a/spec/controllers/journey_patterns_collections_controller_spec.rb +++ b/spec/controllers/journey_patterns_collections_controller_spec.rb @@ -25,4 +25,20 @@ RSpec.describe JourneyPatternsCollectionsController, :type => :controller do                                                     'journey_patterns.update'  => true }.to_json)      end    end + +  context "get show" do +    login_user + +    let( :referential ){ Referential.first } +    let( :line ){ create(:line) } +    let( :route ){ create(:route, line: line) } + +    let(:request){ +      get :show, referential_id: referential.id, line_id: line.id, route_id: route.id, format: :json +    } +    it 'should be successful' do +      request +      expect(response).to be_success +    end +  end  end diff --git a/spec/factories/chouette_journey_pattern.rb b/spec/factories/chouette_journey_pattern.rb index 05d8d536a..d96302e7f 100644 --- a/spec/factories/chouette_journey_pattern.rb +++ b/spec/factories/chouette_journey_pattern.rb @@ -13,6 +13,12 @@ FactoryGirl.define do          j.stop_point_ids = j.route.stop_points.map(&:id)          j.departure_stop_point_id = j.route.stop_points.first.id          j.arrival_stop_point_id = j.route.stop_points.last.id +        j.costs = { +          "1-2": { +            distance: 10, +            time: 10 +          } +        }        end      end @@ -35,5 +41,3 @@ FactoryGirl.define do    end  end - - diff --git a/spec/javascript/journey_patterns/actions_spec.js b/spec/javascript/journey_patterns/actions_spec.js index 2542fa2f4..60d6d88bb 100644 --- a/spec/javascript/journey_patterns/actions_spec.js +++ b/spec/javascript/journey_patterns/actions_spec.js @@ -112,6 +112,22 @@ describe('when clicking on a journey pattern delete button', () => {      expect(actions.deleteJourneyPattern(index)).toEqual(expectedAction)    })  }) +describe('when changing on a journey pattern costs', () => { +  it('should create an action to update journey pattern', () => { +    const index = 1 +    const costs = { +      "1-2": { +        distance: 1 +      } +    } +    const expectedAction = { +      type: 'UPDATE_JOURNEYPATTERN_COSTS', +      index, +      costs +    } +    expect(actions.updateJourneyPatternCosts(index, costs)).toEqual(expectedAction) +  }) +})  describe('when clicking on validate button inside edit modal', () => {    it('should create an action to save journey pattern modifications', () => {      const index = 1 diff --git a/spec/javascript/journey_patterns/reducers/journey_patterns_spec.js b/spec/javascript/journey_patterns/reducers/journey_patterns_spec.js index 24780ab5a..bfa87d24a 100644 --- a/spec/javascript/journey_patterns/reducers/journey_patterns_spec.js +++ b/spec/javascript/journey_patterns/reducers/journey_patterns_spec.js @@ -47,7 +47,10 @@ describe('journeyPatterns reducer', () => {          object_id : 'o1',          published_name: 'M1',          registration_number: '', -        stop_points: fakeStopPoints +        stop_points: fakeStopPoints, +        costs: { + +        }        },        {          deletable: false, @@ -55,7 +58,13 @@ describe('journeyPatterns reducer', () => {          object_id : 'o2',          published_name: 'M2',          registration_number: '', -        stop_points: fakeStopPoints +        stop_points: fakeStopPoints, +        costs: { +          "1-2": { +            distance: 0, +            time: 10, +          } +        }        }      ]    }) @@ -83,7 +92,8 @@ describe('journeyPatterns reducer', () => {        published_name: 'M3',        registration_number: '',        deletable: false, -      stop_points: stopPoints +      stop_points: stopPoints, +      costs: {}      }, ...state])    }) @@ -100,6 +110,28 @@ describe('journeyPatterns reducer', () => {      ).toEqual([newState, state[1]])    }) +  it('should handle UPDATE_JOURNEYPATTERN_COSTS', () => { +    const costs = { +      "1-2": { +        distance: 1 +      } +    } +    const new_costs = { +      "1-2": { +        distance: 1, +        time: 10, +      } +    } +    const new_state = Object.assign({}, state[1], {costs: new_costs}) +    expect( +      jpReducer(state, { +        type: 'UPDATE_JOURNEYPATTERN_COSTS', +        index: 1, +        costs +      }) +    ).toEqual([state[0], new_state]) +  }) +    it('should handle DELETE_JOURNEYPATTERN', () => {      expect(        jpReducer(state, { diff --git a/spec/models/chouette/journey_pattern_spec.rb b/spec/models/chouette/journey_pattern_spec.rb index ea7c2a2e9..077c85e85 100644 --- a/spec/models/chouette/journey_pattern_spec.rb +++ b/spec/models/chouette/journey_pattern_spec.rb @@ -39,6 +39,7 @@ describe Chouette::JourneyPattern, :type => :model do          item['stop_points'] = jp.stop_points.map do |sp|            { 'id' => sp.stop_area_id }          end +        item['costs'] = jp.costs        end      end @@ -72,6 +73,14 @@ describe Chouette::JourneyPattern, :type => :model do        expect(new_state['new_record']).to be_truthy      end +    it 'should create journey_pattern with state_update' do +      new_state = journey_pattern_to_state(build(:journey_pattern, objectid: nil, route: route)) +      collection = [new_state] +      expect { +        Chouette::JourneyPattern.state_update route, collection +      }.to change{Chouette::JourneyPattern.count}.by(1) +    end +      it 'should delete journey_pattern' do        state['deletable'] = true        collection = [state] diff --git a/tmp/pids/.gitkeep b/tmp/pids/.gitkeep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tmp/pids/.gitkeep | 
