diff options
| author | Robert | 2018-01-08 14:01:13 +0100 |
|---|---|---|
| committer | Robert | 2018-01-08 14:01:13 +0100 |
| commit | 6123fa3580b0048f39157c35cecef3c914e06b4c (patch) | |
| tree | 15939baa35d07581bc2adf0804da7fcd5905ec46 | |
| parent | dcc56c4d02f9f71b13dd38251367306842cd10c7 (diff) | |
| parent | 2ea51b513170b1b6578b47ca10b995e07ee11fef (diff) | |
| download | chouette-core-6123fa3580b0048f39157c35cecef3c914e06b4c.tar.bz2 | |
Merge branch 'master' of github.com:af83/stif-boiv
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 |
