diff options
269 files changed, 4476 insertions, 2602 deletions
diff --git a/.gitignore b/.gitignore index 28960565b..373908d42 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ # Ignore all logfiles and tempfiles. /log/*.log /log/importers +/log/exporters /tmp *~ public/assets/ @@ -135,7 +135,6 @@ gem 'rabl' gem 'carrierwave', '~> 1.0' gem 'sidekiq' -gem 'sinatra' gem 'whenever', github: 'af83/whenever', require: false # '~> 0.9' gem 'rake' gem 'devise-async' diff --git a/Gemfile.lock b/Gemfile.lock index 63d78f9cd..251de0e1a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -332,7 +332,7 @@ GEM net-ssh (4.1.0) net-ssh-gateway (2.0.0) net-ssh (>= 4.0.0) - newrelic_rpm (4.0.0.332) + newrelic_rpm (4.8.0.341) nokogiri (1.8.2) mini_portile2 (~> 2.3.0) open4 (1.3.4) @@ -368,10 +368,10 @@ GEM railties (>= 3.1, < 5.0) rabl (0.13.1) activesupport (>= 2.3.14) - rack (1.6.8) + rack (1.6.9) rack-livereload (0.3.16) rack - rack-protection (1.5.3) + rack-protection (1.5.4) rack rack-proxy (0.6.3) rack @@ -509,10 +509,6 @@ GEM simplecov-html (0.10.0) simplecov-rcov (0.2.3) simplecov (>= 0.4.1) - sinatra (1.4.8) - rack (~> 1.5) - rack-protection (~> 1.4) - tilt (>= 1.3, < 3) sixarm_ruby_unaccent (1.2.0) slim (3.0.7) temple (~> 0.7.6) @@ -700,7 +696,6 @@ DEPENDENCIES simple_form (~> 3.1.0) simplecov simplecov-rcov - sinatra slim-rails (~> 3.1) spring spring-commands-rspec diff --git a/INSTALL.md b/INSTALL.md index e44b072f4..392ef5d9f 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -8,6 +8,17 @@ Example with [rvm](https://rvm.io/) (other solutions : rbenv, packages..): rvm install 2.3.1 ``` +Nokogiri on macOS + +http://www.nokogiri.org/tutorials/installing_nokogiri.html tells us that `xz` can cause troubles, here is what to do + +``` +brew unlink xz +gem install nokogiri # or bundle install +brew link xz +``` + + ## Node and Yarn Yarn needs node. If you use Node Version Manager [NVM](https://github.com/creationix/nvm) you can rely on the content of `.nvmrc`. Otherwise please make sure to use a compatible version, still best to use the same as indicated by `.nvrmc`. @@ -15,7 +26,7 @@ Yarn needs node. If you use Node Version Manager [NVM](https://github.com/creati * Install node ```sh -nvm install 6.12.0 +nvm install 6.13.0 ``` * Install [yarn](https://yarnpkg.com/lang/en/docs/install/) @@ -36,41 +47,6 @@ sudo apt-get update && sudo apt-get install yarn yarn install ``` -### Installation Caveats - -#### Node Related Issue, libv8 - -`libv8` might cause you troubles, depending on your local configuration. If you have `libv8` installed (probably because of `node.js`) you might need to tell bundler/Rubygems to use the system version. - -```sh -bundle config build.libv8 --with-system-v8 -bundle -``` - -or - -```sh -gem install libv8 -v '<version>' -- --with-system-v8 -bundle -``` - -You will get the correct value of `<version>` from bundler's error message. - -#### Node Related Issue, therubyracer - -Even after `libv8` installation working, the gem `therubyracer` might not like the `libv8` version chosen. - -In that case however we can let the gem make its own choice: - -```sh -gem uninstall libv8 -gem install therubyracer -v '<version>' -``` - -The version to be installed is indicated in the error message bundler gave us in the first place. - -This will install an appropriate `libv8` version and we can continue with `bundle`. - ## Postgres ### Create user @@ -89,53 +65,12 @@ When promted for the password enter the highly secure string `chouette`. As documented [here](https://github.com/dryade/georuby-ext/issues/2) we need some more libs before we can start the `rake` setup tasks. - On mac/OS : ```sh brew install postgis ``` -<<<<<<< HEAD -### Authentication - -See `config.chouette_authentication_settings`. - -Use the database authentication or get an invitation to [STIF Portail](http://stif-portail-dev.af83.priv/). - -### Run seed - -Run : - - bundle exec rake db:seed - -Two users are created : stif-boiv@af83.com/secret and stif-boiv+transporteur@af83.com/secret - -If you have access to STIF CodifLigne and Reflex : - - bundle exec rake codifligne:sync - bundle exec rake reflex:sync - -To create Referential with some data (Route, JourneyPattern, VehicleJourney, etc) : - - bundle exec rake referential:create - -# Troubleshooting - -If PG complains about illegal type `hstore` in your tests that is probably because the shared extension is not installed, here is what to do: - -#### Check installation - -* Run tests - - bundle exec rake spec - bundle exec rake teaspoon - -* Start local server - - bundle exec rails server - -======= On debian/ubuntu system : ```sh @@ -156,50 +91,34 @@ Go into your local repository and install the gems bundle install ``` -#### Nokogiri on macOS - -http://www.nokogiri.org/tutorials/installing_nokogiri.html tells us that `xz` can cause troubles, here is what to do - -``` -brew unlink xz -gem install nokogiri # or bundle install -brew link xz -``` - ### Database #### Create database ```sh bundle exec rake db:create db:migrate -RAILS_ENV=test bundle exec rake db:create db:migrate ``` -#### Load seed datas ->>>>>>> master +#### Use seed + +Run : ```sh bundle exec rake db:seed:stif ``` -#### Synchronise datas with lines and stop areas referentials - -* Launch Sidekiq - -```sh -bundle exec sidekiq -``` +Two users are created : stif-boiv@af83.com/secret and stif-boiv+transporteur@af83.com/secret -* Execute the Synchronization Tasks +#### Synchronize with STIF CODIFLIGNE (Line) and REFLEX (StopArea) ```sh bundle exec rake codifligne:sync bundle exec rake reflex:sync ``` -**N.B.** These are asynchronious tasks, you can observe the launched jobs in your [Sidekiq Console](http://localhost:3000/sidekiq) +**N.B.** These are asynchronous tasks, you can observe the launched jobs in your [Sidekiq Console](http://localhost:3000/sidekiq) -#### Data in various Apartments (Referentials) +#### Create Referential To create `Referential` objects with some data (`Route`, `JourneyPattern`, `VehicleJourney`, etc), you need to wait codifligne and reflex jobs finished. And then you can launch : @@ -207,33 +126,32 @@ To create `Referential` objects with some data (`Route`, `JourneyPattern`, `Vehi bundle exec rake referential:create ``` -### Check installation +### Run tests -#### Run tests - -#### Rspec +* Rspec (Rails test) ```sh bundle exec rake spec -bundle exec rake teaspoon ``` -If Postgres complains about illegal type `hstore` or `unaccent` in your tests that is probably because the shared extension is not installed, here is what to do: - - bundle exec rake db:test:purge - -Thanks to `lib/tasks/extensions.rake`. +* Jest (JavaScript tests) -#### Jest (React integration specs) +```sh +grunt jest #to run the whole specs. +grunt #to watch for changes and automatically run corresponding tests. +``` -`grunt jest` to run the whole specs. +### Run -`grunt` to watch for changes and automatically run corresponding tests. +Launch Sidekiq -#### Start local server +```sh +bundle exec sidekiq +``` ```sh bin/webpack-dev-server // Launch webpack server to compile assets on the fly bundle exec rails server // Launch rails server ``` + You need to have an account on [STIF Portail](http://stif-portail-dev.af83.priv/) to connect to the Rails application. diff --git a/app/assets/javascripts/forms.coffee b/app/assets/javascripts/forms.coffee index b7ae3c6ca..9543220d0 100644 --- a/app/assets/javascripts/forms.coffee +++ b/app/assets/javascripts/forms.coffee @@ -25,7 +25,7 @@ isEdge = !isIE && !!window.StyleMedia if $('.page-action').children('.formSubmitr').length > 0 $('.page-action').children('.formSubmitr').remove() - $('.formSubmitr').appendTo('.page-action') + $('.formSubmitr').appendTo('.page-action').addClass('sticky-action') if isIE || isEdge $('.formSubmitr').off() diff --git a/app/assets/javascripts/main_menu.coffee b/app/assets/javascripts/main_menu.coffee index e943f448a..22ecebd25 100644 --- a/app/assets/javascripts/main_menu.coffee +++ b/app/assets/javascripts/main_menu.coffee @@ -21,8 +21,9 @@ $ -> sticker = () -> limit = 51 + offset = 30 - if $(window).scrollTop() >= limit + if $(window).scrollTop() >= limit + offset if stickyActions.length == 0 if ($('.page-action .small').length > 0) stickyActions.push @@ -53,7 +54,7 @@ $ -> for child in item.content child.appendTo $('.sticky-paction') - else + else if $(window).scrollTop() <= limit - offset $('#main_nav').removeClass 'sticky' if $('#menu_top').find('.sticky-content').length > 0 diff --git a/app/assets/stylesheets/OpenLayers/custom.sass b/app/assets/stylesheets/OpenLayers/custom.sass index fa874d924..0675b0ba6 100644 --- a/app/assets/stylesheets/OpenLayers/custom.sass +++ b/app/assets/stylesheets/OpenLayers/custom.sass @@ -2,15 +2,20 @@ .list-group-item & margin-top: 15px - .routes-labels + .ol-routes-layers + position: absolute + right: 42px + top: .5em padding: 0 - display: flex - justify-content: space-between - flex-wrap: wrap margin: 5px -5px - li - list-style-type: none - flex: 1 0 25% + width: 30% + background-color: transparentize(white, 0.2) + border-radius: 3px + max-height: 90% + overflow-y: scroll + a + display: block + width: calc(100% - 10px) border: 1px solid $lightgrey padding: 5px margin: 5px @@ -18,11 +23,22 @@ cursor: pointer color: $blue white-space: nowrap - + text-overflow: ellipsis + overflow: hidden + font-size: 0.6em &:hover - background: $orange + text-decoration: none + font-weight: bold + + &.active + background: $blue color: white + .ol-routes-layers-button-wrapper + position: absolute + right: .5em + top: .5em + .ol-scale-line background-color: transparent bottom: 5px @@ -55,20 +71,20 @@ .ol-zoom background-color: transparent - .ol-zoom-in, .ol-zoom-out - background-color: $darkblue - color: #fff - border-radius: 3px - margin: 0 + .ol-zoom-in, .ol-zoom-out, .ol-routes-layers-button + background-color: $darkblue + color: #fff + border-radius: 3px + margin: 0 - .ol-zoom-in - border-bottom-left-radius: 0 - border-bottom-right-radius: 0 + .ol-zoom-in + border-bottom-left-radius: 0 + border-bottom-right-radius: 0 - .ol-zoom-out - margin-top: 1px - border-top-left-radius: 0 - border-top-right-radius: 0 + .ol-zoom-out + margin-top: 1px + border-top-left-radius: 0 + border-top-right-radius: 0 .ol-zoomslider margin: 2px diff --git a/app/assets/stylesheets/components/_modals.sass b/app/assets/stylesheets/components/_modals.sass index e52a2e125..14b783c51 100644 --- a/app/assets/stylesheets/components/_modals.sass +++ b/app/assets/stylesheets/components/_modals.sass @@ -50,3 +50,9 @@ $modalW: 600px .modal-footer border-color: rgba($blue, 0.25) padding: 15px 30px + + .has-error .form-group + margin-bottom: -10px + + .form-group + margin-bottom: 25px diff --git a/app/assets/stylesheets/components/_referential_overview.sass b/app/assets/stylesheets/components/_referential_overview.sass index fc48411a3..7a0cc98c5 100644 --- a/app/assets/stylesheets/components/_referential_overview.sass +++ b/app/assets/stylesheets/components/_referential_overview.sass @@ -117,6 +117,7 @@ padding: 7px 10px border-bottom: 1px solid $grey font-size: 0.8em + display: block &:last-child border-bottom: none .number @@ -135,10 +136,9 @@ overflow: hidden .name display: inline-block - width: $left-size - 50px() + width: $left-size - 10px white-space: nowrap line-height: 20px - margin-left: 5px text-overflow: ellipsis overflow: hidden vertical-align: bottom @@ -182,8 +182,8 @@ height: 100% transition: margin 0.5s background: white - &:last-child - box-shadow: 0 -10px 10px rgba(0,0,0,0.5) + // &:last-child + // box-shadow: 0 -10px 10px rgba(0,0,0,0.5) .week-span left: 15px top: 15px @@ -246,18 +246,17 @@ &.selected, &:hover color: $blue background-color: transparentize($blue, 0.7) + + &:hover + background-color: transparentize(white, 0.7) &:after content: "" left: -1px right: -1px top: 100% height: 10000px - background-color: transparentize($blue, 0.7) position: absolute z-index: 4 - &:hover - background-color: transparentize(white, 0.7) - &:after background-color: transparentize(white, 0.7) .line diff --git a/app/assets/stylesheets/modules/_vj_collection.sass b/app/assets/stylesheets/modules/_vj_collection.sass index d9079daa2..3ff0828ea 100644 --- a/app/assets/stylesheets/modules/_vj_collection.sass +++ b/app/assets/stylesheets/modules/_vj_collection.sass @@ -88,10 +88,12 @@ .table-2entries .t2e-head - .detailed-timetables + .detailed-timetables, .detailed-purchase-windows + &:after + left: 0 .fa margin-right: 5px - .detailed-timetables-bt + .detailed-timetables-bt, .detailed-purchase-windows-bt text-decoration: none .fa margin-right: 5px @@ -117,7 +119,7 @@ top: 50% margin-top: -8px - .detailed-timetables + .detailed-timetables, .detailed-purchase-windows padding-top: 10px text-align: left margin-bottom: -5px @@ -129,6 +131,13 @@ a text-decoration: none border: none + white-space: nowrap + overflow: hidden + text-overflow: ellipsis + display: inline-block + margin: 0 + line-height: 1em + max-width: 100% &:before position: absolute left: 0px @@ -147,7 +156,19 @@ padding-top: 8px font-weight: bold - .t2e-item-list .detailed-timetables > div + .detailed-purchase-windows + margin-bottom: 12px + position: relative + &:after + position: absolute + left: -8px + bottom: 0 + right: -8px + content: "" + border-top: 1px solid $lightgrey + + .t2e-item-list .detailed-timetables > div, + .t2e-item-list .detailed-purchase-windows > div, border-left: none &:after top: 50% diff --git a/app/controllers/api/v1/imports_controller.rb b/app/controllers/api/v1/imports_controller.rb index 3d7f4ca79..dc2df0697 100644 --- a/app/controllers/api/v1/imports_controller.rb +++ b/app/controllers/api/v1/imports_controller.rb @@ -1,11 +1,11 @@ class Api::V1::ImportsController < Api::V1::IbooController - defaults :resource_class => WorkbenchImport + defaults :resource_class => Import::Workbench belongs_to :workbench def create args = workbench_import_params.merge(creator: 'Webservice') @import = parent.workbench_imports.create(args) - if @import.valid? + if @import.valid? create! else render json: { status: "error", messages: @import.errors.full_messages } diff --git a/app/controllers/api/v1/internals/netex_imports_controller.rb b/app/controllers/api/v1/internals/netex_imports_controller.rb index c8e33f7b8..c2b7b20cc 100644 --- a/app/controllers/api/v1/internals/netex_imports_controller.rb +++ b/app/controllers/api/v1/internals/netex_imports_controller.rb @@ -25,13 +25,13 @@ module Api private def find_netex_import - @netex_import = NetexImport.find(params[:id]) + @netex_import = Import::Netex.find(params[:id]) rescue ActiveRecord::RecordNotFound render json: { - status: "error", + status: "error", message: "Record not found" } - finish_action! + finish_action! end def find_workbench @@ -52,7 +52,7 @@ module Api attributes = attributes.merge referential_id: @new_referential.id - @netex_import = NetexImport.new attributes + @netex_import = Import::Netex.new attributes @netex_import.save! unless @netex_import.referential diff --git a/app/controllers/api/v1/netex_imports_controller.rb b/app/controllers/api/v1/netex_imports_controller.rb index d86c1fcd8..2654fa088 100644 --- a/app/controllers/api/v1/netex_imports_controller.rb +++ b/app/controllers/api/v1/netex_imports_controller.rb @@ -34,7 +34,7 @@ module Api attributes = attributes.merge referential_id: @new_referential.id - @netex_import = NetexImport.new attributes + @netex_import = Import::Netex.new attributes @netex_import.save! unless @netex_import.referential diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 45b7f55f6..c4961123d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -28,7 +28,7 @@ class ApplicationController < ActionController::Base protected def user_not_authorized - redirect_to forbidden_path + render 'errors/forbidden', status: 403 end def current_organisation diff --git a/app/controllers/autocomplete_lines_controller.rb b/app/controllers/autocomplete_lines_controller.rb new file mode 100644 index 000000000..8398a92c1 --- /dev/null +++ b/app/controllers/autocomplete_lines_controller.rb @@ -0,0 +1,17 @@ +class AutocompleteLinesController < ChouetteController + include ReferentialSupport + + respond_to :json, only: :index + + protected + + def collection + @lines = referential.line_referential.lines + + @lines = @lines + .by_name(params[:q]) + .search(params[:q]) + .result + .paginate(page: params[:page]) + end +end diff --git a/app/controllers/calendars_controller.rb b/app/controllers/calendars_controller.rb index 75d4cbd09..cc7570d65 100644 --- a/app/controllers/calendars_controller.rb +++ b/app/controllers/calendars_controller.rb @@ -31,10 +31,11 @@ class CalendarsController < ChouetteController end def create - create! do - if @calendar.valid? && has_feature?('application_days_on_calendars') - redirect_to([:edit, @calendar]) - return + create! do |success, failure| + if has_feature?('application_days_on_calendars') + success.html do + redirect_to([:edit, @workgroup, @calendar]) + end end end end @@ -125,4 +126,4 @@ class CalendarsController < ChouetteController scope end -end
\ No newline at end of file +end diff --git a/app/controllers/compliance_check_messages_controller.rb b/app/controllers/compliance_check_messages_controller.rb index 7c416584e..36745981e 100644 --- a/app/controllers/compliance_check_messages_controller.rb +++ b/app/controllers/compliance_check_messages_controller.rb @@ -7,7 +7,7 @@ class ComplianceCheckMessagesController < ChouetteController def index index! do |format| format.csv { - send_data ComplianceCheckMessageExport.new(compliance_check_messages: collection).to_csv(:col_sep => "\;", :quote_char=>'"', force_quotes: true, server_url: request.base_url) , :filename => "compliance_check_set_errors_#{line_code}_#{Time.now.to_i}.csv" + send_data ComplianceCheckMessageExport.new(compliance_check_messages: collection).to_csv(:col_sep => "\;", :quote_char=>'"', force_quotes: true, server_url: request.base_url) , :filename => "compliance_check_set_errors_#{line_code}_#{Date.today.to_s}.csv" } end end diff --git a/app/controllers/compliance_check_sets_controller.rb b/app/controllers/compliance_check_sets_controller.rb index 271598428..62b0e6ba3 100644 --- a/app/controllers/compliance_check_sets_controller.rb +++ b/app/controllers/compliance_check_sets_controller.rb @@ -12,7 +12,7 @@ class ComplianceCheckSetsController < ChouetteController @q_for_form = scope.ransack(params[:q]) format.html { @compliance_check_sets = ComplianceCheckSetDecorator.decorate( - @q_for_form.result.order(created_at: :desc) + @q_for_form.result.order(created_at: :desc).paginate(page: params[:page], per_page: 30) ) } end diff --git a/app/controllers/compliance_checks_controller.rb b/app/controllers/compliance_checks_controller.rb index 81749e292..ad32bc538 100644 --- a/app/controllers/compliance_checks_controller.rb +++ b/app/controllers/compliance_checks_controller.rb @@ -1,4 +1,5 @@ class ComplianceChecksController < InheritedResources::Base - - + belongs_to :workbench do + belongs_to :compliance_check_set + end end diff --git a/app/controllers/compliance_control_sets_controller.rb b/app/controllers/compliance_control_sets_controller.rb index 8f9251155..6461b38c8 100644 --- a/app/controllers/compliance_control_sets_controller.rb +++ b/app/controllers/compliance_control_sets_controller.rb @@ -36,11 +36,15 @@ class ComplianceControlSetsController < ChouetteController private def collection - scope = self.ransack_period_range(scope: ComplianceControlSet.all, error_message: t('imports.filters.error_period_filter'), query: :where_updated_at_between) - @q_for_form = scope.ransack(params[:q]) - compliance_control_sets = @q_for_form.result - compliance_control_sets = joins_with_associated_objects(compliance_control_sets).order(sort_column + ' ' + sort_direction) if sort_column && sort_direction - @compliance_control_sets = compliance_control_sets.paginate(page: params[:page], per_page: 30) + @compliance_control_sets ||= begin + scope = end_of_association_chain.all + scope = self.ransack_period_range(scope: scope, error_message: t('imports.filters.error_period_filter'), query: :where_updated_at_between) + @q_for_form = scope.ransack(params[:q]) + compliance_control_sets = @q_for_form.result + compliance_control_sets = joins_with_associated_objects(compliance_control_sets).order(sort_column + ' ' + sort_direction) if sort_column && sort_direction + compliance_control_sets = compliance_control_sets.paginate(page: params[:page], per_page: 30) + end + end def decorate_compliance_control_sets(compliance_control_sets) @@ -82,9 +86,9 @@ class ComplianceControlSetsController < ChouetteController case params[:sort] when 'owner_jdc' collection.joins("LEFT JOIN organisations ON compliance_control_sets.organisation_id = organisations.id") - when 'control_numbers' + when 'control_numbers' collection.joins("LEFT JOIN compliance_controls ON compliance_controls.compliance_control_set_id = compliance_control_sets.id").group(:id) - else + else collection end end diff --git a/app/controllers/concerns/iev_interfaces.rb b/app/controllers/concerns/iev_interfaces.rb new file mode 100644 index 000000000..aa4d3fe6a --- /dev/null +++ b/app/controllers/concerns/iev_interfaces.rb @@ -0,0 +1,69 @@ +module IevInterfaces + extend ActiveSupport::Concern + + included do + before_action only: [:index] { set_date_time_params("started_at", DateTime) } + before_action :ransack_status_params, only: [:index] + respond_to :html + belongs_to :workbench + end + + def show + show! do + instance_variable_set "@#{collection_name.singularize}", resource.decorate(context: { + workbench: @workbench + }) + end + end + + def index + index! do |format| + format.html { + if collection.out_of_bounds? + redirect_to params.merge(:page => 1) + end + collection = decorate_collection(collection) + } + end + end + + protected + + def collection + scope = parent.send(collection_name).where(parent_id: nil) + if index_model.name.demodulize != "Base" + scope = scope.where(type: index_model.name) + end + + scope = self.ransack_period_range(scope: scope, error_message: t("#{collection_name}.filters.error_period_filter"), query: :where_started_at_in) + + @q = scope.search(params[:q]) + + unless instance_variable_get "@#{collection_name}" + coll = if sort_column && sort_direction + @q.result(distinct: true).order(sort_column + ' ' + sort_direction).paginate(page: params[:page], per_page: 10) + else + @q.result(distinct: true).order(:name).paginate(page: params[:page], per_page: 10) + end + instance_variable_set "@#{collection_name}", decorate_collection(coll) + end + instance_variable_get "@#{collection_name}" + end + + private + def ransack_status_params + if params[:q] + return params[:q].delete(:status_eq_any) if params[:q][:status_eq_any].empty? || ( (resource_class.status.values & params[:q][:status_eq_any]).length >= 4 ) + params[:q][:status_eq_any].push("new", "running") if params[:q][:status_eq_any].include?("pending") + params[:q][:status_eq_any].push("aborted", "canceled") if params[:q][:status_eq_any].include?("failed") + end + end + + def sort_column + parent.imports.column_names.include?(params[:sort]) ? params[:sort] : 'created_at' + end + + def sort_direction + %w[asc desc].include?(params[:direction]) ? params[:direction] : 'desc' + end +end diff --git a/app/controllers/export_tasks_controller.rb b/app/controllers/export_tasks_controller.rb deleted file mode 100644 index b889c1882..000000000 --- a/app/controllers/export_tasks_controller.rb +++ /dev/null @@ -1,87 +0,0 @@ -class ExportTasksController < ChouetteController - include ReferentialSupport - defaults :resource_class => ExportTask - - respond_to :html, :only => [:new, :create] - respond_to :js, :only => [:new, :create] - belongs_to :referential - - def new - @available_exports = available_exports - begin - new! - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t(error.locale_for_error) - redirect_to referential_path(@referential) - end - end - - def create - @available_exports = available_exports - begin - create! do |success, failure| - success.html { redirect_to referential_exports_path(@referential) } - end - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t(error.locale_for_error) - redirect_to referential_path(@referential) - end - end - - def references - references_type = params[:filter].pluralize - references = @referential.send(references_type).where("name ilike ?", "%#{params[:q]}%").select("id, name") - respond_to do |format| - format.json do - render :json => references.collect { |child| { :id => child.id, :name => child.name } } - end - end - end - - protected - - def available_exports - export_task_parameters = params[:export_task] - if export_task_parameters.present? - @available_exports = [ - export_task_parameters[:data_format] == "neptune" ? build_resource : NeptuneExport.new(:referential_id => @referential.id ), - export_task_parameters[:data_format] == "netex" ? build_resource : NetexExport.new(:referential_id => @referential.id ), - export_task_parameters[:data_format] == "gtfs" ? build_resource : GtfsExport.new(:referential_id => @referential.id ), - export_task_parameters[:data_format] == "hub" ? build_resource : HubExport.new(:referential_id => @referential.id ), - export_task_parameters[:data_format] == "kml" ? build_resource : KmlExport.new(:referential_id => @referential.id ) - ] - else - @available_exports = [ - NeptuneExport.new(:referential_id => @referential.id ), - NetexExport.new(:referential_id => @referential.id ), - GtfsExport.new(:referential_id => @referential.id ), - HubExport.new(:referential_id => @referential.id ), - KmlExport.new(:referential_id => @referential.id ) - ] - end - end - - def build_resource - @export_task ||= if params[:export_task].present? - export_task_parameters = params[:export_task] - case export_task_parameters[:data_format] - when "neptune" - NeptuneExport.new(export_task_parameters) - when "netex" - NetexExport.new(export_task_parameters) - when "gtfs" - GtfsExport.new(export_task_parameters) - when "hub" - HubExport.new(export_task_parameters) - when "kml" - KmlExport.new(export_task_parameters) - end - else - NeptuneExport.new - end - - end - -end diff --git a/app/controllers/exports_controller.rb b/app/controllers/exports_controller.rb index ccc163e34..a5282a514 100644 --- a/app/controllers/exports_controller.rb +++ b/app/controllers/exports_controller.rb @@ -1,74 +1,50 @@ -require 'will_paginate/array' -require 'open-uri' - class ExportsController < ChouetteController - include ReferentialSupport - defaults :resource_class => Export - - respond_to :html, :only => [:show, :index, :destroy, :exported_file] - respond_to :js, :only => [:index] - belongs_to :referential - - def index - begin - index! - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t(error.locale_for_error) - redirect_to referential_path(@referential) + include PolicyChecker + include RansackDateFilter + include IevInterfaces + skip_before_action :authenticate_user!, only: [:upload] + defaults resource_class: Export::Base, collection_name: 'exports', instance_name: 'export' + + def upload + if params[:token] == resource.token_upload + resource.file = params[:file] + resource.save! + redirect_to [resource.workbench, resource] + else + user_not_authorized end end - def show - begin - show! - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t(error.locale_for_error) - redirect_to referential_path(@referential) - end - end + private - def destroy - begin - destroy! - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t(error.locale_for_error) - redirect_to referential_path(@referential) - end + def index_model + Export::Base end - def exported_file - # WARNING : files under 10kb in size get treated as StringIO by OpenUri - # http://stackoverflow.com/questions/10496874/why-does-openuri-treat-files-under-10kb-in-size-as-stringio - OpenURI::Buffer.send :remove_const, 'StringMax' if OpenURI::Buffer.const_defined?('StringMax') - OpenURI::Buffer.const_set 'StringMax', 0 - begin - send_file open(resource.file_path), { :type => "application/#{resource.filename_extension}", :disposition => "attachment", :filename => resource.filename } - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t(error.locale_for_error) - redirect_to referential_path(@referential) + def build_resource + Export::Base.force_load_descendants if Rails.env.development? + @export ||= Export::Base.new(*resource_params) do |export| + export.workbench = parent + export.creator = current_user.name end + @export end - protected - - def export_service - ExportService.new(@referential) - end - - def resource - @export ||= export_service.find( params[:id] ) - @line_items = @export.report.line_items - if @line_items.size > 500 - @line_items = @line_items.paginate(page: params[:page], per_page: 20) + def export_params + permitted_keys = %i(name type referential_id) + export_class = params[:export] && params[:export][:type] && params[:export][:type].safe_constantize + if export_class + permitted_keys += export_class.options.keys end - @export + params.require(:export).permit(permitted_keys) end - def collection - @exports ||= export_service.all.sort_by{ |export| export.created_at }.reverse.paginate(:page => params[:page]) + def decorate_collection(exports) + ExportDecorator.decorate( + exports, + context: { + workbench: @workbench + } + ) end end diff --git a/app/controllers/import_messages_controller.rb b/app/controllers/import_messages_controller.rb index 286bb0ce8..5e39445fb 100644 --- a/app/controllers/import_messages_controller.rb +++ b/app/controllers/import_messages_controller.rb @@ -1,15 +1,15 @@ class ImportMessagesController < ChouetteController - defaults resource_class: ImportMessage, collection_name: 'import_messages', instance_name: 'import_message' + defaults resource_class: Import::Message, collection_name: 'import_messages', instance_name: 'import_message' respond_to :csv - belongs_to :import, :parent_class => Import do - belongs_to :import_resource, :parent_class => ImportResource + belongs_to :import, :parent_class => Import::Base do + belongs_to :import_resource, :parent_class => Import::Resource end def index index! do |format| format.csv { - send_data ImportMessageExport.new(:import_messages => @import_messages).to_csv(:col_sep => "\;", :quote_char=>'"', force_quotes: true) , :filename => "import_errors_#{@import_resource.name.gsub('.xml', '')}_#{Time.now.to_i}.csv" + send_data ImportMessageExport.new(:import_messages => @import_messages).to_csv(:col_sep => "\;", :quote_char=>'"', force_quotes: true) , :filename => "import_errors_#{@import_resource.name.gsub('.xml', '')}_#{Date.today.to_s}.csv" } end end @@ -20,7 +20,7 @@ class ImportMessagesController < ChouetteController end def parent - @import_resource ||= ImportResource.find(params[:import_resource_id]) + @import_resource ||= Import::Resource.find(params[:import_resource_id]) end end diff --git a/app/controllers/import_resources_controller.rb b/app/controllers/import_resources_controller.rb index ea78394a1..6d1977e0c 100644 --- a/app/controllers/import_resources_controller.rb +++ b/app/controllers/import_resources_controller.rb @@ -1,5 +1,5 @@ class ImportResourcesController < ChouetteController - defaults resource_class: ImportResource, collection_name: 'import_resources', instance_name: 'import_resource' + defaults resource_class: Import::Resource, collection_name: 'import_resources', instance_name: 'import_resource' respond_to :html belongs_to :import diff --git a/app/controllers/import_tasks_controller.rb b/app/controllers/import_tasks_controller.rb deleted file mode 100644 index 1a349087d..000000000 --- a/app/controllers/import_tasks_controller.rb +++ /dev/null @@ -1,69 +0,0 @@ -class ImportTasksController < ChouetteController - include ReferentialSupport - defaults :resource_class => ImportTask - - respond_to :html, :only => [:new, :create] - respond_to :js, :only => [:new, :create] - belongs_to :referential - - def new - @available_imports = available_imports - begin - new! - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t('iev.exception.default') - redirect_to referential_path(@referential) - end - end - - def create - @available_imports = available_imports - begin - create! do |success, failure| - success.html { redirect_to referential_imports_path(@referential) } - end - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t('iev.exception.default') - redirect_to referential_path(@referential) - end - end - - protected - - def available_imports - import_task_parameters = params[:import_task] - - if import_task_parameters.present? - @available_imports = [ - import_task_parameters[:data_format] == "neptune" ? build_resource : NeptuneImport.new(:referential_id => @referential.id ), - import_task_parameters[:data_format] == "netex" ? build_resource : NetexImport.new(:referential_id => @referential.id ), - import_task_parameters[:data_format] == "gtfs" ? build_resource : GtfsImport.new(:referential_id => @referential.id ) - ] - else - @available_imports = [ - NeptuneImport.new(:referential_id => @referential.id ), - NetexImport.new(:referential_id => @referential.id ), - GtfsImport.new(:referential_id => @referential.id ) - ] - end - end - - def build_resource - @import_task ||= if params[:import_task].present? - import_task_parameters = params[:import_task] - case import_task_parameters[:data_format] - when "neptune" - NeptuneImport.new(import_task_parameters) - when "netex" - @import_task = NetexImport.new(import_task_parameters) - when "gtfs" - @import_task = GtfsImport.new(import_task_parameters) - end - else - @import_task = NeptuneImport.new - end - end - -end diff --git a/app/controllers/imports_controller.rb b/app/controllers/imports_controller.rb index 7a999d657..8d7a723a0 100644 --- a/app/controllers/imports_controller.rb +++ b/app/controllers/imports_controller.rb @@ -1,31 +1,9 @@ class ImportsController < ChouetteController include PolicyChecker include RansackDateFilter - before_action only: [:index] { set_date_time_params("started_at", DateTime) } + include IevInterfaces skip_before_action :authenticate_user!, only: [:download] - defaults resource_class: Import, collection_name: 'imports', instance_name: 'import' - before_action :ransack_status_params, only: [:index] - respond_to :html - belongs_to :workbench - - def show - show! do - @import = @import.decorate(context: { - workbench: @workbench - }) - end - end - - def index - index! do |format| - format.html { - if collection.out_of_bounds? - redirect_to params.merge(:page => 1) - end - @imports = decorate_imports(@imports) - } - end - end + defaults resource_class: Import::Base, collection_name: 'imports', instance_name: 'import' def download if params[:token] == resource.token_download @@ -35,33 +13,14 @@ class ImportsController < ChouetteController end end - protected - def collection - scope = parent.imports.where(type: "WorkbenchImport") - - scope = self.ransack_period_range(scope: scope, error_message: t('imports.filters.error_period_filter'), query: :where_started_at_in) - - @q = scope.search(params[:q]) - - if sort_column && sort_direction - @imports ||= @q.result(distinct: true).order(sort_column + ' ' + sort_direction).paginate(page: params[:page], per_page: 10) - else - @imports ||= @q.result(distinct: true).order(:name).paginate(page: params[:page], per_page: 10) - end - end - private - def ransack_status_params - if params[:q] - return params[:q].delete(:status_eq_any) if params[:q][:status_eq_any].empty? || ( (Import.status.values & params[:q][:status_eq_any]).length >= 4 ) - params[:q][:status_eq_any].push("new", "running") if params[:q][:status_eq_any].include?("pending") - params[:q][:status_eq_any].push("aborted", "canceled") if params[:q][:status_eq_any].include?("failed") - end + def index_model + Import::Workbench end - + def build_resource - @import ||= WorkbenchImport.new(*resource_params) do |import| + @import ||= Import::Workbench.new(*resource_params) do |import| import.workbench = parent import.creator = current_user.name end @@ -76,14 +35,7 @@ class ImportsController < ChouetteController ) end - def sort_column - parent.imports.column_names.include?(params[:sort]) ? params[:sort] : 'created_at' - end - def sort_direction - %w[asc desc].include?(params[:direction]) ? params[:direction] : 'desc' - end - - def decorate_imports(imports) + def decorate_collection(imports) ImportDecorator.decorate( imports, context: { diff --git a/app/controllers/referentials_controller.rb b/app/controllers/referentials_controller.rb index 5267c15d8..6e3694547 100644 --- a/app/controllers/referentials_controller.rb +++ b/app/controllers/referentials_controller.rb @@ -7,6 +7,8 @@ class ReferentialsController < ChouetteController respond_to :json, :only => :show respond_to :js, :only => :show + before_action :check_cloning_source_is_accessible, only: %i(new create) + def new new! do build_referential @@ -175,6 +177,12 @@ class ReferentialsController < ChouetteController ) end + def check_cloning_source_is_accessible + return unless params[:from] + source = Referential.find params[:from] + return user_not_authorized unless current_user.organisation.workgroups.include?(source.workbench.workgroup) + end + def load_workbench @workbench ||= Workbench.find(params[:workbench_id]) if params[:workbench_id] @workbench ||= resource&.workbench if params[:id] diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index e38a92982..152da4fa2 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -5,7 +5,7 @@ class StatusesController < ChouetteController status = { referentials_blocked: Referential.blocked.count, - imports_blocked: Import.blocked.count, + imports_blocked: Import::Base.blocked.count, compliance_check_sets_blocked: ComplianceCheckSet.blocked.count } status[:status] = global_status status diff --git a/app/controllers/workbenches_controller.rb b/app/controllers/workbenches_controller.rb index 2a71fe811..35438eaaf 100644 --- a/app/controllers/workbenches_controller.rb +++ b/app/controllers/workbenches_controller.rb @@ -3,7 +3,10 @@ class WorkbenchesController < ChouetteController include RansackDateFilter before_action only: [:show] { set_date_time_params("validity_period", Date) } defaults resource_class: Workbench - respond_to :html, only: [:show, :index] + + include PolicyChecker + + respond_to :html, except: :destroy def index redirect_to dashboard_path @@ -24,7 +27,6 @@ class WorkbenchesController < ChouetteController current_workbench_id: params[:id] } ) - show! end def delete_referentials @@ -38,6 +40,11 @@ class WorkbenchesController < ChouetteController end private + + def workbench_params + params.require(:workbench).permit(:import_compliance_control_set_id, :merge_compliance_control_set_id) + end + def resource @workbench = current_organisation.workbenches.find params[:id] end diff --git a/app/decorators/export_decorator.rb b/app/decorators/export_decorator.rb new file mode 100644 index 000000000..26682f383 --- /dev/null +++ b/app/decorators/export_decorator.rb @@ -0,0 +1,21 @@ +class ExportDecorator < AF83::Decorator + decorates Export::Base + + set_scope { context[:workbench] } + + define_instance_method :export_status_css_class do + cls ='' + cls = 'overheaded-success' if object.status == 'successful' + cls = 'overheaded-warning' if object.status == 'warning' + cls = 'overheaded-danger' if %w[failed aborted canceled].include? object.status + cls + end + + create_action_link do |l| + l.content t('exports.actions.new') + end + + with_instance_decorator do |instance_decorator| + instance_decorator.show_action_link + end +end diff --git a/app/decorators/import_decorator.rb b/app/decorators/import_decorator.rb index 1964365ae..a20dfbc42 100644 --- a/app/decorators/import_decorator.rb +++ b/app/decorators/import_decorator.rb @@ -1,5 +1,5 @@ class ImportDecorator < AF83::Decorator - decorates Import + decorates Import::Base set_scope { context[:workbench] } diff --git a/app/decorators/stop_point_decorator.rb b/app/decorators/stop_point_decorator.rb index e777e2b56..4ff5bce9c 100644 --- a/app/decorators/stop_point_decorator.rb +++ b/app/decorators/stop_point_decorator.rb @@ -10,26 +10,5 @@ class StopPointDecorator < AF83::Decorator ) end end - - instance_decorator.edit_action_link do |l| - l.content h.t('stop_points.actions.edit') - l.href do - h.edit_stop_area_referential_stop_area_path( - object.stop_area.stop_area_referential, - object.stop_area - ) - end - end - - instance_decorator.destroy_action_link do |l| - l.content h.destroy_link_content('stop_points.actions.destroy') - l.href do - h.referential_stop_area_path( - object.referential, - object.stop_area - ) - end - l.data confirm: h.t('stop_points.actions.destroy_confirm') - end end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 0058c210d..356c7e69e 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -19,7 +19,7 @@ module ApplicationHelper return object.full_name end - local = "#{object.model_name.name.underscore.pluralize}.#{params[:action]}.title" + local = "#{object.model_name.name.underscore.pluralize}.#{params[:action]}.title" if object.try(:name) t(local, name: object.name || object.id) else diff --git a/app/helpers/compliance_check_resources_helper.rb b/app/helpers/compliance_check_resources_helper.rb index 95cabed88..19b152e8d 100644 --- a/app/helpers/compliance_check_resources_helper.rb +++ b/app/helpers/compliance_check_resources_helper.rb @@ -4,8 +4,8 @@ module ComplianceCheckResourcesHelper def compliance_check_resource_status(status) cls = '' cls = 'success' if status == 'OK' - cls = 'warning' if status == 'WARNING' - cls = 'danger' if %w[ERROR IGNORED].include? status + cls = 'warning' if %w[WARNING IGNORED].include? status + cls = 'danger' if status == 'ERROR' content_tag :span, '', class: "fa fa-circle text-#{cls}" end diff --git a/app/helpers/compliance_control_sets_helper.rb b/app/helpers/compliance_control_sets_helper.rb index 448d5c008..0c63cb139 100644 --- a/app/helpers/compliance_control_sets_helper.rb +++ b/app/helpers/compliance_control_sets_helper.rb @@ -1,7 +1,7 @@ module ComplianceControlSetsHelper def organisations_filters_values - [current_organisation, Organisation.find_by_name("STIF")].uniq + [current_organisation, *Organisation.find_by_name("STIF")].uniq end def floating_links ccs_id diff --git a/app/helpers/exports_helper.rb b/app/helpers/exports_helper.rb index 8ac494cfc..4e92c7e38 100644 --- a/app/helpers/exports_helper.rb +++ b/app/helpers/exports_helper.rb @@ -1,6 +1,25 @@ # -*- coding: utf-8 -*- module ExportsHelper - + def export_status status + import_status status + end + + def export_option_input form, export, attr, option_def, type + opts = { required: option_def[:required], input_html: {value: export.try(attr) || option_def[:default_value]}, as: option_def[:type], selected: export.try(attr) || option_def[:default_value]} + opts[:collection] = option_def[:collection] if option_def.has_key?(:collection) + opts[:collection] = export.instance_exec(&option_def[:collection]) if option_def[:collection].is_a?(Proc) + opts[:label] = t "activerecord.attributes.export.#{type.name.demodulize.underscore}.#{attr}" + form.input attr, opts + end + + def export_message_content message + if message.message_key == "full_text" + message.message_attributes["text"] + else + t([message.class.name.underscore.gsub('/', '_').pluralize, message.message_key].join('.'), message.message_attributes.symbolize_keys) + end + end + def fields_for_export_task_format(form) begin render :partial => export_partial_name(form), :locals => { :form => form } @@ -8,7 +27,7 @@ module ExportsHelper "" end end - + def export_partial_name(form) "fields_#{form.object.format.underscore}_export" end @@ -22,7 +41,7 @@ module ExportsHelper end.join.html_safe end end - + def compliance_icon( export_task) return nil unless export_task.compliance_check_task export_task.compliance_check_task.tap do |cct| @@ -33,5 +52,5 @@ module ExportsHelper end end end - + end diff --git a/app/helpers/stop_areas_helper.rb b/app/helpers/stop_areas_helper.rb index 05ae042f5..fa99f1b4c 100644 --- a/app/helpers/stop_areas_helper.rb +++ b/app/helpers/stop_areas_helper.rb @@ -54,4 +54,20 @@ module StopAreasHelper end end + def stop_area_registration_number_title stop_area + if stop_area&.stop_area_referential&.registration_number_format.present? + return t("formtastic.titles.stop_area.registration_number_format", registration_number_format: stop_area.stop_area_referential.registration_number_format) + end + t "formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.registration_number" + end + + def stop_area_registration_number_is_required stop_area + val = format_restriction_for_locales(@referential) == '.hub' + val ||= stop_area&.stop_area_referential&.registration_number_format.present? + val + end + + def stop_area_registration_number_value stop_area + stop_area&.registration_number || stop_area&.stop_area_referential&.generate_registration_number + end end diff --git a/app/helpers/table_builder_helper.rb b/app/helpers/table_builder_helper.rb index 2068dd23c..d16858678 100644 --- a/app/helpers/table_builder_helper.rb +++ b/app/helpers/table_builder_helper.rb @@ -224,7 +224,7 @@ module TableBuilderHelper if column.linkable? path = column.link_to(item) - link = link_to(value, path) + link = value.present? && path.present? ? link_to(value, path) : "" if overhead.empty? bcont << content_tag(:td, link, title: 'Voir') diff --git a/app/helpers/table_builder_helper/column.rb b/app/helpers/table_builder_helper/column.rb index 05aa9f563..907707670 100644 --- a/app/helpers/table_builder_helper/column.rb +++ b/app/helpers/table_builder_helper/column.rb @@ -2,19 +2,21 @@ module TableBuilderHelper class Column attr_reader :key, :name, :attribute, :sortable - def initialize(key: nil, name: '', attribute:, sortable: true, link_to: nil) + def initialize(key: nil, name: '', attribute:, sortable: true, link_to: nil, **opts) if key.nil? && name.empty? raise ColumnMustHaveKeyOrNameError end - + opts ||= {} @key = key @name = name @attribute = attribute @sortable = sortable @link_to = link_to + @condition = opts[:if] end def value(obj) + return unless check_condition(obj) if @attribute.is_a?(Proc) @attribute.call(obj) else @@ -26,7 +28,9 @@ module TableBuilderHelper return @name if @name.present? # Transform `Chouette::Line` into "line" - model_key = model.to_s.demodulize.underscore + model_key = model.to_s.underscore + model_key.gsub! 'chouette/', '' + model_key.gsub! '/', '.' I18n.t("activerecord.attributes.#{model_key}.#{@key}") end @@ -36,8 +40,18 @@ module TableBuilderHelper end def link_to(obj) + return unless check_condition(obj) @link_to.call(obj) end + + def check_condition(obj) + condition_val = true + if @condition.present? + condition_val = @condition + condition_val = condition_val.call(obj) if condition_val.is_a?(Proc) + end + !!condition_val + end end diff --git a/app/javascript/helpers/routes_map.coffee b/app/javascript/helpers/routes_map.coffee index 6834406fc..de6196372 100644 --- a/app/javascript/helpers/routes_map.coffee +++ b/app/javascript/helpers/routes_map.coffee @@ -1,3 +1,65 @@ +RoutesLayersButton = (options) -> + menu = options.menu + + toggleMenu = (e)=> + $(menu.element).toggleClass 'hidden' + button.innerHTML = if button.innerHTML == "+" then "-" else "+" + + button = document.createElement("button") + button.innerHTML = "+" + button.addEventListener('click', toggleMenu, false) + button.addEventListener('touchstart', toggleMenu, false) + button.className = "ol-routes-layers-button" + + element = document.createElement('div'); + element.className = 'ol-control ol-routes-layers-button-wrapper'; + + element.appendChild(button) + + ol.control.Control.call(this, { + element + target: options.target + }) + +ol.inherits RoutesLayersButton, ol.control.Control + +RoutesLayersControl = (routes, routes_map) -> + + element = document.createElement('div') + element.className = 'ol-unselectable ol-routes-layers hidden' + Object.keys(routes).forEach (id)=> + route = routes[id] + route.active = false + label = document.createElement('a') + label.title = route.name + label.className = '' + label.innerHTML = route.name + element.appendChild label + label.addEventListener "click", => + route.active = !route.active + $(label).toggleClass "active" + route.active + route.vectorPtsLayer.setStyle routes_map.defaultStyles(route.active) + route.vectorEdgesLayer.setStyle routes_map.edgeStyles(route.active) + route.vectorLnsLayer.setStyle routes_map.lineStyle(route.active) + routes_map.fitZoom() + label.addEventListener "mouseenter", => + route.vectorPtsLayer.setStyle routes_map.defaultStyles(true) + route.vectorEdgesLayer.setStyle routes_map.edgeStyles(true) + route.vectorLnsLayer.setStyle routes_map.lineStyle(true) + + label.addEventListener "mouseleave", => + route.vectorPtsLayer.setStyle routes_map.defaultStyles(route.active) + route.vectorEdgesLayer.setStyle routes_map.edgeStyles(route.active) + route.vectorLnsLayer.setStyle routes_map.lineStyle(route.active) + + + ol.control.Control.call(this, { + element + }) + +ol.inherits RoutesLayersControl, ol.control.Control + class RoutesMap constructor: (@target)-> @initMap() @@ -20,6 +82,7 @@ class RoutesMap addRoute: (route)-> geoColPts = [] geoColLns = [] + route.active = true @routes[route.id] = route if route.id stops = route.stops || route geoColEdges = [ @@ -77,68 +140,49 @@ class RoutesMap @map.addLayer vectorEdgesLayer @map.addLayer vectorLnsLayer - lineStyle: (highlighted=false)-> + lineStyle: (active=false)-> new ol.style.Style stroke: new ol.style.Stroke - color: if highlighted then "#ed7f00" else '#007fbb' - width: 3 + color: '#007fbb' + width: if active then 3 else 0 - edgeStyles: (highlighted=false)-> + edgeStyles: (active=false)-> new ol.style.Style image: new ol.style.Circle radius: 5 stroke: new ol.style.Stroke - color: if highlighted then "#ed7f00" else '#007fbb' - width: 2 + color: '#007fbb' + width: if active then 3 else 0 fill: new ol.style.Fill - color: if highlighted then "#ed7f00" else '#007fbb' - width: 2 + color: '#007fbb' + width: if active then 3 else 0 - defaultStyles: (highlighted=false)-> + defaultStyles: (active=false)-> new ol.style.Style image: new ol.style.Circle radius: 4 stroke: new ol.style.Stroke - color: if highlighted then "#ed7f00" else '#007fbb' - width: 2 + color: '#007fbb' + width: if active then 3 else 0 fill: new ol.style.Fill color: '#ffffff' - width: 2 + width: if active then 3 else 0 addRoutesLabels: -> - labelsContainer = $("<ul class='routes-labels'></ul>") - labelsContainer.appendTo $("##{@target}") - @vectorPtsLayer = null - @vectorEdgesLayer = null - @vectorLnsLayer = null + menu = new RoutesLayersControl(@routes, this) + @map.addControl menu + @map.addControl new RoutesLayersButton(menu: menu) + + fitZoom: ()-> + area = [] + found = false Object.keys(@routes).forEach (id)=> route = @routes[id] - label = $("<li>#{route.name}</ul>") - label.appendTo labelsContainer - label.mouseleave => - route.vectorPtsLayer.setStyle @defaultStyles(false) - route.vectorEdgesLayer.setStyle @edgeStyles(false) - route.vectorLnsLayer.setStyle @lineStyle(false) - route.vectorPtsLayer.setZIndex 2 - route.vectorEdgesLayer.setZIndex 3 - route.vectorLnsLayer.setZIndex 1 - @fitZoom() - label.mouseenter => - route.vectorPtsLayer.setStyle @defaultStyles(true) - route.vectorEdgesLayer.setStyle @edgeStyles(true) - route.vectorLnsLayer.setStyle @lineStyle(true) - route.vectorPtsLayer.setZIndex 11 - route.vectorEdgesLayer.setZIndex 12 - route.vectorLnsLayer.setZIndex 10 - @fitZoom(route) - - fitZoom: (route)-> - if route - area = [] - route.stops.forEach (stop, i) => - area.push [parseFloat(stop.longitude), parseFloat(stop.latitude)] - else - area = @area + if route.active + found = true + route.stops.forEach (stop, i) => + area.push [parseFloat(stop.longitude), parseFloat(stop.latitude)] + area = @area unless found boundaries = ol.extent.applyTransform( ol.extent.boundingExtent(area), ol.proj.getTransform('EPSG:4326', 'EPSG:3857') ) diff --git a/app/javascript/packs/exports/new.js b/app/javascript/packs/exports/new.js new file mode 100644 index 000000000..ffe702cdb --- /dev/null +++ b/app/javascript/packs/exports/new.js @@ -0,0 +1,3 @@ +import MasterSlave from "../../helpers/master_slave" + +new MasterSlave("form") diff --git a/app/javascript/packs/referential_lines/show.js b/app/javascript/packs/referential_lines/show.js index 542188018..99c5072ef 100644 --- a/app/javascript/packs/referential_lines/show.js +++ b/app/javascript/packs/referential_lines/show.js @@ -6,5 +6,5 @@ routes = JSON.parse(decodeURIComponent(routes)) var map = new RoutesMap('routes_map') map.addRoutes(routes) -// map.addRoutesLabels() +map.addRoutesLabels() map.fitZoom() diff --git a/app/javascript/vehicle_journeys/actions/index.js b/app/javascript/vehicle_journeys/actions/index.js index 6d9119cdc..e00e9b1b0 100644 --- a/app/javascript/vehicle_journeys/actions/index.js +++ b/app/javascript/vehicle_journeys/actions/index.js @@ -203,11 +203,10 @@ const actions = { let field = fields[key] if(field.validity && !field.validity.valid){ valid = false - $(field).parent().addClass('has-error').children('.help-block').remove() + $(field).parent().parent().addClass('has-error').children('.help-block').remove() $(field).parent().append("<span class='small help-block'>" + field.validationMessage + "</span>") } }) - return valid }, toggleArrivals : () => ({ diff --git a/app/javascript/vehicle_journeys/components/VehicleJourney.js b/app/javascript/vehicle_journeys/components/VehicleJourney.js index d0a9343ed..b469cfecf 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourney.js @@ -48,6 +48,14 @@ export default class VehicleJourney extends Component { } } + displayDelta(delta) { + if(delta > 99){ + return "+" + } + return delta + } + + hasTimeTable(time_tables, tt) { let found = false time_tables.map((t, index) => { @@ -59,6 +67,10 @@ export default class VehicleJourney extends Component { return found } + hasPurchaseWindow(purchase_windows, window) { + return this.hasTimeTable(purchase_windows, window) + } + isDisabled(bool1, bool2) { return (bool1 || bool2) } @@ -67,6 +79,8 @@ export default class VehicleJourney extends Component { this.previousCity = undefined let detailed_calendars = this.hasFeature('detailed_calendars') && !this.disabled let detailed_calendars_shown = $('.detailed-timetables-bt').hasClass('active') + let detailed_purchase_windows = this.hasFeature('detailed_purchase_windows') && !this.disabled + let detailed_purchase_windows_shown = $('.detailed-purchase-windows-bt').hasClass('active') let {time_tables, purchase_windows} = this.props.value return ( @@ -89,6 +103,13 @@ export default class VehicleJourney extends Component { {purchase_windows.length > 3 && <span className='vj_tt'> + {purchase_windows.length - 3}</span>} </div> } + { detailed_purchase_windows && + <div className={"detailed-purchase-windows" + (detailed_purchase_windows_shown ? "" : " hidden")}> + {this.props.allPurchaseWindows.map((w, i) => + <div key={i} className={(this.hasPurchaseWindow(purchase_windows, w) ? "active" : "inactive")}></div> + )} + </div> + } <div> {time_tables.slice(0,3).map((tt, i)=> <span key={i} className='vj_tt'>{this.timeTableURL(tt)}</span> @@ -117,6 +138,8 @@ export default class VehicleJourney extends Component { )} </div> } + + </div> {this.props.value.vehicle_journey_at_stops.map((vj, i) => <div key={i} className='td text-center'> @@ -152,7 +175,7 @@ export default class VehicleJourney extends Component { } <div className={(this.columnHasDelta() ? '' : 'hidden')}> {(vj.delta != 0) && - <span className='sb sb-chrono sb-lg text-warning' data-textinside={vj.delta}></span> + <span className='sb sb-chrono sb-lg text-warning' data-textinside={this.displayDelta(vj.delta)}></span> } </div> <div data-headline={I18n.t("vehicle_journeys.form.departure_at")}> @@ -201,4 +224,5 @@ VehicleJourney.propTypes = { onSelectVehicleJourney: PropTypes.func.isRequired, vehicleJourneys: PropTypes.object.isRequired, allTimeTables: PropTypes.array.isRequired, + allPurchaseWindows: PropTypes.array.isRequired, } diff --git a/app/javascript/vehicle_journeys/components/VehicleJourneys.js b/app/javascript/vehicle_journeys/components/VehicleJourneys.js index 384afba17..ae38a21af 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourneys.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourneys.js @@ -12,6 +12,7 @@ export default class VehicleJourneys extends Component { this.stopPoints(), this.props.filters.features ) + this.togglePurchaseWindows = this.togglePurchaseWindows.bind(this) this.toggleTimetables = this.toggleTimetables.bind(this) } @@ -49,23 +50,42 @@ export default class VehicleJourneys extends Component { return this.headerManager.showHeader(object_id) } - allTimeTables() { - if(this._allTimeTables){ - return this._allTimeTables - } - let keys = [] + getPurchaseWindowsAndTimeTables(){ + let timetables_keys = [] + let windows_keys = [] this._allTimeTables = [] + this._allPurchaseWindows = [] this.vehicleJourneysList().map((vj, index) => { vj.time_tables.map((tt, _) => { - if(keys.indexOf(tt.id) < 0){ - keys.push(tt.id) + if(timetables_keys.indexOf(tt.id) < 0){ + timetables_keys.push(tt.id) this._allTimeTables.push(tt) } }) + vj.purchase_windows.map((tt, _) => { + if(windows_keys.indexOf(tt.id) < 0){ + windows_keys.push(tt.id) + this._allPurchaseWindows.push(tt) + } + }) }) + } + + allTimeTables() { + if(! this._allTimeTables){ + this.getPurchaseWindowsAndTimeTables() + } return this._allTimeTables } + allPurchaseWindows() { + if(!this._allPurchaseWindows){ + this.getPurchaseWindowsAndTimeTables() + } + + return this._allPurchaseWindows + } + toggleTimetables(e) { $('.table-2entries .detailed-timetables').toggleClass('hidden') $('.table-2entries .detailed-timetables-bt').toggleClass('active') @@ -74,6 +94,14 @@ export default class VehicleJourneys extends Component { false } + togglePurchaseWindows(e) { + $('.table-2entries .detailed-purchase-windows').toggleClass('hidden') + $('.table-2entries .detailed-purchase-windows-bt').toggleClass('active') + this.componentDidUpdate() + e.preventDefault() + false + } + componentDidUpdate(prevProps, prevState) { if(this.props.status.isFetching == false){ $('.table-2entries').each(function() { @@ -127,10 +155,20 @@ export default class VehicleJourneys extends Component { ) } + purchaseWindowURL(tt) { + let refURL = window.location.pathname.split('/', 3).join('/') + let ttURL = refURL + '/purchase_windows/' + tt.id + return ( + <a href={ttURL} title='Voir le calendrier commercial'><span className='fa fa-calendar' style={{color: (tt.color ? tt.color : '#4B4B4B')}}></span>{tt.name}</a> + ) + } + render() { this.previousBreakpoint = undefined this._allTimeTables = null + this._allPurchaseWindows = null let detailed_calendars = this.hasFeature('detailed_calendars') && !this.isReturn() && (this.allTimeTables().length > 0) + let detailed_purchase_windows = this.hasFeature('detailed_purchase_windows') && !this.isReturn() && (this.allPurchaseWindows().length > 0) if(this.props.status.isFetching == true) { return ( <div className="isLoading" style={{marginTop: 80, marginBottom: 80}}> @@ -170,17 +208,39 @@ export default class VehicleJourneys extends Component { <div>{I18n.attribute_name("vehicle_journey", "name")}</div> <div>{I18n.attribute_name("vehicle_journey", "journey_pattern_id")}</div> <div>{I18n.model_name("company")}</div> - { this.hasFeature('purchase_windows') && <div>{I18n.model_name("purchase_window", "plural": true)}</div> } + { this.hasFeature('purchase_windows') && + <div> + { detailed_purchase_windows && + <a href='#' onClick={this.togglePurchaseWindows} className='detailed-purchase-windows-bt'> + <span className='fa fa-angle-up'></span> + {I18n.model_name("purchase_window", {"plural": true})} + </a> + } + { !detailed_purchase_windows && I18n.model_name("purchase_window", {"plural": true})} + </div> + } + { detailed_purchase_windows && + <div className="detailed-purchase-windows hidden"> + {this.allPurchaseWindows().map((tt, i)=> + <div key={i}> + <p> + {this.purchaseWindowURL(tt)} + </p> + <p>{tt.bounding_dates.join(' > ')}</p> + </div> + )} + </div> + } <div> { detailed_calendars && <a href='#' onClick={this.toggleTimetables} className='detailed-timetables-bt'> <span className='fa fa-angle-up'></span> - {I18n.model_name("time_table", "plural": true)} + {I18n.model_name("time_table", {"plural": true})} </a> } - { !detailed_calendars && I18n.model_name("time_table", "plural": true)} + { !detailed_calendars && I18n.model_name("time_table", {"plural": true})} </div> - { !this.isReturn() && + { detailed_calendars && <div className="detailed-timetables hidden"> {this.allTimeTables().map((tt, i)=> <div key={i}> @@ -217,6 +277,7 @@ export default class VehicleJourneys extends Component { vehicleJourneys={this} disabled={this.isReturn()} allTimeTables={this.allTimeTables()} + allPurchaseWindows={this.allPurchaseWindows()} /> )} </div> diff --git a/app/javascript/vehicle_journeys/components/tools/CreateModal.js b/app/javascript/vehicle_journeys/components/tools/CreateModal.js index 24d9a23c2..a60429765 100644 --- a/app/javascript/vehicle_journeys/components/tools/CreateModal.js +++ b/app/javascript/vehicle_journeys/components/tools/CreateModal.js @@ -12,7 +12,13 @@ export default class CreateModal extends Component { } handleSubmit() { - if (actions.validateFields(...this.refs, $('.vjCreateSelectJP')[0]) && this.props.modal.modalProps.selectedJPModal) { + if(!this.props.modal.modalProps.selectedJPModal){ + let field = $('#NewVehicleJourneyModal').find(".vjCreateSelectJP") + field.parent().parent().addClass('has-error').children('.help-block').remove() + field.parent().append("<span class='small help-block'>" + I18n.t("simple_form.required.text") + "</span>") + return + } + if (actions.validateFields(...this.refs, $('.vjCreateSelectJP')[0])) { this.props.onAddVehicleJourney(_.assign({}, this.refs, {custom_fields: this.custom_fields}), this.props.modal.modalProps.selectedJPModal, this.props.stopPointsList, this.props.modal.modalProps.vehicleJourney && this.props.modal.modalProps.vehicleJourney.company) this.props.onModalClose() $('#NewVehicleJourneyModal').modal('hide') diff --git a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js index 9f78a4163..6524f8d6f 100644 --- a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js +++ b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js @@ -90,6 +90,11 @@ const vehicleJourney= (state = {}, action, keep) => { } }) + let lastStop = action.selectedJourneyPattern.stop_areas && action.selectedJourneyPattern.stop_areas[action.selectedJourneyPattern.stop_areas.length - 1] + if(lastStop && lastStop.stop_area_short_description.id == sp.id){ + newVjas.departure_time = newVjas.arrival_time + } + if(newVjas.dummy){ newVjas.departure_time = {hour: "00", minute: "00"} newVjas.arrival_time = {hour: "00", minute: "00"} diff --git a/app/models/chouette/access_link.rb b/app/models/chouette/access_link.rb index 4b99ab5ba..6b08443be 100644 --- a/app/models/chouette/access_link.rb +++ b/app/models/chouette/access_link.rb @@ -2,8 +2,6 @@ module Chouette class AccessLink < Chouette::TridentActiveRecord has_paper_trail include ObjectidSupport - # FIXME http://jira.codehaus.org/browse/JRUBY-6358 - self.primary_key = "id" attr_accessor :access_link_type, :link_orientation_type, :link_key diff --git a/app/models/chouette/access_point.rb b/app/models/chouette/access_point.rb index b6f78f239..ac6580015 100644 --- a/app/models/chouette/access_point.rb +++ b/app/models/chouette/access_point.rb @@ -5,8 +5,6 @@ require 'geo_ruby' module Chouette class AccessPoint < Chouette::ActiveRecord has_paper_trail - # FIXME http://jira.codehaus.org/browse/JRUBY-6358 - self.primary_key = "id" include Geokit::Mappable include ProjectionFields diff --git a/app/models/chouette/connection_link.rb b/app/models/chouette/connection_link.rb index d5ddc606a..c53d6f5f1 100644 --- a/app/models/chouette/connection_link.rb +++ b/app/models/chouette/connection_link.rb @@ -3,8 +3,6 @@ module Chouette has_paper_trail include ObjectidSupport include ConnectionLinkRestrictions - # FIXME http://jira.codehaus.org/browse/JRUBY-6358 - self.primary_key = "id" attr_accessor :connection_link_type diff --git a/app/models/chouette/group_of_line.rb b/app/models/chouette/group_of_line.rb index 75ee1ce73..3b6a7cea7 100644 --- a/app/models/chouette/group_of_line.rb +++ b/app/models/chouette/group_of_line.rb @@ -5,8 +5,6 @@ module Chouette include GroupOfLineRestrictions include LineReferentialSupport - # FIXME http://jira.codehaus.org/browse/JRUBY-6358 - self.primary_key = "id" has_and_belongs_to_many :lines, :class_name => 'Chouette::Line', :order => 'lines.name' diff --git a/app/models/chouette/journey_pattern.rb b/app/models/chouette/journey_pattern.rb index 830e985d9..ff85f376a 100644 --- a/app/models/chouette/journey_pattern.rb +++ b/app/models/chouette/journey_pattern.rb @@ -4,8 +4,6 @@ module Chouette include ChecksumSupport include JourneyPatternRestrictions include ObjectidSupport - # FIXME http://jira.codehaus.org/browse/JRUBY-6358 - self.primary_key = "id" belongs_to :route has_many :vehicle_journeys, :dependent => :destroy @@ -171,6 +169,19 @@ module Chouette full end + def distance_to stop + val = 0 + i = 0 + _end = stop_points.first + while _end != stop + i += 1 + _start = _end + _end = stop_points[i] + val += costs_between(_start, _end)[:distance] + end + val + end + def set_distances distances raise "inconsistent data: #{distances.count} values for #{stop_points.count} stops" unless distances.count == stop_points.count prev = distances[0].to_i diff --git a/app/models/chouette/line.rb b/app/models/chouette/line.rb index d077d5c9d..2f83fc5bf 100644 --- a/app/models/chouette/line.rb +++ b/app/models/chouette/line.rb @@ -8,8 +8,6 @@ module Chouette include StifTransportSubmodeEnumerations extend ActiveModel::Naming - # FIXME http://jira.codehaus.org/browse/JRUBY-6358 - self.primary_key = "id" belongs_to :company belongs_to :network @@ -45,6 +43,16 @@ module Chouette scope :by_text, ->(text) { where('lower(name) LIKE :t or lower(published_name) LIKE :t or lower(objectid) LIKE :t or lower(comment) LIKE :t or lower(number) LIKE :t', t: "%#{text.downcase}%") } + scope :by_name, ->(name) { + joins('LEFT OUTER JOIN public.companies ON companies.id = lines.company_id') + .where(' + lines.number LIKE :q + OR lines.name LIKE :q + OR companies.name ILIKE :q', + q: "%#{sanitize_sql_like(name)}%" + ) + } + def self.nullable_attributes [:published_name, :number, :comment, :url, :color, :text_color, :stable_id] end diff --git a/app/models/chouette/network.rb b/app/models/chouette/network.rb index 6843c69ad..942fc5d67 100644 --- a/app/models/chouette/network.rb +++ b/app/models/chouette/network.rb @@ -5,8 +5,6 @@ module Chouette include LineReferentialSupport include ObjectidSupport extend Enumerize - # FIXME http://jira.codehaus.org/browse/JRUBY-6358 - self.primary_key = "id" has_many :lines attr_accessor :source_type_name diff --git a/app/models/chouette/pt_link.rb b/app/models/chouette/pt_link.rb index d14d5f29c..399539d44 100644 --- a/app/models/chouette/pt_link.rb +++ b/app/models/chouette/pt_link.rb @@ -3,8 +3,6 @@ require 'geokit' module Chouette class PtLink < Chouette::ActiveRecord has_paper_trail - # FIXME http://jira.codehaus.org/browse/JRUBY-6358 - self.primary_key = "id" include Geokit::Mappable def geometry diff --git a/app/models/chouette/purchase_window.rb b/app/models/chouette/purchase_window.rb index 157390a21..4c8014780 100644 --- a/app/models/chouette/purchase_window.rb +++ b/app/models/chouette/purchase_window.rb @@ -39,6 +39,13 @@ module Chouette self.slice(*attrs).values + ranges_attrs end + def bounding_dates + [ + date_ranges.map(&:first).min, + date_ranges.map(&:last).max, + ] + end + # def checksum_attributes # end diff --git a/app/models/chouette/route.rb b/app/models/chouette/route.rb index 3729deb7d..26b80733d 100644 --- a/app/models/chouette/route.rb +++ b/app/models/chouette/route.rb @@ -12,8 +12,6 @@ module Chouette enumerize :direction, in: %i(straight_forward backward clockwise counter_clockwise north north_west west south_west south south_east east north_east) enumerize :wayback, in: %i(outbound inbound), default: :outbound - # FIXME http://jira.codehaus.org/browse/JRUBY-6358 - self.primary_key = "id" def self.nullable_attributes [:published_name, :comment, :number, :name, :direction, :wayback] diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb index 7170dd217..0a27b2f39 100644 --- a/app/models/chouette/stop_area.rb +++ b/app/models/chouette/stop_area.rb @@ -46,6 +46,11 @@ module Chouette validates_numericality_of :waiting_time, greater_than_or_equal_to: 0, only_integer: true, if: :waiting_time validate :parent_area_type_must_be_greater validate :area_type_of_right_kind + validate :registration_number_is_set + + before_validation do + self.registration_number ||= self.stop_area_referential.generate_registration_number + end def self.nullable_attributes [:registration_number, :street_name, :country_code, :fare_code, @@ -73,6 +78,22 @@ module Chouette end end + def registration_number_is_set + return unless self.stop_area_referential.registration_number_format.present? + if self.stop_area_referential.stop_areas.where(registration_number: self.registration_number).\ + where.not(id: self.id).exists? + errors.add(:registration_number, I18n.t('stop_areas.errors.registration_number.already_taken')) + end + + unless self.registration_number.present? + errors.add(:registration_number, I18n.t('stop_areas.errors.registration_number.cannot_be_empty')) + end + + unless self.stop_area_referential.validates_registration_number(self.registration_number) + errors.add(:registration_number, I18n.t('stop_areas.errors.registration_number.invalid')) + end + end + after_update :clean_invalid_access_links before_save :coordinates_to_lat_lng @@ -390,9 +411,12 @@ module Chouette ActiveSupport::TimeZone[time_zone]&.utc_offset end - def country_name + def country return unless country_code country = ISO3166::Country[country_code] + end + + def country_name return unless country country.translations[I18n.locale.to_s] || country.name end diff --git a/app/models/chouette/stop_point.rb b/app/models/chouette/stop_point.rb index 3b9eaa2f6..6b363cd93 100644 --- a/app/models/chouette/stop_point.rb +++ b/app/models/chouette/stop_point.rb @@ -9,8 +9,6 @@ module Chouette include ForAlightingEnumerations include ObjectidSupport - # FIXME http://jira.codehaus.org/browse/JRUBY-6358 - self.primary_key = "id" belongs_to :stop_area belongs_to :route, inverse_of: :stop_points diff --git a/app/models/chouette/time_table.rb b/app/models/chouette/time_table.rb index 20d6e69ac..506e498b8 100644 --- a/app/models/chouette/time_table.rb +++ b/app/models/chouette/time_table.rb @@ -7,8 +7,6 @@ module Chouette include ApplicationDaysSupport include TimetableSupport - # FIXME http://jira.codehaus.org/browse/JRUBY-6358 - self.primary_key = "id" acts_as_taggable attr_accessor :tag_search diff --git a/app/models/chouette/time_table_date.rb b/app/models/chouette/time_table_date.rb index 98d8fa765..6a68d7fe1 100644 --- a/app/models/chouette/time_table_date.rb +++ b/app/models/chouette/time_table_date.rb @@ -2,7 +2,6 @@ module Chouette class TimeTableDate < Chouette::ActiveRecord include ChecksumSupport - self.primary_key = "id" belongs_to :time_table, inverse_of: :dates acts_as_list :scope => 'time_table_id = #{time_table_id}',:top_of_list => 0 diff --git a/app/models/chouette/time_table_period.rb b/app/models/chouette/time_table_period.rb index d9b707675..6965d828a 100644 --- a/app/models/chouette/time_table_period.rb +++ b/app/models/chouette/time_table_period.rb @@ -2,7 +2,6 @@ module Chouette class TimeTablePeriod < Chouette::ActiveRecord include ChecksumSupport - self.primary_key = "id" belongs_to :time_table, inverse_of: :periods acts_as_list :scope => 'time_table_id = #{time_table_id}',:top_of_list => 0 diff --git a/app/models/chouette/timeband.rb b/app/models/chouette/timeband.rb index 6155ffc77..5a4e17b98 100644 --- a/app/models/chouette/timeband.rb +++ b/app/models/chouette/timeband.rb @@ -10,7 +10,6 @@ module Chouette class Timeband < Chouette::TridentActiveRecord include ObjectidSupport has_paper_trail - self.primary_key = "id" validates :start_time, :end_time, presence: true validates_with Chouette::TimebandValidator diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb index 9b94f7f0e..46522c354 100644 --- a/app/models/chouette/vehicle_journey.rb +++ b/app/models/chouette/vehicle_journey.rb @@ -3,11 +3,10 @@ module Chouette class VehicleJourney < Chouette::TridentActiveRecord has_paper_trail include ChecksumSupport + include CustomFieldsSupport include VehicleJourneyRestrictions include ObjectidSupport include StifTransportModeEnumerations - # FIXME http://jira.codehaus.org/browse/JRUBY-6358 - self.primary_key = "id" enum journey_category: { timed: 0, frequency: 1 } @@ -89,7 +88,6 @@ module Chouette end } - scope :in_purchase_window, ->(range){ purchase_windows = Chouette::PurchaseWindow.overlap_dates(range) sql = purchase_windows.joins(:vehicle_journeys).select('vehicle_journeys.id').uniq.to_sql @@ -156,6 +154,14 @@ module Chouette end end + def sales_start + purchase_windows.map{|p| p.date_ranges.map &:first}.flatten.min + end + + def sales_end + purchase_windows.map{|p| p.date_ranges.map &:last}.flatten.max + end + def calculate_vehicle_journey_at_stop_day_offset Chouette::VehicleJourneyAtStopsDayOffset.new( vehicle_journey_at_stops @@ -340,21 +346,6 @@ module Chouette end end - def self.custom_fields - CustomField.where(resource_type: self.name.split("::").last) - end - - - def custom_fields - Hash[*self.class.custom_fields.map do |v| - [v.code, v.slice(:code, :name, :field_type, :options).update(value: custom_field_value(v.code))] - end.flatten] - end - - def custom_field_value key - (custom_field_values || {})[key.to_s] - end - def self.matrix(vehicle_journeys) Hash[*VehicleJourneyAtStop.where(vehicle_journey_id: vehicle_journeys.pluck(:id)).map do |vjas| [ "#{vjas.vehicle_journey_id}-#{vjas.stop_point_id}", vjas] @@ -417,10 +408,5 @@ module Chouette ') .where('"time_tables_vehicle_journeys"."vehicle_journey_id" IS NULL') end - - def self.lines - lines_query = joins(:route).select("routes.line_id").reorder(nil).except(:group).pluck(:'routes.line_id') - Chouette::Line.where(id: lines_query) - end end end diff --git a/app/models/chouette/vehicle_journey_at_stop.rb b/app/models/chouette/vehicle_journey_at_stop.rb index 3b4f35f13..3f5bd5abf 100644 --- a/app/models/chouette/vehicle_journey_at_stop.rb +++ b/app/models/chouette/vehicle_journey_at_stop.rb @@ -4,10 +4,10 @@ module Chouette include Chouette::ForAlightingEnumerations include ChecksumSupport - DAY_OFFSET_MAX = 1 + DAY_OFFSET_MAX = 2 - # FIXME http://jira.codehaus.org/browse/JRUBY-6358 - self.primary_key = "id" + @@day_offset_max = DAY_OFFSET_MAX + mattr_accessor :day_offset_max belongs_to :stop_point belongs_to :vehicle_journey @@ -42,7 +42,7 @@ module Chouette I18n.t( 'vehicle_journey_at_stops.errors.day_offset_must_not_exceed_max', short_id: vehicle_journey&.get_objectid&.short_id, - max: DAY_OFFSET_MAX + 1 + max: Chouette::VehicleJourneyAtStop.day_offset_max + 1 ) ) end @@ -53,7 +53,7 @@ module Chouette I18n.t( 'vehicle_journey_at_stops.errors.day_offset_must_not_exceed_max', short_id: vehicle_journey&.get_objectid&.short_id, - max: DAY_OFFSET_MAX + 1 + max: Chouette::VehicleJourneyAtStop.day_offset_max + 1 ) ) end @@ -64,7 +64,7 @@ module Chouette # nil offsets. Handle these gracefully by forcing them to a 0 offset. offset ||= 0 - offset < 0 || offset > DAY_OFFSET_MAX + offset < 0 || offset > Chouette::VehicleJourneyAtStop.day_offset_max end def checksum_attributes @@ -84,12 +84,12 @@ module Chouette format_time arrival_time.utc end - def departure_local_time - local_time departure_time + def departure_local_time offset=nil + local_time departure_time, offset end - def arrival_local_time - local_time arrival_time + def arrival_local_time offset=nil + local_time arrival_time, offset end def departure_local @@ -100,12 +100,15 @@ module Chouette format_time arrival_local_time end + def time_zone_offset + return 0 unless stop_point&.stop_area&.time_zone.present? + ActiveSupport::TimeZone[stop_point.stop_area.time_zone]&.utc_offset || 0 + end + private - def local_time time - return unless time - return time unless stop_point&.stop_area&.time_zone.present? - return time unless ActiveSupport::TimeZone[stop_point.stop_area.time_zone].present? - time + ActiveSupport::TimeZone[stop_point.stop_area.time_zone].utc_offset + def local_time time, offset=nil + return nil unless time + time + (offset || time_zone_offset) end def format_time time diff --git a/app/models/chouette/vehicle_journey_at_stops_day_offset.rb b/app/models/chouette/vehicle_journey_at_stops_day_offset.rb index 7497cd72c..cfa0e8bfc 100644 --- a/app/models/chouette/vehicle_journey_at_stops_day_offset.rb +++ b/app/models/chouette/vehicle_journey_at_stops_day_offset.rb @@ -4,31 +4,32 @@ module Chouette @at_stops = at_stops end - def calculate! - arrival_offset = 0 - departure_offset = 0 + def time_from_fake_date fake_date + fake_date - fake_date.to_date.to_time + end + def calculate! + offset = 0 + tz_offset = @at_stops.first&.time_zone_offset @at_stops.inject(nil) do |prior_stop, stop| next stop if prior_stop.nil? # we only compare time of the day, not actual times - stop_arrival_time = stop.arrival_time - stop.arrival_time.to_date.to_time - stop_departure_time = stop.departure_time - stop.departure_time.to_date.to_time - prior_stop_arrival_time = prior_stop.arrival_time - prior_stop.arrival_time.to_date.to_time - prior_stop_departure_time = prior_stop.departure_time - prior_stop.departure_time.to_date.to_time - - if stop_arrival_time < prior_stop_departure_time || - stop_arrival_time < prior_stop_arrival_time - arrival_offset += 1 + stop_arrival_time = time_from_fake_date stop.arrival_local_time(tz_offset) + stop_departure_time = time_from_fake_date stop.departure_local_time(tz_offset) + prior_stop_departure_time = time_from_fake_date prior_stop.departure_local_time(tz_offset) + + if stop_arrival_time < prior_stop_departure_time + offset += 1 end - if stop_departure_time < stop_arrival_time || - stop_departure_time < prior_stop_departure_time - departure_offset += 1 + stop.arrival_day_offset = offset + + if stop_departure_time < stop_arrival_time + offset += 1 end - stop.arrival_day_offset = arrival_offset - stop.departure_day_offset = departure_offset + stop.departure_day_offset = offset stop end diff --git a/app/models/compliance_check.rb b/app/models/compliance_check.rb index 55f2ae228..9d817e146 100644 --- a/app/models/compliance_check.rb +++ b/app/models/compliance_check.rb @@ -1,14 +1,23 @@ class ComplianceCheck < ActiveRecord::Base + include ComplianceItemSupport self.inheritance_column = nil extend Enumerize belongs_to :compliance_check_set belongs_to :compliance_check_block - + enumerize :criticity, in: %i(warning error), scope: true, default: :warning validates :criticity, presence: true validates :name, presence: true validates :code, presence: true validates :origin_code, presence: true + + def control_class + compliance_control_name.present? ? compliance_control_name.constantize : nil + end + + delegate :predicate, to: :control_class, allow_nil: true + delegate :prerequisite, to: :control_class, allow_nil: true + end diff --git a/app/models/compliance_check_message_export.rb b/app/models/compliance_check_message_export.rb index 04e1a9caa..bbaaa8e3f 100644 --- a/app/models/compliance_check_message_export.rb +++ b/app/models/compliance_check_message_export.rb @@ -26,12 +26,14 @@ class ComplianceCheckMessageExport end def to_csv(options = {}) - CSV.generate(options.slice(:col_sep, :quote_char, :force_quotes)) do |csv| + csv_string = CSV.generate(options.slice(:col_sep, :quote_char, :force_quotes)) do |csv| csv << column_names compliance_check_messages.each do |compliance_check_message| csv << [compliance_check_message.compliance_check.criticity, *compliance_check_message.message_attributes.values_at('test_id', 'source_objectid'), options[:server_url] + compliance_check_message.message_attributes['source_object_path'], I18n.t("compliance_check_messages.#{compliance_check_message.message_key}", compliance_check_message.message_attributes.deep_symbolize_keys)] end end + # We add a BOM to indicate we use UTF-8 + "\uFEFF" + csv_string end def to_zip(temp_file,options = {}) diff --git a/app/models/compliance_check_resource.rb b/app/models/compliance_check_resource.rb index 2989bf3cf..777254aaf 100644 --- a/app/models/compliance_check_resource.rb +++ b/app/models/compliance_check_resource.rb @@ -3,7 +3,7 @@ class ComplianceCheckResource < ActiveRecord::Base belongs_to :compliance_check_set - enumerize :status, in: %i(OK ERROR WARNING IGNORED), scope: true + enumerize :status, in: %i(OK ERROR WARNING IGNORED) validates_presence_of :compliance_check_set end diff --git a/app/models/compliance_check_set.rb b/app/models/compliance_check_set.rb index 289fc134f..49d324c53 100644 --- a/app/models/compliance_check_set.rb +++ b/app/models/compliance_check_set.rb @@ -49,39 +49,25 @@ class ComplianceCheckSet < ActiveRecord::Base end def update_status - statuses = compliance_check_resources.map do |resource| - case resource.status - when 'ERROR' - return update(status: 'failed') - when 'WARNING' - return update(status: 'warning') - else - resource.status + status = + if compliance_check_resources.where(status: 'ERROR').count > 0 + 'failed' + elsif compliance_check_resources.where(status: ["WARNING", "IGNORED"]).count > 0 + 'warning' + elsif compliance_check_resources.where(status: "OK").count == compliance_check_resources.count + 'successful' end - end - if statuses_ok_or_ignored?(statuses) - return update(status: 'successful') + attributes = { + status: status + } + + if self.class.finished_statuses.include?(status) + attributes[:ended_at] = Time.now end - true + update attributes end - private - - def statuses_ok_or_ignored?(statuses) - uniform_statuses = statuses.uniq - - ( - # All statuses OK - uniform_statuses.length == 1 && - uniform_statuses.first == 'OK' - ) || - ( - # Statuses OK or IGNORED - uniform_statuses.length == 2 && - uniform_statuses.include?('OK') && - uniform_statuses.include?('IGNORED') - ) - end + end diff --git a/app/models/compliance_control.rb b/app/models/compliance_control.rb index 298a63ab9..537343005 100644 --- a/app/models/compliance_control.rb +++ b/app/models/compliance_control.rb @@ -1,18 +1,16 @@ class ComplianceControl < ActiveRecord::Base + include ComplianceItemSupport class << self def criticities; %i(warning error) end def default_code; "" end - def dynamic_attributes - stored_attributes[:control_attributes] || [] - end def policy_class ComplianceControlPolicy end def subclass_patterns - { + { generic: 'Generic', journey_pattern: 'JourneyPattern', line: 'Line', @@ -30,6 +28,9 @@ class ComplianceControl < ActiveRecord::Base end super end + + def predicate; I18n.t("compliance_controls.#{self.name.underscore}.description") end + def prerequisite; I18n.t("compliance_controls.#{self.name.underscore}.prerequisite") end end extend Enumerize @@ -45,26 +46,25 @@ class ComplianceControl < ActiveRecord::Base validates :compliance_control_set, presence: true validate def coherent_control_set - return true if compliance_control_block_id.nil? - ids = [compliance_control_block.compliance_control_set_id, compliance_control_set_id] - return true if ids.first == ids.last - names = ids.map{|id| ComplianceControlSet.find(id).name} - errors.add(:coherent_control_set, - I18n.t('compliance_controls.errors.incoherent_control_sets', - indirect_set_name: names.first, - direct_set_name: names.last)) -end - + return true if compliance_control_block_id.nil? + ids = [compliance_control_block.compliance_control_set_id, compliance_control_set_id] + return true if ids.first == ids.last + names = ids.map{|id| ComplianceControlSet.find(id).name} + errors.add(:coherent_control_set, + I18n.t('compliance_controls.errors.incoherent_control_sets', + indirect_set_name: names.first, + direct_set_name: names.last)) + end -def initialize(attributes = {}) - super - self.name ||= I18n.t("activerecord.models.#{self.class.name.underscore}.one") - self.code ||= self.class.default_code - self.origin_code ||= self.class.default_code -end + def initialize(attributes = {}) + super + self.name ||= I18n.t("activerecord.models.#{self.class.name.underscore}.one") + self.code ||= self.class.default_code + self.origin_code ||= self.class.default_code + end -def predicate; I18n.t("compliance_controls.#{self.class.name.underscore}.description") end -def prerequisite; I18n.t('compliance_controls.metas.no_prerequisite'); end + def predicate; self.class.predicate end + def prerequisite; self.class.prerequisite end end diff --git a/app/models/concerns/application_days_support.rb b/app/models/concerns/application_days_support.rb index 348436aa4..2d00b5847 100644 --- a/app/models/concerns/application_days_support.rb +++ b/app/models/concerns/application_days_support.rb @@ -15,41 +15,34 @@ module ApplicationDaysSupport end def day_by_mask(flag) - int_day_types & flag == flag + self.class.day_by_mask int_day_types, flag end - def self.day_by_mask(int_day_types,flag) - int_day_types & flag == flag + def valid_day? wday + valid_days.include?(wday) end - def valid_days - # Build an array with day of calendar week (1-7, Monday is 1). - [].tap do |valid_days| - valid_days << 1 if monday - valid_days << 2 if tuesday - valid_days << 3 if wednesday - valid_days << 4 if thursday - valid_days << 5 if friday - valid_days << 6 if saturday - valid_days << 7 if sunday + included do + def self.valid_days(int_day_types) + # Build an array with day of calendar week (1-7, Monday is 1). + [].tap do |valid_days| + valid_days << 1 if day_by_mask(int_day_types,MONDAY) + valid_days << 2 if day_by_mask(int_day_types,TUESDAY) + valid_days << 3 if day_by_mask(int_day_types,WEDNESDAY) + valid_days << 4 if day_by_mask(int_day_types,THURSDAY) + valid_days << 5 if day_by_mask(int_day_types,FRIDAY) + valid_days << 6 if day_by_mask(int_day_types,SATURDAY) + valid_days << 7 if day_by_mask(int_day_types,SUNDAY) + end end - end - def valid_day? wday - valid_days.include?(wday) + def self.day_by_mask(int_day_types,flag) + int_day_types & flag == flag + end end - def self.valid_days(int_day_types) - # Build an array with day of calendar week (1-7, Monday is 1). - [].tap do |valid_days| - valid_days << 1 if day_by_mask(int_day_types,MONDAY) - valid_days << 2 if day_by_mask(int_day_types,TUESDAY) - valid_days << 3 if day_by_mask(int_day_types,WEDNESDAY) - valid_days << 4 if day_by_mask(int_day_types,THURSDAY) - valid_days << 5 if day_by_mask(int_day_types,FRIDAY) - valid_days << 6 if day_by_mask(int_day_types,SATURDAY) - valid_days << 7 if day_by_mask(int_day_types,SUNDAY) - end + def valid_days + self.class.valid_days int_day_types end def monday diff --git a/app/models/concerns/compliance_item_support.rb b/app/models/concerns/compliance_item_support.rb new file mode 100644 index 000000000..f44f5719f --- /dev/null +++ b/app/models/concerns/compliance_item_support.rb @@ -0,0 +1,13 @@ +module ComplianceItemSupport + extend ActiveSupport::Concern + included do + + end + + module ClassMethods + def dynamic_attributes + stored_attributes[:control_attributes] || [] + end + end + +end diff --git a/app/models/concerns/custom_fields_support.rb b/app/models/concerns/custom_fields_support.rb new file mode 100644 index 000000000..6c76bd653 --- /dev/null +++ b/app/models/concerns/custom_fields_support.rb @@ -0,0 +1,24 @@ +module CustomFieldsSupport + extend ActiveSupport::Concern + + included do + validate :custom_fields_values_are_valid + + def self.custom_fields + CustomField.where(resource_type: self.name.split("::").last) + end + + def custom_fields + CustomField::Collection.new self + end + + def custom_field_value key + (custom_field_values || {})[key.to_s] + end + + private + def custom_fields_values_are_valid + custom_fields.values.all?{|cf| cf.valid?} + end + end +end diff --git a/app/models/concerns/iev_interfaces/message.rb b/app/models/concerns/iev_interfaces/message.rb new file mode 100644 index 000000000..ad41e98b7 --- /dev/null +++ b/app/models/concerns/iev_interfaces/message.rb @@ -0,0 +1,9 @@ +module IevInterfaces::Message + extend ActiveSupport::Concern + + included do + extend Enumerize + enumerize :criticity, in: %i(info warning error) + validates :criticity, presence: true + end +end diff --git a/app/models/concerns/iev_interfaces/resource.rb b/app/models/concerns/iev_interfaces/resource.rb new file mode 100644 index 000000000..7f8c3eefd --- /dev/null +++ b/app/models/concerns/iev_interfaces/resource.rb @@ -0,0 +1,9 @@ +module IevInterfaces::Resource + extend ActiveSupport::Concern + + included do + extend Enumerize + enumerize :status, in: %i(OK ERROR WARNING IGNORED), scope: true + validates_presence_of :name, :resource_type, :reference + end +end diff --git a/app/models/concerns/iev_interfaces/task.rb b/app/models/concerns/iev_interfaces/task.rb new file mode 100644 index 000000000..fdd976f39 --- /dev/null +++ b/app/models/concerns/iev_interfaces/task.rb @@ -0,0 +1,119 @@ +module IevInterfaces::Task + extend ActiveSupport::Concern + + included do + belongs_to :parent, polymorphic: true + belongs_to :workbench, class_name: "::Workbench" + belongs_to :referential + + mount_uploader :file, ImportUploader + + has_many :children, foreign_key: :parent_id, class_name: self.name, dependent: :destroy + + extend Enumerize + enumerize :status, in: %w(new pending successful warning failed running aborted canceled), scope: true, default: :new + + validates :name, presence: true + validates_presence_of :workbench, :creator + + has_many :messages, class_name: messages_class_name, dependent: :destroy, foreign_key: "#{messages_class_name.split('::').first.downcase}_id" + has_many :resources, class_name: resources_class_name, dependent: :destroy, foreign_key: "#{resources_class_name.split('::').first.downcase}_id" + + scope :where_started_at_in, ->(period_range) do + where('started_at BETWEEN :begin AND :end', begin: period_range.begin, end: period_range.end) + end + + scope :blocked, -> { where('created_at < ? AND status = ?', 4.hours.ago, 'running') } + + before_create :initialize_fields + after_save :notify_parent + end + + module ClassMethods + def launched_statuses + %w(new pending) + end + + def failed_statuses + %w(failed aborted canceled) + end + + def finished_statuses + %w(successful failed warning aborted canceled) + end + + def abort_old + where( + 'created_at < ? AND status NOT IN (?)', + 4.hours.ago, + finished_statuses + ).update_all(status: 'aborted') + end + end + + def notify_parent + return unless parent.present? + return unless status_changed? + parent.child_change + t = Time.now + self.notified_parent_at = t + self.class.where(id: self.id).update_all notified_parent_at: t + end + + def children_succeedeed + children.with_status(:successful, :warning).count + end + + def update_status + status = + if children.where(status: self.class.failed_statuses).count > 0 + 'failed' + elsif children.where(status: "warning").count > 0 + 'warning' + elsif children.where(status: "successful").count == children.count + 'successful' + else + 'running' + end + + attributes = { + current_step: children.count, + status: status + } + + if self.class.finished_statuses.include?(status) + attributes[:ended_at] = Time.now + end + + update attributes + end + + def child_change + return if self.class.finished_statuses.include?(status) + update_status + end + + def call_iev_callback + return if self.class.finished_statuses.include?(status) + threaded_call_boiv_iev + end + + private + + def threaded_call_boiv_iev + Thread.new(&method(:call_boiv_iev)) + end + + def call_boiv_iev + Rails.logger.error("Begin IEV call for import") + Net::HTTP.get iev_callback_url + Rails.logger.error("End IEV call for import") + rescue Exception => e + logger.error "IEV server error : #{e.message}" + logger.error e.backtrace.inspect + end + + private + def initialize_fields + end +end diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index 774c8b0f6..4a840744e 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -6,4 +6,80 @@ class CustomField < ActiveRecord::Base validates :name, uniqueness: {scope: [:resource_type, :workgroup_id]} validates :code, uniqueness: {scope: [:resource_type, :workgroup_id], case_sensitive: false} + + class Collection < HashWithIndifferentAccess + def initialize object + vals = object.class.custom_fields.map do |v| + [v.code, CustomField::Value.new(object, v, object.custom_field_value(v.code))] + end + super Hash[*vals.flatten] + end + + def to_hash + HashWithIndifferentAccess[*self.map{|k, v| [k, v.to_hash]}.flatten(1)] + end + end + + class Value + def self.new owner, custom_field, value + field_type = custom_field.options["field_type"] + klass_name = field_type && "CustomField::Value::#{field_type.classify}" + klass = klass_name && const_defined?(klass_name) ? klass_name.constantize : CustomField::Value::Base + klass.new owner, custom_field, value + end + + class Base + def initialize owner, custom_field, value + @custom_field = custom_field + @raw_value = value + @owner = owner + @errors = [] + @validated = false + @valid = false + end + + delegate :code, :name, :field_type, :options, to: :@custom_field + + def validate + @valid = true + end + + def valid? + validate unless @validated + @valid + end + + def value + @raw_value + end + + def errors_key + "custom_fields.#{code}" + end + + def to_hash + HashWithIndifferentAccess[*%w(code name field_type options value).map{|k| [k, send(k)]}.flatten(1)] + end + end + + class Integer < Base + def value + @raw_value.to_i + end + + def validate + @valid = true + unless @raw_value =~ /\A\d*\Z/ + @owner.errors.add errors_key, "'#{@raw_value}' is not a valid integer" + @valid = false + end + end + end + + class String < Base + def value + "#{@raw_value}" + end + end + end end diff --git a/app/models/dashboard.rb b/app/models/dashboard.rb index 46c621266..e0857dca3 100644 --- a/app/models/dashboard.rb +++ b/app/models/dashboard.rb @@ -27,4 +27,5 @@ class Dashboard def current_organisation context.send(:current_organisation) end + end diff --git a/app/models/export.rb b/app/models/export.rb deleted file mode 100644 index 8c38d6684..000000000 --- a/app/models/export.rb +++ /dev/null @@ -1,53 +0,0 @@ -class Export - include JobConcern - - def initialize( response ) - @datas = response - end - - def report? - links["action_report"].present? - end - - def report - Rails.cache.fetch("#{cache_key}/action_report", expires_in: cache_expiration) do - report_path = links["action_report"] - if report_path - response = Ievkit.get(report_path) - ExportReport.new(response) - else - nil - end - end - end - - def destroy - delete_path = links["delete"] - cancel_path = links["cancel"] - - if delete_path - Ievkit.delete(delete_path) - elsif cancel_path - Ievkit.delete(cancel_path) - else - nil - end - end - - def file_path? - links["data"].present? - end - - def file_path - links["data"] - end - - def filename - File.basename(file_path) if file_path - end - - def filename_extension - File.extname(filename).gsub(".", "") if filename - end - -end diff --git a/app/models/export/base.rb b/app/models/export/base.rb new file mode 100644 index 000000000..6085e0ffb --- /dev/null +++ b/app/models/export/base.rb @@ -0,0 +1,89 @@ +class Export::Base < ActiveRecord::Base + self.table_name = "exports" + + validates :type, presence: true + + def self.messages_class_name + "Export::Message" + end + + def self.resources_class_name + "Export::Resource" + end + + def self.human_name + I18n.t("export.#{self.name.demodulize.underscore}") + end + + def self.file_extension_whitelist + %w(zip csv json) + end + + if Rails.env.development? + def self.force_load_descendants + path = Rails.root.join 'app/models/export' + Dir.chdir path do + Dir['**/*.rb'].each do |src| + next if src =~ /^base/ + klass_name = "Export::#{src[0..-4].camelize}" + Rails.logger.info "Loading #{klass_name}" + begin + klass_name.constantize + rescue => e + Rails.logger.info "Failed: #{e.message}" + nil + end + end + end + end + end + + def self.option name, opts={} + store_accessor :options, name + if !!opts[:required] + validates name, presence: true + end + @options ||= {} + @options[name] = opts + end + + def self.options + @options ||= {} + end + + include IevInterfaces::Task + + def self.model_name + ActiveModel::Name.new Export::Base, Export::Base, "Export" + end + + def self.user_visible_descendants + descendants.select &:user_visible? + end + + def self.user_visible? + true + end + + def visible_options + options.select{|k, v| ! k.match /^_/} + end + + def display_option_value option_name, context + option = self.class.options[option_name.to_sym] + val = self.options[option_name.to_s] + if option[:display] + context.instance_exec(val, &option[:display]) + else + val + end + end + + private + + def initialize_fields + super + self.token_upload = SecureRandom.urlsafe_base64 + end + +end diff --git a/app/models/export/message.rb b/app/models/export/message.rb new file mode 100644 index 000000000..b64b524ac --- /dev/null +++ b/app/models/export/message.rb @@ -0,0 +1,8 @@ +class Export::Message < ActiveRecord::Base + self.table_name = :export_messages + + include IevInterfaces::Message + + belongs_to :export, class_name: Export::Base + belongs_to :resource, class_name: Export::Resource +end diff --git a/app/models/export/netex.rb b/app/models/export/netex.rb new file mode 100644 index 000000000..069ec2209 --- /dev/null +++ b/app/models/export/netex.rb @@ -0,0 +1,22 @@ +class Export::Netex < Export::Base + after_commit :call_iev_callback, on: :create + option :export_type, collection: %w(line full), required: true + option :duration, type: :integer, default_value: 90, required: true + option :line_code + + private + + def iev_callback_url + URI("#{Rails.configuration.iev_url}/boiv_iev/referentials/exporter/new?id=#{id}") + end + + # def self.user_visible? + # false + # end + + def destroy_non_ready_referential + if referential && !referential.ready + referential.destroy + end + end +end diff --git a/app/models/export/referential_companies.rb b/app/models/export/referential_companies.rb new file mode 100644 index 000000000..0b6187060 --- /dev/null +++ b/app/models/export/referential_companies.rb @@ -0,0 +1,92 @@ +class Export::ReferentialCompanies < Export::Base + option :referential_id, + type: :select, + collection: ->(){workbench.referentials.all}, + required: true, + display: ->(val){r = Referential.find(val); link_to(r.name, [r])} + + after_create :call_exporter_async + + def referential + Referential.find referential_id + end + + def call_exporter_async + SimpleExportWorker.perform_async(id) + end + + def exporter + SimpleExporter.define :referential_companies do |config| + config.separator = ";" + config.encoding = 'ISO-8859-1' + config.add_column :name + config.add_column :registration_number + end + + @exporter ||= begin + if options[:_exporter_id] + exporter = SimpleExporter.find options[:exporter_id] + else + exporter = SimpleExporter.create configuration_name: :referential_companies + options[:_exporter_id] = exporter.id + end + exporter + end + end + + def call_exporter + tmp = Tempfile.new ["referential_companies", ".csv"] + referential.switch + exporter.configure do |config| + config.collection = referential.companies.order(:name) + end + exporter.filepath = tmp.path + exporter.export + set_status_from_exporter + convert_exporter_journal_to_messages + self.file = tmp + self.save! + end + + def set_status_from_exporter + if exporter.status == :error + self.status = :failed + else + if exporter.status == :success + self.status = :successful + else + self.status = :warning + end + end + end + + def convert_exporter_journal_to_messages + self.messages.destroy_all + exporter.journal.each do |journal_item| + journal_item.symbolize_keys! + vals = {} + + if journal_item[:kind].to_s == "warning" + vals[:criticity] = :warning + elsif journal_item[:kind].to_s == "error" + vals[:criticity] = :error + else + vals[:criticity] = :info + if journal_item[:event].to_s == "success" + vals[:message_key] = :success + end + end + vals[:resource_attributes] = journal_item[:row] + + if journal_item[:message].present? + vals[:message_key] = :full_text + vals[:message_attributes] = { + text: journal_item[:message] + } + end + vals[:message_attributes] ||= {} + vals[:message_attributes][:line] = journal_item[:line] + self.messages.build vals + end + end +end diff --git a/app/models/export/resource.rb b/app/models/export/resource.rb new file mode 100644 index 000000000..98f103be4 --- /dev/null +++ b/app/models/export/resource.rb @@ -0,0 +1,8 @@ +class Export::Resource < ActiveRecord::Base + self.table_name = :export_resources + + include IevInterfaces::Resource + + belongs_to :export, class_name: Export::Base + has_many :messages, class_name: "ExportMessage", foreign_key: :resource_id +end diff --git a/app/models/export/workgroup.rb b/app/models/export/workgroup.rb new file mode 100644 index 000000000..3430596c7 --- /dev/null +++ b/app/models/export/workgroup.rb @@ -0,0 +1,9 @@ +class Export::Workgroup < Export::Base + after_commit :launch_worker, :on => :create + + option :duration, required: true, type: :integer, default_value: 90 + + def launch_worker + WorkgroupExportWorker.perform_async(id) + end +end diff --git a/app/models/export_log_message.rb b/app/models/export_log_message.rb deleted file mode 100644 index 4bb9d3cc7..000000000 --- a/app/models/export_log_message.rb +++ /dev/null @@ -1,42 +0,0 @@ -class ExportLogMessage < ActiveRecord::Base - belongs_to :export - - acts_as_list :scope => :export - - validates_presence_of :key - validates_inclusion_of :severity, :in => %w{info warning error ok uncheck fatal} - - def arguments=(arguments) - write_attribute :arguments, (arguments.to_json if arguments.present?) - end - - def arguments - @decoded_arguments ||= - begin - if (stored_arguments = raw_attributes).present? - ActiveSupport::JSON.decode stored_arguments - else - {} - end - end - end - - def raw_attributes - read_attribute(:arguments) - end - - before_validation :define_default_attributes, :on => :create - def define_default_attributes - self.severity ||= "info" - end - - def full_message - last_key=key.rpartition("|").last - begin - I18n.translate last_key, arguments.symbolize_keys.merge(:scope => "export_log_messages.messages").merge(:default => :undefined).merge(:key => last_key) - rescue => e - Rails.logger.error "missing arguments for message "+last_key - I18n.translate "WRONG_DATA",{"0"=>last_key}.symbolize_keys.merge(:scope => "export_log_messages.messages").merge(:default => :undefined).merge(:key => "WRONG_DATA") - end - end -end diff --git a/app/models/export_report.rb b/app/models/export_report.rb deleted file mode 100644 index 3c0788106..000000000 --- a/app/models/export_report.rb +++ /dev/null @@ -1,8 +0,0 @@ -class ExportReport - #include ReportConcern - - def initialize( response ) - @datas = response.action_report - end - -end diff --git a/app/models/export_service.rb b/app/models/export_service.rb deleted file mode 100644 index 2dbe0d7b3..000000000 --- a/app/models/export_service.rb +++ /dev/null @@ -1,23 +0,0 @@ -class ExportService - - attr_reader :referential - - def initialize(referential) - @referential = referential - end - - # Find an export whith his id - def find(id) - Export.new( Ievkit.scheduled_job(referential.slug, id, { :action => "exporter" }) ) - end - - # Find all exports - def all - [].tap do |jobs| - Ievkit.jobs(referential.slug, { :action => "exporter" }).each do |job| - jobs << Export.new( job ) - end - end - end - -end diff --git a/app/models/export_task.rb b/app/models/export_task.rb deleted file mode 100644 index f02cb914e..000000000 --- a/app/models/export_task.rb +++ /dev/null @@ -1,119 +0,0 @@ -class ExportTask - extend Enumerize - extend ActiveModel::Naming - extend ActiveModel::Translation - extend ActiveModel::Callbacks - include ActiveModel::Validations - include ActiveModel::Conversion - - attr_accessor :start_date, :end_date - - define_model_callbacks :initialize, only: :after - - enumerize :data_format, in: %w( neptune netex gtfs hub kml ) - attr_accessor :referential_id, :user_id, :user_name, :references_type, :data_format, :name, :projection_type, :reference_ids - - validates_presence_of :referential_id - validates_presence_of :user_id - validates_presence_of :user_name - validates_presence_of :name - validates_presence_of :data_format - - validate :period_validation - - after_initialize :init_period - - def initialize( params = {} ) - run_callbacks :initialize do - params.each {|k,v| send("#{k}=",v)} - end - end - - def period_validation - st_date = start_date.is_a?(String) ? Date.parse(start_date) : start_date - ed_date = end_date.is_a?(String) ? Date.parse(end_date) : end_date - - unless Chouette::TimeTable.start_validity_period.nil? || st_date.nil? - tt_st_date = Chouette::TimeTable.start_validity_period - errors.add(:start_date, ExportTask.human_attribute_name("start_date_greater_than" , {:tt_st_date => tt_st_date})) unless tt_st_date <= st_date - end - unless st_date.nil? || ed_date.nil? - errors.add(:end_date, ExportTask.human_attribute_name("end_date_greater_than_start_date")) unless st_date <= ed_date - end - unless ed_date.nil? || Chouette::TimeTable.end_validity_period.nil? - tt_ed_date = Chouette::TimeTable.end_validity_period - errors.add(:end_date, ExportTask.human_attribute_name("end_date_less_than", {:tt_ed_date => tt_ed_date})) unless ed_date <= tt_ed_date - end - end - - def init_period - unless Chouette::TimeTable.start_validity_period.nil? - if start_date.nil? - self.start_date = Chouette::TimeTable.start_validity_period - end - if end_date.nil? - self.end_date = Chouette::TimeTable.end_validity_period - end - end - end - - def referential - Referential.find(referential_id) - end - - def organisation - referential.organisation - end - - def save - if self.valid? - # Call Iev Server - begin - Ievkit.create_job( referential.slug, "exporter", data_format, { - :file1 => params_io, - } ) - rescue Exception => exception - raise exception - end - true - else - false - end - end - - def self.data_formats - self.data_format.values - end - - def self.references_types - self.references_type.values - end - - def params - {}.tap do |h| - h["parameters"] = action_params - end - end - - def action_params - {} - end - - def params_io - file = StringIO.new( params.to_json ) - Faraday::UploadIO.new(file, "application/json", "parameters.json") - end - - def self.optional_attributes(references_type) - [] - end - - def optional_attributes - self.class.optional_attributes(references_type.to_s) - end - - def optional_attribute?(attribute) - optional_attributes.include? attribute.to_sym - end - -end diff --git a/app/models/gtfs_export.rb b/app/models/gtfs_export.rb deleted file mode 100644 index d0b9fc4f9..000000000 --- a/app/models/gtfs_export.rb +++ /dev/null @@ -1,47 +0,0 @@ -class GtfsExport < ExportTask - - validates_presence_of :time_zone, unless: Proc.new { |e| e.optional_attribute? :time_zone } - attr_accessor :object_id_prefix, :time_zone - - enumerize :references_type, in: %w( network line company group_of_line stop_area ) - - after_initialize :init_params - - def init_params - if time_zone.nil? - self.time_zone = "Paris" - end - end - - def real_time_zone - ActiveSupport::TimeZone.find_tzinfo(time_zone).name - end - - def action_params - { - "gtfs-export" => { - "name" => name, - "references_type" => references_type, - "reference_ids" => reference_ids, - "user_name" => user_name, - "organisation_name" => organisation.name, - "referential_name" => referential.name, - "time_zone" => real_time_zone, - "object_id_prefix" => object_id_prefix, - "start_date" => start_date, - "end_date" => end_date - } - } - end - - def self.optional_attributes(references_type) - super.tap do |optional_attributes| - optional_attributes.push :time_zone, :start_date, :end_date if references_type == "stop_area" - end - end - - def data_format - "gtfs" - end - -end diff --git a/app/models/gtfs_import.rb b/app/models/gtfs_import.rb deleted file mode 100644 index 1d7b5c6f5..000000000 --- a/app/models/gtfs_import.rb +++ /dev/null @@ -1,36 +0,0 @@ -class GtfsImport < ImportTask - - enumerize :references_type, in: %w( stop_area ) - - attr_accessor :object_id_prefix, :max_distance_for_commercial, :ignore_last_word, :ignore_end_chars, :max_distance_for_connection_link, :references_type - - validates_presence_of :object_id_prefix - - def references_types - self.references_type.values - end - - def action_params - { - "gtfs-import" => { - "no_save" => no_save, - "user_name" => user_name, - "name" => name, - "organisation_name" => organisation.name, - "referential_name" => referential.name, - "object_id_prefix" => object_id_prefix, - "max_distance_for_commercial" => max_distance_for_commercial, - "ignore_last_word" => ignore_last_word, - "ignore_end_chars" => ignore_end_chars, - "max_distance_for_connection_link" => max_distance_for_connection_link, - "references_type" => references_type - } - } - end - - - def data_format - "gtfs" - end - -end diff --git a/app/models/hub_export.rb b/app/models/hub_export.rb deleted file mode 100644 index 802600692..000000000 --- a/app/models/hub_export.rb +++ /dev/null @@ -1,24 +0,0 @@ -class HubExport < ExportTask - - enumerize :references_type, in: %w( network line company group_of_line ) - - def action_params - { - "hub-export" => { - "name" => name, - "references_type" => references_type, - "reference_ids" => reference_ids, - "user_name" => user_name, - "organisation_name" => organisation.name, - "referential_name" => referential.name, - "start_date" => start_date, - "end_date" => end_date - } - } - end - - def data_format - "hub" - end - -end diff --git a/app/models/import.rb b/app/models/import.rb deleted file mode 100644 index 29aadcd56..000000000 --- a/app/models/import.rb +++ /dev/null @@ -1,103 +0,0 @@ -class Import < ActiveRecord::Base - mount_uploader :file, ImportUploader - belongs_to :workbench - belongs_to :referential - - belongs_to :parent, polymorphic: true - - has_many :messages, class_name: "ImportMessage", dependent: :destroy - has_many :resources, class_name: "ImportResource", dependent: :destroy - has_many :children, foreign_key: :parent_id, class_name: "Import", dependent: :destroy - - scope :where_started_at_in, ->(period_range) do - where('started_at BETWEEN :begin AND :end', begin: period_range.begin, end: period_range.end) - end - - scope :blocked, -> { where('created_at < ? AND status = ?', 4.hours.ago, 'running') } - - extend Enumerize - enumerize :status, in: %w(new pending successful warning failed running aborted canceled), scope: true, default: :new - - validates :name, presence: true - validates :file, presence: true - validates_presence_of :workbench, :creator - - before_create :initialize_fields - - def self.model_name - ActiveModel::Name.new Import, Import, "Import" - end - - def children_succeedeed - children.with_status(:successful, :warning).count - end - - def self.launched_statuses - %w(new pending) - end - - def self.failed_statuses - %w(failed aborted canceled) - end - - def self.finished_statuses - %w(successful failed warning aborted canceled) - end - - def self.abort_old - where( - 'created_at < ? AND status NOT IN (?)', - 4.hours.ago, - finished_statuses - ).update_all(status: 'aborted') - end - - def notify_parent - parent.child_change - update(notified_parent_at: DateTime.now) - end - - def child_change - return if self.class.finished_statuses.include?(status) - - update_status - update_referentials - end - - def update_status - status = - if children.where(status: self.class.failed_statuses).count > 0 - 'failed' - elsif children.where(status: "warning").count > 0 - 'warning' - elsif children.where(status: "successful").count == children.count - 'successful' - end - - attributes = { - current_step: children.count, - status: status - } - - if self.class.finished_statuses.include?(status) - attributes[:ended_at] = Time.now - end - - update attributes - end - - def update_referentials - return unless self.class.finished_statuses.include?(status) - - children.each do |import| - import.referential.update(ready: true) if import.referential - end - end - - private - - def initialize_fields - self.token_download = SecureRandom.urlsafe_base64 - end - -end diff --git a/app/models/import/base.rb b/app/models/import/base.rb new file mode 100644 index 000000000..1dd9c4195 --- /dev/null +++ b/app/models/import/base.rb @@ -0,0 +1,45 @@ +class Import::Base < ActiveRecord::Base + self.table_name = "imports" + validates :file, presence: true + + def self.messages_class_name + "Import::Message" + end + + def self.resources_class_name + "Import::Resource" + end + + def self.file_extension_whitelist + %w(zip) + end + + include IevInterfaces::Task + + def self.model_name + ActiveModel::Name.new Import::Base, Import::Base, "Import" + end + + def child_change + return if self.class.finished_statuses.include?(status) + + super + update_referentials + end + + def update_referentials + return unless self.class.finished_statuses.include?(status) + + children.each do |import| + import.referential.update(ready: true) if import.referential + end + end + + private + + def initialize_fields + super + self.token_download = SecureRandom.urlsafe_base64 + end + +end diff --git a/app/models/netex_import.rb b/app/models/import/gtfs.rb index b21af3408..03cf49e60 100644 --- a/app/models/netex_import.rb +++ b/app/models/import/gtfs.rb @@ -1,5 +1,5 @@ require 'net/http' -class NetexImport < Import +class Import::Gtfs < Import::Base before_destroy :destroy_non_ready_referential after_commit :launch_java_import, on: :create @@ -7,8 +7,6 @@ class NetexImport < Import self.status = 'aborted' unless referential end - validates_presence_of :parent - def launch_java_import return if self.class.finished_statuses.include?(status) threaded_call_boiv_iev diff --git a/app/models/import/message.rb b/app/models/import/message.rb new file mode 100644 index 000000000..c1900a718 --- /dev/null +++ b/app/models/import/message.rb @@ -0,0 +1,8 @@ +class Import::Message < ActiveRecord::Base + self.table_name = :import_messages + + include IevInterfaces::Message + + belongs_to :import, class_name: Import::Base + belongs_to :resource, class_name: Import::Resource +end diff --git a/app/models/import/netex.rb b/app/models/import/netex.rb new file mode 100644 index 000000000..2b0982229 --- /dev/null +++ b/app/models/import/netex.rb @@ -0,0 +1,24 @@ +require 'net/http' +class Import::Netex < Import::Base + before_destroy :destroy_non_ready_referential + + after_commit :call_iev_callback, on: :create + + before_save def abort_unless_referential + self.status = 'aborted' unless referential + end + + validates_presence_of :parent + + private + + def iev_callback_url + URI("#{Rails.configuration.iev_url}/boiv_iev/referentials/importer/new?id=#{id}") + end + + def destroy_non_ready_referential + if referential && !referential.ready + referential.destroy + end + end +end diff --git a/app/models/import/resource.rb b/app/models/import/resource.rb new file mode 100644 index 000000000..73be04d0e --- /dev/null +++ b/app/models/import/resource.rb @@ -0,0 +1,8 @@ +class Import::Resource < ActiveRecord::Base + self.table_name = :import_resources + + include IevInterfaces::Resource + + belongs_to :import, class_name: Import::Base + has_many :messages, class_name: "ImportMessage", foreign_key: :resource_id +end diff --git a/app/models/workbench_import.rb b/app/models/import/workbench.rb index 27f53a44f..f6e15cb89 100644 --- a/app/models/workbench_import.rb +++ b/app/models/import/workbench.rb @@ -1,4 +1,4 @@ -class WorkbenchImport < Import +class Import::Workbench < Import::Base after_commit :launch_worker, :on => :create def launch_worker diff --git a/app/models/import_message.rb b/app/models/import_message.rb deleted file mode 100644 index de70c35d1..000000000 --- a/app/models/import_message.rb +++ /dev/null @@ -1,8 +0,0 @@ -class ImportMessage < ActiveRecord::Base - extend Enumerize - belongs_to :import - belongs_to :resource, class_name: ImportResource - enumerize :criticity, in: %i(info warning error) - - validates :criticity, presence: true -end diff --git a/app/models/import_message_export.rb b/app/models/import_message_export.rb deleted file mode 100644 index 05f8a2cc7..000000000 --- a/app/models/import_message_export.rb +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -require "csv" -require "zip" - -class ImportMessageExport - include ActiveModel::Validations - include ActiveModel::Conversion - extend ActiveModel::Naming - - attr_accessor :import_messages - - def initialize(attributes = {}) - attributes.each { |name, value| send("#{name}=", value) } - end - - def persisted? - false - end - - def label(name) - I18n.t "vehicle_journey_exports.label.#{name}" - end - - def column_names - ["criticity", "message key", "message", "file name", "line", "column"] - end - - def to_csv(options = {}) - CSV.generate(options) do |csv| - csv << column_names - import_messages.each do |import_message| - csv << [import_message.criticity, import_message.message_key, I18n.t("import_messages.#{import_message.message_key}", import_message.message_attributes.deep_symbolize_keys), *import_message.resource_attributes.values_at("filename", "line_number", "column_number") ] - end - end - end - - def to_zip(temp_file,options = {}) - ::Zip::OutputStream.open(temp_file) { |zos| } - ::Zip::File.open(temp_file.path, ::Zip::File::CREATE) do |zipfile| - zipfile.get_output_stream(label("vj_filename")+route.id.to_s+".csv") { |f| f.puts to_csv(options) } - zipfile.get_output_stream(label("tt_filename")+".csv") { |f| f.puts time_tables_to_csv(options) } - zipfile.get_output_stream(label("ftn_filename")+".csv") { |f| f.puts footnotes_to_csv(options) } - end - end - -end diff --git a/app/models/import_report.rb b/app/models/import_report.rb deleted file mode 100644 index ba13f0118..000000000 --- a/app/models/import_report.rb +++ /dev/null @@ -1,8 +0,0 @@ -class ImportReport - #include ReportConcern - - def initialize( response ) - @datas = response.action_report - end - -end diff --git a/app/models/import_resource.rb b/app/models/import_resource.rb deleted file mode 100644 index 55e752e74..000000000 --- a/app/models/import_resource.rb +++ /dev/null @@ -1,11 +0,0 @@ -class ImportResource < ActiveRecord::Base - belongs_to :import - - extend Enumerize - enumerize :status, in: %i(OK ERROR WARNING IGNORED), scope: true - - validates_presence_of :name, :resource_type, :reference - - has_many :messages, class_name: "ImportMessage", foreign_key: :resource_id - -end diff --git a/app/models/import_service.rb b/app/models/import_service.rb deleted file mode 100644 index 2e3c1012b..000000000 --- a/app/models/import_service.rb +++ /dev/null @@ -1,23 +0,0 @@ -class ImportService - - attr_reader :referential - - def initialize( referential ) - @referential = referential - end - - # Find an import whith his id - def find(id) - Import.new( Ievkit.scheduled_job(referential.slug, id, { :action => "importer" }) ) - end - - # Find all imports - def all - [].tap do |jobs| - Ievkit.jobs(referential.slug, { :action => "importer" }).each do |job| - jobs << Import.new( job ) - end - end - end - -end diff --git a/app/models/import_task.rb b/app/models/import_task.rb deleted file mode 100644 index 7dfa2c644..000000000 --- a/app/models/import_task.rb +++ /dev/null @@ -1,141 +0,0 @@ -require "zip" - -class ImportTask - extend Enumerize - extend ActiveModel::Naming - extend ActiveModel::Translation - include ActiveModel::Validations - include ActiveModel::Conversion - - # TODO : Move in configuration - @@root = "#{Rails.root}/tmp/imports" - cattr_accessor :root - - enumerize :data_format, in: %w( neptune netex gtfs ) - attr_accessor :referential_id, :user_id, :user_name, :data_format, :resources, :name, :no_save - - validates_presence_of :referential_id - validates_presence_of :resources - validates_presence_of :user_id - validates_presence_of :user_name - validates_presence_of :name - - validate :validate_file_size, :validate_file_content - - def initialize( params = {} ) - params.each {|k,v| send("#{k}=",v)} - end - - def referential - Referential.find(referential_id) - end - - def organisation - referential.organisation - end - - def save - if valid? - # Save resources - save_resources - - # Call Iev Server - begin - Ievkit.create_job(referential.slug, "importer", data_format, { - :file1 => params_io, - :file2 => transport_data_io - } - - ) - - # Delete resources - delete_resources - rescue Exception => exception - # If iev server has an error must delete resources before - delete_resources - - raise exception - end - true - else - false - end - end - - def params - {}.tap do |h| - h["parameters"] = {} - end - end - - def self.data_formats - self.data_format.values - end - - def params_io - file = StringIO.new( params.to_json ) - Faraday::UploadIO.new(file, "application/json", "parameters.json") - end - - def transport_data_io - file = File.new(saved_resources_path, "r") - if file_extname == ".zip" - Faraday::UploadIO.new(file, "application/zip", original_filename ) - elsif file_extname == ".xml" - Faraday::UploadIO.new(file, "application/xml", original_filename ) - end - end - - def save_resources - FileUtils.mkdir_p root - FileUtils.cp resources.path, saved_resources_path - end - - def delete_resources - FileUtils.rm saved_resources_path if File.exists? saved_resources_path - end - - def original_filename - resources.original_filename - end - - def file_extname - File.extname(original_filename) if original_filename - end - - def saved_resources_path - @saved_resources_path ||= "#{root}/#{Time.now.to_i}#{file_extname}" - end - - @@maximum_file_size = 80.megabytes - cattr_accessor :maximum_file_size - - def validate_file_size - return unless resources.present? and resources.path.present? and File.exists? resources.path - - if File.size(resources.path) > maximum_file_size - message = I18n.t("activemodel.errors.models.import_task.attributes.resources.maximum_file_size", file_size: ActionController::Base.helpers.number_to_human_size(File.size(resources.path)), maximum_file_size: ActionController::Base.helpers.number_to_human_size(maximum_file_size)) - errors.add(:resources, message) - end - end - - @@valid_mime_types = { - neptune: %w{application/zip application/xml}, - netex: %w{application/zip}, - gtfs: %w{application/zip text/plain} - } - cattr_accessor :valid_mime_types - - def validate_file_content - return unless resources.present? and resources.path.present? and File.exists? resources.path - - mime_type = (File.open(resources.path) { |f| MimeMagic.by_magic f }).try :type - expected_mime_types = valid_mime_types[data_format.to_sym] - - unless expected_mime_types.include? mime_type - message = I18n.t("activemodel.errors.models.import_task.attributes.resources.invalid_mime_type", mime_type: mime_type) - errors.add(:resources, message) - end - end - -end diff --git a/app/models/kml_export.rb b/app/models/kml_export.rb deleted file mode 100644 index f6db77172..000000000 --- a/app/models/kml_export.rb +++ /dev/null @@ -1,24 +0,0 @@ -class KmlExport < ExportTask - - enumerize :references_type, in: %w( network line company group_of_line ) - - def action_params - { - "kml-export" => { - "name" => name, - "references_type" => references_type, - "reference_ids" => reference_ids, - "user_name" => user_name, - "organisation_name" => organisation.name, - "referential_name" => referential.name, - "start_date" => start_date, - "end_date" => end_date - } - } - end - - def data_format - "kml" - end - -end diff --git a/app/models/neptune_export.rb b/app/models/neptune_export.rb deleted file mode 100644 index f25db69c0..000000000 --- a/app/models/neptune_export.rb +++ /dev/null @@ -1,27 +0,0 @@ -class NeptuneExport < ExportTask - - attr_accessor :extensions, :export_type - enumerize :references_type, in: %w( network line company group_of_line ) - - def action_params - { - "neptune-export" => { - "name" => name, - "references_type" => references_type, - "reference_ids" => reference_ids, - "user_name" => user_name, - "organisation_name" => organisation.name, - "referential_name" => referential.name, - "projection_type" => projection_type || "", - "add_extension" => extensions, - "start_date" => start_date, - "end_date" => end_date - } - } - end - - def data_format - "neptune" - end - -end diff --git a/app/models/neptune_import.rb b/app/models/neptune_import.rb deleted file mode 100644 index 1f0bdaa13..000000000 --- a/app/models/neptune_import.rb +++ /dev/null @@ -1,19 +0,0 @@ -class NeptuneImport < ImportTask - - def action_params - { - "neptune-import" => { - "no_save" => no_save, - "user_name" => user_name, - "name" => name, - "organisation_name" => organisation.name, - "referential_name" => referential.name, - } - } - end - - def data_format - "neptune" - end - -end diff --git a/app/models/netex_export.rb b/app/models/netex_export.rb deleted file mode 100644 index a4c3e2454..000000000 --- a/app/models/netex_export.rb +++ /dev/null @@ -1,24 +0,0 @@ -class NetexExport < ExportTask - - enumerize :references_type, in: %w( network line company group_of_line ) - - def action_params - { - "netex-export" => { - "name" => name, - "references_type" => references_type, - "reference_ids" => reference_ids, - "user_name" => user_name, - "organisation_name" => organisation.name, - "referential_name" => referential.name, - "start_date" => start_date, - "end_date" => end_date - } - } - end - - def data_format - "netex" - end - -end diff --git a/app/models/organisation.rb b/app/models/organisation.rb index e8fb4e060..745bc0d22 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -13,6 +13,8 @@ class Organisation < ActiveRecord::Base has_many :line_referentials, through: :line_referential_memberships has_many :workbenches + has_many :workgroups, through: :workbenches + has_many :calendars has_many :api_keys, class_name: 'Api::V1::ApiKey' diff --git a/app/models/simple_exporter.rb b/app/models/simple_exporter.rb new file mode 100644 index 000000000..c267b5b8c --- /dev/null +++ b/app/models/simple_exporter.rb @@ -0,0 +1,182 @@ +# coding: utf-8 +class SimpleExporter < SimpleInterface + def export opts={} + configuration.validate! + + init_env opts + + @csv = nil + fail_with_error "Unable to write in file: #{self.filepath}" do + dir = Pathname.new(self.filepath).dirname + FileUtils.mkdir_p dir + @csv = CSV.open(self.filepath, 'w', self.configuration.csv_options) + end + + self.configuration.before_actions(:parsing).each do |action| action.call self end + + @statuses = "" + + process_collection + + self.status ||= :success + rescue SimpleInterface::FailedOperation + self.status = :failed + ensure + @csv&.close + task_finished + end + + def collection + @collection ||= begin + coll = configuration.collection + coll = coll.call() if coll.is_a?(Proc) + coll + end + end + + def encode_string s + s.encode("utf-8").force_encoding("utf-8") + end + + protected + def init_env opts + @number_of_lines = collection.size + super opts + end + + def process_collection + self.configuration.before_actions(:all).each do |action| action.call self end + log "Starting export ..." + log "Export will be written in #{filepath}" + @csv << self.configuration.columns.map(&:name) + if collection.is_a?(ActiveRecord::Relation) && collection.model.column_names.include?("id") + ids = collection.pluck :id + ids.in_groups_of(configuration.batch_size).each do |batch_ids| + collection.where(id: batch_ids).each do |item| + handle_item item + end + end + else + collection.each{|item| handle_item item } + end + print_state + end + + def map_item_to_rows item + return [item] unless configuration.item_to_rows_mapping + configuration.item_to_rows_mapping.call(item).map {|row| row.is_a?(ActiveRecord::Base) ? row : CustomRow.new(row) } + end + + def resolve_value item, col + scoped_item = col.scope.inject(item){|tmp, scope| tmp.send(scope)} + val = col[:value] + if val.nil? || val.is_a?(Proc) + if val.is_a?(Proc) + val = instance_exec(scoped_item, &val) + else + attributes = [col.attribute].flatten + val = attributes.inject(scoped_item){|tmp, attr| tmp.send(attr) if tmp } + end + end + if val.nil? && !col.omit_nil? + push_in_journal({event: :attribute_not_found, message: "Value missing for: #{[col.scope, col.attribute].flatten.join('.')}", kind: :warning}) + self.status ||= :success_with_warnings + @new_status ||= colorize("✓", :orange) + end + + if val.nil? && col.required? + @new_status = colorize("x", :red) + raise "MISSING VALUE FOR COLUMN #{col.name}" + end + + val = encode_string(val) if val.is_a?(String) + val + end + + def handle_item item + number_of_lines = @number_of_lines + @current_item = item + map_item_to_rows(item).each_with_index do |item, i| + @number_of_lines = number_of_lines + i + @current_row = item.attributes + @current_row = @current_row.slice(*configuration.logged_attributes) if configuration.logged_attributes.present? + row = [] + @new_status = nil + self.configuration.columns.each do |col| + val = resolve_value item, col + @new_status ||= colorize("✓", :green) + row << val + end + push_in_journal({event: :success, kind: :log}) + @statuses += @new_status + print_state if @current_line % 20 == 0 || i > 0 + @current_line += 1 + @csv << row + end + end + + class CustomRow < OpenStruct + def initialize data + super data + @data = data + end + + def attributes + flatten_hash @data + end + + protected + def flatten_hash h + h.each_with_object({}) do |(k, v), h| + if v.is_a? Hash + flatten_hash(v).map do |h_k, h_v| + h["#{k}.#{h_k}".to_sym] = h_v + end + elsif v.is_a? ActiveRecord::Base + flatten_hash(v.attributes).map do |h_k, h_v| + h["#{k}.#{h_k}".to_sym] = h_v + end + else + h[k] = v + end + end + end + end + + class Configuration < SimpleInterface::Configuration + attr_accessor :collection + attr_accessor :batch_size + attr_accessor :logged_attributes + attr_accessor :item_to_rows_mapping + + def initialize import_name, opts={} + super import_name, opts + @collection = opts[:collection] + @batch_size = opts[:batch_size] || 1000 + @logged_attributes = opts[:logged_attributes] + @item_to_rows_mapping = opts[:item_to_rows_mapping] + end + + def options + super.update({ + collection: collection, + batch_size: batch_size, + logged_attributes: logged_attributes, + item_to_rows_mapping: item_to_rows_mapping, + }) + end + + def map_item_to_rows &block + @item_to_rows_mapping = block + end + + def add_column name, opts={} + raise "Column already defined: #{name}" if @columns.any?{|c| c.name == name.to_s} + super name, opts + end + + def validate! + raise "Incomplete configuration, missing collection for #{@import_name}" if collection.nil? + end + end +end diff --git a/app/models/simple_importer.rb b/app/models/simple_importer.rb index d6ba64494..4cfe90cff 100644 --- a/app/models/simple_importer.rb +++ b/app/models/simple_importer.rb @@ -1,38 +1,5 @@ -class SimpleImporter < ActiveRecord::Base - attr_accessor :configuration - - def self.define name - @importers ||= {} - configuration = Configuration.new name - yield configuration - configuration.validate! - @importers[name.to_sym] = configuration - end - - def self.find_configuration name - @importers ||= {} - configuration = @importers[name.to_sym] - raise "Importer not found: #{name}" unless configuration - configuration - end - - def initialize *args - super *args - self.configuration = self.class.find_configuration self.configuration_name - self.journal ||= [] - end - - def configure - new_config = configuration.duplicate - yield new_config - new_config.validate! - self.configuration = new_config - end - - def context - self.configuration.context - end - +# coding: utf-8 +class SimpleImporter < SimpleInterface def resolve col_name, value, &block val = block.call(value) return val if val.present? @@ -41,20 +8,15 @@ class SimpleImporter < ActiveRecord::Base end def import opts={} - @verbose = opts.delete :verbose - + configuration.validate! - @resolution_queue = Hash.new{|h,k| h[k] = []} - @errors = [] - @messages = [] - @number_of_lines = 0 - @padding = 1 - @current_line = 0 fail_with_error "File not found: #{self.filepath}" do @number_of_lines = CSV.read(self.filepath, self.configuration.csv_options).length - @padding = [1, Math.log(@number_of_lines, 10).ceil()].max end + init_env opts + + @resolution_queue = Hash.new{|h,k| h[k] = []} self.configuration.before_actions(:parsing).each do |action| action.call self end @@ -68,34 +30,19 @@ class SimpleImporter < ActiveRecord::Base end end self.status ||= :success - rescue FailedImport + rescue SimpleInterface::FailedOperation self.status = :failed ensure - self.save! - end - - def fail_with_error msg=nil, opts={} - begin - yield - rescue => e - msg = msg.call if msg.is_a?(Proc) - custom_print "\nFAILED: \n errors: #{msg}\n exception: #{e.message}\n#{e.backtrace.join("\n")}", color: :red unless self.configuration.ignore_failures - push_in_journal({message: msg, error: e.message, event: :error, kind: :error}) - @new_status = colorize("x", :red) - if self.configuration.ignore_failures - raise FailedRow if opts[:abort_row] - else - raise FailedImport - end - end + task_finished end def encode_string s + return if s.nil? s.encode("utf-8").force_encoding("utf-8") end def dump_csv_from_context - dir = context[:output_dir] || "log/importers" + dir = context[:logs_output_dir] || "log/importers" filepath = File.join dir, "#{self.configuration_name}_#{Time.now.strftime "%y%m%d%H%M"}.csv" # for some reason, context[:csv].to_csv does not work CSV.open(filepath, 'w') do |csv| @@ -109,16 +56,6 @@ class SimpleImporter < ActiveRecord::Base log "CSV file dumped in #{filepath}" end - def log msg, opts={} - msg = colorize msg, opts[:color] if opts[:color] - if opts[:append] - @messages[-1] = (@messages[-1] || "") + msg - else - @messages << msg - end - print_state - end - protected def process_csv_file @@ -144,6 +81,7 @@ class SimpleImporter < ActiveRecord::Base if self.configuration.ignore_failures unless @current_record.save @new_status = colorize("x", :red) + self.status = :success_with_errors push_in_journal({message: "errors: #{@current_record.errors.messages}", error: "invalid record", event: :error, kind: :error}) end else @@ -154,23 +92,23 @@ class SimpleImporter < ActiveRecord::Base action.call self, @current_record end end - rescue FailedRow + rescue SimpleInterface::FailedRow @new_status = colorize("x", :red) end push_in_journal({event: @event, kind: :log}) if @current_record&.valid? @statuses += @new_status self.configuration.columns.each do |col| - if @current_record && col.name && @resolution_queue.any? - val = @current_record.send col[:attribute] - (@resolution_queue.delete([col.name, val]) || []).each do |res| - record = res[:record] - attribute = res[:attribute] - value = res[:block].call(val, record) - record.send "#{attribute}=", value - record.save! + if @current_record && col.name && @resolution_queue.any? + val = @current_record.send col[:attribute] + (@resolution_queue.delete([col.name, val]) || []).each do |res| + record = res[:record] + attribute = res[:attribute] + value = res[:block].call(val, record) + record.send "#{attribute}=", value + record.save! + end end end - end print_state @current_line += 1 end @@ -179,7 +117,7 @@ class SimpleImporter < ActiveRecord::Base self.configuration.after_actions(:all).each do |action| action.call self end - rescue FailedRow + rescue SimpleInterface::FailedRow end end @@ -215,211 +153,20 @@ class SimpleImporter < ActiveRecord::Base end end - def push_in_journal data - line = @current_line + 1 - line += 1 if configuration.headers - self.journal.push data.update(line: line, row: @current_row) - if data[:kind] == :error || data[:kind] == :warning - @errors.push data - end - end - - def colorize txt, color - color = { - red: "31", - green: "32", - orange: "33", - }[color] || "33" - "\e[#{color}m#{txt}\e[0m" - end - - def print_state - return unless @verbose - - @status_width ||= begin - term_width = %x(tput cols).to_i - term_width - @padding - 10 - rescue - 100 - end - - @status_height ||= begin - term_height = %x(tput lines).to_i - term_height - 3 - rescue - 50 - end - - full_status = @statuses || "" - full_status = full_status.last(@status_width*10) || "" - padding_size = [(@number_of_lines - @current_line - 1), (@status_width - full_status.size/10)].min - full_status = "#{full_status}#{"."*[padding_size, 0].max}" - - msg = "#{"%#{@padding}d" % (@current_line + 1)}/#{@number_of_lines}: #{full_status}" - - lines_count = [(@status_height / 2) - 3, 1].max - - if @messages.any? - msg += "\n\n" - msg += colorize "=== MESSAGES (#{@messages.count}) ===\n", :green - msg += "[...]\n" if @messages.count > lines_count - msg += @messages.last(lines_count).map{|m| m.truncate(@status_width)}.join("\n") - msg += "\n"*[lines_count-@messages.count, 0].max - end - - if @errors.any? - msg += "\n\n" - msg += colorize "=== ERRORS (#{@errors.count}) ===\n", :red - msg += "[...]\n" if @errors.count > lines_count - msg += @errors.last(lines_count).map do |j| - kind = j[:kind] - kind = colorize(kind, kind == :error ? :red : :orange) - kind = "[#{kind}]" - kind += " "*(25 - kind.size) - encode_string("#{kind}L#{j[:line]}\t#{j[:error]}\t\t#{j[:message]}").truncate(@status_width) - end.join("\n") - end - custom_print msg, clear: true - end - - def custom_print msg, opts={} - return unless @verbose - out = "" - msg = colorize(msg, opts[:color]) if opts[:color] - puts "\e[H\e[2J" if opts[:clear] - out += msg - print out - end - - class FailedImport < RuntimeError - end - - class FailedRow < RuntimeError - end - - class Configuration - attr_accessor :model, :headers, :separator, :key, :context, :encoding, :ignore_failures, :scope - attr_reader :columns + class Configuration < SimpleInterface::Configuration + attr_accessor :model def initialize import_name, opts={} - @import_name = import_name - @key = opts[:key] || "id" - @headers = opts.has_key?(:headers) ? opts[:headers] : true - @separator = opts[:separator] || "," - @encoding = opts[:encoding] - @columns = opts[:columns] || [] + super import_name, opts @model = opts[:model] - @custom_handler = opts[:custom_handler] - @before = opts[:before] - @after = opts[:after] - @ignore_failures = opts[:ignore_failures] - @context = opts[:context] || {} - @scope = opts[:scope] - end - - def duplicate - Configuration.new @import_name, self.options end def options - { - key: @key, - headers: @headers, - separator: @separator, - encoding: @encoding, - columns: @columns.map(&:duplicate), - model: model, - custom_handler: @custom_handler, - before: @before, - after: @after, - ignore_failures: @ignore_failures, - context: @context, - scope: @scope - } + super.update({model: model}) end def validate! raise "Incomplete configuration, missing model for #{@import_name}" unless model.present? end - - def attribute_for_col col_name - column = self.columns.find{|c| c.name == col_name} - column && column[:attribute] || col_name - end - - def record_scope - _scope = @scope - _scope = instance_exec(&_scope) if _scope.is_a?(Proc) - _scope || model - end - - def find_record attrs - record_scope.find_or_initialize_by(attribute_for_col(@key) => attrs[@key.to_s]) - end - - def csv_options - { - headers: self.headers, - col_sep: self.separator, - encoding: self.encoding - } - end - - def add_column name, opts={} - @columns.push Column.new({name: name.to_s}.update(opts)) - end - - def add_value attribute, value - @columns.push Column.new({attribute: attribute, value: value}) - end - - def before group=:all, &block - @before ||= Hash.new{|h, k| h[k] = []} - @before[group].push block - end - - def after group=:all, &block - @after ||= Hash.new{|h, k| h[k] = []} - @after[group].push block - end - - def before_actions group=:all - @before ||= Hash.new{|h, k| h[k] = []} - @before[group] - end - - def after_actions group=:all - @after ||= Hash.new{|h, k| h[k] = []} - @after[group] - end - - def custom_handler &block - @custom_handler = block - end - - def get_custom_handler - @custom_handler - end - - class Column - attr_accessor :name - def initialize opts={} - @name = opts[:name] - @options = opts - @options[:attribute] ||= @name - end - - def duplicate - Column.new @options.dup - end - - def required? - !!@options[:required] - end - - def [](key) - @options[key] - end - end end end diff --git a/app/models/simple_interface.rb b/app/models/simple_interface.rb new file mode 100644 index 000000000..43c740b57 --- /dev/null +++ b/app/models/simple_interface.rb @@ -0,0 +1,372 @@ +class SimpleInterface < ActiveRecord::Base + attr_accessor :configuration, :interfaces_group + + class << self + def configuration_class + "#{self.name}::Configuration".constantize + end + + def define name + @importers ||= {} + configuration = configuration_class.new name + yield configuration if block_given? + @importers[name.to_sym] = configuration + end + + def find_configuration name + @importers ||= {} + configuration = @importers[name.to_sym] + raise "#{self.name} not found: #{name}" unless configuration + configuration + end + end + + def initialize *args + super *args + self.configuration = self.class.find_configuration self.configuration_name + self.journal ||= [] + end + + def configuration + @configuration ||= self.class.find_configuration self.configuration_name + end + + def init_env opts + @verbose = opts.delete :verbose + + @_errors = [] + @messages = [] + @padding = 1 + @current_line = -1 + @number_of_lines ||= 1 + @padding = [1, Math.log([@number_of_lines, 1].max, 10).ceil()].max + @output_dir = opts[:output_dir] || Rails.root.join('tmp', self.class.name.tableize) + @start_time = Time.now + end + + def configure + new_config = configuration.duplicate + yield new_config + self.configuration = new_config + end + + def context + self.configuration.context + end + + def fail_with_error msg=nil, opts={} + begin + yield + rescue => e + msg = msg.call if msg.is_a?(Proc) + custom_print "\nFAILED: \n errors: #{msg}\n exception: #{e.message}\n#{e.backtrace.join("\n")}", color: :red unless self.configuration.ignore_failures + push_in_journal({message: msg, error: e.message, event: :error, kind: :error}) + @new_status = colorize("x", :red) + self.status = :success_with_errors + if self.configuration.ignore_failures + raise SimpleInterface::FailedRow if opts[:abort_row] + else + raise FailedOperation + end + end + end + + def log msg, opts={} + msg = msg.to_s + msg = colorize msg, opts[:color] if opts[:color] + @start_time ||= Time.now + time = Time.now - @start_time + @messages ||= [] + if opts[:append] + _time, _msg = @messages.pop || [] + _time ||= time + _msg ||= "" + @messages.push [_time, _msg+msg] + elsif opts[:replace] + @messages.pop + @messages << [time, msg] + else + @messages << [time, msg] + end + print_state true + end + + def output_filepath + @output_filepath ||= File.join @output_dir, "#{self.configuration_name}_#{Time.now.strftime "%y%m%d%H%M"}_out.csv" + end + + def write_output_to_csv + cols = %i(line kind event message error) + if self.journal.size > 0 && self.journal.first[:row].present? + log "Writing output log" + FileUtils.mkdir_p @output_dir + keys = self.journal.first[:row].map(&:first) + CSV.open(output_filepath, "w") do |csv| + csv << cols + keys + self.journal.each do |j| + csv << cols.map{|c| j[c]} + j[:row].map(&:last) + end + end + log "Output written in #{output_filepath}", replace: true + end + end + + protected + + def task_finished + log "Saving..." + self.save! + log "Saved", replace: true + write_output_to_csv + log "FINISHED, status: " + log status, color: SimpleInterface.status_color(status), append: true + print_state true + end + + def push_in_journal data + line = (@current_line || 0) + 1 + line += 1 if configuration.headers + @_errors ||= [] + self.journal.push data.update(line: line, row: @current_row) + if data[:kind] == :error || data[:kind] == :warning + @_errors.push data + end + end + + def self.colorize txt, color + color = { + red: "31", + green: "32", + orange: "33", + }[color] || "33" + "\e[#{color}m#{txt}\e[0m" + end + + def self.status_color status + color = :green + color = :orange if status.to_s == "success_with_warnings" + color = :red if status.to_s == "success_with_errors" + color = :red if status.to_s == "error" + color + end + + def colorize txt, color + SimpleInterface.colorize txt, color + end + + def print_state force=false + return unless @verbose + return if !@last_repaint.nil? && (Time.now - @last_repaint < 0.1) && !force + + @status_width ||= begin + @term_width = %x(tput cols).to_i + @term_width - @padding - 10 + rescue + 100 + end + + @status_height ||= begin + term_height = %x(tput lines).to_i + term_height - 3 + rescue + 50 + end + + msg = "" + + if @banner.nil? && interfaces_group.present? + @banner = interfaces_group.banner @status_width + @status_height -= @banner.lines.count + 2 + end + + if @banner.present? + msg += @banner + msg += "\n" + "-"*@term_width + "\n" + end + + full_status = @statuses || "" + full_status = full_status.last(@status_width*10) || "" + padding_size = [(@number_of_lines - @current_line - 1), (@status_width - full_status.size/10)].min + full_status = "#{full_status}#{"."*[padding_size, 0].max}" + + msg += "#{"%#{@padding}d" % (@current_line + 1)}/#{@number_of_lines}: #{full_status}" + + lines_count = [(@status_height / 2) - 3, 1].max + + if @messages.any? + msg += "\n\n" + msg += colorize "=== MESSAGES (#{@messages.count}) ===\n", :green + msg += "[...]\n" if @messages.count > lines_count + msg += @messages.last(lines_count).map do |m| + "[#{"%.5f" % m[0]}]\t" + m[1].truncate(@status_width - 10) + end.join("\n") + msg += "\n"*[lines_count-@messages.count, 0].max + end + + if @_errors.any? + msg += "\n\n" + msg += colorize "=== ERRORS (#{@_errors.count}) ===\n", :red + msg += "[...]\n" if @_errors.count > lines_count + msg += @_errors.last(lines_count).map do |j| + kind = j[:kind] + kind = colorize(kind, kind == :error ? :red : :orange) + kind = "[#{kind}]" + kind += " "*(25 - kind.size) + encode_string("#{kind}L#{j[:line]}\t#{j[:error]}\t\t#{j[:message]}").truncate(@status_width) + end.join("\n") + end + custom_print msg, clear: true + @last_repaint = Time.now + end + + def custom_print msg, opts={} + return unless @verbose + out = "" + msg = colorize(msg, opts[:color]) if opts[:color] + puts "\e[H\e[2J" if opts[:clear] + out += msg + print out + end + + class FailedRow < RuntimeError + end + + class FailedOperation < RuntimeError + end + + class Configuration + attr_accessor :headers, :separator, :key, :context, :encoding, :ignore_failures, :scope + attr_reader :columns + + def initialize import_name, opts={} + @import_name = import_name + @key = opts[:key] || "id" + @headers = opts.has_key?(:headers) ? opts[:headers] : true + @separator = opts[:separator] || "," + @encoding = opts[:encoding] + @columns = opts[:columns] || [] + @custom_handler = opts[:custom_handler] + @before = opts[:before] + @after = opts[:after] + @ignore_failures = opts[:ignore_failures] + @context = opts[:context] || {} + @scope = opts[:scope] + end + + def on_relation relation_name + @current_scope ||= [] + @current_scope.push relation_name + yield + @current_scope.pop + end + + def duplicate + self.class.new @import_name, self.options + end + + def options + { + key: @key, + headers: @headers, + separator: @separator, + encoding: @encoding, + columns: @columns.map(&:duplicate), + custom_handler: @custom_handler, + before: @before, + after: @after, + ignore_failures: @ignore_failures, + context: @context, + scope: @scope + } + end + + def attribute_for_col col_name + column = self.columns.find{|c| c.name == col_name} + column && column[:attribute] || col_name + end + + def record_scope + _scope = @scope + _scope = instance_exec(&_scope) if _scope.is_a?(Proc) + _scope || model + end + + def find_record attrs + record_scope.find_or_initialize_by(attribute_for_col(@key) => attrs[@key.to_s]) + end + + def csv_options + { + headers: self.headers, + col_sep: self.separator, + encoding: self.encoding + } + end + + def add_column name, opts={} + @current_scope ||= [] + @columns.push Column.new({name: name.to_s, scope: @current_scope.dup}.update(opts)) + end + + def add_value attribute, value + @columns.push Column.new({attribute: attribute, value: value}) + end + + def before group=:all, &block + @before ||= Hash.new{|h, k| h[k] = []} + @before[group].push block + end + + def after group=:all, &block + @after ||= Hash.new{|h, k| h[k] = []} + @after[group].push block + end + + def before_actions group=:all + @before ||= Hash.new{|h, k| h[k] = []} + @before[group] + end + + def after_actions group=:all + @after ||= Hash.new{|h, k| h[k] = []} + @after[group] + end + + def custom_handler &block + @custom_handler = block + end + + def get_custom_handler + @custom_handler + end + + class Column + attr_accessor :name, :attribute + def initialize opts={} + @name = opts[:name] + @options = opts + @attribute = @options[:attribute] ||= @name + end + + def duplicate + Column.new @options.dup + end + + def required? + !!@options[:required] + end + + def omit_nil? + !!@options[:omit_nil] + end + + def scope + @options[:scope] || [] + end + + def [](key) + @options[key] + end + end + end +end diff --git a/app/models/simple_interfaces_group.rb b/app/models/simple_interfaces_group.rb new file mode 100644 index 000000000..808be6570 --- /dev/null +++ b/app/models/simple_interfaces_group.rb @@ -0,0 +1,76 @@ +class SimpleInterfacesGroup + attr_accessor :name, :shared_options + + def initialize name + @name = name + @interfaces = [] + @current_step = 0 + end + + def add_interface interface, name, action, opts={} + @interfaces.push({interface: interface, name: name, action: action, opts: opts}) + end + + def run + @interfaces.each do |interface_def| + interface = interface_def[:interface] + interface.interfaces_group = self + interface.send interface_def[:action], interface_def[:opts].reverse_update(shared_options || {}) + return if interface.status == :error + @current_step += 1 + end + + print_summary + end + + def banner width=nil + width ||= @width + width ||= 128 + @width = width + + name = "### #{self.name} ###" + centered_name = " " * ([width - name.size, 0].max / 2) + name + banner = [centered_name, ""] + banner << @interfaces.each_with_index.map do |interface, i| + if interface[:interface].status.present? + SimpleInterface.colorize interface[:name], SimpleInterface.status_color(interface[:interface].status) + elsif i == @current_step + "☕︎ #{interface[:name]}" + else + interface[:name] + end + end.join(' > ') + banner.join("\n") + end + + def print_summary + puts "\e[H\e[2J" + out = [banner] + out << "-" * @width + out << "" + out << SimpleInterface.colorize("=== STATUSES ===", :green) + out << "" + @interfaces.each do |i| + out << "#{i[:name].rjust(@interfaces.map{|i| i[:name].size}.max)}:\t#{SimpleInterface.colorize i[:interface].status, SimpleInterface.status_color(i[:interface].status)}" + end + out << "" + out << SimpleInterface.colorize("=== OUTPUTS ===", :green) + out << "" + @interfaces.each do |i| + if i[:interface].is_a? SimpleExporter + out << "#{i[:name].rjust(@interfaces.map{|i| i[:name].size}.max)}:\t#{i[:interface].filepath}" + end + end + out << "" + out << "" + out << SimpleInterface.colorize("=== DEBUG OUTPUTS ===", :green) + out << "" + @interfaces.each do |i| + out << "#{i[:name].rjust(@interfaces.map{|i| i[:name].size}.max)}:\t#{i[:interface].output_filepath}" + end + out << "" + out << "" + + print out.join("\n") + end +end diff --git a/app/models/simple_json_exporter.rb b/app/models/simple_json_exporter.rb new file mode 100644 index 000000000..024d75c97 --- /dev/null +++ b/app/models/simple_json_exporter.rb @@ -0,0 +1,183 @@ +class SimpleJsonExporter < SimpleExporter + + def export opts={} + configuration.validate! + + init_env opts + + if self.configuration.root + @out = {self.configuration.root => []} + else + @out = [] + end + + fail_with_error "Unable to write in file: #{self.filepath}" do + dir = Pathname.new(self.filepath).dirname + FileUtils.mkdir_p dir + @file = File.open(self.filepath, 'w', self.configuration.file_options) + end + + self.configuration.before_actions(:parsing).each do |action| action.call self end + + @statuses = "" + + process_collection + self.status ||= :success + rescue SimpleInterface::FailedOperation + self.status = :failed + ensure + if @file + log "Writing to JSON file..." + @file.write @out.to_json + log "JSON file written", replace: true + @file.close + end + task_finished + end + + protected + def root + if self.configuration.root + @out[self.configuration.root] + else + @out + end + end + + def process_collection + self.configuration.before_actions(:all).each do |action| action.call self end + log "Starting export ..." + log "Export will be written in #{filepath}" + + if collection.is_a?(ActiveRecord::Relation) && collection.model.column_names.include?("id") + log "Using paginated collection", color: :green + ids = collection.pluck :id + ids.in_groups_of(configuration.batch_size).each do |batch_ids| + collection.where(id: batch_ids.compact).each do |item| + handle_item item + end + end + else + log "Using non-paginated collection", color: :orange + collection.each{|item| handle_item item } + end + print_state true + end + + def resolve_node item, node + vals = [] + scoped_item = node.scope.inject(item){|tmp, scope| tmp.send(scope)} + + [scoped_item.send(node.attribute)].flatten.each do |node_item| + item_val = {} + apply_configuration node_item, node.configuration, item_val + vals.push item_val + end + node.multiple ? vals : vals.first + end + + def apply_configuration item, configuration, output + configuration.columns.each do |col| + val = resolve_value item, col + output[col.name] = val unless val.nil? && col.omit_nil? + end + + configuration.nodes.each do |node| + val = resolve_node item, node + output[node.name] = val + end + end + + def handle_item item + number_of_lines = @number_of_lines + @current_item = item + map_item_to_rows(item).each_with_index do |item, i| + @number_of_lines = number_of_lines + i + serialized_item = {} + @current_row = item.attributes.symbolize_keys + @current_row = @current_row.slice(*configuration.logged_attributes) if configuration.logged_attributes.present? + @new_status = nil + + apply_configuration item, self.configuration, serialized_item + + @new_status ||= colorize("✓", :green) + + push_in_journal({event: :success, kind: :log}) + @statuses += @new_status + print_state if @current_line % 20 == 0 || i > 0 + @current_line += 1 + append_item serialized_item + end + end + + def append_item serialized_item + root.push serialized_item + end + + class Configuration < SimpleExporter::Configuration + attr_reader :nodes + attr_accessor :root + + alias_method :add_field, :add_column + + def initialize import_name, opts={} + super import_name, opts + @collection = opts[:collection] + @nodes = opts[:nodes] || [] + @root = opts[:root] + end + + def options + super.update({ + nodes: @nodes, + root: @root, + }) + end + + def add_node name, opts={} + @nodes ||= [] + @current_scope ||= [] + node = Node.new({name: name.to_s, scope: @current_scope.dup}.update(opts)) + yield node.configuration + @nodes.push node + end + + def add_nodes name, opts={}, &block + self.add_node name, opts.update({multiple: true}), &block + end + + def file_options + { + encoding: self.encoding + } + end + end + + class NodeConfiguration < Configuration + def initialize node + super + end + end + + class Node + attr_accessor :name, :configuration + + def initialize opts={} + @name = opts[:name] + @options = opts + @configuration = NodeConfiguration.new self + end + + def attribute + @options[:attribute] || name + end + + def multiple + !!@options[:multiple] + end + + def scope + @options[:scope] || [] + end + end +end diff --git a/app/models/stop_area_referential.rb b/app/models/stop_area_referential.rb index 54e895cd0..9e9e02d80 100644 --- a/app/models/stop_area_referential.rb +++ b/app/models/stop_area_referential.rb @@ -1,4 +1,6 @@ class StopAreaReferential < ActiveRecord::Base + validates :registration_number_format, format: { with: /\AX*\z/ } + include ObjectidFormatterSupport has_many :stop_area_referential_memberships has_many :organisations, through: :stop_area_referential_memberships @@ -15,4 +17,30 @@ class StopAreaReferential < ActiveRecord::Base def last_sync stop_area_referential_syncs.last end + + def generate_registration_number + return "" unless registration_number_format.present? + last = self.stop_areas.order("registration_number DESC NULLS LAST").limit(1).first&.registration_number + if self.stop_areas.count == 26**self.registration_number_format.size + raise "NO MORE AVAILABLE VALUES FOR registration_number in referential #{self.name}" + end + + return "A" * self.registration_number_format.size unless last + + if last == "Z" * self.registration_number_format.size + val = "A" * self.registration_number_format.size + while self.stop_areas.where(registration_number: val).exists? + val = val.next + end + val + else + last.next + end + end + + def validates_registration_number value + return false unless value.size == registration_number_format.size + return false unless value =~ /^[A-Z]*$/ + true + end end diff --git a/app/models/workbench.rb b/app/models/workbench.rb index eb53af7aa..b5f4673bb 100644 --- a/app/models/workbench.rb +++ b/app/models/workbench.rb @@ -13,8 +13,9 @@ class Workbench < ActiveRecord::Base has_many :companies, through: :line_referential has_many :group_of_lines, through: :line_referential has_many :stop_areas, through: :stop_area_referential - has_many :imports - has_many :workbench_imports + has_many :imports, class_name: Import::Base + has_many :exports, class_name: Export::Base + has_many :workbench_imports, class_name: Import::Workbench has_many :compliance_check_sets has_many :compliance_control_sets has_many :merges @@ -42,6 +43,10 @@ class Workbench < ActiveRecord::Base end end + def calendars + workgroup.calendars.where('(organisation_id = ? OR shared = ?)', organisation.id, true) + end + def self.default self.last if self.count == 1 where(name: DEFAULT_WORKBENCH_NAME).last diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb index c44937c9e..33d88660c 100644 --- a/app/policies/application_policy.rb +++ b/app/policies/application_policy.rb @@ -95,7 +95,6 @@ class ApplicationPolicy referential.try(:organisation_id) || record.try(:organisation_id) end - # # Helpers # ------- diff --git a/app/policies/compliance_control_set_policy.rb b/app/policies/compliance_control_set_policy.rb index 011f6c0c7..55507ffd9 100644 --- a/app/policies/compliance_control_set_policy.rb +++ b/app/policies/compliance_control_set_policy.rb @@ -5,6 +5,10 @@ class ComplianceControlSetPolicy < ApplicationPolicy end end + def show? + organisation_match? + end + def destroy? user.has_permission?('compliance_control_sets.destroy') end diff --git a/app/policies/export_policy.rb b/app/policies/export_policy.rb new file mode 100644 index 000000000..e667f3207 --- /dev/null +++ b/app/policies/export_policy.rb @@ -0,0 +1,15 @@ +class ExportPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope + end + end + + def create? + user.has_permission?('exports.create') + end + + def update? + user.has_permission?('exports.update') + end +end diff --git a/app/policies/workbench_policy.rb b/app/policies/workbench_policy.rb new file mode 100644 index 000000000..7b925e91a --- /dev/null +++ b/app/policies/workbench_policy.rb @@ -0,0 +1,11 @@ +class WorkbenchPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope + end + end + + def update? + true + end +end diff --git a/app/services/referential_overview.rb b/app/services/referential_overview.rb index ccfe0617a..7ef2909ad 100644 --- a/app/services/referential_overview.rb +++ b/app/services/referential_overview.rb @@ -208,7 +208,11 @@ class ReferentialOverview end def span - h.l(@start_date, format: "#{@start_date.day}-#{@end_date.day} %b") + if @start_date.month == @end_date.month + h.l(@start_date, format: "#{@start_date.day}-#{@end_date.day} %b %Y") + else + "#{h.l(@start_date, format: "%d %b")} - #{h.l(@end_date, format: "%d %b %Y")}" + end end def number diff --git a/app/uploaders/import_uploader.rb b/app/uploaders/import_uploader.rb index 60e17ca0f..3491768f6 100644 --- a/app/uploaders/import_uploader.rb +++ b/app/uploaders/import_uploader.rb @@ -37,7 +37,7 @@ class ImportUploader < CarrierWave::Uploader::Base # Add a white list of extensions which are allowed to be uploaded. # For images you might use something like this: def extension_whitelist - %w(zip) + model.class.try(:file_extension_whitelist) || %w(zip) end # Override the filename of the uploaded files: diff --git a/app/views/autocomplete_lines/index.rabl b/app/views/autocomplete_lines/index.rabl new file mode 100644 index 000000000..1d235ef94 --- /dev/null +++ b/app/views/autocomplete_lines/index.rabl @@ -0,0 +1,8 @@ +collection @lines + +node do |line| + { + id: line.id, + text: line.display_name + } +end diff --git a/app/views/calendars/_form_advanced.html.slim b/app/views/calendars/_form_advanced.html.slim index e796e2e36..aa4fd4e40 100644 --- a/app/views/calendars/_form_advanced.html.slim +++ b/app/views/calendars/_form_advanced.html.slim @@ -3,6 +3,6 @@ = javascript_tag do | window.actionType = "#{raw params[:action]}"; // | window.I18n = #{(I18n.backend.send(:translations)[I18n.locale].to_json).html_safe}; - | window.timetablesUrl = "#{calendar_url(@calendar).html_safe}"; + | window.timetablesUrl = "#{workgroup_calendar_url(@workgroup, @calendar).html_safe}"; = javascript_pack_tag 'calendars/edit.js' diff --git a/app/views/compliance_check_sets/index.html.slim b/app/views/compliance_check_sets/index.html.slim index 31ad31e5b..9abd95dd1 100644 --- a/app/views/compliance_check_sets/index.html.slim +++ b/app/views/compliance_check_sets/index.html.slim @@ -23,9 +23,10 @@ ), \ TableBuilderHelper::Column.new( \ key: :associated_object, \ - attribute: Proc.new{|n| n.referential.present? ? n.referential.name : ''}, \ + if: ->(compliance_check_set){ compliance_check_set.referential.present? }, \ + attribute: Proc.new{|n| n.referential.name}, \ link_to: lambda do |compliance_check_set| \ - compliance_check_set.referential.present? ? referential_path(compliance_check_set.referential_id) : '#' \ + referential_path(compliance_check_set.referential_id) \ end \ ), \ TableBuilderHelper::Column.new( \ @@ -42,6 +43,8 @@ ], sortable: true, cls: 'table has-filter has-search' + + = new_pagination @compliance_check_sets, 'pull-right' - unless @compliance_check_sets.any? .row.mt-xs .col-lg-12 diff --git a/app/views/compliance_checks/show.html.slim b/app/views/compliance_checks/show.html.slim new file mode 100644 index 000000000..3b3861e0c --- /dev/null +++ b/app/views/compliance_checks/show.html.slim @@ -0,0 +1,13 @@ +- breadcrumb :compliance_check, @workbench, resource +- page_header_content_for resource + + +.page_content + .container-fluid + .row + .col-lg-6.col-md-6.col-sm-12.col-xs-12 + = render partial: "shared/controls/metadatas" + - if resource.compliance_check_block + = definition_list t('compliance_controls.show.metadatas.compliance_check_block'), + I18n.t('activerecord.attributes.compliance_control_blocks.transport_mode') => I18n.t("enumerize.transport_mode.#{resource.compliance_check_block.transport_mode}"), + I18n.t('activerecord.attributes.compliance_control_blocks.transport_submode') => resource.compliance_check_block.transport_submode.empty? ? I18n.t("enumerize.transport_submode.undefined") : I18n.t("enumerize.transport_submode.#{resource.compliance_check_block.transport_submode}") diff --git a/app/views/compliance_control_sets/_filters.html.slim b/app/views/compliance_control_sets/_filters.html.slim index 4348defac..5cf282559 100644 --- a/app/views/compliance_control_sets/_filters.html.slim +++ b/app/views/compliance_control_sets/_filters.html.slim @@ -11,7 +11,7 @@ = f.input :organisation_name_eq_any, collection: organisations_filters_values, as: :check_boxes, label: false, label_method: lambda {|w| ("<span>#{w.name}</span>").html_safe}, required: false, wrapper_html: {class: 'checkbox_list'} .form-group.togglable class=filter_item_class(params[:q], :updated_at) - = f.label Import.human_attribute_name(:updated_at), required: false, class: 'control-label' + = f.label Import::Base.human_attribute_name(:updated_at), required: false, class: 'control-label' .filter_menu = f.simple_fields_for :updated_at do |p| = p.input :start_date, as: :date, label: false, wrapper_html: {class: 'date smart_date filter_menu-item'}, default: @begin_range, include_blank: @begin_range ? false : true diff --git a/app/views/compliance_controls/_filters.html.slim b/app/views/compliance_controls/_filters.html.slim index f6b9970f2..a16d2c33d 100644 --- a/app/views/compliance_controls/_filters.html.slim +++ b/app/views/compliance_controls/_filters.html.slim @@ -5,8 +5,8 @@ class: 'form form-filter' do |f| .ffg-row - .input-group.search_bar class=filter_item_class(params[:q], :name_cont) - = f.search_field :name_cont, + .input-group.search_bar class=filter_item_class(params[:q], :name_or_code_cont) + = f.search_field :name_or_code_cont, class: 'form-control', placeholder: t('compliance_controls.filters.name') span.input-group-btn diff --git a/app/views/compliance_controls/show.html.slim b/app/views/compliance_controls/show.html.slim index ab25747a9..643237676 100644 --- a/app/views/compliance_controls/show.html.slim +++ b/app/views/compliance_controls/show.html.slim @@ -6,23 +6,8 @@ .container-fluid .row .col-lg-6.col-md-6.col-sm-12.col-xs-12 - /- @compliance_control.control_attributes.each_with_index do |(key,value), index| - = definition_list t('metadatas'), - { \ - ComplianceControl.human_attribute_name(:name) => @compliance_control.name, - ComplianceControl.human_attribute_name(:code) => @compliance_control.code, - ComplianceControl.human_attribute_name(:criticity) => @compliance_control.criticity, - ComplianceControl.human_attribute_name(:comment) => @compliance_control.comment, - I18n.t('activerecord.attributes.compliance_control.predicate') => @compliance_control.predicate, - I18n.t('activerecord.attributes.compliance_control.prerequisite') => @compliance_control.prerequisite, - }.merge( \ - {}.tap do |hash| \ - @compliance_control.class.dynamic_attributes.each do |attribute| \ - hash[ComplianceControl.human_attribute_name(attribute)] = @compliance_control.send(attribute) \ - end \ - end \ - ) + = render partial: "shared/controls/metadatas" - if @compliance_control.compliance_control_block = definition_list t('compliance_controls.show.metadatas.compliance_control_block'), I18n.t('activerecord.attributes.compliance_control_blocks.transport_mode') => I18n.t("enumerize.transport_mode.#{@compliance_control.compliance_control_block.transport_mode}"), - I18n.t('activerecord.attributes.compliance_control_blocks.transport_submode') => I18n.t("enumerize.transport_submode.#{@compliance_control.compliance_control_block.transport_submode}") + I18n.t('activerecord.attributes.compliance_control_blocks.transport_submode') => @compliance_control.compliance_control_block.transport_submode.empty? ? I18n.t("enumerize.transport_submode.undefined") : I18n.t("enumerize.transport_submode.#{@compliance_control.compliance_control_block.transport_submode}") diff --git a/app/views/dashboards/_dashboard.html.slim b/app/views/dashboards/_dashboard.html.slim index 7f78934a6..4adf335d2 100644 --- a/app/views/dashboards/_dashboard.html.slim +++ b/app/views/dashboards/_dashboard.html.slim @@ -22,13 +22,13 @@ .panel.panel-default .panel-heading h3.panel-title.with_actions - = link_to I18n.t("activerecord.models.calendar", count: @dashboard.current_organisation.calendars.size), workgroup_calendars_path(workbench.workgroup) + = link_to I18n.t("activerecord.models.calendar", count: workbench.calendars.size), workgroup_calendars_path(workbench.workgroup) div = link_to '', workgroup_calendars_path(workbench.workgroup), class: ' fa fa-chevron-right pull-right' - - if @dashboard.current_organisation.calendars.present? + - if workbench.calendars.present? .list-group - - @dashboard.current_organisation.calendars.order("updated_at desc").limit(5).each do |calendar| - = link_to calendar.name, workgroup_calendars_path(workbench.workgroup, calendar), class: 'list-group-item' + - workbench.calendars.order("updated_at desc").limit(5).each do |calendar| + = link_to calendar.name, workgroup_calendar_path(workbench.workgroup, calendar), class: 'list-group-item' - else .panel-body em.small.text-muted diff --git a/app/views/exports/_export.html.slim b/app/views/exports/_export.html.slim deleted file mode 100644 index f1f7e9753..000000000 --- a/app/views/exports/_export.html.slim +++ /dev/null @@ -1,20 +0,0 @@ -#index_item.panel.panel-default.export - .panel-heading - .panel-title.clearfix - span.pull-right - = link_to referential_export_path(@referential, export.id), :method => :delete, :data => {:confirm => t('exports.actions.destroy_confirm')}, class: 'btn btn-danger btn-sm' do - span.fa.fa-trash-o - - h5 - = link_to( referential_export_path(@referential, export.id), :class => "preview", :title => "#{Export.model_name.human.capitalize} #{export.name}") do - = job_status_title(export) - - .panel-body - p - = link_to( font_awesome_classic_tag("fa-file-#{export.filename_extension}-o") + t("exports.show.exported_file"), exported_file_referential_export_path(@referential, export.id) ) if export.file_path - - .panel-footer - = export_attributes_tag(export) - .history - = l export.created_at, :format => "%d/%m/%Y %H:%M" - = " | #{export.user_name}"
\ No newline at end of file diff --git a/app/views/exports/_exports.html.slim b/app/views/exports/_exports.html.slim deleted file mode 100644 index 7a0461def..000000000 --- a/app/views/exports/_exports.html.slim +++ /dev/null @@ -1,9 +0,0 @@ -.page_info - span.search = t("will_paginate.page_entries_info.search") - = page_entries_info @exports - -.exports.paginated_content - = paginated_content @exports, "exports/export" - -.pagination - = will_paginate @exports, :container => false, renderer: RemoteBootstrapPaginationLinkRenderer
\ No newline at end of file diff --git a/app/views/exports/_form.html.slim b/app/views/exports/_form.html.slim new file mode 100644 index 000000000..7817fdf1a --- /dev/null +++ b/app/views/exports/_form.html.slim @@ -0,0 +1,16 @@ += simple_form_for export, as: :export, url: workbench_exports_path(workbench), html: {class: 'form-horizontal', id: 'wb_export_form'}, wrapper: :horizontal_form do |form| + + .row + .col-lg-12 + = form.input :name + .col-lg-12 + = form.input :type, as: :select, collection: Export::Base.user_visible_descendants, label_method: :human_name + + - Export::Base.user_visible_descendants.each do |child| + .slave data-master="[name='export[type]']" data-value=child.name + - child.options.each do |attr, option_def| + = export_option_input form, export, attr, option_def, child + + = form.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'wb_export_form' + += javascript_pack_tag "exports/new" diff --git a/app/views/exports/index.html.slim b/app/views/exports/index.html.slim index bbcb2a5a7..f97b07231 100644 --- a/app/views/exports/index.html.slim +++ b/app/views/exports/index.html.slim @@ -1,10 +1,44 @@ -= title_tag t('.title') +- breadcrumb :exports, @workbench -.warning = t('.warning') +.page_content + .container-fluid + - if params[:q].present? or collection.any? + .row + .col-lg-12 + = render 'shared/iev_interfaces/filters' -#exports = render 'exports' + - if collection.any? + .row + .col-lg-12 + = table_builder_2 collection, + [ \ + TableBuilderHelper::Column.new( \ + key: :status, \ + attribute: Proc.new { |n| export_status(n.status) }, \ + ), \ + TableBuilderHelper::Column.new( \ + key: :started_at, \ + attribute: Proc.new { |n| l(n.started_at, format: :long) if n.started_at }, \ + ), \ + TableBuilderHelper::Column.new( \ + key: :name, \ + attribute: 'name', \ + link_to: lambda do |export| \ + workbench_export_path(@workbench, export) \ + end \ + ), \ + TableBuilderHelper::Column.new( \ + key: :creator, \ + attribute: 'creator' \ + ) \ + ], + cls: 'table has-search' -- content_for :sidebar do - ul.actions - li - = link_to t('exports.actions.new'), new_referential_export_task_path(@referential), class: 'add'
\ No newline at end of file + = new_pagination collection, 'pull-right' + + - unless collection.any? + .row.mt-xs + .col-lg-12 + = replacement_msg t('exports.search_no_results') + += javascript_pack_tag 'date_filters' diff --git a/app/views/exports/index.js.slim b/app/views/exports/index.js.slim deleted file mode 100644 index b9a413b1b..000000000 --- a/app/views/exports/index.js.slim +++ /dev/null @@ -1 +0,0 @@ -| $('#exports').html("#{escape_javascript(render('exports'))}");
\ No newline at end of file diff --git a/app/views/exports/new.html.slim b/app/views/exports/new.html.slim new file mode 100644 index 000000000..f62386caf --- /dev/null +++ b/app/views/exports/new.html.slim @@ -0,0 +1,7 @@ +- breadcrumb :exports, @workbench + +.page_content + .container-fluid + .row + .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 + = render 'form', export: @export, workbench: @workbench diff --git a/app/views/exports/show.html.slim b/app/views/exports/show.html.slim index 1631e0e7e..2a7d7583c 100644 --- a/app/views/exports/show.html.slim +++ b/app/views/exports/show.html.slim @@ -1,26 +1,49 @@ -.title.row - .col-md-8 - = title_tag job_status_title(@export) +- breadcrumb :export, @workbench, @export - .col-md-4 - = export_attributes_tag(@export) +- page_header_content_for @export -- if @export.report.failure_code? - .alert.alert-danger - = t("iev.failure.#{@export.report.failure_code}") +.page_content + .container-fluid + .row + .col-lg-6.col-md-6.col-sm-12.col-xs-12 + - metadatas = { I18n.t("activerecord.attributes.export.type") => @export.object.class.human_name } + - metadatas = metadatas.update({I18n.t("activerecord.attributes.export.status") => export_status(@export.status)}) + - metadatas = metadatas.update({I18n.t("activerecord.attributes.export.parent") => link_to(@export.parent.name, [@export.parent.workbench, @export.parent])}) if @export.parent.present? + - metadatas = metadatas.update Hash[*@export.visible_options.map{|k, v| [t("activerecord.attributes.export.#{@export.object.class.name.demodulize.underscore}.#{k}"), @export.display_option_value(k, self)]}.flatten] + - metadatas = metadatas.update({I18n.t("activerecord.attributes.export.file") => (@export.file.present? ? link_to(t("actions.download"), @export.file.url) : "-")}) + = definition_list t('metadatas'), metadatas -.progress_bars - = progress_bar_tag(@export) + .row + .col-lg-12 + .error_messages + = render 'shared/iev_interfaces/messages', messages: @export.messages -.export_show - .links - = link_to( font_awesome_classic_tag("fa-file-#{@export.filename_extension}-o") + t("exports.show.exported_file"), exported_file_referential_export_path(@referential, @export.id) ) if @export.file_path - - = render partial: "shared/ie_report.html", locals: { job: @export, line_items: @line_items } + - if @export.children.any? + .row + .col-lg-12 + - coll = @export.children.paginate(page: params[:page] || 1) + = table_builder_2 coll, + [ \ + TableBuilderHelper::Column.new( \ + key: :status, \ + attribute: Proc.new { |n| export_status(n.status) }, \ + ), \ + TableBuilderHelper::Column.new( \ + key: :started_at, \ + attribute: Proc.new { |n| l(n.started_at, format: :long) if n.started_at }, \ + ), \ + TableBuilderHelper::Column.new( \ + key: :name, \ + attribute: 'name', \ + link_to: lambda do |export| \ + workbench_export_path(@workbench, export) \ + end \ + ), \ + TableBuilderHelper::Column.new( \ + key: :creator, \ + attribute: 'creator' \ + ) \ + ], + cls: 'table has-search' -- content_for :sidebar do - ul.actions - li - = link_to t('exports.actions.destroy'), referential_export_path(@referential, @export.id), :method => :delete, :data => {:confirm => t('exports.actions.destroy_confirm')}, class: 'remove' - - = history_tag(@export)
\ No newline at end of file + = new_pagination coll, 'pull-right' diff --git a/app/views/imports/_import_messages.html.slim b/app/views/imports/_import_messages.html.slim deleted file mode 100644 index af10b23e5..000000000 --- a/app/views/imports/_import_messages.html.slim +++ /dev/null @@ -1,8 +0,0 @@ -- if import_messages.any? - ul.list-unstyled.import_message-list - - import_messages.each do | import_message | - li - span(class="#{bootstrap_class_for_message_criticity import_message.criticity}") - = t( ['import_messages', - 'compliance_check_messages', - import_message.message_key].join('.'), import_message.message_attributes.symbolize_keys) diff --git a/app/views/imports/index.html.slim b/app/views/imports/index.html.slim index 4fc077bd6..3dff4d80e 100644 --- a/app/views/imports/index.html.slim +++ b/app/views/imports/index.html.slim @@ -2,15 +2,15 @@ .page_content .container-fluid - - if params[:q].present? or @imports.any? + - if params[:q].present? or collection.any? .row .col-lg-12 - = render 'filters' + = render 'shared/iev_interfaces/filters' - if @imports.any? .row .col-lg-12 - = table_builder_2 @imports, + = table_builder_2 collection, [ \ TableBuilderHelper::Column.new( \ key: :status, \ @@ -34,9 +34,9 @@ ], cls: 'table has-search' - = new_pagination @imports, 'pull-right' + = new_pagination collection, 'pull-right' - - unless @imports.any? + - unless collection.any? .row.mt-xs .col-lg-12 = replacement_msg t('imports.search_no_results') diff --git a/app/views/imports/show.html.slim b/app/views/imports/show.html.slim index 7a9d02077..48a4f334c 100644 --- a/app/views/imports/show.html.slim +++ b/app/views/imports/show.html.slim @@ -11,7 +11,7 @@ .row .col-lg-12 .error_messages - = render 'import_messages', import_messages: @import.messages + = render 'shared/iev_interfaces/messages', messages: @import.messages - if @import.children.any? .row diff --git a/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim b/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim index cb0698cf8..02614dcab 100644 --- a/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim +++ b/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim @@ -29,6 +29,8 @@ span Jeux de données = link_to workbench_imports_path(current_workbench), class: "list-group-item #{(params[:controller] == 'imports') ? 'active' : ''}" do span Import + = link_to workbench_exports_path(current_workbench), class: "list-group-item #{(params[:controller] == 'exports') ? 'active' : ''}" do + span Export = link_to workgroup_calendars_path(current_workbench.workgroup), class: 'list-group-item' do span Modèles de calendrier = link_to workbench_compliance_check_sets_path(current_workbench), class: 'list-group-item' do diff --git a/app/views/layouts/navigation/_page_header.html.slim b/app/views/layouts/navigation/_page_header.html.slim index e407e53da..49f56544b 100644 --- a/app/views/layouts/navigation/_page_header.html.slim +++ b/app/views/layouts/navigation/_page_header.html.slim @@ -1,6 +1,5 @@ - action_links = resource.action_links(params[:action]) rescue nil - action_links ||= decorated_collection.action_links(params[:action]) rescue nil - .page_header .container-fluid .row @@ -13,7 +12,7 @@ h1 = yield :page_header_title - else - if defined?(resource_class) - h1 = t("#{resource_class.model_name.name.underscore.pluralize}.#{params[:action]}.title") + h1 = t("#{resource_class.model_name.name.underscore.gsub('/', '.').pluralize}.#{params[:action]}.title") .col-lg-3.col-md-4.col-sm-5.col-xs-5.text-right .page-action diff --git a/app/views/lines/show.html.slim b/app/views/lines/show.html.slim index 34b907bdd..96bb5bb0d 100644 --- a/app/views/lines/show.html.slim +++ b/app/views/lines/show.html.slim @@ -6,13 +6,13 @@ .row .col-lg-6.col-md-6.col-sm-12.col-xs-12 = definition_list t('metadatas'), - { 'ID Codif' => @line.get_objectid.short_id, - 'Activé' => (@line.deactivated? ? t('false') : t('true')), + { t('id_codif') => @line.get_objectid.short_id, + @line.human_attribute_name(:deactivated) => (@line.deactivated? ? t('false') : t('true')), @line.human_attribute_name(:network_id) => (@line.network.nil? ? t('lines.index.unset') : @line.network.name), @line.human_attribute_name(:company_id) => (@line.company.nil? ? t('lines.index.unset') : @line.company.name), - 'Transporteur(s) secondaire(s)' => (@line.secondary_companies.nil? ? t('lines.index.unset') : array_to_html_list(@line.secondary_companies.collect(&:name))), - 'Nom court' => @line.number, - 'Code public' => (@line.registration_number ? @line.registration_number : '-'), + @line.human_attribute_name(:secondary_companies) => (@line.secondary_companies.nil? ? t('lines.index.unset') : array_to_html_list(@line.secondary_companies.collect(&:name))), + @line.human_attribute_name(:number) => @line.number, + @line.human_attribute_name(:registration_number) => (@line.registration_number ? @line.registration_number : '-'), @line.human_attribute_name(:transport_mode) => (@line.transport_mode.present? ? t("enumerize.transport_mode.#{@line.transport_mode}") : '-'), @line.human_attribute_name(:transport_submode) => (@line.transport_submode.present? ? t("enumerize.transport_submode.#{@line.transport_submode}") : '-'), @line.human_attribute_name(:url) => (@line.url ? @line.url : '-'), diff --git a/app/views/referential_lines/show.html.slim b/app/views/referential_lines/show.html.slim index 5ea0e31bb..ef32ef6b0 100644 --- a/app/views/referential_lines/show.html.slim +++ b/app/views/referential_lines/show.html.slim @@ -82,6 +82,6 @@ = replacement_msg t('routes.search_no_results') = javascript_tag do - | window.routes = "#{URI.escape(@routes.map{|r| {name: r.name, id: r.id, stops: route_json_for_edit(r, serialize: false)}}.to_json)}" + | window.routes = "#{URI.escape(@routes.select{|r| r.wayback == :outbound}.map{|r| {name: r.name, id: r.id, stops: route_json_for_edit(r, serialize: false)}}.to_json)}" = javascript_pack_tag 'referential_lines/show.js' diff --git a/app/views/referential_vehicle_journeys/_filters.html.slim b/app/views/referential_vehicle_journeys/_filters.html.slim index 5f102ae1b..f1fbdb5d8 100644 --- a/app/views/referential_vehicle_journeys/_filters.html.slim +++ b/app/views/referential_vehicle_journeys/_filters.html.slim @@ -24,14 +24,15 @@ = f.input :route_line_id_eq, as: :select, include_blank: t(".all"), - collection: @vehicle_journeys.lines, - selected: params[:q] && params[:q][:route_line_id_eq], input_html: { \ - 'data-select2ed': 'true', - 'data-select2ed-placeholder': t('referentials.filters.line') \ + data: { \ + 'select2-ajax': 'true', + 'select2ed-placeholder': t('referentials.filters.line'), + url: autocomplete_referential_lines_path(@referential, format: :json), + 'select2ed-allow-clear': true \ + } \ }, label: false, - label_method: :display_name, wrapper_html: { class: 'filter_menu-item select2ed' } .form-group.togglable.name-filter class=filter_item_class(params[:q], :published_journey_name_gteq) diff --git a/app/views/referentials/_form.html.slim b/app/views/referentials/_form.html.slim index 1e59ab566..96d847ec1 100644 --- a/app/views/referentials/_form.html.slim +++ b/app/views/referentials/_form.html.slim @@ -47,8 +47,13 @@ = link_to_add_association t('simple_form.labels.referential.actions.add_period'), subform, :periods, class: 'btn btn-outline-primary' .separator + .row + .col-lg-11 + = subform.input :lines, as: :select, collection: Chouette::Line.includes(:company).order(:name).where(objectid: current_functional_scope), selected: subform.object.line_ids, label_method: :display_name, input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': t('simple_form.labels.referential.placeholders.select_lines'), 'multiple': 'multiple', style: 'width: 100%' } + .col-lg-1 + a.clear-lines.btn.btn-default href='#' + .fa.fa-trash - = subform.input :lines, as: :select, collection: Chouette::Line.includes(:company).order(:name).where(objectid: current_functional_scope), selected: subform.object.line_ids, label_method: :display_name, input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': t('simple_form.labels.referential.placeholders.select_lines'), 'multiple': 'multiple', style: 'width: 100%' } .hidden = form.input :workbench_id, as: :hidden @@ -58,3 +63,10 @@ class: 'btn btn-default formSubmitr', data: { disable_with: t('actions.processing') }, form: 'referential_form' + + - content_for :javascript do + coffee: + $(".clear-lines").click (e)-> + e.preventDefault() + $(e.currentTarget).parents('.row').first().find('[name*=line]').val('').trigger('change') + false diff --git a/app/views/referentials/_overview.html.slim b/app/views/referentials/_overview.html.slim index 870f642d4..6bed5f282 100644 --- a/app/views/referentials/_overview.html.slim +++ b/app/views/referentials/_overview.html.slim @@ -35,9 +35,12 @@ .lines= I18n.t("referentials.overview.head.lines") .lines - overview.lines.each do |line| - .line - a.number style="background-color: #{line.color.present? ? "##{line.color}" : 'whitesmoke'}" title=line.name - = line.number + a.line title=line.name + - if line.number.present? + .number style="background-color: #{line.color.present? ? "##{line.color}" : 'whitesmoke'}" + = line.number + - else + .name= line.name .company= line.company&.name .mode= line.transport_mode.present? ? t("enumerize.transport_mode.#{line.transport_mode}") : "" .right diff --git a/app/views/shared/controls/_metadatas.html.slim b/app/views/shared/controls/_metadatas.html.slim new file mode 100644 index 000000000..49df06166 --- /dev/null +++ b/app/views/shared/controls/_metadatas.html.slim @@ -0,0 +1,15 @@ += definition_list t('metadatas'), + { \ + ComplianceControl.human_attribute_name(:name) => resource.name, + ComplianceControl.human_attribute_name(:code) => resource.code, + ComplianceControl.human_attribute_name(:criticity) => resource.criticity, + ComplianceControl.human_attribute_name(:comment) => resource.comment, + I18n.t('activerecord.attributes.compliance_control.predicate') => resource.predicate, + I18n.t('activerecord.attributes.compliance_control.prerequisite') => resource.prerequisite, + }.merge( \ + {}.tap do |hash| \ + resource.class.dynamic_attributes.each do |attribute| \ + hash[ComplianceControl.human_attribute_name(attribute)] = resource.send(attribute) \ + end \ + end \ + ) diff --git a/app/views/imports/_filters.html.slim b/app/views/shared/iev_interfaces/_filters.html.slim index 25c0d10d9..9b114c38d 100644 --- a/app/views/imports/_filters.html.slim +++ b/app/views/shared/iev_interfaces/_filters.html.slim @@ -8,11 +8,11 @@ .ffg-row .form-group.togglable class=filter_item_class(params[:q], :status_eq_any) - = f.label Import.human_attribute_name(:status), required: false, class: 'control-label' + = f.label Import::Base.human_attribute_name(:status), required: false, class: 'control-label' = f.input :status_eq_any, collection: %w(pending successful warning failed), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + import_status(l) + "</span>").html_safe}, required: false, wrapper_html: { class: "checkbox_list"} .form-group.togglable class=filter_item_class(params[:q], :started_at) - = f.label Import.human_attribute_name(:started_at), required: false, class: 'control-label' + = f.label Import::Base.human_attribute_name(:started_at), required: false, class: 'control-label' .filter_menu = f.simple_fields_for :started_at do |p| = p.input :start_date, as: :date, label: false, wrapper_html: { class: 'date smart_date filter_menu-item' }, default: @begin_range, include_blank: @begin_range ? false : true diff --git a/app/views/shared/iev_interfaces/_messages.html.slim b/app/views/shared/iev_interfaces/_messages.html.slim new file mode 100644 index 000000000..82f1add57 --- /dev/null +++ b/app/views/shared/iev_interfaces/_messages.html.slim @@ -0,0 +1,14 @@ +- if messages.any? + ul.list-unstyled.import_message-list + - messages.order(:created_at).each do | message | + li + .row class=bootstrap_class_for_message_criticity(message.criticity) + - if message.message_attributes["line"] + .col-md-1= "L. #{message.message_attributes["line"]}" + .col-md-5= export_message_content message + - else + .col-md-6= export_message_content message + .col-md-6 + - if message.criticity != "info" + pre + = JSON.pretty_generate message.resource_attributes || {} diff --git a/app/views/stif/dashboards/_dashboard.html.slim b/app/views/stif/dashboards/_dashboard.html.slim index 83a2106bb..e0f754fd4 100644 --- a/app/views/stif/dashboards/_dashboard.html.slim +++ b/app/views/stif/dashboards/_dashboard.html.slim @@ -57,7 +57,7 @@ .panel-heading h3.panel-title.with_actions = I18n.t("calendars.index.title") - span.badge.ml-xs = @dashboard.calendars.count if @dashboard.calendars.present? + span.badge.ml-xs = @dashboard.workbench.calendars.count if @dashboard.calendars.present? div = link_to '', workgroup_calendars_path(@dashboard.workbench.workgroup), class: ' fa fa-chevron-right pull-right', title: t('.see') diff --git a/app/views/stop_areas/_form.html.slim b/app/views/stop_areas/_form.html.slim index bb1fbe1e9..c63e95c89 100644 --- a/app/views/stop_areas/_form.html.slim +++ b/app/views/stop_areas/_form.html.slim @@ -48,7 +48,7 @@ - if has_feature?(:stop_area_waiting_time) = f.input :waiting_time, input_html: { min: 0 } - = f.input :registration_number, required: format_restriction_for_locales(@referential) == '.hub', :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.registration_number")} + = f.input :registration_number, required: stop_area_registration_number_is_required(f.object), :input_html => {title: stop_area_registration_number_title(f.object), value: stop_area_registration_number_value(f.object)} = f.input :fare_code = f.input :nearest_topic_name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.nearest_topic_name")} = f.input :comment, as: :text, :input_html => {:rows => 5, :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.comment")} diff --git a/app/views/stop_areas/show.html.slim b/app/views/stop_areas/show.html.slim index a4e14a272..34b872e91 100644 --- a/app/views/stop_areas/show.html.slim +++ b/app/views/stop_areas/show.html.slim @@ -20,7 +20,7 @@ @stop_area.human_attribute_name(:zip_code) => @stop_area.zip_code, @stop_area.human_attribute_name(:city_name) => @stop_area.city_name, @stop_area.human_attribute_name(:country_code) => @stop_area.country_code.presence || '-', - 'Etat' => (@stop_area.deleted_at ? 'Supprimé' : 'Actif'), + t('activerecord.attributes.stop_area.state') => (@stop_area.deleted_at ? t('stop_areas.show.state.deactivated') : t('stop_areas.show.state.active')), @stop_area.human_attribute_name(:comment) => @stop_area.try(:comment), }) = definition_list t('metadatas'), attributes diff --git a/app/views/vehicle_journeys/show.rabl b/app/views/vehicle_journeys/show.rabl index bb26ce797..d218038a6 100644 --- a/app/views/vehicle_journeys/show.rabl +++ b/app/views/vehicle_journeys/show.rabl @@ -36,7 +36,7 @@ end if has_feature? :purchase_windows child(:purchase_windows, :object_root => false) do |purchase_windows| - attributes :id, :objectid, :name, :color + attributes :id, :objectid, :name, :color, :bounding_dates end end diff --git a/app/views/workbenches/_form.html.slim b/app/views/workbenches/_form.html.slim new file mode 100644 index 000000000..534a5f378 --- /dev/null +++ b/app/views/workbenches/_form.html.slim @@ -0,0 +1,9 @@ += simple_form_for @workbench, html: { class: 'form-horizontal', id: 'workbench_form' }, wrapper: :horizontal_form do |f| + .row + .col-lg-12 + = f.input :import_compliance_control_set_id, as: :select, collection: current_organisation.compliance_control_sets, value_method: :id + = f.input :merge_compliance_control_set_id, as: :select, collection: current_organisation.compliance_control_sets, value_method: :id + + .separator + + = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'workbench_form' diff --git a/app/views/workbenches/edit.html.slim b/app/views/workbenches/edit.html.slim new file mode 100644 index 000000000..893024490 --- /dev/null +++ b/app/views/workbenches/edit.html.slim @@ -0,0 +1,8 @@ +- breadcrumb @workbench +- page_header_content_for @workbench + +.page_content + .container-fluid + .row + .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 + == render 'form' diff --git a/app/views/workbenches/show.html.slim b/app/views/workbenches/show.html.slim index 159aa8ea2..5c2468296 100644 --- a/app/views/workbenches/show.html.slim +++ b/app/views/workbenches/show.html.slim @@ -3,6 +3,8 @@ - content_for :page_header_content do .row.mb-sm .col-lg-12.text-right + - if policy(Workbench).update? + = link_to t('workbenches.actions.configure'), edit_workbench_path(@workbench), class: 'btn btn-primary' - if policy(Referential).create? = link_to t('actions.import'), workbench_imports_path(@workbench), class: 'btn btn-primary' = link_to t('actions.add'), new_workbench_referential_path(@workbench), class: 'btn btn-primary' diff --git a/app/workers/simple_export_worker.rb b/app/workers/simple_export_worker.rb new file mode 100644 index 000000000..d41736307 --- /dev/null +++ b/app/workers/simple_export_worker.rb @@ -0,0 +1,10 @@ +class SimpleExportWorker + include Sidekiq::Worker + + def perform(export_id) + export = Export::Base.find(export_id) + export.update(status: 'running', started_at: Time.now) + export.call_exporter + export.update(ended_at: Time.now) + end +end diff --git a/app/workers/workbench_import_worker.rb b/app/workers/workbench_import_worker.rb index 53cbb222a..fd2a888f0 100644 --- a/app/workers/workbench_import_worker.rb +++ b/app/workers/workbench_import_worker.rb @@ -12,7 +12,7 @@ class WorkbenchImportWorker def perform(import_id) @entries = 0 - @workbench_import ||= WorkbenchImport.find(import_id) + @workbench_import ||= Import::Workbench.find(import_id) workbench_import.update(status: 'running', started_at: Time.now) zip_service = ZipService.new(downloaded, allowed_lines) @@ -44,7 +44,6 @@ class WorkbenchImportWorker raise end - def upload_entry_group entry, element_count update_object_state entry, element_count.succ return unless entry.ok? @@ -80,7 +79,6 @@ class WorkbenchImportWorker File.unlink(eg_file.path) end - # Queries # ======= diff --git a/app/workers/workgroup_export_worker.rb b/app/workers/workgroup_export_worker.rb new file mode 100644 index 000000000..29493cea6 --- /dev/null +++ b/app/workers/workgroup_export_worker.rb @@ -0,0 +1,38 @@ +class WorkgroupExportWorker + include Sidekiq::Worker + + attr_reader :workbench_export + + # Workers + # ======= + + def perform(export_id) + @entries = 0 + @workbench_export ||= Export::Workgroup.find(export_id) + + workbench_export.update(status: 'running', started_at: Time.now) + create_sub_jobs + rescue Exception => e + logger.error e.message + workbench_export.update( status: 'failed' ) + raise + end + + def create_sub_jobs + # XXX TO DO + workbench_export.workbench.workgroup.referentials.each do |ref| + ref.lines.each do |line| + netex_export = Export::Netex.new + netex_export.name = "Export line #{line.name} of Referential #{ref.name}" + netex_export.workbench = workbench_export.workbench + netex_export.creator = workbench_export.creator + netex_export.export_type = :line + netex_export.duration = workbench_export.duration + netex_export.line_code = line.objectid + netex_export.parent = workbench_export + netex_export.save! + end + end + end + +end diff --git a/config/breadcrumbs.rb b/config/breadcrumbs.rb index a7cdb4313..87d6f5846 100644 --- a/config/breadcrumbs.rb +++ b/config/breadcrumbs.rb @@ -96,6 +96,11 @@ crumb :compliance_check_set do |workbench, compliance_check_set| parent :compliance_check_sets, workbench end +crumb :compliance_check do |workbench, compliance_check| + link breadcrumb_name(compliance_check), workbench_compliance_check_set_compliance_check_path(workbench, compliance_check.compliance_check_set, compliance_check) + parent :compliance_check_set_executed, workbench, compliance_check.compliance_check_set +end + crumb :compliance_check_set_executed do |workbench, compliance_check_set| link I18n.t('compliance_check_sets.executed.title', name: compliance_check_set.name), executed_workbench_compliance_check_set_path(workbench, compliance_check_set) parent :compliance_check_sets, workbench @@ -106,11 +111,21 @@ crumb :imports do |workbench| parent :workbench, workbench end +crumb :exports do |workbench| + link I18n.t('exports.index.title'), workbench_exports_path(workbench) + parent :workbench, workbench +end + crumb :import do |workbench, import| link breadcrumb_name(import), workbench_import_path(workbench, import) parent :imports, workbench end +crumb :export do |workbench, export| + link breadcrumb_name(export), workbench_export_path(workbench, export) + parent :exports, workbench +end + crumb :import_resources do |import, import_resources| link I18n.t('import_resources.index.title'), workbench_import_import_resources_path(import.workbench, import.parent) parent :import, import.workbench, import.parent diff --git a/config/database/ci.yml b/config/database/ci.yml new file mode 100644 index 000000000..44103454a --- /dev/null +++ b/config/database/ci.yml @@ -0,0 +1,11 @@ +test: + adapter: <%= ENV.fetch 'RAILS_DB_ADAPTER', 'postgis' %> + encoding: unicode + pool: <%= ENV.fetch 'RAILS_DB_POOLSIZE', '5' %> + host: <%= ENV.fetch 'RAILS_DB_HOST', 'localhost' %> + port: <%= ENV.fetch 'RAILS_DB_PORT', '5432' %> + schema_search_path: 'public,shared_extensions' + postgis_schema: 'shared_extensions' + database: <%= ENV.fetch 'RAILS_DB_NAME', 'stif_boiv_test' %> + username: <%= ENV['RAILS_DB_USER'] || ENV['POSTGRESQL_ENV_POSTGRES_USER'] || 'jenkins' %> + password: <%= ENV['RAILS_DB_PASSWORD'] || ENV['POSTGRESQL_ENV_POSTGRES_PASSWORD'] %> diff --git a/config/database/jenkins.yml b/config/database/jenkins.yml deleted file mode 100644 index 8b7a8ef2f..000000000 --- a/config/database/jenkins.yml +++ /dev/null @@ -1,8 +0,0 @@ -test: - adapter: postgis - encoding: unicode - schema_search_path: 'public,shared_extensions' - postgis_schema: 'shared_extensions' - username: <%= ENV['POSTGRESQL_ENV_POSTGRES_USER'] || 'jenkins' %> - password: <%= ENV['POSTGRESQL_ENV_POSTGRES_PASSWORD'] %> - database: stif_boiv_test diff --git a/config/environments/development.rb b/config/environments/development.rb index 446e72190..7bd049979 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,3 +1,5 @@ +require Rails.root + 'config/middlewares/cachesettings' + Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. @@ -96,6 +98,13 @@ Rails.application.configure do config.middleware.insert_after(ActionDispatch::Static, Rack::LiveReload) if ENV['LIVERELOAD'] config.middleware.use I18n::JS::Middleware + config.middleware.use CacheSettings, { + /\/assets\/.*/ => { + cache_control: "max-age=86400, public", + expires: 86400 + } + } + config.development_toolbar = false if ENV['TOOLBAR'] && File.exists?("config/development_toolbar.rb") config.development_toolbar = OpenStruct.new diff --git a/config/environments/production.rb b/config/environments/production.rb index 9a699eb44..eb44e1ab1 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,3 +1,5 @@ +require Rails.root + 'config/middlewares/cachesettings' + Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. @@ -136,6 +138,12 @@ Rails.application.configure do config.iev_url = ENV.fetch('IEV_URL',"http://iev:8080") config.rails_host = ENV.fetch('RAILS_HOST','http://front:3000') + config.middleware.use CacheSettings, { + /\/assets\/.*/ => { + cache_control: "max-age=#{1.year.to_i}, public", + expires: 1.year.to_i + } + } # Set node env for browserify-rails # config.browserify_rails.node_env = "production" end diff --git a/config/initializers/apartment.rb b/config/initializers/apartment.rb index a996549fd..6b817caed 100644 --- a/config/initializers/apartment.rb +++ b/config/initializers/apartment.rb @@ -18,70 +18,76 @@ Apartment.configure do |config| # config.excluded_models = %w{Tenant} # config.excluded_models = [ - 'Referential', - 'ReferentialMetadata', - 'ReferentialSuite', - 'Organisation', - 'User', 'Api::V1::ApiKey', - 'StopAreaReferential', - 'StopAreaReferentialMembership', - 'StopAreaReferentialSync', - 'StopAreaReferentialSyncMessage', - 'Chouette::StopArea', - 'LineReferential', - 'LineReferentialMembership', - 'LineReferentialSync', - 'LineReferentialSyncMessage', - 'Chouette::Line', - 'Chouette::GroupOfLine', + 'Calendar', 'Chouette::Company', + 'Chouette::GroupOfLine', + 'Chouette::Line', 'Chouette::Network', - 'ReferentialCloning', - 'Workbench', - 'Workgroup', + 'Chouette::StopArea', 'CleanUp', 'CleanUpResult', - 'Calendar', - 'Import', - 'NetexImport', - 'WorkbenchImport', - 'ImportMessage', - 'ImportResource', + 'ComplianceCheck', + 'ComplianceCheckBlock', + 'ComplianceCheckMessage', + 'ComplianceCheckResource', + 'ComplianceCheckSet', 'ComplianceControl', + 'ComplianceControlBlock', + 'ComplianceControlSet', + 'CustomField', + 'Export::Base', + 'Export::Message', + 'Export::Resource', 'GenericAttributeControl::MinMax', 'GenericAttributeControl::Pattern', 'GenericAttributeControl::Uniqueness', + 'Import::Base', + 'Import::Gtfs', + 'Import::Message', + 'Import::Netex', + 'Import::Resource', + 'Import::Workbench', 'JourneyPatternControl::Duplicates', 'JourneyPatternControl::VehicleJourney', 'LineControl::Route', + 'LineReferential', + 'LineReferentialMembership', + 'LineReferentialSync', + 'LineReferentialSyncMessage', + 'Merge', + 'Organisation', + 'Referential', + 'ReferentialCloning', + 'ReferentialMetadata', + 'ReferentialSuite', 'RouteControl::Duplicates', 'RouteControl::JourneyPattern', 'RouteControl::MinimumLength', 'RouteControl::OmnibusJourneyPattern', - 'RouteControl::OppositeRouteTerminus', 'RouteControl::OppositeRoute', + 'RouteControl::OppositeRouteTerminus', 'RouteControl::StopPointsInJourneyPattern', 'RouteControl::UnactivatedStopPoint', 'RouteControl::ZDLStopArea', 'RoutingConstraintZoneControl::MaximumLength', 'RoutingConstraintZoneControl::MinimumLength', 'RoutingConstraintZoneControl::UnactivatedStopPoint', + 'SimpleExporter', + 'SimpleImporter', + 'SimpleInterface', + 'StopAreaReferential', + 'StopAreaReferentialMembership', + 'StopAreaReferentialSync', + 'StopAreaReferentialSyncMessage', + 'User', 'VehicleJourneyControl::Delta', - 'VehicleJourneyControl::WaitingTime', 'VehicleJourneyControl::Speed', 'VehicleJourneyControl::TimeTable', 'VehicleJourneyControl::VehicleJourneyAtStops', - 'ComplianceControlSet', - 'ComplianceControlBlock', - 'ComplianceCheck', - 'ComplianceCheckSet', - 'ComplianceCheckBlock', - 'ComplianceCheckResource', - 'ComplianceCheckMessage', - 'Merge', - 'CustomField', - 'SimpleImporter', + 'VehicleJourneyControl::WaitingTime', + 'Workbench', + 'Workgroup', ] # use postgres schemas? diff --git a/config/initializers/exporters.rb b/config/initializers/exporters.rb new file mode 100644 index 000000000..dfd82a54c --- /dev/null +++ b/config/initializers/exporters.rb @@ -0,0 +1,6 @@ +SimpleExporter.define :referential_companies do |config| + config.separator = ";" + config.encoding = 'ISO-8859-1' + config.add_column :name + config.add_column :registration_number +end diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 2f65b8800..a177e7091 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -14,8 +14,4 @@ Sidekiq.configure_client do |config| config.redis = { url: ENV.fetch('SIDEKIQ_REDIS_URL', 'redis://localhost:6379/12') } end -Sidekiq.configure_client do |config| - config.redis = { url: ENV.fetch('SIDEKIQ_REDIS_URL', 'redis://localhost:6379/12') } -end - Sidekiq.default_worker_options = { retry: false } diff --git a/config/initializers/stif.rb b/config/initializers/stif.rb index a73e4931b..2ddadbc7e 100644 --- a/config/initializers/stif.rb +++ b/config/initializers/stif.rb @@ -27,8 +27,6 @@ Rails.application.config.to_prepare do Organisation.before_validation(on: :create) do |organisation| organisation.custom_view = "stif" end -end - -Rails.application.config.to_prepare do Dashboard.default_class = Stif::Dashboard + Chouette::VehicleJourneyAtStop.day_offset_max = 1 end diff --git a/config/locales/compliance_checks.en.yml b/config/locales/compliance_checks.en.yml index 177c87852..f960755da 100644 --- a/config/locales/compliance_checks.en.yml +++ b/config/locales/compliance_checks.en.yml @@ -8,3 +8,7 @@ en: subclass: Object criticity: Severity name: Name + show: + title: "Compliance check" + metadatas: + compliance_control_block: "Control block informations" diff --git a/config/locales/compliance_checks.fr.yml b/config/locales/compliance_checks.fr.yml index d11d37003..041ab4f43 100644 --- a/config/locales/compliance_checks.fr.yml +++ b/config/locales/compliance_checks.fr.yml @@ -11,4 +11,7 @@ fr: subclass: Objet criticity: Criticité name: Nom - + show: + title: "Consulter un contrôle" + metadatas: + compliance_control_block: "Informations sur le groupe de contrôle" diff --git a/config/locales/compliance_controls.en.yml b/config/locales/compliance_controls.en.yml index ca9d83872..f7d461fdb 100644 --- a/config/locales/compliance_controls.en.yml +++ b/config/locales/compliance_controls.en.yml @@ -29,8 +29,6 @@ en: title: "Add a new compliance control" edit: title: "Update compliance control" - metas: - no_prerequisite: "None" actions: new: Add edit: Edit @@ -41,6 +39,7 @@ en: messages: 3_route_1: "The route with %{source_objectid} objectid connect the stop points %{target_0_label} (%{target_0_objectid}) and %{target_1_label} (%{target_1_objectid}) which belong to the same ZDL" description: "Two stop points which belong to the same ZDL cannot follow one another in a route" + prerequisite: "None" route_control/opposite_route: messages: 3_route_2: "The route with %{source_objectid} objectid references an incoherent oppposite route %{target_0_objectid}" @@ -53,10 +52,12 @@ en: messages: 3_route_3: "The route with %{source_objectid} objectid doesn't have any journey pattern" description: "A route must have at least one journey pattern" + prerequisite: "None" route_control/duplicates: messages: 3_route_4: "The route with %{source_objectid} objectid is identical with another route %{target_0_objectid}" description: "2 routes cannot connect the same stop points with the same order and the same boarding and alighting characteristics" + prerequisite: "None" route_control/opposite_route_terminus: messages: 3_route_5: "The route with %{source_objectid} objectid has a first stop from the %{target_0_label} ZDL whereas its oppoite route's last stop is from the ZDL %{target_1_label}" @@ -66,78 +67,95 @@ en: messages: 3_route_6: "The route with %{source_objectid} objectid does not connect enough stop points (required 2 stop points)" description: "A route must have at least 2 stop points" + prerequisite: "None" route_control/stop_points_in_journey_pattern: messages: 3_route_8: "The stop point %{target_0_label} (%{target_0_objectid}) of the route %{source_objectid} is not used by any journey pattern" description: "The stop points of a route must be used by at least one journey pattern" + prerequisite: "None" route_control/omnibus_journey_pattern: messages: 3_route_9: "The route with %{source_objectid} objectid does not have a journey pattern that connect all of its stop points" description: "A journey pattern of a route should connect all of a route's stop points" + prerequisite: "None" route_control/unactivated_stop_point: messages: 3_route_10: "L'itinéraire %{source_objectid} référence un arrêt (ZDEp) désactivé %{target_0_label} (%{target_0_objectid})" description: "Les arrêts d'un itinéraire ne doivent pas être désactivés" + prerequisite: "None" journey_pattern_control/duplicates: messages: 3_journeypattern_1: "The journey pattern with objectid %{source_objectid} is identical with another one %{target_0_objectid}" description: "Two journey patterns belonging to the same line must not connect the same stop points in the same order" + prerequisite: "None" journey_pattern_control/vehicle_journey: messages: 3_journeypattern_2: "The journey pattern with %{source_objectid} objectid doesn't have any vehicle journey" description: "A journey pattern must have at least one vehicle journey" + prerequisite: "None" vehicle_journey_control/waiting_time: messages: 3_vehiclejourney_1: "On the following vehicle journey %{source_objectid}, the waiting time %{error_value} a this stop point %{target_0_label} (%{target_0_objectid}) is greater than the threshold (%{reference_value})" description: "The waiting time, in minutes, at a specific stop point cannot be too big" + prerequisite: "None" vehicle_journey_control/speed: messages: 3_vehiclejourney_2_1: "On the following vehicle journey %{source_objectid}, the computed speed %{error_value} between the stop points %{target_0_label} (%{target_0_objectid}) and %{target_1_label} (%{target_1_objectid}) is greater than the threshold (%{reference_value})" 3_vehiclejourney_2_2: "On the following vehicle journey %{source_objectid}, the computed speed %{error_value} between the stop points %{target_0_label} (%{target_0_objectid}) and %{target_1_label} (%{target_1_objectid}) is smaller than the threshold (%{reference_value})" description: "The speed between 2 stop points should be confined between thresholds" + prerequisite: "None" vehicle_journey_control/delta: messages: 3_vehiclejourney_3: "The travel time on the vehicle journey with %{source_objectid} objectid between the stop points %{target_0_label} (%{target_0_objectid}) and %{target_1_label} (%{target_1_objectid}) is too far off %{error_value} the average waiting on the journey pattern" description: "The travel time between two following stop points must be close to all the vehicle journey of a journey pattern" + prerequisite: "None" vehicle_journey_control/time_table: messages: 3_vehiclejourney_4: "The vehicle journey with %{source_objectid} objectid does not have a timetable" description: "A vehicle journey must have at least one timetable" + prerequisite: "None" vehicle_journey_control/vehicle_journey_at_stops: messages: 3_vehiclejourney_5_1: "The vehicle journey with %{source_objectid} objectid has an arrival time %{error_value} greater than the departure time %{reference_value} at the stop point %{target_0_label} (%{target_0_objectid})" 3_vehiclejourney_5_2: "The vehicle journey with %{source_objectid} objectid has an departure time %{error_value} at stop point %{target_0_label} (%{target_0_objectid}) greater than the arrival %{reference_value} at the next stop point" description: "The arrival time of a stop point must be smaller than the departure time of this stop point AND the departure time of the stop points must be in chronological order" + prerequisite: "None" routing_constraint_zone_control/vehicle_journey_at_stops: messages: 3_routingconstraint_1: "The Routing Constraint Zone %{source_objectid} references an unactivated stop point (ZDEp) %{target_0_label} (%{target_0_objectid})" description: "The stop points of a Routing Constraint Zone must be activated" + prerequisite: "None" routing_constraint_zone_control/maximum_length: messages: 3_routingconstraint_2: "The Routing Constraint Zone %{source_objectid} covers all the stop points of its related route : %{target_0_objectid}." description: "A Routing Constraint Zone cannot cover all the stop points of a route" + prerequisite: "None" routing_constraint_zone_control/minimum_length: messages: 3_routingconstraint_3: "The Routing Constraint Zone %{source_objectid} has less than 2 stop points" description: "A Routing Constraint Zone must have at least 2 stop points" + prerequisite: "None" line_control/route: messages: 3_line_1: "On line :%{source_label} (%{source_objectid}), no route has an opposite route" description: "The routes of a line must have an opposite route" - prerequisite: Lign has multiple routes + prerequisite: Line has multiple routes generic_attribute_control/pattern: messages: 3_generic_1: "%{source_objectid} : the %{source_attribute} attribute value (%{error_value}) does not respect the following pattern : %{reference_value}" description: "The object attribute must respect a patten (regular expression)" + prerequisite: "None" generic_attribute_control/min_max: messages: 3_generic_2_1: "%{source_objectid} : the %{source_attribute} attributes's value (%{error_value}) is greater than the authorized maximum value : %{reference_value}" 3_generic_2_2: "%{source_objectid} : the %{source_attribute} attributes's value (%{error_value}) is smaller than the authorized minimum value %{reference_value}" description: "The numeric value of an attribute must be contained between 2 values" + prerequisite: "None" generic_attribute_control/uniqueness: messages: 3_generic_3: "%{source_objectid} : the %{source_attribute} attribute (%{error_value}) has a value shared with : %{target_0_objectid}" description: "The attribute's value must be unique compared to the other objects ofthe same type (related to the same line)" + prerequisite: "None" shape_control: 3_shape_1: "Tracé %{source_objectid} : le tracé passe trop loin de l'arrêt %{target_0_label} (%{target_0_objectid}) : %{error_value} > %{reference_value}" 3_shape_2: "Tracé %{source_objectid} : le tracé n'est pas défini entre les arrêts %{target_0_label} (%{target_0_objectid}) et %{target_1_label} (%{target_1_objectid})" diff --git a/config/locales/compliance_controls.fr.yml b/config/locales/compliance_controls.fr.yml index f5f7e351f..78b92451f 100644 --- a/config/locales/compliance_controls.fr.yml +++ b/config/locales/compliance_controls.fr.yml @@ -28,8 +28,6 @@ fr: title: "Editer un contrôle" select_type: title: "Sélectionner un type de contrôle" - metas: - no_prerequisite: "Aucun" actions: new: Ajouter edit: Editer @@ -40,6 +38,7 @@ fr: messages: 3_route_1: "L'itinéraire %{source_objectid} dessert successivement les arrêts %{target_0_label} (%{target_0_objectid}) et %{target_1_label} (%{target_1_objectid}) de la même zone de lieu" description: "Deux arrêts d’une même ZDL ne peuvent pas se succéder dans un itinéraire" + prerequisite: "Aucun" route_control/opposite_route: messages: 3_route_2: "L'itinéraire %{source_objectid} référence un itinéraire retour %{target_0_objectid} incohérent" @@ -52,10 +51,12 @@ fr: messages: 3_route_3: "L'itinéraire %{source_objectid} n'a pas de mission" description: "Un itinéraire doit avoir au moins une mission" + prerequisite: "Aucun" route_control/duplicates: messages: 3_route_4: "L'itinéraire %{source_objectid} est identique à l'itinéraire %{target_0_objectid}" description: "2 itinéraires ne doivent pas desservir strictement les mêmes arrêts dans le même ordre avec les mêmes critères de monté/descente" + prerequisite: "Aucun" route_control/opposite_route_terminus: messages: 3_route_5: "L'itinéraire %{source_objectid} dessert au départ un arrêt de la ZDL %{target_0_label} alors que l'itinéraire inverse dessert à l'arrivée un arrêt de la ZDL %{target_1_label}" @@ -65,60 +66,74 @@ fr: messages: 3_route_6: "L'itinéraire %{source_objectid} ne dessert pas assez d'arrêts (minimum 2 requis)" description: "Un itinéraire doit référencer au moins 2 arrêts" + prerequisite: "Aucun" route_control/stop_points_in_journey_pattern: messages: 3_route_8: "l'arrêt %{target_0_label} (%{target_0_objectid}) de l'itinéraire %{source_objectid} n'est desservi par aucune mission" description: "Les arrêts de l'itinéraire doivent être desservis par au moins une mission" + prerequisite: "Aucun" route_control/omnibus_journey_pattern: messages: 3_route_9: "L'itinéraire %{source_objectid} n'a aucune mission desservant l'ensemble de ses arrêts" description: "Une mission de l'itinéraire devrait desservir l'ensemble des arrêts de celui-ci" + prerequisite: "Aucun" route_control/unactivated_stop_point: messages: 3_route_10: "L'itinéraire %{source_objectid} référence un arrêt (ZDEp) désactivé %{target_0_label} (%{target_0_objectid})" description: "Les arrêts d'un itinéraire ne doivent pas être désactivés" + prerequisite: "Aucun" journey_pattern_control/duplicates: messages: 3_journeypattern_1: "La mission %{source_objectid} est identique à la mission %{target_0_objectid}" description: "Deux missions de la même ligne ne doivent pas desservir les mêmes arrêts dans le même ordre" + prerequisite: "Aucun" journey_pattern_control/vehicle_journey: messages: 3_journeypattern_2: "La mission %{source_objectid} n'a pas de course" description: "Une mission doit avoir au moins une course" + prerequisite: "Aucun" vehicle_journey_control/waiting_time: messages: 3_vehiclejourney_1: "Sur la course %{source_objectid}, le temps d'attente %{error_value} à l'arrêt %{target_0_label} (%{target_0_objectid}) est supérieur au seuil toléré (%{reference_value})" description: "La durée d’attente, en minutes, à un arrêt ne doit pas être trop grande" + prerequisite: "Aucun" vehicle_journey_control/speed: messages: 3_vehiclejourney_2_1: "Sur la course %{source_objectid}, la vitesse calculée %{error_value} entre les arrêts %{target_0_label} (%{target_0_objectid}) et %{target_1_label} (%{target_1_objectid}) est supérieure au seuil toléré (%{reference_value})" 3_vehiclejourney_2_2: "Sur la course %{source_objectid}, la vitesse calculée %{error_value} entre les arrêts %{target_0_label} (%{target_0_objectid}) et %{target_1_label} (%{target_1_objectid}) est inférieure au seuil toléré (%{reference_value})" description: "La vitesse entre deux arrêts doit être dans une fourchette paramétrable" + prerequisite: "Aucun" vehicle_journey_control/delta: messages: 3_vehiclejourney_3: "Le temps de parcours sur la course %{source_objectid} entre les arrêts %{target_0_label} (%{target_0_objectid}) et %{target_1_label} (%{target_1_objectid}) s'écarte de %{error_value} du temps moyen constaté sur la mission" description: "Les temps de parcours entre 2 arrêts successifs doivent être similaires pour toutes les courses d’une même mission" + prerequisite: "Aucun" vehicle_journey_control/time_table: messages: 3_vehiclejourney_4: "La course %{source_objectid} n'a pas de calendrier d'application" description: "Une course doit avoir au moins un calendrier d’application" + prerequisite: "Aucun" vehicle_journey_control/vehicle_journey_at_stops: messages: 3_vehiclejourney_5_1: "La course %{source_objectid} a un horaire d'arrivé %{error_value} supérieur à l'horaire de départ %{reference_value} à l'arrêt %{target_0_label} (%{target_0_objectid})" 3_vehiclejourney_5_2: "La course %{source_objectid} a un horaire de départ %{error_value} à l'arrêt %{target_0_label} (%{target_0_objectid}) supérieur à l'horaire d'arrivé %{reference_value} à l'arrêt suivant" description: "L'horaire d'arrivée à un arrêt doit être antérieur à l'horaire de départ de cet arrêt ET les horaires de départ aux arrêts doivent être dans l'ordre chronologique croissant." + prerequisite: "Aucun" routing_constraint_zone_control/unactivated_stop_point: messages: 3_routingconstraint_1: "L'ITL %{source_objectid} référence un arrêt (ZDEp) désactivé %{target_0_label} (%{target_0_objectid})" description: "Les arrêts d'une ITL ne doivent pas être désactivés" + prerequisite: "Aucun" routing_constraint_zone_control/maximum_length: messages: 3_routingconstraint_2: "L'ITL %{source_objectid} couvre tous les arrêts de l'itinéraire %{target_0_objectid}." description: "Une ITL ne peut pas couvrir l'ensemble des arrêts de l'itinéraire" + prerequisite: "Aucun" routing_constraint_zone_control/minimum_length: messages: 3_routingconstraint_3: "L'ITL %{source_objectid} n'a pas suffisament d'arrêts (minimum 2 arrêts requis)" description: "Une ITL doit référencer au moins 2 arrêts" + prerequisite: "Aucun" line_control/route: messages: 3_line_1: "Sur la ligne %{source_label} (%{source_objectid}), aucun itinéraire n'a d'itinéraire inverse" @@ -128,15 +143,18 @@ fr: messages: 3_generic_1: "%{source_objectid} : l'attribut %{source_attribute} a une valeur %{error_value} qui ne respecte pas le motif %{reference_value}" description: "l'attribut de l'objet doit respecter un motif (expression régulière)" + prerequisite: "Aucun" generic_attribute_control/min_max: messages: 3_generic_2_1: "%{source_objectid} : l'attribut %{source_attribute} a une valeur %{error_value} supérieure à la valeur maximale autorisée %{reference_value}" 3_generic_2_2: "%{source_objectid} : l'attribut %{source_attribute} a une valeur %{error_value} inférieure à la valeur minimale autorisée %{reference_value}" description: "La valeur numérique de l'attribut doit rester comprise entre 2 valeurs" + prerequisite: "Aucun" generic_attribute_control/uniqueness: messages: 3_generic_3: "%{source_objectid} : l'attribut %{source_attribute} a une valeur %{error_value} partagée avec %{target_0_objectid}" description: "La valeur de l'attribut doit être unique au sein des objets de la ligne" + prerequisite: "Aucun" shape_control: 3_shape_1: "Tracé %{source_objectid} : le tracé passe trop loin de l'arrêt %{target_0_label} (%{target_0_objectid}) : %{error_value} > %{reference_value}" 3_shape_2: "Tracé %{source_objectid} : le tracé n'est pas défini entre les arrêts %{target_0_label} (%{target_0_objectid}) et %{target_1_label} (%{target_1_objectid})" @@ -173,7 +191,7 @@ fr: vehicle_journey_control/speed: one: "La vitesse entre deux arrêts doit être dans une fourchette paramétrable" vehicle_journey_control/delta: - one: "Les vitesses entre 2 arrêts doivent être similaires pour toutes les courses d’une même mission" + one: "Les temps de parcours entre 2 arrêts doivent être similaires pour toutes les courses d’une même mission" vehicle_journey_control/time_table: one: "Une course doit avoir au moins un calendrier d’application" vehicle_journey_control/vehicle_journey_at_stops: diff --git a/config/locales/export_messages.en.yml b/config/locales/export_messages.en.yml new file mode 100644 index 000000000..f7951a103 --- /dev/null +++ b/config/locales/export_messages.en.yml @@ -0,0 +1,3 @@ +en: + export_messages: + success: Success diff --git a/config/locales/export_messages.fr.yml b/config/locales/export_messages.fr.yml new file mode 100644 index 000000000..5c2191f35 --- /dev/null +++ b/config/locales/export_messages.fr.yml @@ -0,0 +1,3 @@ +fr: + export_messages: + success: Succès diff --git a/config/locales/exports.en.yml b/config/locales/exports.en.yml index 2a47fba54..88c1b99f8 100644 --- a/config/locales/exports.en.yml +++ b/config/locales/exports.en.yml @@ -1,25 +1,32 @@ en: - exports: + exports: &exports + search_no_results: "No export matching your query" + filters: + referential: "Select data space..." + name_or_creator_cont: "Select an export or creator name..." + error_period_filter: "End date must be greater or equal than begin date" actions: new: "New export" + create: "New export" + show: "Export report" + download: "Download original file" destroy: "Destroy" destroy_confirm: "Are you sure you want destroy this export?" - new: - title: "New export" - all: "All" - flash: "Export task on queue, refresh page to see progression" index: title: "Exports" warning: "" + new: + title: "Generate a new export" + create: + title: "Generate a new export" show: + title: "Export %{name}" report: "Report" - exported_file: "Exported file" - statuses: - started: "Started" - scheduled: "Processing ..." - terminated: "Completed" - canceled: "Canceled" - aborted: "Failed" + exported_file: "Original file" + compliance_check: "Validation report" + compliance_check_of: "Validation of export: " + export_of_validation: "Export of the validation" + compliance_check_task: "Validate Report" severities: info: "Information" uncheck: "Unchecked" @@ -27,7 +34,13 @@ en: warning: "Warning" error: "Error" fatal: "Fatal" - activemodel: + export: + workgroup: Workgroup + netex: Netex + referential_companies: Companies + base: + <<: *exports + activerecord: models: export: zero: "export" @@ -37,6 +50,10 @@ en: zero: "export" one: "Neptune export" other: "exports" + csv_export: + zero: "export" + one: "CSV export" + other: "exports" gtfs_export: zero: "export" one: "GTFS export" @@ -44,4 +61,39 @@ en: netex_export: zero: "export" one: "NeTEx export" - other: "exports"
\ No newline at end of file + other: "exports" + errors: + models: + export: + base: + attributes: + file: + wrong_file_extension: "The exported file must be a zip file" + attributes: + attrs: &attrs + resources: "File to export" + created_at: "Created on" + started_at: "Started at" + name: "Name" + status: "Status" + creator: "Creator" + references_type: "Data to be exported" + no_save: "No save" + object_id_prefix: "Neptune Id prefix" + max_distance_for_commercial: "Max distance for commercial stop" + max_distance_for_connection_link: "Max distance for connection link" + ignore_last_word: "ignore last word" + ignore_end_chars: "ignore last chars" + parent: Parent + export: + <<: *attrs + base: + <<: *attrs + workgroup: + duration: Duration + referential_companies: + referential_id: Referential + flash: + exports: + create: + notice: "The export is in progress. Please wait and refresh the page in a few moments." diff --git a/config/locales/exports.fr.yml b/config/locales/exports.fr.yml index 2d7cc0259..fa3ac8fc7 100644 --- a/config/locales/exports.fr.yml +++ b/config/locales/exports.fr.yml @@ -1,33 +1,46 @@ fr: - exports: + exports: &exports + search_no_results: "Aucun export ne correspond à votre recherche" + filters: + referential: "Sélectionnez un jeu de données..." + name_or_creator_cont: "Indiquez un nom d'export ou d'opérateur..." + error_period_filter: "La date de fin doit être supérieure ou égale à la date de début" actions: new: "Nouvel export" + create: "Nouvel export" + show: "Rapport d'export" + download: "Téléch. fichier source" destroy: "Supprimer cet export" destroy_confirm: "Etes vous sûr de supprimer cet export ?" - new: - title: "Nouvel export" - all: "Toutes" - flash: "La demande d'export est mise en file d'attente, veuillez rafraichir régulièrement la page pour en suivre la progression" index: title: "Exports" warning: "" + new: + title: "Générer un export" + create: + title: "Générer un export" show: + title: "Export %{name}" report: "Rapport" - exported_file: "Fichier exporté" - statuses: - started: "En file d'attente..." - scheduled: "En cours..." - terminated: "Achevé" - canceled: "Annulé" - aborted: "Echoué" + exported_file: "Fichier source" + compliance_check: "Test de conformité" + compliance_check_of: "Validation de l'export : " + export_of_validation: "L'export de la validation" + compliance_check_task: "Validation" severities: info: "Information" - uncheck: "Non disponible" + uncheck: "Non testé" ok: "Ok" warning: "Alerte" error: "Erreur" fatal: "Fatal" - activemodel: + export: + workgroup: Groupe de travail + netex: Netex + referential_companies: Transporteurs + base: + <<: *exports + activerecord: models: export: zero: "export" @@ -35,7 +48,11 @@ fr: other: "exports" neptune_export: zero: "export" - one: "export neptune" + one: "export Neptune" + other: "exports" + csv_export: + zero: "export" + one: "export CSV" other: "exports" gtfs_export: zero: "export" @@ -45,3 +62,40 @@ fr: zero: "export" one: "export NeTEx" other: "exports" + errors: + models: + export: + base: + attributes: + file: + wrong_file_extension: "Le fichier exporté doit être au format zip" + attributes: + attrs: &attrs + resources: "Fichier à exporter" + created_at: "Créé le" + started_at: Démarrage + name: "Nom de l'export" + status: "Etat" + creator: "Opérateur" + no_save: "Pas de sauvegarde" + references_type: "Données à exporter" + object_id_prefix: "Préfixe d'identifiants" + max_distance_for_commercial: "Distance max pour créer les zones" + max_distance_for_connection_link: "Distance max pour créer les correspondances" + ignore_last_word: "ignorer le dernier mot" + ignore_end_chars: "ignorer les n derniers caractères" + type: "Type d'export" + file: "Résultat" + parent: Parent + export: + <<: *attrs + base: + <<: *attrs + workgroup: + duration: Durée + referential_companies: + referential_id: Jeu de données + flash: + exports: + create: + notice: "L'export est en cours, veuillez patienter. Actualiser votre page si vous voulez voir l'avancement de votre traitement." diff --git a/config/locales/import_messages.en.yml b/config/locales/import_messages.en.yml index aad4fb772..bc06c46f0 100644 --- a/config/locales/import_messages.en.yml +++ b/config/locales/import_messages.en.yml @@ -1,5 +1,5 @@ en: - import_messages: + import_message: corrupt_zip_file: "The zip file %{source_filename} is corrupted and cannot be read" inconsistent_zip_file: "The zip file %{source_filename} contains unexpected directories: %{spurious_dirs}, which are ignored" referential_creation: "Le référentiel n'a pas pu être créé car un référentiel existe déjà sur les mêmes périodes et lignes" @@ -50,4 +50,4 @@ en: 2_netexstif_servicejourneypattern_2: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number} : l'objet ServiceJourneyPattern d'identifiant %{source_objectid} doit contenir au moins 2 StopPointInJourneyPattern" 2_netexstif_servicejourneypattern_3_1: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number} : l'objet ServiceJourneyPattern d'identifiant %{source_objectid} n'a pas de valeur pour l'attribut ServiceJourneyPatternType" 2_netexstif_servicejourneypattern_3_2: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number} : l'objet ServiceJourneyPattern d'identifiant %{source_objectid} a une valeur interdite %{error_value} pour l'attribut ServiceJourneyPatternType différente de 'passenger'" - 2_netexstif_servicejourneypattern_4: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number}, objet ServiceJourneyPattern d'identifiant %{source_objectid} : les attributs 'order' des StopPointInJourneyPattern ne sont pas croissants."
\ No newline at end of file + 2_netexstif_servicejourneypattern_4: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number}, objet ServiceJourneyPattern d'identifiant %{source_objectid} : les attributs 'order' des StopPointInJourneyPattern ne sont pas croissants." diff --git a/config/locales/import_resources.en.yml b/config/locales/import_resources.en.yml index 5f0f3213e..3e737f9bc 100644 --- a/config/locales/import_resources.en.yml +++ b/config/locales/import_resources.en.yml @@ -1,5 +1,5 @@ en: - import_resources: + import_resources: &resources index: title: "NeTEx conformity" table_state: "%{lines_imported} line(s) imported on %{lines_in_zipfile} presents in zipfile" diff --git a/config/locales/imports.en.yml b/config/locales/imports.en.yml index b0644acd3..d0db87fb1 100644 --- a/config/locales/imports.en.yml +++ b/config/locales/imports.en.yml @@ -59,11 +59,12 @@ en: errors: models: import: - attributes: - file: - wrong_file_extension: "The imported file must be a zip file" + base: + attributes: + file: + wrong_file_extension: "The imported file must be a zip file" attributes: - import: + attrs: &attrs resources: "File to import" created_at: "Created on" started_at: "Started at" @@ -77,6 +78,10 @@ en: max_distance_for_connection_link: "Max distance for connection link" ignore_last_word: "ignore last word" ignore_end_chars: "ignore last chars" + import: + <<: *attrs + base: + <<: *attrs flash: imports: create: diff --git a/config/locales/imports.fr.yml b/config/locales/imports.fr.yml index 2380eac45..40272889a 100644 --- a/config/locales/imports.fr.yml +++ b/config/locales/imports.fr.yml @@ -1,5 +1,5 @@ fr: - imports: + imports: &imports search_no_results: "Aucun import ne correspond à votre recherche" filters: referential: "Sélectionnez un jeu de données..." @@ -34,6 +34,9 @@ fr: warning: "Alerte" error: "Erreur" fatal: "Fatal" + import: + base: + <<: *imports activerecord: models: import: @@ -59,11 +62,12 @@ fr: errors: models: import: - attributes: - file: - wrong_file_extension: "Le fichier importé doit être au format zip" + base: + attributes: + file: + wrong_file_extension: "Le fichier importé doit être au format zip" attributes: - import: + attrs: &attrs resources: "Fichier à importer" created_at: "Créé le" started_at: Démarrage @@ -77,6 +81,12 @@ fr: max_distance_for_connection_link: "Distance max pour créer les correspondances" ignore_last_word: "ignorer le dernier mot" ignore_end_chars: "ignorer les n derniers caractères" + + import: + <<: *attrs + base: + <<: *attrs + flash: imports: create: diff --git a/config/locales/stop_areas.en.yml b/config/locales/stop_areas.en.yml index c1ced1094..33722b60b 100644 --- a/config/locales/stop_areas.en.yml +++ b/config/locales/stop_areas.en.yml @@ -5,6 +5,10 @@ en: errors: empty: Aucun stop_area_id parent_area_type: can not be of type %{area_type} + registration_number: + already_taken: Already taken + cannot_be_empty: This field is mandatory + invalid: Incorrect value default_geometry_success: "%{count} modified stop areas" stop_area: no_position: "No Position" @@ -66,6 +70,9 @@ en: access_managment: "Access Points and Links managment" access_points: "Access Points" not_editable: "Le type d'arrêt est non modifiable" + state: + active: Active + deactivated: Deactivated genealogical: genealogical: "Links between stop area" genealogical_routing: "Routing constraint's links" @@ -139,11 +146,13 @@ en: created_at: Created at updated_at: Updated at waiting_time: Waiting time (minutes) + state: State formtastic: titles: stop_area: name: "" registration_number: "only alphanumerical or underscore characters" + registration_number_format: "authorized format : %{registration_number_format}" objectid: "[prefix]:StopArea:[unique_key] : prefix contains only alphanumerical or underscore characters, unique_key accepts also minus character" nearest_topic_name: "" city_name: "" diff --git a/config/locales/stop_areas.fr.yml b/config/locales/stop_areas.fr.yml index ede1aada6..605e6158e 100644 --- a/config/locales/stop_areas.fr.yml +++ b/config/locales/stop_areas.fr.yml @@ -6,6 +6,10 @@ fr: empty: Aucun stop_area_id parent_area_type: ne peut être de type %{area_type} incorrect_kind_area_type: Ce type d'arrêt est invalide pour cette catégorie + registration_number: + already_taken: Déjà utilisé + cannot_be_empty: Ce champ est requis + invalid: Valeur invalide default_geometry_success: "%{count} arrêts édités" stop_area: no_position: "Pas de position" @@ -67,6 +71,9 @@ fr: access_managment: "Gestion des accès et liens associés" access_points: "Points d'accès" not_editable: "Le type d'arrêt est non modifiable" + state: + active: Actif + deactivated: Désactivé genealogical: genealogical: "Lien entre arrêts" genealogical_routing: "Liens de l'ITL" @@ -141,11 +148,13 @@ fr: created_at: "Créé le" updated_at: "Edité le" waiting_time: Temps de desserte (minutes) + state: État formtastic: titles: stop_area: name: "" registration_number: "caractères autorisés : alphanumériques et 'souligné'" + registration_number_format: "format autorisé: %{registration_number_format}" objectid: "[prefixe]:StopArea:[clé_unique] caractères autorisés : alphanumériques et 'souligné' pour le préfixe, la clé unique accepte en plus le 'moins'" nearest_topic_name: "" city_name: "" diff --git a/config/locales/workbenches.en.yml b/config/locales/workbenches.en.yml index 7f21f47a0..2d9b27045 100644 --- a/config/locales/workbenches.en.yml +++ b/config/locales/workbenches.en.yml @@ -2,6 +2,14 @@ en: workbenches: show: title: "%{name}" + edit: + title: "Configure the workbench" + update: + title: "Configure the workbench" + referential_count: + zero: "There are no referentials in your workbench" + one: "There is one referential in your workbench" + other: "There are #{count} referentials in your workbench" index: title: "%{organisation} dashboard" offers: @@ -12,10 +20,9 @@ en: calendars: "Calendars" see: "See the list" no_content: "No content yet." - referential_count: - zero: "There is no referential in your workbench" - one: "There is one referential in your workbench" - other: "There are #{count} referentials in your workbench" + actions: + show_output: "Merge offer" + affect_ccset: "Configure" activerecord: models: workbench: diff --git a/config/locales/workbenches.fr.yml b/config/locales/workbenches.fr.yml index e7a392e66..8eee1a516 100644 --- a/config/locales/workbenches.fr.yml +++ b/config/locales/workbenches.fr.yml @@ -2,12 +2,27 @@ fr: workbenches: show: title: "%{name}" + edit: + title: "Configurer l'espace de travail" + update: + title: "Configurer l'espace de travail" referential_count: zero: "Aucun jeu de données dans cet espace de travail" one: "1 jeu de données dans cet espace de travail" other: "#{count} jeux de données dans cet espace de travail" + index: + title: "%{organisation} dashboard" + offers: + title: "Offre de transport" + organisation: "Offres de mon Organisation" + idf: "Offres IDF" + referentials: "Jeux de données" + calendars: "Calendriers" + see: "Voir la liste" + no_content: "Aucun contenu" actions: show_output: "Finaliser l'Offre" + configure: "Configurer" activerecord: models: workbench: diff --git a/config/middlewares/cachesettings.rb b/config/middlewares/cachesettings.rb new file mode 100644 index 000000000..8a122891f --- /dev/null +++ b/config/middlewares/cachesettings.rb @@ -0,0 +1,20 @@ +class CacheSettings + def initialize app, pat + @app = app + @pat = pat + end + + def call env + res = @app.call(env) + path = env["REQUEST_PATH"] + @pat.each do |pattern,data| + if path =~ pattern + res[1]["Cache-Control"] = data[:cache_control] if data.has_key?(:cache_control) + res[1]["Expires"] = (Time.now + data[:expires]).utc.rfc2822 if data.has_key?(:expires) + res[1]["X-Custom-Cache-Control"] = "yes" if Rails.env.development? + return res + end + end + res + end +end diff --git a/config/routes.rb b/config/routes.rb index 456cb66f5..6313b5678 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,7 +3,7 @@ require 'sidekiq/web' ChouetteIhm::Application.routes.draw do resource :dashboard - resources :workbenches, only: [:show, :index] do + resources :workbenches, except: [:destroy] do delete :referentials, on: :member, action: :delete_referentials resources :imports do get :download, on: :member @@ -11,6 +11,12 @@ ChouetteIhm::Application.routes.draw do resources :import_messages, only: [:index] end end + resources :exports do + post :upload, on: :member + resources :export_resources, only: [:index] do + resources :export_messages, only: [:index] + end + end resources :compliance_check_sets, only: [:index, :show] do get :executed, on: :member resources :compliance_checks, only: [:show] @@ -57,6 +63,8 @@ ChouetteIhm::Application.routes.draw do match 'lines' => 'lines#destroy_all', :via => :delete resources :lines, controller: "referential_lines", except: :index do + get :autocomplete, on: :collection, to: 'autocomplete_lines#index' + resource :footnotes, controller: "line_footnotes" delete :index, on: :collection, action: :delete_all collection do @@ -174,7 +182,7 @@ ChouetteIhm::Application.routes.draw do namespace :api do namespace :v1 do - resources :workbenches, only: [:index, :show] do + resources :workbenches, except: [:destroy] do resources :imports, only: [:index, :show, :create] end resources :access_links, only: [:index, :show] diff --git a/db/migrate/20171106111448_update_import_message_criticity_type_to_string.rb b/db/migrate/20171106111448_update_import_message_criticity_type_to_string.rb index c14450387..a08a10b9a 100644 --- a/db/migrate/20171106111448_update_import_message_criticity_type_to_string.rb +++ b/db/migrate/20171106111448_update_import_message_criticity_type_to_string.rb @@ -7,10 +7,10 @@ class UpdateImportMessageCriticityTypeToString < ActiveRecord::Migration when 0 then "info" when 1 then "warning" when 2 then "error" - else + else "info" end end - ImportMessage.all.each { |im| im.update_attribute(:criticity, change_criticity_value(im.criticity)) } + Import::Message.all.each { |im| im.update_attribute(:criticity, change_criticity_value(im.criticity)) } end end diff --git a/db/migrate/20180208174834_add_attributes_to_workbench.rb b/db/migrate/20180208174834_add_attributes_to_workbench.rb new file mode 100644 index 000000000..02002a772 --- /dev/null +++ b/db/migrate/20180208174834_add_attributes_to_workbench.rb @@ -0,0 +1,9 @@ +class AddAttributesToWorkbench < ActiveRecord::Migration + def change + add_column :workbenches, :import_compliance_control_set_id, :integer, limit: 8 + add_column :workbenches, :merge_compliance_control_set_id, :integer, limit: 8 + + add_index :workbenches, :import_compliance_control_set_id + add_index :workbenches, :merge_compliance_control_set_id + end +end diff --git a/db/migrate/20180226074739_add_registration_number_format_to_stop_area_referentials.rb b/db/migrate/20180226074739_add_registration_number_format_to_stop_area_referentials.rb new file mode 100644 index 000000000..3f4231acb --- /dev/null +++ b/db/migrate/20180226074739_add_registration_number_format_to_stop_area_referentials.rb @@ -0,0 +1,5 @@ +class AddRegistrationNumberFormatToStopAreaReferentials < ActiveRecord::Migration + def change + add_column :stop_area_referentials, :registration_number_format, :string + end +end diff --git a/db/migrate/20180227151937_add_compliance_control_name_to_compliance_checks.rb b/db/migrate/20180227151937_add_compliance_control_name_to_compliance_checks.rb new file mode 100644 index 000000000..49ab0da11 --- /dev/null +++ b/db/migrate/20180227151937_add_compliance_control_name_to_compliance_checks.rb @@ -0,0 +1,5 @@ +class AddComplianceControlNameToComplianceChecks < ActiveRecord::Migration + def change + add_column :compliance_checks, :compliance_control_name, :string + end +end diff --git a/db/migrate/20180301142531_create_simple_exporters.rb b/db/migrate/20180301142531_create_simple_exporters.rb new file mode 100644 index 000000000..c007546c2 --- /dev/null +++ b/db/migrate/20180301142531_create_simple_exporters.rb @@ -0,0 +1,7 @@ +class CreateSimpleExporters < ActiveRecord::Migration + def change + rename_table :simple_importers, :simple_interfaces + add_column :simple_interfaces, :type, :string + SimpleInterface.update_all type: :SimpleImporter + end +end diff --git a/db/migrate/20180306135204_clean_former_exports.rb b/db/migrate/20180306135204_clean_former_exports.rb new file mode 100644 index 000000000..46a595c12 --- /dev/null +++ b/db/migrate/20180306135204_clean_former_exports.rb @@ -0,0 +1,5 @@ +class CleanFormerExports < ActiveRecord::Migration + def change + drop_table :exports + end +end diff --git a/db/migrate/20180306152953_update_imports_names.rb b/db/migrate/20180306152953_update_imports_names.rb new file mode 100644 index 000000000..fc57ff849 --- /dev/null +++ b/db/migrate/20180306152953_update_imports_names.rb @@ -0,0 +1,13 @@ +class UpdateImportsNames < ActiveRecord::Migration + def change + Import::Base.all.pluck(:type).uniq.each do |type| + next if type =~ /^Import/ + Import::Base.where(type: type).update_all type: "Import::#{type.gsub 'Import', ''}" + end + + Import::Base.all.pluck(:parent_type).uniq.compact.each do |type| + next if type =~ /^Import/ + Import::Base.where(parent_type: type).update_all parent_type: "Import::#{type.gsub 'Import', ''}" + end + end +end diff --git a/db/migrate/20180307071448_create_new_exports.rb b/db/migrate/20180307071448_create_new_exports.rb new file mode 100644 index 000000000..74921d108 --- /dev/null +++ b/db/migrate/20180307071448_create_new_exports.rb @@ -0,0 +1,56 @@ +class CreateNewExports < ActiveRecord::Migration + def change + create_table :exports do |t| + t.string "status" + t.string "current_step_id" + t.float "current_step_progress" + t.integer "workbench_id", limit: 8 + t.integer "referential_id", limit: 8 + t.string "name" + t.datetime "created_at" + t.datetime "updated_at" + t.string "file" + t.datetime "started_at" + t.datetime "ended_at" + t.string "token_upload" + t.string "type" + t.integer "parent_id", limit: 8 + t.string "parent_type" + t.datetime "notified_parent_at" + t.integer "current_step", default: 0 + t.integer "total_steps", default: 0 + t.string "creator" + end + + add_index "exports", ["referential_id"], name: "index_exports_on_referential_id", using: :btree + add_index "exports", ["workbench_id"], name: "index_exports_on_workbench_id", using: :btree + + create_table "export_messages", id: :bigserial, force: :cascade do |t| + t.string "criticity" + t.string "message_key" + t.hstore "message_attributes" + t.integer "export_id", limit: 8 + t.integer "resource_id", limit: 8 + t.datetime "created_at" + t.datetime "updated_at" + t.hstore "resource_attributes" + end + + add_index "export_messages", ["export_id"], name: "index_export_messages_on_export_id", using: :btree + add_index "export_messages", ["resource_id"], name: "index_export_messages_on_resource_id", using: :btree + + create_table "export_resources", id: :bigserial, force: :cascade do |t| + t.integer "export_id", limit: 8 + t.string "status" + t.datetime "created_at" + t.datetime "updated_at" + t.string "resource_type" + t.string "reference" + t.string "name" + t.hstore "metrics" + end + + add_index "export_resources", ["export_id"], name: "index_export_resources_on_export_id", using: :btree + + end +end diff --git a/db/migrate/20180308095116_add_options_to_exports.rb b/db/migrate/20180308095116_add_options_to_exports.rb new file mode 100644 index 000000000..02744c5cb --- /dev/null +++ b/db/migrate/20180308095116_add_options_to_exports.rb @@ -0,0 +1,5 @@ +class AddOptionsToExports < ActiveRecord::Migration + def change + add_column :exports, :options, :hstore + end +end diff --git a/db/schema.rb b/db/schema.rb index c709290f5..cf0a32a3b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,12 +11,11 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180202170009) do - +ActiveRecord::Schema.define(version: 20180308095116) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" - enable_extension "hstore" enable_extension "postgis" + enable_extension "hstore" enable_extension "unaccent" create_table "access_links", id: :bigserial, force: :cascade do |t| @@ -119,7 +118,6 @@ ActiveRecord::Schema.define(version: 20180202170009) do t.datetime "updated_at" t.date "end_date" t.string "date_type" - t.string "mode" end add_index "clean_ups", ["referential_id"], name: "index_clean_ups_on_referential_id", using: :btree @@ -221,6 +219,7 @@ ActiveRecord::Schema.define(version: 20180202170009) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "origin_code" + t.string "compliance_control_name" end add_index "compliance_checks", ["compliance_check_block_id"], name: "index_compliance_checks_on_compliance_check_block_id", using: :btree @@ -299,18 +298,58 @@ ActiveRecord::Schema.define(version: 20180202170009) do add_index "custom_fields", ["resource_type"], name: "index_custom_fields_on_resource_type", using: :btree + create_table "export_messages", id: :bigserial, force: :cascade do |t| + t.string "criticity" + t.string "message_key" + t.hstore "message_attributes" + t.integer "export_id", limit: 8 + t.integer "resource_id", limit: 8 + t.datetime "created_at" + t.datetime "updated_at" + t.hstore "resource_attributes" + end + + add_index "export_messages", ["export_id"], name: "index_export_messages_on_export_id", using: :btree + add_index "export_messages", ["resource_id"], name: "index_export_messages_on_resource_id", using: :btree + + create_table "export_resources", id: :bigserial, force: :cascade do |t| + t.integer "export_id", limit: 8 + t.string "status" + t.datetime "created_at" + t.datetime "updated_at" + t.string "resource_type" + t.string "reference" + t.string "name" + t.hstore "metrics" + end + + add_index "export_resources", ["export_id"], name: "index_export_resources_on_export_id", using: :btree + create_table "exports", id: :bigserial, force: :cascade do |t| - t.integer "referential_id", limit: 8 t.string "status" - t.string "type" - t.string "options" + t.string "current_step_id" + t.float "current_step_progress" + t.integer "workbench_id", limit: 8 + t.integer "referential_id", limit: 8 + t.string "name" t.datetime "created_at" t.datetime "updated_at" - t.string "references_type" - t.string "reference_ids" + t.string "file" + t.datetime "started_at" + t.datetime "ended_at" + t.string "token_upload" + t.string "type" + t.integer "parent_id", limit: 8 + t.string "parent_type" + t.datetime "notified_parent_at" + t.integer "current_step", default: 0 + t.integer "total_steps", default: 0 + t.string "creator" + t.hstore "options" end add_index "exports", ["referential_id"], name: "index_exports_on_referential_id", using: :btree + add_index "exports", ["workbench_id"], name: "index_exports_on_workbench_id", using: :btree create_table "facilities", id: :bigserial, force: :cascade do |t| t.integer "stop_area_id", limit: 8 @@ -421,9 +460,9 @@ ActiveRecord::Schema.define(version: 20180202170009) do t.string "type" t.integer "parent_id", limit: 8 t.string "parent_type" + t.datetime "notified_parent_at" t.integer "current_step", default: 0 t.integer "total_steps", default: 0 - t.datetime "notified_parent_at" t.string "creator" end @@ -725,11 +764,12 @@ ActiveRecord::Schema.define(version: 20180202170009) do t.integer "line_id", limit: 8 end - create_table "simple_importers", id: :bigserial, force: :cascade do |t| + create_table "simple_interfaces", id: :bigserial, force: :cascade do |t| t.string "configuration_name" t.string "filepath" t.string "status" t.json "journal" + t.string "type" end create_table "stop_area_referential_memberships", id: :bigserial, force: :cascade do |t| @@ -765,6 +805,7 @@ ActiveRecord::Schema.define(version: 20180202170009) do t.datetime "created_at" t.datetime "updated_at" t.string "objectid_format" + t.string "registration_number_format" end create_table "stop_areas", id: :bigserial, force: :cascade do |t| @@ -1007,17 +1048,21 @@ ActiveRecord::Schema.define(version: 20180202170009) do create_table "workbenches", id: :bigserial, force: :cascade do |t| t.string "name" - t.integer "organisation_id", limit: 8 + t.integer "organisation_id", limit: 8 t.datetime "created_at" t.datetime "updated_at" - t.integer "line_referential_id", limit: 8 - t.integer "stop_area_referential_id", limit: 8 - t.integer "output_id", limit: 8 + t.integer "line_referential_id", limit: 8 + t.integer "stop_area_referential_id", limit: 8 + t.integer "output_id", limit: 8 t.string "objectid_format" - t.integer "workgroup_id", limit: 8 + t.integer "workgroup_id", limit: 8 + t.integer "import_compliance_control_set_id", limit: 8 + t.integer "merge_compliance_control_set_id", limit: 8 end + add_index "workbenches", ["import_compliance_control_set_id"], name: "index_workbenches_on_import_compliance_control_set_id", using: :btree add_index "workbenches", ["line_referential_id"], name: "index_workbenches_on_line_referential_id", using: :btree + add_index "workbenches", ["merge_compliance_control_set_id"], name: "index_workbenches_on_merge_compliance_control_set_id", using: :btree add_index "workbenches", ["organisation_id"], name: "index_workbenches_on_organisation_id", using: :btree add_index "workbenches", ["stop_area_referential_id"], name: "index_workbenches_on_stop_area_referential_id", using: :btree add_index "workbenches", ["workgroup_id"], name: "index_workbenches_on_workgroup_id", using: :btree diff --git a/lib/compliance_control_set_copier.rb b/lib/compliance_control_set_copier.rb index 58d40cdbf..06622f302 100644 --- a/lib/compliance_control_set_copier.rb +++ b/lib/compliance_control_set_copier.rb @@ -21,7 +21,7 @@ class ComplianceControlSetCopier # Workers # ------- def check_organisation_coherence! - return true if cc_set.organisation_id == referential.organisation_id + return true if cc_set.organisation_id == referential.organisation_id raise ArgumentError, "Incoherent organisation of referential" end @@ -66,7 +66,8 @@ class ComplianceControlSetCopier name: name_with_refid(compliance_control.name), comment: compliance_control.comment, code: compliance_control.code, - origin_code: compliance_control.origin_code + origin_code: compliance_control.origin_code, + compliance_control_name: compliance_control.class.name ).tap do | compliance_check | control_id_to_check.update compliance_control.id => compliance_check end diff --git a/lib/stif/dashboard.rb b/lib/stif/dashboard.rb index 46c635091..f558b79fb 100644 --- a/lib/stif/dashboard.rb +++ b/lib/stif/dashboard.rb @@ -1,7 +1,7 @@ module Stif class Dashboard < ::Dashboard def workbench - @workbench ||= current_organisation.workbenches.find_by(name: "Gestion de l'offre") + @workbench ||= current_organisation.workbenches.default end def workgroup @@ -13,7 +13,7 @@ module Stif end def calendars - @calendars ||= Calendar.where('(organisation_id = ? OR shared = ?) AND workgroup_id = ?', current_organisation.id, true, workgroup.id) + workbench.calendars end end end diff --git a/lib/stif/permission_translator.rb b/lib/stif/permission_translator.rb index 9e0feb9b8..09a7c610c 100644 --- a/lib/stif/permission_translator.rb +++ b/lib/stif/permission_translator.rb @@ -21,6 +21,7 @@ module Stif calendars footnotes imports + exports merges journey_patterns referentials diff --git a/lib/tasks/ci.rake b/lib/tasks/ci.rake index 89f9aa9c8..5b2c8ae3c 100644 --- a/lib/tasks/ci.rake +++ b/lib/tasks/ci.rake @@ -1,9 +1,18 @@ namespace :ci do + + def database_name + @database_name ||= + begin + config = YAML.load(ERB.new(File.read('config/database.yml')).result) + config["test"]["database"] + end + end + desc "Prepare CI build" task :setup do - cp "config/database/jenkins.yml", "config/database.yml" + cp "config/database/ci.yml", "config/database.yml" + puts "Use #{database_name} database" sh "RAILS_ENV=test rake db:drop db:create db:migrate" - sh "yarn --no-progress install" end def git_branch @@ -59,7 +68,18 @@ namespace :ci do sh "rm -rf log/test.log" sh "RAILS_ENV=test bundle exec rake assets:clobber" end + + task :build => ["ci:setup", "ci:assets", "ci:i18n_js_export", "spec", "ci:jest", "cucumber", "ci:check_security"] + + namespace :docker do + task :clean do + puts "Drop #{database_name} database" + sh "RAILS_ENV=test rake db:drop" + end + end + + task :docker => ["ci:build"] end desc "Run continuous integration tasks (spec, ...)" -task :ci => ["ci:setup", "ci:assets", "ci:i18n_js_export", "spec", "ci:jest", "cucumber", "ci:check_security", "ci:deploy", "ci:clean"] +task :ci => ["ci:build", "ci:deploy", "ci:clean"] diff --git a/lib/tasks/exports.rake b/lib/tasks/exports.rake new file mode 100644 index 000000000..845d581d3 --- /dev/null +++ b/lib/tasks/exports.rake @@ -0,0 +1,98 @@ +require 'csv' +require 'tasks/helpers/simple_interfaces' + +namespace :export do + desc "Notify parent imports when children finish" + task notify_parent: :environment do + ParentNotifier.new(Import).notify_when_finished + end + + desc "Mark old unfinished Netex imports as 'aborted'" + task netex_abort_old: :environment do + NetexImport.abort_old + end + + desc "export companies in the give LineReferential using the given exporter" + task :companies, [:referential_id, :configuration_name, :filepath, :logs_output_dir] => :environment do |t, args| + args.with_defaults(filepath: "./companies.csv", logs_output_dir: "./log/exporters/") + FileUtils.mkdir_p args[:logs_output_dir] + + referential = LineReferential.find args[:referential_id] + exporter = SimpleExporter.create configuration_name: args[:configuration_name], filepath: args[:filepath] + exporter.configure do |config| + config.collection = referential.companies.order(:name) + end + + SimpleInterfacesHelper.run_interface_controlling_interruption exporter, :export, args + end + + desc "export lines in the give LineReferential using the given exporter" + task :lines, [:referential_id, :configuration_name, :filepath, :logs_output_dir] => :environment do |t, args| + args.with_defaults(filepath: "./companies.csv", logs_output_dir: "./log/exporters/") + FileUtils.mkdir_p args[:logs_output_dir] + + referential = LineReferential.find args[:referential_id] + exporter = SimpleExporter.create configuration_name: args[:configuration_name], filepath: args[:filepath] + exporter.configure do |config| + config.collection = referential.lines.order(:name) + end + + SimpleInterfacesHelper.run_interface_controlling_interruption exporter, :export, args + end + + desc "export a complete offer from the given referential in the given X next days" + task :full_offer, [:referential_id, :configuration_name, :timelapse, :output_dir, :logs_output_dir] => :environment do |t, args| + referential = Referential.find args[:referential_id] + args.with_defaults(output_dir: "#{referential.name.parameterize}/#{Time.now.strftime "%y%m%d%H%M"}", logs_output_dir: "./log/exporters/", timelapse: 90) + + referential.switch + + journeys = Chouette::VehicleJourney.with_matching_timetable (Time.now.to_date..args[:timelapse].to_i.days.from_now.to_date) + if journeys.count == 0 + puts "No maching journeys were found".red + else + exports_group = SimpleInterfacesGroup.new "Export Complet \"#{referential.name}\" du #{Time.now.to_date} au #{args[:timelapse].to_i.days.from_now.to_date}" + exports_group.shared_options = {verbose: true} + + exporter = SimpleJsonExporter.create configuration_name: "#{args[:configuration_name]}_companies", filepath: "#{args[:output_dir]}/#{args[:configuration_name]}_companies.json" + ids = journeys.pluck :company_id + ids += journeys.joins(route: :line).pluck :"lines.company_id" + + exporter.configure do |config| + config.collection = Chouette::Company.where(id: ids.uniq).order('name') + end + + exports_group.add_interface exporter, "Services Types", :export + + exporter = SimpleJsonExporter.create configuration_name: "#{args[:configuration_name]}_schedules", filepath: "#{args[:output_dir]}/#{args[:configuration_name]}_schedules.json" + exporter.configure do |config| + config.collection = journeys + end + + exports_group.add_interface exporter, "Schedules", :export + + exporter = SimpleJsonExporter.create configuration_name: "#{args[:configuration_name]}_routes", filepath: "#{args[:output_dir]}/#{args[:configuration_name]}_routes.json" + exporter.configure do |config| + config.collection = Chouette::JourneyPattern.where(id: journeys.pluck(:journey_pattern_id).uniq) + end + + exports_group.add_interface exporter, "Routes", :export + + exporter = SimpleJsonExporter.create configuration_name: "#{args[:configuration_name]}_stops", filepath: "#{args[:output_dir]}/#{args[:configuration_name]}_stops.json" + exporter.configure do |config| + config.collection = Chouette::StopArea.where(id: journeys.joins(:stop_points).pluck(:"stop_points.stop_area_id").uniq).order('parent_id ASC NULLS FIRST') + end + + exports_group.add_interface exporter, "Stops", :export + + exporter = SimpleJsonExporter.create configuration_name: "#{args[:configuration_name]}_journeys", filepath: "#{args[:output_dir]}/#{args[:configuration_name]}_journeys.json" + exporter.configure do |config| + config.collection = journeys + end + + exports_group.add_interface exporter, "Services", :export + + exports_group.run + end + end +end diff --git a/lib/tasks/helpers/simple_interfaces.rb b/lib/tasks/helpers/simple_interfaces.rb new file mode 100644 index 000000000..61dd38399 --- /dev/null +++ b/lib/tasks/helpers/simple_interfaces.rb @@ -0,0 +1,11 @@ +module SimpleInterfacesHelper + def self.run_interface_controlling_interruption interface, method, args + begin + interface.send(method, verbose: true) + rescue Interrupt + interface.write_output_to_csv + raise + ensure + end + end +end diff --git a/lib/tasks/imports.rake b/lib/tasks/imports.rake index f01d3f34f..7162f2ada 100644 --- a/lib/tasks/imports.rake +++ b/lib/tasks/imports.rake @@ -1,4 +1,5 @@ require 'csv' +require 'tasks/helpers/simple_interfaces' namespace :import do desc "Notify parent imports when children finish" @@ -11,25 +12,10 @@ namespace :import do NetexImport.abort_old end - def importer_output_to_csv importer, output_dir - filepath = File.join output_dir, + "#{importer.configuration_name}_#{Time.now.strftime "%y%m%d%H%M"}_out.csv" - cols = %w(line kind event message error) - if importer.reload.journal.size > 0 - keys = importer.journal.first["row"].map(&:first) - CSV.open(filepath, "w") do |csv| - csv << cols + keys - importer.journal.each do |j| - csv << cols.map{|c| j[c]} + j["row"].map(&:last) - end - end - puts "Import Output written in #{filepath}" - end - end - desc "import the given file with the corresponding importer" - task :import, [:configuration_name, :filepath, :referential_id, :output_dir] => :environment do |t, args| - args.with_defaults(output_dir: "./log/importers/") - FileUtils.mkdir_p args[:output_dir] + task :import, [:configuration_name, :filepath, :referential_id, :logs_output_dir] => :environment do |t, args| + args.with_defaults(logs_output_dir: "./log/importers/") + FileUtils.mkdir_p args[:logs_output_dir] importer = SimpleImporter.create configuration_name: args[:configuration_name], filepath: args[:filepath] @@ -37,46 +23,32 @@ namespace :import do referential = Referential.find args[:referential_id] importer.configure do |config| config.add_value :referential, referential - config.context = {referential: referential, output_dir: args[:output_dir]} + config.context = {referential: referential, logs_output_dir: args[:logs_output_dir]} end end - puts "\e[33m***\e[0m Start importing" - begin - importer.import(verbose: true) - rescue Interrupt - raise - ensure - puts "\n\e[33m***\e[0m Import done, status: " + (importer.status == "success" ? "\e[32m" : "\e[31m" ) + (importer.status || "") + "\e[0m" - importer_output_to_csv importer, args[:output_dir] - end + + SimpleInterfacesHelper.run_interface_controlling_interruption importer, :import, args end desc "import the given file with the corresponding importer in the given StopAreaReferential" - task :import_in_stop_area_referential, [:referential_id, :configuration_name, :filepath] => :environment do |t, args| - args.with_defaults(output_dir: "./log/importers/") - FileUtils.mkdir_p args[:output_dir] + task :import_in_stop_area_referential, [:referential_id, :configuration_name, :filepath, :logs_output_dir] => :environment do |t, args| + args.with_defaults(logs_output_dir: "./log/importers/") + FileUtils.mkdir_p args[:logs_output_dir] referential = StopAreaReferential.find args[:referential_id] importer = SimpleImporter.create configuration_name: args[:configuration_name], filepath: args[:filepath] importer.configure do |config| config.add_value :stop_area_referential, referential - config.context = {stop_area_referential: referential, output_dir: args[:output_dir]} - end - puts "\e[33m***\e[0m Start importing" - begin - importer.import(verbose: true) - rescue Interrupt - raise - ensure - puts "\n\e[33m***\e[0m Import done, status: " + (importer.status == "success" ? "\e[32m" : "\e[31m" ) + (importer.status || "") + "\e[0m" - importer_output_to_csv importer, args[:output_dir] + config.context = {stop_area_referential: referential, logs_output_dir: args[:logs_output_dir]} end + + SimpleInterfacesHelper.run_interface_controlling_interruption importer, :import, args end desc "import the given routes files" - task :import_routes, [:referential_id, :configuration_name, :mapping_filepath, :filepath] => :environment do |t, args| - args.with_defaults(output_dir: "./log/importers/") - FileUtils.mkdir_p args[:output_dir] + task :import_routes, [:referential_id, :configuration_name, :mapping_filepath, :filepath, :logs_output_dir] => :environment do |t, args| + args.with_defaults(logs_output_dir: "./log/importers/") + FileUtils.mkdir_p args[:logs_output_dir] referential = Referential.find args[:referential_id] referential.switch @@ -84,38 +56,24 @@ namespace :import do importer = SimpleImporter.create configuration_name: args[:configuration_name], filepath: args[:filepath] importer.configure do |config| config.add_value :stop_area_referential, referential - config.context = {stop_area_referential: stop_area_referential, mapping_filepath: args[:mapping_filepath], output_dir: args[:output_dir]} - end - puts "\e[33m***\e[0m Start importing" - begin - importer.import(verbose: true) - rescue Interrupt - raise - ensure - puts "\n\e[33m***\e[0m Import done, status: " + (importer.status == "success" ? "\e[32m" : "\e[31m" ) + (importer.status || "") + "\e[0m" - importer_output_to_csv importer, args[:output_dir] + config.context = {stop_area_referential: stop_area_referential, line_referential: line_referential, mapping_filepath: args[:mapping_filepath], logs_output_dir: args[:logs_output_dir]} end + + SimpleInterfacesHelper.run_interface_controlling_interruption importer, :import, args end desc "import the given file with the corresponding importer in the given LineReferential" - task :import_in_line_referential, [:referential_id, :configuration_name, :filepath, :output_dir] => :environment do |t, args| - args.with_defaults(output_dir: "./log/importers/") - FileUtils.mkdir_p args[:output_dir] + task :import_in_line_referential, [:referential_id, :configuration_name, :filepath, :logs_output_dir] => :environment do |t, args| + args.with_defaults(logs_output_dir: "./log/importers/") + FileUtils.mkdir_p args[:logs_output_dir] referential = LineReferential.find args[:referential_id] importer = SimpleImporter.create configuration_name: args[:configuration_name], filepath: args[:filepath] importer.configure do |config| config.add_value :line_referential, referential - config.context = {line_referential: referential, output_dir: args[:output_dir]} - end - puts "\e[33m***\e[0m Start importing" - begin - importer.import(verbose: true) - rescue Interrupt - raise - ensure - puts "\n\e[33m***\e[0m Import done, status: " + (importer.status == "success" ? "\e[32m" : "\e[31m" ) + (importer.status || "") + "\e[0m" - importer_output_to_csv importer, args[:output_dir] + config.context = {line_referential: referential, logs_output_dir: args[:logs_output_dir]} end + + SimpleInterfacesHelper.run_interface_controlling_interruption importer, :import, args end end diff --git a/lib/tasks/seeds.rake b/lib/tasks/seeds.rake new file mode 100644 index 000000000..9038b64f9 --- /dev/null +++ b/lib/tasks/seeds.rake @@ -0,0 +1,19 @@ +namespace :db do + + include Seedbank::DSL + + base_dependencies = ['db:seed:original'] + override_dependency = ['db:seed:common'] + + namespace :seed do + seeds_environment = ENV.fetch("SEED_ENV", Rails.env) + glob_seed_files_matching('/*/').each do |directory| + environment = File.basename(directory) + override_dependency << "db:seed:#{environment}" if defined?(Rails) && seeds_environment == environment + end + end + + # Override db:seed to run all the common and environments seeds plus the original db:seed. + desc 'Load the seed data from db/seeds.rb, db/seeds/*.seeds.rb and db/seeds/ENVIRONMENT/*.seeds.rb. ENVIRONMENT is the env var SEED_ENV or the current environment in Rails.env.' + override_seed_task :seed => override_dependency +end diff --git a/script/launch-cron b/script/launch-cron index 183e5a331..1a38ffcbd 100644 --- a/script/launch-cron +++ b/script/launch-cron @@ -6,7 +6,7 @@ function append_var_if_defined 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" +VAR_LIST="SESAME_API_SETTINGS 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 REFLEX_API_URL REDIS_CACHE_STORE_URL RAILS_LOG_TO_STDOUT PATH" TMPF=$(tempfile) for v in $VAR_LIST; do diff --git a/spec/controllers/api/v1/imports_controller_spec.rb b/spec/controllers/api/v1/imports_controller_spec.rb index 8077dd052..f7022115a 100644 --- a/spec/controllers/api/v1/imports_controller_spec.rb +++ b/spec/controllers/api/v1/imports_controller_spec.rb @@ -30,7 +30,7 @@ RSpec.describe Api::V1::ImportsController, type: :controller do it 'should be successful' do expect { post :create, workbench_id: workbench.id, workbench_import: {name: "test", file: file, creator: 'test'}, format: :json - }.to change{WorkbenchImport.count}.by(1) + }.to change{Import::Workbench.count}.by(1) expect(response).to be_success end end diff --git a/spec/controllers/autocomplete_lines_controller_spec.rb b/spec/controllers/autocomplete_lines_controller_spec.rb new file mode 100644 index 000000000..d52cecd04 --- /dev/null +++ b/spec/controllers/autocomplete_lines_controller_spec.rb @@ -0,0 +1,67 @@ +RSpec.describe AutocompleteLinesController, type: :controller do + login_user + + describe "GET #index" do + let(:referential) { Referential.first } + let(:company) { create(:company, name: 'Standard Rail') } + let!(:line) do + create( + :line, + number: '15', + name: 'Continent Express', + company: company + ) + end + + let!(:line_without_company) do + create( + :line, + number: '15', + name: 'Continent Express', + company: nil + ) + end + + before(:each) do + excluded_company = create(:company, name: 'excluded company') + create( + :line, + number: 'different', + name: 'other', + company: excluded_company + ) + end + + it "filters by `number`" do + get :index, + referential_id: referential.id, + q: '15' + + expect(assigns(:lines).order(:id)).to eq([line, line_without_company]) + end + + it "filters by `name`" do + get :index, + referential_id: referential.id, + q: 'Continent' + + expect(assigns(:lines).order(:id)).to eq([line, line_without_company]) + end + + it "escapes the query" do + get :index, + referential_id: referential.id, + q: 'Continent%' + + expect(assigns(:lines).order(:id)).to be_empty + end + + it "filters by company `name`" do + get :index, + referential_id: referential.id, + q: 'standard' + + expect(assigns(:lines).to_a).to eq([line]) + end + end +end diff --git a/spec/controllers/exports_controller_spec.rb b/spec/controllers/exports_controller_spec.rb index 6cd6e4c54..3a67497ec 100644 --- a/spec/controllers/exports_controller_spec.rb +++ b/spec/controllers/exports_controller_spec.rb @@ -1,22 +1,97 @@ -require 'spec_helper' - -describe ExportsController, :type => :controller do +RSpec.describe ExportsController, :type => :controller do login_user - describe "GET 'new'" do - it "returns http success" do - pending - get 'new' + let(:workbench) { create :workbench } + let(:export) { create(:netex_export, workbench: workbench) } + + describe 'GET #new' do + it 'should be successful if authorized' do + get :new, workbench_id: workbench.id expect(response).to be_success end + + it 'should be unsuccessful unless authorized' do + remove_permissions('exports.create', from_user: @user, save: true) + get :new, workbench_id: workbench.id + expect(response).not_to be_success + end end - describe "GET 'index'" do - it "returns http success" do - pending - get 'index' - expect(response).to be_success + describe "POST #create" do + let(:params){ {name: "foo"} } + let(:request){ post :create, workbench_id: workbench.id, export: params } + it 'should create no objects' do + expect{request}.to_not change{Export::Base.count} + end + + context "with full params" do + let(:params){{ + name: "foo", + type: "Export::Netex", + duration: 12, + export_type: :line + }} + + it 'should be successful' do + expect{request}.to change{Export::Base.count}.by(1) + end + + it "displays a flash message" do + request + expect(controller).to set_flash[:notice].to( + I18n.t('flash.exports.create.notice') + ) + end + end + + context "with missing options" do + let(:params){{ + name: "foo", + type: "Export::Workgroup" + }} + + it 'should be unsuccessful' do + expect{request}.to change{Export::Base.count}.by(0) + end + end + + context "with all options" do + let(:params){{ + name: "foo", + type: "Export::Workgroup", + duration: 90 + }} + + it 'should be successful' do + expect{request}.to change{Export::Base.count}.by(1) + end + end + + context "with wrong type" do + let(:params){{ + name: "foo", + type: "Export::Foo" + }} + + it 'should be unsuccessful' do + expect{request}.to raise_error ActiveRecord::SubclassNotFound + end end end + describe 'POST #upload' do + context "with the token" do + it 'should be successful' do + post :upload, workbench_id: workbench.id, id: export.id, token: export.token_upload + expect(response).to be_redirect + end + end + + context "without the token" do + it 'should be unsuccessful' do + post :upload, workbench_id: workbench.id, id: export.id, token: "foo" + expect(response).to_not be_success + end + end + end end diff --git a/spec/controllers/line_referentials_controller_spec.rb b/spec/controllers/line_referentials_controller_spec.rb index 17ffb670d..8e8d48fda 100644 --- a/spec/controllers/line_referentials_controller_spec.rb +++ b/spec/controllers/line_referentials_controller_spec.rb @@ -6,8 +6,8 @@ RSpec.describe LineReferentialsController, :type => :controller do describe 'PUT sync' do let(:request){ put :sync, id: line_referential.id } - it 'should redirect to 403' do - expect(request).to redirect_to "/403" + it 'should respond with 403' do + expect(request).to have_http_status 403 end with_permission "line_referentials.synchronize" do diff --git a/spec/controllers/lines_controller_spec.rb b/spec/controllers/lines_controller_spec.rb index 65fe88b96..96f49bb36 100644 --- a/spec/controllers/lines_controller_spec.rb +++ b/spec/controllers/lines_controller_spec.rb @@ -7,8 +7,8 @@ RSpec.describe LinesController, :type => :controller do describe 'PUT deactivate' do let(:request){ put :deactivate, id: line.id, line_referential_id: line_referential.id } - it 'should redirect to 403' do - expect(request).to redirect_to "/403" + it 'should respond with 403' do + expect(request).to have_http_status 403 end with_permission "lines.change_status" do @@ -24,8 +24,8 @@ RSpec.describe LinesController, :type => :controller do before(:each){ line.deactivate! } - it 'should redirect to 403' do - expect(request).to redirect_to "/403" + it 'should respond with 403' do + expect(request).to have_http_status 403 end with_permission "lines.change_status" do diff --git a/spec/controllers/referentials_controller_spec.rb b/spec/controllers/referentials_controller_spec.rb index 5e0b1e505..ff450c905 100644 --- a/spec/controllers/referentials_controller_spec.rb +++ b/spec/controllers/referentials_controller_spec.rb @@ -6,6 +6,42 @@ describe ReferentialsController, :type => :controller do let(:organisation) { create :organisation } let(:other_referential) { create :referential, organisation: organisation } + describe "GET new" do + let(:request){ get :new, workbench_id: referential.workbench_id } + before{ request } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + context "when cloning another referential" do + let(:source){ referential } + let(:request){ get :new, workbench_id: referential.workbench_id, from: source.id } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + context "when the referential is in another organisation but accessible by the user" do + let(:source){ create(:workbench_referential) } + before do + source.workbench.update_attribute :workgroup_id, referential.workbench.workgroup_id + end + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + context "when the referential is not accessible by the user" do + let(:source){ create(:workbench_referential) } + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + end + end + describe 'PUT archive' do context "user's organisation matches referential's organisation" do it 'returns http success' do diff --git a/spec/controllers/stop_area_referentials_controller_spec.rb b/spec/controllers/stop_area_referentials_controller_spec.rb index 384323334..737ef631f 100644 --- a/spec/controllers/stop_area_referentials_controller_spec.rb +++ b/spec/controllers/stop_area_referentials_controller_spec.rb @@ -6,7 +6,9 @@ RSpec.describe StopAreaReferentialsController, :type => :controller do describe 'PUT sync' do let(:request){ put :sync, id: stop_area_referential.id } - it { expect(request).to redirect_to "/403" } + it 'should respond with 403' do + expect(request).to have_http_status 403 + end with_permission "stop_area_referentials.synchronize" do it 'returns HTTP success' do diff --git a/spec/controllers/stop_areas_controller_spec.rb b/spec/controllers/stop_areas_controller_spec.rb index 23bca3c36..f39ac5776 100644 --- a/spec/controllers/stop_areas_controller_spec.rb +++ b/spec/controllers/stop_areas_controller_spec.rb @@ -7,8 +7,8 @@ RSpec.describe StopAreasController, :type => :controller do describe 'PUT deactivate' do let(:request){ put :deactivate, id: stop_area.id, stop_area_referential_id: stop_area_referential.id } - it 'should redirect to 403' do - expect(request).to redirect_to "/403" + it 'should respond with 403' do + expect(request).to have_http_status 403 end with_permission "stop_areas.change_status" do @@ -24,8 +24,8 @@ RSpec.describe StopAreasController, :type => :controller do before(:each){ stop_area.deactivate! } - it 'should redirect to 403' do - expect(request).to redirect_to "/403" + it 'should respond with 403' do + expect(request).to have_http_status 403 end with_permission "stop_areas.change_status" do diff --git a/spec/controllers/vehicle_journey_imports_controller_spec.rb b/spec/controllers/vehicle_journey_imports_controller_spec.rb deleted file mode 100644 index 633f90b70..000000000 --- a/spec/controllers/vehicle_journey_imports_controller_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'spec_helper' - -describe ImportTasksController, :type => :controller do - login_user -end diff --git a/spec/factories/custom_fields.rb b/spec/factories/custom_fields.rb index 2f5fae555..7c43a6147 100644 --- a/spec/factories/custom_fields.rb +++ b/spec/factories/custom_fields.rb @@ -4,6 +4,6 @@ FactoryGirl.define do resource_type "VehicleJourney" sequence(:name){|n| "custom field ##{n}"} field_type "list" - options( { "capacity" => "0" } ) + options( { capacity: "0" } ) end end diff --git a/spec/factories/exports.rb b/spec/factories/exports.rb deleted file mode 100644 index 34427edb8..000000000 --- a/spec/factories/exports.rb +++ /dev/null @@ -1,5 +0,0 @@ -FactoryGirl.define do - factory :export do - referential { Referential.find_by_slug("first") } - end -end diff --git a/spec/factories/exports/export_messages.rb b/spec/factories/exports/export_messages.rb new file mode 100644 index 000000000..55394ec45 --- /dev/null +++ b/spec/factories/exports/export_messages.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :export_message, class: Export::Message do + association :export + association :resource, factory: :export_resource + criticity :info + end +end diff --git a/spec/factories/exports/export_resources.rb b/spec/factories/exports/export_resources.rb new file mode 100644 index 000000000..8e38235cd --- /dev/null +++ b/spec/factories/exports/export_resources.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do + factory :export_resource, class: Export::Resource do + sequence(:name) { |n| "Export resource #{n}" } + association :export, factory: :netex_export + status :WARNING + resource_type 'type' + reference 'reference' + end +end diff --git a/spec/factories/exports/exports.rb b/spec/factories/exports/exports.rb new file mode 100644 index 000000000..c8aaf30a9 --- /dev/null +++ b/spec/factories/exports/exports.rb @@ -0,0 +1,34 @@ +FactoryGirl.define do + factory :export, class: Export::Base do + sequence(:name) { |n| "Export #{n}" } + current_step_id "MyString" + current_step_progress 1.5 + association :workbench + association :referential + status :new + started_at nil + ended_at nil + creator 'rspec' + + after(:build) do |export| + export.class.skip_callback(:create, :before, :initialize_fields) + end + end + + factory :bad_export, class: Export::Base do + sequence(:name) { |n| "Export #{n}" } + current_step_id "MyString" + current_step_progress 1.5 + association :workbench + association :referential + file {File.open(File.join(Rails.root, 'spec', 'fixtures', 'terminated_job.json'))} + status :new + started_at nil + ended_at nil + creator 'rspec' + + after(:build) do |export| + export.class.skip_callback(:create, :before, :initialize_fields) + end + end +end diff --git a/spec/factories/exports/netex_exports.rb b/spec/factories/exports/netex_exports.rb new file mode 100644 index 000000000..0648bbc56 --- /dev/null +++ b/spec/factories/exports/netex_exports.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :netex_export, class: Export::Netex, parent: :export do + association :parent, factory: :workgroup_export + export_type :line + duration 90 + end +end diff --git a/spec/factories/exports/workgroup_exports.rb b/spec/factories/exports/workgroup_exports.rb new file mode 100644 index 000000000..f5dfb6b94 --- /dev/null +++ b/spec/factories/exports/workgroup_exports.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :workgroup_export, class: Export::Workgroup, parent: :export do + duration 90 + end +end diff --git a/spec/factories/import_messages.rb b/spec/factories/imports/import_messages.rb index 5d936679a..f5edf1685 100644 --- a/spec/factories/import_messages.rb +++ b/spec/factories/imports/import_messages.rb @@ -1,5 +1,5 @@ FactoryGirl.define do - factory :import_message do + factory :import_message, class: Import::Message do association :import association :resource, factory: :import_resource criticity :info diff --git a/spec/factories/import_resources.rb b/spec/factories/imports/import_resources.rb index 76afcc486..aaf7e3111 100644 --- a/spec/factories/import_resources.rb +++ b/spec/factories/imports/import_resources.rb @@ -1,5 +1,5 @@ FactoryGirl.define do - factory :import_resource do + factory :import_resource, class: Import::Resource do association :import status :WARNING sequence(:name) { |n| "Import resource #{n}" } diff --git a/spec/factories/imports.rb b/spec/factories/imports/imports.rb index e07447b60..cb7764cc6 100644 --- a/spec/factories/imports.rb +++ b/spec/factories/imports/imports.rb @@ -1,5 +1,5 @@ FactoryGirl.define do - factory :import do + factory :import, class: Import::Base do sequence(:name) { |n| "Import #{n}" } current_step_id "MyString" current_step_progress 1.5 @@ -16,7 +16,7 @@ FactoryGirl.define do end end - factory :bad_import do + factory :bad_import, class: Import::Base do sequence(:name) { |n| "Import #{n}" } current_step_id "MyString" current_step_progress 1.5 diff --git a/spec/factories/netex_imports.rb b/spec/factories/imports/netex_imports.rb index b59267a0a..7ee6839e8 100644 --- a/spec/factories/netex_imports.rb +++ b/spec/factories/imports/netex_imports.rb @@ -1,7 +1,7 @@ FactoryGirl.define do - factory :netex_import, class: NetexImport, parent: :import do + factory :netex_import, class: Import::Netex, parent: :import do file { File.open(Rails.root.join('spec', 'fixtures', 'OFFRE_TRANSDEV_2017030112251.zip')) } association :parent, factory: :workbench_import - + end end diff --git a/spec/factories/workbench_imports.rb b/spec/factories/imports/workbench_imports.rb index 466bfe688..5ed1ee4e5 100644 --- a/spec/factories/workbench_imports.rb +++ b/spec/factories/imports/workbench_imports.rb @@ -1,5 +1,5 @@ FactoryGirl.define do - factory :workbench_import, class: WorkbenchImport, parent: :import do + factory :workbench_import, class: Import::Workbench, parent: :import do file { File.open(Rails.root.join('spec', 'fixtures', 'OFFRE_TRANSDEV_2017030112251.zip')) } end end diff --git a/spec/features/compliance_control_sets_spec.rb b/spec/features/compliance_control_sets_spec.rb index 36dc5c2a9..0f4597db3 100644 --- a/spec/features/compliance_control_sets_spec.rb +++ b/spec/features/compliance_control_sets_spec.rb @@ -8,6 +8,9 @@ RSpec.describe "ComplianceControlSets", type: :feature do let( :control_set ){ create :compliance_control_set, organisation: organisation } let( :controls ){ control_set.compliance_controls } + let(:other_orga) { create :organisation } + let(:other_control_cset) { create :compliance_control_set, organisation: other_orga } + let(:blox){ 2.times.map{ | _ | create :compliance_control_block, compliance_control_set: control_set } } @@ -95,6 +98,18 @@ RSpec.describe "ComplianceControlSets", type: :feature do end + describe 'index' do + + before do + visit compliance_control_sets_path + end + + it "only showw compliance control sets from user organisation" do + expect(page).not_to have_content (other_orga.name) + expect(page).to have_content (organisation.name) + end + end + def make_control ccblock=nil, times: 1, severity: :warning times.times do make_one_control ccblock, severity diff --git a/spec/helpers/table_builder_helper_spec.rb b/spec/helpers/table_builder_helper_spec.rb index 478875118..b0c17859f 100644 --- a/spec/helpers/table_builder_helper_spec.rb +++ b/spec/helpers/table_builder_helper_spec.rb @@ -441,6 +441,53 @@ describe TableBuilderHelper, type: :helper do allow(helper).to receive(:mutual_workbench).and_return(referential.workbench) } + context "with a condition" do + let(:columns){ + [ + TableBuilderHelper::Column.new( + key: :name, + attribute: 'name', + if: condition + ), + ] + } + + context "when the condition is true" do + let(:condition){ ->(obj){true} } + it "should show the value" do + items.each do |i| + tr = helper.send(:tr, i, columns, selectable, links, overhead, model_name, :index) + klass = "#{TableBuilderHelper.item_row_class_name([referential])}-#{i.id}" + expect(tr).to include(i.name) + end + end + end + + context "when the condition depends on the object" do + let(:condition){ ->(obj){ obj == referential } } + it "should show the value accordingly" do + tr = helper.send(:tr, item, columns, selectable, links, overhead, model_name, :index) + klass = "#{TableBuilderHelper.item_row_class_name([referential])}-#{referential.id}" + expect(tr).to include(referential.name) + tr = helper.send(:tr, other_item, columns, selectable, links, overhead, model_name, :index) + klass = "#{TableBuilderHelper.item_row_class_name([referential])}-#{other_referential.id}" + expect(tr).to_not include(other_referential.name) + end + end + + context "when the condition is false" do + let(:condition){ ->(obj){false} } + it "should not show the value" do + items.each do |i| + tr = helper.send(:tr, i, columns, selectable, links, overhead, model_name, :index) + klass = "#{TableBuilderHelper.item_row_class_name([referential])}-#{i.id}" + expect(tr).to_not include(i.name) + end + end + end + + end + context "with all rows non-selectable" do let(:selectable){ false } it "sets all rows as non selectable" do diff --git a/spec/javascript/vehicle_journeys/reducers/vehicleJourneys_spec.js b/spec/javascript/vehicle_journeys/reducers/vehicleJourneys_spec.js index 608115727..bfa0942c6 100644 --- a/spec/javascript/vehicle_journeys/reducers/vehicleJourneys_spec.js +++ b/spec/javascript/vehicle_journeys/reducers/vehicleJourneys_spec.js @@ -219,7 +219,7 @@ describe('vehicleJourneys reducer', () => { type: 'ADD_VEHICLEJOURNEY', data: fakeData, selectedJourneyPattern: fakeSelectedJourneyPattern, - stopPointsList: [{object_id: 'test-1', city_name: 'city', stop_area_id: 1, id: 1, time_zone_offset: 0, waiting_time: null}, {object_id: 'test-2', city_name: 'city', stop_area_id: 2, id: 2, time_zone_offset: -3600, waiting_time: 10}, {object_id: 'test-3', city_name: 'city', stop_area_id: 3, id: 3, time_zone_offset: 0, waiting_time: 20}, {object_id: 'test-4', city_name: 'city', stop_area_id: 4, id: 4, time_zone_offset: 0}], + stopPointsList: [{object_id: 'test-1', city_name: 'city', stop_area_id: 1, id: 1, time_zone_offset: 0, waiting_time: 10}, {object_id: 'test-2', city_name: 'city', stop_area_id: 2, id: 2, time_zone_offset: -3600, waiting_time: 10}, {object_id: 'test-3', city_name: 'city', stop_area_id: 3, id: 3, time_zone_offset: 0, waiting_time: 20}, {object_id: 'test-4', city_name: 'city', stop_area_id: 4, id: 4, time_zone_offset: 0, waiting_time: 100}], selectedCompany: fakeSelectedCompany }) ).toEqual([{ diff --git a/spec/models/chouette/journey_pattern_spec.rb b/spec/models/chouette/journey_pattern_spec.rb index 7c767e4d1..dac45d6b5 100644 --- a/spec/models/chouette/journey_pattern_spec.rb +++ b/spec/models/chouette/journey_pattern_spec.rb @@ -71,6 +71,23 @@ describe Chouette::JourneyPattern, :type => :model do end end + describe "distance_to" do + let(:journey_pattern) { create :journey_pattern } + before do + journey_pattern.costs = generate_journey_pattern_costs(10, 10) + end + subject{ journey_pattern.distance_to stop} + context "for the first stop" do + let(:stop){ journey_pattern.stop_points.first } + it { should eq 0 } + end + + context "for the last stop" do + let(:stop){ journey_pattern.stop_points.last } + it { should eq 40 } + end + end + describe "set_distances" do let(:journey_pattern) { create :journey_pattern } let(:distances){ [] } diff --git a/spec/models/chouette/stop_area_spec.rb b/spec/models/chouette/stop_area_spec.rb index 32ee5a3a6..e35300caf 100644 --- a/spec/models/chouette/stop_area_spec.rb +++ b/spec/models/chouette/stop_area_spec.rb @@ -24,6 +24,82 @@ describe Chouette::StopArea, :type => :model do end end + describe "#registration_number" do + let(:registration_number){ nil } + let(:registration_number_format){ nil } + let(:stop_area_referential){ create :stop_area_referential, registration_number_format: registration_number_format} + let(:stop_area){ build :stop_area, stop_area_referential: stop_area_referential, registration_number: registration_number} + context "without registration_number_format on the StopAreaReferential" do + it "should not generate a registration_number" do + stop_area.save! + expect(stop_area.registration_number).to_not be_present + end + + it "should not validate the registration_number format" do + stop_area.registration_number = "1234455" + expect(stop_area).to be_valid + end + + it "should not validate the registration_number uniqueness" do + stop_area.registration_number = "1234455" + create :stop_area, stop_area_referential: stop_area_referential, registration_number: stop_area.registration_number + expect(stop_area).to be_valid + end + end + + context "with a registration_number_format on the StopAreaReferential" do + let(:registration_number_format){ "XXX" } + + it "should generate a registration_number" do + stop_area.save! + expect(stop_area.registration_number).to be_present + expect(stop_area.registration_number).to match /[A-Z]{3}/ + end + + context "with a previous stop_area" do + it "should generate a registration_number" do + create :stop_area, stop_area_referential: stop_area_referential, registration_number: "AAA" + stop_area.save! + expect(stop_area.registration_number).to be_present + expect(stop_area.registration_number).to eq "AAB" + end + + it "should generate a registration_number" do + create :stop_area, stop_area_referential: stop_area_referential, registration_number: "ZZZ" + stop_area.save! + expect(stop_area.registration_number).to be_present + expect(stop_area.registration_number).to eq "AAA" + end + + it "should generate a registration_number" do + create :stop_area, stop_area_referential: stop_area_referential, registration_number: "AAA" + create :stop_area, stop_area_referential: stop_area_referential, registration_number: "ZZZ" + stop_area.save! + expect(stop_area.registration_number).to be_present + expect(stop_area.registration_number).to eq "AAB" + end + end + + it "should validate the registration_number format" do + stop_area.registration_number = "1234455" + expect(stop_area).to_not be_valid + stop_area.registration_number = "ABC" + expect(stop_area).to be_valid + expect{ stop_area.save! }.to_not raise_error + end + + it "should validate the registration_number uniqueness" do + stop_area.registration_number = "ABC" + create :stop_area, stop_area_referential: stop_area_referential, registration_number: stop_area.registration_number + expect(stop_area).to_not be_valid + + stop_area.registration_number = "ABD" + create :stop_area, registration_number: stop_area.registration_number + expect(stop_area).to be_valid + end + end + end + # describe ".latitude" do # it "should accept -90 value" do # subject = create :stop_area, :area_type => "BoardingPosition" diff --git a/spec/models/chouette/vehicle_journey_at_stop_spec.rb b/spec/models/chouette/vehicle_journey_at_stop_spec.rb index f79d19c88..ae9823243 100644 --- a/spec/models/chouette/vehicle_journey_at_stop_spec.rb +++ b/spec/models/chouette/vehicle_journey_at_stop_spec.rb @@ -27,13 +27,13 @@ RSpec.describe Chouette::VehicleJourneyAtStop, type: :model do it "disallows offsets greater than DAY_OFFSET_MAX" do expect(at_stop.day_offset_outside_range?( - Chouette::VehicleJourneyAtStop::DAY_OFFSET_MAX + 1 + Chouette::VehicleJourneyAtStop.day_offset_max + 1 )).to be true end it "allows offsets between 0 and DAY_OFFSET_MAX inclusive" do expect(at_stop.day_offset_outside_range?( - Chouette::VehicleJourneyAtStop::DAY_OFFSET_MAX + Chouette::VehicleJourneyAtStop.day_offset_max )).to be false end @@ -79,7 +79,7 @@ RSpec.describe Chouette::VehicleJourneyAtStop, type: :model do describe "#validate" do it "displays the proper error message when day offset exceeds the max" do - bad_offset = Chouette::VehicleJourneyAtStop::DAY_OFFSET_MAX + 1 + bad_offset = Chouette::VehicleJourneyAtStop.day_offset_max + 1 at_stop = build_stubbed( :vehicle_journey_at_stop, diff --git a/spec/models/chouette/vehicle_journey_at_stops_day_offset_spec.rb b/spec/models/chouette/vehicle_journey_at_stops_day_offset_spec.rb index 69a2d5cb9..91cbf9097 100644 --- a/spec/models/chouette/vehicle_journey_at_stops_day_offset_spec.rb +++ b/spec/models/chouette/vehicle_journey_at_stops_day_offset_spec.rb @@ -86,5 +86,76 @@ describe Chouette::VehicleJourneyAtStop do expect(at_stops[3].arrival_day_offset).to eq(2) expect(at_stops[3].departure_day_offset).to eq(2) end + + context "with stops in a different timezone" do + before do + allow_any_instance_of(Chouette::VehicleJourneyAtStop).to receive(:local_time).and_wrap_original {|m, t| m.call(t - 12.hours)} + end + + it "should apply the TZ" do + at_stops = [] + [ + ['22:30', '22:35'], + ['01:02', '01:14'], + ['12:02', '12:14'], + ].each do |arrival_time, departure_time| + at_stops << build_stubbed( + :vehicle_journey_at_stop, + arrival_time: arrival_time, + departure_time: departure_time + ) + end + offsetter = Chouette::VehicleJourneyAtStopsDayOffset.new(at_stops) + + offsetter.calculate! + + expect(at_stops[0].arrival_day_offset).to eq(0) + expect(at_stops[0].departure_day_offset).to eq(0) + + expect(at_stops[1].arrival_day_offset).to eq(0) + expect(at_stops[1].departure_day_offset).to eq(0) + + expect(at_stops[2].arrival_day_offset).to eq(1) + expect(at_stops[2].departure_day_offset).to eq(1) + end + end + + context "with stops in different timezones" do + + it "should apply the TZ" do + at_stops = [] + + stop_area = create(:stop_area, time_zone: "Atlantic Time (Canada)") + stop_point = create(:stop_point, stop_area: stop_area) + vehicle_journey_at_stop = build_stubbed( + :vehicle_journey_at_stop, + stop_point: stop_point, + arrival_time: '09:00', + departure_time: '09:05' + ) + + at_stops << vehicle_journey_at_stop + + stop_area = create(:stop_area, time_zone: "Paris") + stop_point = create(:stop_point, stop_area: stop_area) + vehicle_journey_at_stop = build_stubbed( + :vehicle_journey_at_stop, + stop_point: stop_point, + arrival_time: '05:00', + departure_time: '05:05' + ) + at_stops << vehicle_journey_at_stop + + offsetter = Chouette::VehicleJourneyAtStopsDayOffset.new(at_stops) + + offsetter.calculate! + + expect(at_stops[0].arrival_day_offset).to eq(0) + expect(at_stops[0].departure_day_offset).to eq(0) + + expect(at_stops[1].arrival_day_offset).to eq(1) + expect(at_stops[1].departure_day_offset).to eq(1) + end + end end end diff --git a/spec/models/chouette/vehicle_journey_spec.rb b/spec/models/chouette/vehicle_journey_spec.rb index 76e73d9cf..c69655bd4 100644 --- a/spec/models/chouette/vehicle_journey_spec.rb +++ b/spec/models/chouette/vehicle_journey_spec.rb @@ -260,7 +260,7 @@ describe Chouette::VehicleJourney, :type => :model do item['purchase_windows'] = [] item['footnotes'] = [] item['purchase_windows'] = [] - item['custom_fields'] = vj.custom_fields + item['custom_fields'] = vj.custom_fields.to_hash vj.vehicle_journey_at_stops.each do |vjas| item['vehicle_journey_at_stops'] << vehicle_journey_at_stop_to_state(vjas) @@ -282,7 +282,6 @@ describe Chouette::VehicleJourney, :type => :model do Chouette::VehicleJourney.state_update(route, collection) }.to change {Chouette::VehicleJourney.count}.by(1) - obj = Chouette::VehicleJourney.last expect(obj).to receive(:after_commit_objectid).and_call_original @@ -292,7 +291,7 @@ describe Chouette::VehicleJourney, :type => :model do expect(collection.last['objectid']).to eq obj.objectid expect(obj.published_journey_name).to eq 'dummy' - expect(obj.custom_fields["energy"]["value"]).to eq 99 + expect(obj.custom_fields["energy"].value).to eq 99 end it 'should expect local times' do diff --git a/spec/models/compliance_check_set_spec.rb b/spec/models/compliance_check_set_spec.rb index b981a68bb..61421287a 100644 --- a/spec/models/compliance_check_set_spec.rb +++ b/spec/models/compliance_check_set_spec.rb @@ -81,14 +81,9 @@ RSpec.describe ComplianceCheckSet, type: :model do check_set.update_status - expect(check_set.status).to eq('successful') + expect(check_set.status).to eq('warning') end - it "returns true when the status did not get updated" do - check_set = create(:compliance_check_set) - - expect(check_set.update_status).to be true - end end describe 'possibility to delete the associated compliance_control_set' do diff --git a/spec/models/custom_field_spec.rb b/spec/models/custom_field_spec.rb index 51128b0a2..b92bcfbdb 100644 --- a/spec/models/custom_field_spec.rb +++ b/spec/models/custom_field_spec.rb @@ -16,7 +16,6 @@ RSpec.describe CustomField, type: :model do end end - context "custom fields for a resource" do let!( :fields ){ [create(:custom_field), create(:custom_field, code: :energy)] } let!( :instance_fields ){ @@ -26,10 +25,49 @@ RSpec.describe CustomField, type: :model do } } it { expect(Chouette::VehicleJourney.custom_fields).to eq(fields) } - it { expect(vj.custom_fields).to eq(instance_fields) } + it { + instance_fields.each do |code, cf| + cf.each do |k, v| + expect(vj.custom_fields[code].send(k)).to eq(v) + end + end + } end context "custom field_values for a resource" do it { expect(vj.custom_field_value("energy")).to eq(99) } end + + context "with an 'integer' field_type" do + let!(:field){ [create(:custom_field, code: :energy, options: {field_type: 'integer'})] } + let!( :vj ){ create :vehicle_journey, custom_field_values: {energy: "99"} } + it "should cast the value" do + expect(vj.custom_fields[:energy].value).to eq 99 + end + + it "should validate the value" do + { + "99" => true, + "azerty" => false, + "91a" => false, + "a91" => false + }.each do |val, valid| + vj = build :vehicle_journey, custom_field_values: {energy: val} + if valid + expect(vj.validate).to be_truthy + else + expect(vj.validate).to be_falsy + expect(vj.errors.messages[:"custom_fields.energy"]).to be_present + end + end + end + end + + context "with a 'string' field_type" do + let!(:field){ [create(:custom_field, code: :energy, options: {field_type: 'string'})] } + let!( :vj ){ create :vehicle_journey, custom_field_values: {energy: 99} } + it "should cast the value" do + expect(vj.custom_fields[:energy].value).to eq '99' + end + end end diff --git a/spec/models/export/export_message_spec.rb b/spec/models/export/export_message_spec.rb new file mode 100644 index 000000000..61a3b6319 --- /dev/null +++ b/spec/models/export/export_message_spec.rb @@ -0,0 +1,7 @@ +require 'rails_helper' + +RSpec.describe Export::Message, :type => :model do + it { should validate_presence_of(:criticity) } + it { should belong_to(:export) } + it { should belong_to(:resource) } +end diff --git a/spec/models/export/export_resource_spec.rb b/spec/models/export/export_resource_spec.rb new file mode 100644 index 000000000..7537cd2a8 --- /dev/null +++ b/spec/models/export/export_resource_spec.rb @@ -0,0 +1,19 @@ +require 'rails_helper' + +RSpec.describe Export::Resource, :type => :model do + it { should belong_to(:export) } + + it { should enumerize(:status).in("OK", "ERROR", "WARNING", "IGNORED") } + + it { should validate_presence_of(:name) } + it { should validate_presence_of(:resource_type) } + it { should validate_presence_of(:reference) } + + describe 'states' do + let(:export_resource) { create(:export_resource) } + + it 'should initialize with new state' do + expect(export_resource.status).to eq("WARNING") + end + end +end diff --git a/spec/models/export/export_spec.rb b/spec/models/export/export_spec.rb new file mode 100644 index 000000000..edc0788f2 --- /dev/null +++ b/spec/models/export/export_spec.rb @@ -0,0 +1,239 @@ +RSpec.describe Export::Base, type: :model do + + it { should belong_to(:referential) } + it { should belong_to(:workbench) } + it { should belong_to(:parent) } + + it { should enumerize(:status).in("aborted", "canceled", "failed", "new", "pending", "running", "successful", "warning") } + + it { should validate_presence_of(:workbench) } + it { should validate_presence_of(:creator) } + + include ActionDispatch::TestProcess + it { should allow_value(fixture_file_upload('OFFRE_TRANSDEV_2017030112251.zip')).for(:file) } + it { should_not allow_value(fixture_file_upload('reflex_updated.xml')).for(:file).with_message(I18n.t('errors.messages.extension_whitelist_error', extension: '"xml"', allowed_types: "zip, csv, json")) } + + let(:workgroup_export) {netex_export.parent} + let(:workgroup_export_with_completed_steps) do + build_stubbed( + :workgroup_export, + total_steps: 2, + current_step: 2 + ) + end + + let(:netex_export) do + create( + :netex_export + ) + end + + describe ".abort_old" do + it "changes exports older than 4 hours to aborted" do + Timecop.freeze(Time.now) do + old_export = create( + :workgroup_export, + status: 'pending', + created_at: 4.hours.ago - 1.minute + ) + current_export = create(:workgroup_export, status: 'pending') + + Export::Base.abort_old + + expect(current_export.reload.status).to eq('pending') + expect(old_export.reload.status).to eq('aborted') + end + end + + it "doesn't work on exports with a `finished_status`" do + Timecop.freeze(Time.now) do + export = create( + :workgroup_export, + status: 'successful', + created_at: 4.hours.ago - 1.minute + ) + + Export::Base.abort_old + + expect(export.reload.status).to eq('successful') + end + end + + it "only works on the caller type" do + Timecop.freeze(Time.now) do + workgroup_export = create( + :workgroup_export, + status: 'pending', + created_at: 4.hours.ago - 1.minute + ) + netex_export = create( + :netex_export, + status: 'pending', + created_at: 4.hours.ago - 1.minute + ) + + Export::Netex.abort_old + + expect(workgroup_export.reload.status).to eq('pending') + expect(netex_export.reload.status).to eq('aborted') + end + end + end + + describe "#destroy" do + it "must destroy all child exports" do + netex_export = create(:netex_export) + + netex_export.parent.destroy + + expect(netex_export.parent).to be_destroyed + expect(Export::Netex.count).to eq(0) + end + + it "must destroy all associated Export::Messages" do + export = create(:netex_export) + create(:export_resource, export: export) + + export.destroy + + expect(Export::Resource.count).to eq(0) + end + + it "must destroy all associated Export::Resources" do + export = create(:netex_export) + create(:export_message, export: export) + + export.destroy + + expect(Export::Message.count).to eq(0) + end + end + + describe "#notify_parent" do + it "must call #child_change on its parent" do + allow(netex_export).to receive(:update) + + expect(workgroup_export).to receive(:child_change) + netex_export.status = :foo + netex_export.notify_parent + end + + it "must update the :notified_parent_at field of the child export" do + allow(workgroup_export).to receive(:child_change) + + Timecop.freeze(Time.now) do + netex_export.status = :bar + + netex_export.notify_parent + expect(netex_export.notified_parent_at.strftime('%Y-%m-%d %H:%M:%S.%3N')).to eq Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N') + expect(netex_export.reload.notified_parent_at.strftime('%Y-%m-%d %H:%M:%S.%3N')).to eq Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N') + end + end + end + + describe "#child_change" do + it "calls #update_status" do + allow(workgroup_export).to receive(:update) + + expect(workgroup_export).to receive(:update_status) + workgroup_export.child_change + end + end + + describe "#update_status" do + shared_examples( + "updates :status to failed when >=1 child has failing status" + ) do |failure_status| + it "updates :status to failed when >=1 child has failing status" do + workgroup_export = create(:workgroup_export) + create( + :netex_export, + parent: workgroup_export, + status: failure_status + ) + + workgroup_export.update_status + + expect(workgroup_export.status).to eq('failed') + end + end + + include_examples( + "updates :status to failed when >=1 child has failing status", + "failed" + ) + include_examples( + "updates :status to failed when >=1 child has failing status", + "aborted" + ) + include_examples( + "updates :status to failed when >=1 child has failing status", + "canceled" + ) + + it "updates :status to successful when all children are successful" do + workgroup_export = create(:workgroup_export) + exports = create_list( + :netex_export, + 2, + parent: workgroup_export, + status: 'successful' + ) + + workgroup_export.update_status + + expect(workgroup_export.status).to eq('successful') + end + + it "updates :status to failed when any child has failed" do + workgroup_export = create(:workgroup_export) + [ + 'failed', + 'successful' + ].each do |status| + create( + :netex_export, + parent: workgroup_export, + status: status + ) + end + + workgroup_export.update_status + + expect(workgroup_export.status).to eq('failed') + end + + it "updates :status to warning when any child has warning or successful" do + workgroup_export = create(:workgroup_export) + [ + 'warning', + 'successful' + ].each do |status| + create( + :netex_export, + parent: workgroup_export, + status: status + ) + end + + workgroup_export.update_status + + expect(workgroup_export.status).to eq('warning') + end + + it "updates :ended_at to now when status is finished" do + workgroup_export = create(:workgroup_export) + create( + :netex_export, + parent: workgroup_export, + status: 'failed' + ) + + Timecop.freeze(Time.now) do + workgroup_export.update_status + + expect(workgroup_export.ended_at).to eq(Time.now) + end + end + end +end diff --git a/spec/models/export/netex_export_spec.rb b/spec/models/export/netex_export_spec.rb new file mode 100644 index 000000000..d9cccd6ad --- /dev/null +++ b/spec/models/export/netex_export_spec.rb @@ -0,0 +1,19 @@ +RSpec.describe Export::Netex, type: [:model, :with_commit] do + + let( :boiv_iev_uri ){ URI("#{Rails.configuration.iev_url}/boiv_iev/referentials/exporter/new?id=#{subject.id}")} + + before do + allow(Thread).to receive(:new).and_yield + end + + context 'with referential' do + subject{ build( :netex_export, id: random_int ) } + + it 'will trigger the Java API' do + with_stubbed_request(:get, boiv_iev_uri) do |request| + with_commit{ subject.save! } + expect(request).to have_been_requested + end + end + end +end diff --git a/spec/models/export/workgroup_export_spec.rb b/spec/models/export/workgroup_export_spec.rb new file mode 100644 index 000000000..c812b2b21 --- /dev/null +++ b/spec/models/export/workgroup_export_spec.rb @@ -0,0 +1,10 @@ +RSpec.describe Export::Workgroup, type: [:model, :with_commit] do + it { should validate_presence_of(:duration) } + + it "should set options" do + expect(Export::Workgroup.options).to have_key :duration + expect(Export::Workgroup.options[:duration][:required]).to be_truthy + expect(Export::Workgroup.options[:duration][:default_value]).to eq 90 + expect(Export::Workgroup.options[:duration][:type]).to eq :integer + end +end diff --git a/spec/models/export_log_message_spec.rb b/spec/models/export_log_message_spec.rb deleted file mode 100644 index 5ab32dec0..000000000 --- a/spec/models/export_log_message_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'spec_helper' - -describe ExportLogMessage, :type => :model do - - # describe "#attributes" do - - # subject { create :export_log_message } - - # it "should read json stored in database" do - # subject.update_attribute :arguments, { "key" => "value"} - # expect(subject.raw_attributes).to eq({ "key" => "value"}.to_json) - # end - - # end - -end diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb deleted file mode 100644 index 13953078a..000000000 --- a/spec/models/export_spec.rb +++ /dev/null @@ -1,66 +0,0 @@ -# require 'spec_helper' - -# describe Export, :type => :model do - -# subject { create :export } - -# RSpec::Matchers.define :be_log_message do |expected| -# match do |actual| -# actual and expected.all? { |k,v| actual[k.to_s] == v } -# end -# end - -# describe "#export" do - -# before(:each) do -# allow(subject).to receive_messages :exporter => double(:export => true) -# end - -# it "should create a ExportLogmessage :started when started" do -# subject.export -# expect(subject.log_messages.first).to be_log_message(:key => "started") -# end - -# it "should create a ExportLogmessage :completed when completed" do -# subject.export -# expect(subject.log_messages.last).to be_log_message(:key => "completed") -# end - -# it "should create a ExportLogmessage :failed when failed" do -# pending -# # subject.loader.stub(:export).and_raise("export failed") -# subject.export -# expect(subject.log_messages.last).to be_log_message(:key => "failed") -# end - -# end - -# describe "#options" do - -# it "should be empty by default" do -# expect(subject.options).to be_empty -# end - -# end - -# describe ".types" do - -# it "should return available Export implementations" do -# expect(Export.types).to match_array(%w{NeptuneExport CsvExport GtfsExport NetexExport KmlExport HubExport}) -# end - -# end - -# describe ".new" do - -# it "should use type attribute to create a subclass" do -# expect(Export.new(:type => "NeptuneExport")).to be_an_instance_of(NeptuneExport) -# end - -# end - -# it_behaves_like TypeIdsModelable do -# let(:type_ids_model) { subject} -# end - -# end diff --git a/spec/models/export_task_spec.rb b/spec/models/export_task_spec.rb deleted file mode 100644 index 1a52a6175..000000000 --- a/spec/models/export_task_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'spec_helper' - -describe ExportTask, :type => :model do - - it { should_not validate_presence_of(:start_date) } - it { should_not validate_presence_of(:end_date) } - -end diff --git a/spec/models/gtfs_export_spec.rb b/spec/models/gtfs_export_spec.rb index ccc98e872..0ef3660f5 100644 --- a/spec/models/gtfs_export_spec.rb +++ b/spec/models/gtfs_export_spec.rb @@ -1,33 +1,33 @@ require 'spec_helper' -describe GtfsExport, :type => :model do - - describe "#time_zone" do - - context "when exported data are not StopAreas" do - - before do - subject.references_type = "network" - end - - it "should be mandatory" do - should validate_presence_of(:time_zone) - end - - end - - context "when export data are StopArea" do - - before do - subject.references_type = "stop_area" - end - - it "should be mandatory" do - should_not validate_presence_of(:time_zone) - end - - end - - end - -end +# describe GtfsExport, :type => :model do +# +# describe "#time_zone" do +# +# context "when exported data are not StopAreas" do +# +# before do +# subject.references_type = "network" +# end +# +# it "should be mandatory" do +# should validate_presence_of(:time_zone) +# end +# +# end +# +# context "when export data are StopArea" do +# +# before do +# subject.references_type = "stop_area" +# end +# +# it "should be mandatory" do +# should_not validate_presence_of(:time_zone) +# end +# +# end +# +# end +# +# end diff --git a/spec/models/gtfs_import_spec.rb b/spec/models/gtfs_import_spec.rb index 07cc1905d..5cb69332c 100644 --- a/spec/models/gtfs_import_spec.rb +++ b/spec/models/gtfs_import_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe GtfsImport, :type => :model do +describe Import::Gtfs, :type => :model do # describe "#object_id_prefix" do diff --git a/spec/models/import_message_spec.rb b/spec/models/import/import_message_spec.rb index 2d8aac2b7..48e03a2cc 100644 --- a/spec/models/import_message_spec.rb +++ b/spec/models/import/import_message_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe ImportMessage, :type => :model do +RSpec.describe Import::Message, :type => :model do it { should validate_presence_of(:criticity) } it { should belong_to(:import) } it { should belong_to(:resource) } diff --git a/spec/models/import_resource_spec.rb b/spec/models/import/import_resource_spec.rb index c88bb5dd2..7d2eab8f1 100644 --- a/spec/models/import_resource_spec.rb +++ b/spec/models/import/import_resource_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe ImportResource, :type => :model do +RSpec.describe Import::Resource, :type => :model do it { should belong_to(:import) } it { should enumerize(:status).in("OK", "ERROR", "WARNING", "IGNORED") } diff --git a/spec/models/import_spec.rb b/spec/models/import/import_spec.rb index 8b85f151b..102c0e1d6 100644 --- a/spec/models/import_spec.rb +++ b/spec/models/import/import_spec.rb @@ -1,4 +1,4 @@ -RSpec.describe Import, type: :model do +RSpec.describe Import::Base, type: :model do it { should belong_to(:referential) } it { should belong_to(:workbench) } @@ -24,7 +24,7 @@ RSpec.describe Import, type: :model do end let(:netex_import) do - build_stubbed( + create( :netex_import ) end @@ -39,7 +39,7 @@ RSpec.describe Import, type: :model do ) current_import = create(:workbench_import, status: 'pending') - Import.abort_old + Import::Base.abort_old expect(current_import.reload.status).to eq('pending') expect(old_import.reload.status).to eq('aborted') @@ -54,7 +54,7 @@ RSpec.describe Import, type: :model do created_at: 4.hours.ago - 1.minute ) - Import.abort_old + Import::Base.abort_old expect(import.reload.status).to eq('successful') end @@ -73,7 +73,7 @@ RSpec.describe Import, type: :model do created_at: 4.hours.ago - 1.minute ) - NetexImport.abort_old + Import::Netex.abort_old expect(workbench_import.reload.status).to eq('pending') expect(netex_import.reload.status).to eq('aborted') @@ -88,25 +88,25 @@ RSpec.describe Import, type: :model do netex_import.parent.destroy expect(netex_import.parent).to be_destroyed - expect(NetexImport.count).to eq(0) + expect(Import::Netex.count).to eq(0) end - it "must destroy all associated ImportMessages" do + it "must destroy all associated Import::Messages" do import = create(:import) create(:import_resource, import: import) import.destroy - expect(ImportResource.count).to eq(0) + expect(Import::Resource.count).to eq(0) end - it "must destroy all associated ImportResources" do + it "must destroy all associated Import::Resources" do import = create(:import) create(:import_message, import: import) import.destroy - expect(ImportMessage.count).to eq(0) + expect(Import::Message.count).to eq(0) end end @@ -115,19 +115,18 @@ RSpec.describe Import, type: :model do allow(netex_import).to receive(:update) expect(workbench_import).to receive(:child_change) - + netex_import.status = :foo netex_import.notify_parent end it "must update the :notified_parent_at field of the child import" do allow(workbench_import).to receive(:child_change) - - Timecop.freeze(DateTime.now) do - expect(netex_import).to receive(:update).with( - notified_parent_at: DateTime.now - ) + Timecop.freeze(Time.now) do + netex_import.status = :bar netex_import.notify_parent + expect(netex_import.notified_parent_at.strftime('%Y-%m-%d %H:%M:%S.%3N')).to eq Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N') + expect(netex_import.reload.notified_parent_at.strftime('%Y-%m-%d %H:%M:%S.%3N')).to eq Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N') end end end diff --git a/spec/models/import/netex_import_spec.rb b/spec/models/import/netex_import_spec.rb index 8ffeed1f4..6424fbfe1 100644 --- a/spec/models/import/netex_import_spec.rb +++ b/spec/models/import/netex_import_spec.rb @@ -1,8 +1,7 @@ -RSpec.describe NetexImport, type: [:model, :with_commit] do +RSpec.describe Import::Netex, type: [:model, :with_commit] do let( :boiv_iev_uri ){ URI("#{Rails.configuration.iev_url}/boiv_iev/referentials/importer/new?id=#{subject.id}")} - before do allow(Thread).to receive(:new).and_yield end @@ -30,4 +29,42 @@ RSpec.describe NetexImport, type: [:model, :with_commit] do end end + describe "#destroy" do + it "must destroy its associated Referential if ready: false" do + workbench_import = create(:workbench_import) + referential_ready_false = create(:referential, ready: false) + referential_ready_true = create(:referential, ready: true) + create( + :netex_import, + parent: workbench_import, + referential: referential_ready_false + ) + create( + :netex_import, + parent: workbench_import, + referential: referential_ready_true + ) + + workbench_import.destroy + + expect( + Referential.where(id: referential_ready_false.id).exists? + ).to be false + expect( + Referential.where(id: referential_ready_true.id).exists? + ).to be true + end + + it "doesn't try to destroy nil referentials" do + workbench_import = create(:workbench_import) + create( + :netex_import, + parent: workbench_import, + referential: nil + ) + + expect { workbench_import.destroy }.not_to raise_error + end + end + end diff --git a/spec/models/import_service_spec.rb b/spec/models/import_service_spec.rb deleted file mode 100644 index e7ee062d6..000000000 --- a/spec/models/import_service_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'spec_helper' - -describe ImportService, :type => :model do - - let(:referential) { create(:referential, :slug => "test") } - - subject { ImportService.new(referential) } - - describe '.find' do - - it "should build an import with a scheduled job" do - end - - it "should build an import with a terminated job" do - end - - end - -end diff --git a/spec/models/import_task_spec.rb b/spec/models/import_task_spec.rb deleted file mode 100644 index 3aa006a69..000000000 --- a/spec/models/import_task_spec.rb +++ /dev/null @@ -1,196 +0,0 @@ -# require 'spec_helper' - -# describe ImportTask, :type => :model do - -# subject { build :import_task } - -# describe ".new" do - -# it "should use type attribute to create a subclass" do -# expect(ImportTask.new(:format => "Neptune")).to be_an_instance_of(NeptuneImport) -# expect(ImportTask.new(:format => "Gtfs")).to be_an_instance_of(GtfsImport) -# expect(ImportTask.new(:format => "Netex")).to be_an_instance_of(NetexImport) -# expect(ImportTask.new(:format => "Csv")).to be_an_instance_of(CsvImport) - -# expect(NeptuneImport.new).to be_an_instance_of(NeptuneImport) -# expect(GtfsImport.new).to be_an_instance_of(GtfsImport) -# expect(NetexImport.new).to be_an_instance_of(NetexImport) -# expect(CsvImport.new).to be_an_instance_of(CsvImport) -# end - -# end - -# describe "#delayed_import" do -# before(:each) do -# allow(subject).to receive_messages( :delay => double( :import => true)) -# end -# it "should call delay#import" do -# expect(subject.delay).to receive( :import) -# subject.send :delayed_import -# end -# end - -# describe ".create" do -# before(:each) do -# allow(subject).to receive_messages( :save_resources => true ) -# end -# it "should call save_resource" do -# expect(subject).to receive( :save_resources) -# subject.send :save -# end -# it "should update file_path with #saved_resources" do -# subject.send :save -# expect(ImportTask.find( subject.id).file_path).to eq(subject.send( :saved_resources)) -# end -# it "should have a compliance_check_task" do -# subject.send :save -# expect(ImportTask.find( subject.id).compliance_check_task).not_to be_nil -# end -# end - -# describe "#compliance_check_task" do -# let(:rule_parameter_set){ Factory( :rule_parameter_set) } -# let(:import_task){ Factory(:import_task, :rule_parameter_set_id => rule_parameter_set.id) } -# let(:compliance_check_task){ import_task.compliance_check_task } - -# it "should have same #referential as import_task" do -# expect(compliance_check_task.referential).to eq(import_task.referential) -# end - -# it "should have same #rule_parameter_set_id as import_task" do -# expect(compliance_check_task.rule_parameter_set_id).to eq(import_task.rule_parameter_set_id) -# end - -# it "should have same #user_id as import_task" do -# expect(compliance_check_task.user_id).to eq(import_task.user_id) -# end - -# it "should have same #user_name as import_task" do -# expect(compliance_check_task.user_name).to eq(import_task.user_name) -# end -# end - -# describe "#file_path_extension" do -# let(:import_task){ Factory(:import_task) } -# context "zip file to import" do -# before(:each) do -# import_task.file_path = "aaa/bbb.zip" -# end -# it "should return zip" do -# expect(import_task.file_path_extension).to eq("zip") -# end -# end -# context "xml file to import" do -# before(:each) do -# import_task.file_path = "aaa/bbb.xml" -# end -# it "should return xml" do -# expect(import_task.file_path_extension).to eq("xml") -# end -# end -# context "csv file to import" do -# before(:each) do -# import_task.file_path = "aaa/bbb.csv" -# end -# it "should return csv" do -# expect(import_task.file_path_extension).to eq("basic") -# end -# end - -# end - -# context "options attributes" do -# let(:import_task){ Factory(:import_task) } -# describe "#no_save" do -# it "should read parameter_set['no_save']" do -# import_task.parameter_set[ "no_save"] = "dummy" -# expect(import_task.no_save).to eq("dummy") -# end -# end -# describe "#format" do -# it "should read parameter_set['format']" do -# import_task.parameter_set[ "format"] = "dummy" -# expect(import_task.format).to eq("dummy") -# end -# end -# describe "#file_path" do -# it "should read parameter_set['file_path']" do -# import_task.parameter_set[ "file_path"] = "dummy" -# expect(import_task.file_path).to eq("dummy") -# end -# end -# describe "#no_save=" do -# it "should read parameter_set['no_save']" do -# import_task.no_save = "dummy" -# expect(import_task.parameter_set[ "no_save"]).to eq(false) -# end -# end -# describe "#format=" do -# it "should read parameter_set['format']" do -# import_task.format = "dummy" -# expect(import_task.parameter_set[ "format"]).to eq("dummy") -# end -# end -# describe "#file_path=" do -# it "should read parameter_set['file_path']" do -# import_task.file_path = "dummy" -# expect(import_task.parameter_set[ "file_path"]).to eq("dummy") -# end -# end -# end - -# describe "#chouette_command" do -# it "should be a Chouette::Command instance" do -# expect(subject.send( :chouette_command).class).to eq(Chouette::Command) -# end -# it "should have schema same as referential.slug" do -# expect(subject.send( :chouette_command).schema).to eq(subject.referential.slug) -# end -# end - -# describe "#import" do -# let(:import_task){ Factory(:import_task) } -# let(:chouette_command) { "dummy" } -# context "for failing import" do -# before(:each) do -# allow(chouette_command).to receive( :run!).and_raise( "dummy") -# allow(import_task).to receive_messages( :chouette_command => chouette_command) -# end -# it "should have status 'failed'" do -# import_task.import -# expect(import_task.status).to eq("failed") -# end -# it "should have status 'failed' for compliance_check_task" do -# import_task.import -# expect(import_task.compliance_check_task.status).to eq("failed") -# end -# end -# context "for successful import" do -# before(:each) do -# allow(import_task).to receive_messages( :chouette_command => double( :run! => true )) -# end -# it "should have status 'completed'" do -# import_task.import -# expect(import_task.status).to eq("completed") -# end -# it "should have status 'completed' for compliance_check_task" do -# import_task.import -# expect(import_task.status).to eq("completed") -# end -# end -# end - -# describe "#import" do -# let(:import_task){ Factory(:import_task) } -# let(:command_args){ "dummy" } -# before(:each) do -# allow(import_task).to receive_messages( :chouette_command => double( :run! => true )) -# allow(import_task).to receive_messages( :chouette_command_args => command_args) -# end -# it "should call chouette_command.run! with :c => 'import', :id => id" do -# expect(import_task.send( :chouette_command)).to receive( :run! ).with( command_args) -# import_task.import -# end -# end - -# end diff --git a/spec/models/netex_export_spec.rb b/spec/models/netex_export_spec.rb index 1d09fa07f..345bf4d5a 100644 --- a/spec/models/netex_export_spec.rb +++ b/spec/models/netex_export_spec.rb @@ -1,10 +1,10 @@ require 'spec_helper' -describe NetexExport, :type => :model do - - # describe '#export_options' do - # subject { super().export_options } - # it { is_expected.to include(:format => :netex) } - # end - -end +# describe NetexExport, :type => :model do +# +# # describe '#export_options' do +# # subject { super().export_options } +# # it { is_expected.to include(:format => :netex) } +# # end +# +# end diff --git a/spec/models/netex_import_spec.rb b/spec/models/netex_import_spec.rb deleted file mode 100644 index c6051a869..000000000 --- a/spec/models/netex_import_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -RSpec.describe NetexImport, type: :model do - describe "#destroy" do - it "must destroy its associated Referential if ready: false" do - workbench_import = create(:workbench_import) - referential_ready_false = create(:referential, ready: false) - referential_ready_true = create(:referential, ready: true) - create( - :netex_import, - parent: workbench_import, - referential: referential_ready_false - ) - create( - :netex_import, - parent: workbench_import, - referential: referential_ready_true - ) - - workbench_import.destroy - - expect( - Referential.where(id: referential_ready_false.id).exists? - ).to be false - expect( - Referential.where(id: referential_ready_true.id).exists? - ).to be true - end - - it "doesn't try to destroy nil referentials" do - workbench_import = create(:workbench_import) - create( - :netex_import, - parent: workbench_import, - referential: nil - ) - - expect { workbench_import.destroy }.not_to raise_error - end - end -end diff --git a/spec/models/referential_spec.rb b/spec/models/referential_spec.rb index 025ad80f9..1d9b3d78a 100644 --- a/spec/models/referential_spec.rb +++ b/spec/models/referential_spec.rb @@ -58,16 +58,21 @@ describe Referential, :type => :model do Referential.new_from(ref, []) end - # let(:saved_clone) do - # clone.tap do |clone| - # clone.organisation = ref.organisation - # clone.metadatas.each do |metadata| - # metadata.line_ids = ref.lines.where(id: clone.line_ids, objectid: JSON.parse(ref.organisation.sso_attributes["functional_scope"]).collect(&:id) - # metadata.periodes = metadata.periodes.map { |period| Range.new(period.end+1, period.end+10) } - # end - # clone.save! - # end - # end + let!(:workbench){ create :workbench } + + let(:saved_clone) do + clone.tap do |clone| + clone.organisation = workbench.organisation + clone.workbench = workbench + clone.metadatas = [create(:referential_metadata, referential: clone)] + clone.save! + end + end + + it 'should create a Referential' do + ref + expect { saved_clone }.to change{Referential.count}.by(1) + end xit 'should create a ReferentialCloning' do expect { saved_clone }.to change{ReferentialCloning.count}.by(1) diff --git a/spec/models/simple_exporter_spec.rb b/spec/models/simple_exporter_spec.rb new file mode 100644 index 000000000..a42daafe1 --- /dev/null +++ b/spec/models/simple_exporter_spec.rb @@ -0,0 +1,118 @@ +RSpec.describe SimpleExporter do + describe "#define" do + context "with an incomplete configuration" do + it "should raise an error" do + SimpleExporter.define :foo + expect do + SimpleExporter.new(configuration_name: :test).export + end.to raise_error(RuntimeError) + end + end + context "with a complete configuration" do + before do + SimpleExporter.define :foo do |config| + config.collection = Chouette::StopArea.all + end + end + + it "should define an exporter" do + expect{SimpleExporter.find_configuration(:foo)}.to_not raise_error + expect{SimpleExporter.new(configuration_name: :foo, filepath: "").export}.to_not raise_error + expect{SimpleExporter.find_configuration(:bar)}.to raise_error(RuntimeError) + expect{SimpleExporter.new(configuration_name: :bar, filepath: "")}.to raise_error(RuntimeError) + expect{SimpleExporter.new(configuration_name: :bar, filepath: "").export}.to raise_error(RuntimeError) + expect{SimpleExporter.create(configuration_name: :foo, filepath: "")}.to change{SimpleExporter.count}.by 1 + end + end + + context "when defining the same col twice" do + it "should raise an error" do + expect do + SimpleExporter.define :foo do |config| + config.collection = Chouette::StopArea.all + config.add_column :name + config.add_column :name + end + end.to raise_error(RuntimeError) + end + end + end + + describe "#export" do + let(:exporter){ importer = SimpleExporter.new(configuration_name: :test, filepath: filepath) } + let(:filepath){ Rails.root + "tmp/" + filename } + let(:filename){ "stop_area.csv" } + # let(:stop_area_referential){ create(:stop_area_referential, objectid_format: :stif_netex) } + + context "with one row per item" do + before(:each) do + @stop_area = create :stop_area + SimpleExporter.define :test do |config| + config.collection = ->{ Chouette::StopArea.all } + config.separator = ";" + config.add_column :name + config.add_column :lat, attribute: :latitude + config.add_column :lng, attribute: [:latitude, :to_i, :next] + config.add_column :type, attribute: :area_type + config.add_column :street_name, value: "Lil Exporter" + config.on_relation :stop_area_referential do + config.add_column :stop_area_referential_id, attribute: :id + config.add_column :stop_area_referential_id_other, value: ->(item){ item.id } + end + config.add_column :forty_two, value: 42 + end + end + + it "should export the given file" do + expect{exporter.export verbose: false}.to_not raise_error + expect(exporter.status).to eq "success" + expect(File.exists?(filepath)).to be_truthy + csv = CSV.read(filepath, headers: true, col_sep: ";") + row = csv.by_row.values_at(0).last + expect(row["name"]).to eq @stop_area.name + expect(row["lat"]).to eq @stop_area.latitude.to_s + expect(row["lng"]).to eq (@stop_area.latitude.to_i + 1).to_s + expect(row["street_name"]).to eq "Lil Exporter" + expect(row["stop_area_referential_id"]).to eq @stop_area.stop_area_referential_id.to_s + expect(row["stop_area_referential_id_other"]).to eq @stop_area.stop_area_referential_id.to_s + expect(row["forty_two"]).to eq "42" + end + end + + context "with several rows for the same object" do + before(:each) do + @stop_area = create :stop_area + @stop_1 = create :stop_point, stop_area: @stop_area + @stop_2 = create :stop_point, stop_area: @stop_area + SimpleExporter.define :test do |config| + config.collection = ->{ Chouette::StopArea.all } + config.map_item_to_rows do |stop_area| + stop_area.stop_points.map do |sp| + { + id: sp.id, + stop_area: stop_area + } + end + end + config.add_column :id + config.on_relation :stop_area do + config.add_column :stop_area_name, attribute: :name + end + end + end + + it "should export the given file" do + expect{exporter.export verbose: false}.to_not raise_error + expect(exporter.status).to eq "success" + expect(File.exists?(filepath)).to be_truthy + csv = CSV.read(filepath, headers: true) + row = csv.by_row.values_at(0).last + expect(row["id"]).to eq @stop_1.id.to_s + expect(row["stop_area_name"]).to eq @stop_area.name + row = csv.by_row.values_at(1).last + expect(row["id"]).to eq @stop_2.id.to_s + expect(row["stop_area_name"]).to eq @stop_area.name + end + end + end +end diff --git a/spec/models/simple_importer_spec.rb b/spec/models/simple_importer_spec.rb index 60d7b7882..8f4d7cfdd 100644 --- a/spec/models/simple_importer_spec.rb +++ b/spec/models/simple_importer_spec.rb @@ -3,9 +3,10 @@ RSpec.describe SimpleImporter do context "with an incomplete configuration" do it "should raise an error" do + SimpleImporter.define :foo expect do - SimpleImporter.define :foo - end.to raise_error + SimpleImporter.new(configuration_name: :foo, filepath: "").import + end.to raise_error(RuntimeError) end end context "with a complete configuration" do @@ -18,8 +19,9 @@ RSpec.describe SimpleImporter do it "should define an importer" do expect{SimpleImporter.find_configuration(:foo)}.to_not raise_error expect{SimpleImporter.new(configuration_name: :foo, filepath: "")}.to_not raise_error - expect{SimpleImporter.find_configuration(:bar)}.to raise_error - expect{SimpleImporter.new(configuration_name: :bar, filepath: "")}.to raise_error + expect{SimpleImporter.new(configuration_name: :foo, filepath: "").import}.to_not raise_error + expect{SimpleImporter.find_configuration(:bar)}.to raise_error(RuntimeError) + expect{SimpleImporter.new(configuration_name: :bar, filepath: "")}.to raise_error(RuntimeError) expect{SimpleImporter.create(configuration_name: :foo, filepath: "")}.to change{SimpleImporter.count}.by 1 end end diff --git a/spec/models/simple_interfaces_group_spec.rb b/spec/models/simple_interfaces_group_spec.rb new file mode 100644 index 000000000..0b6d360de --- /dev/null +++ b/spec/models/simple_interfaces_group_spec.rb @@ -0,0 +1,31 @@ +RSpec.describe SimpleInterfacesGroup do + context "with successful interfaces" do + before do + create :stop_area + SimpleExporter.define :test_1 do |config| + config.collection = Chouette::StopArea.all + config.key = "name" + config.add_column :name + end + + SimpleExporter.define :test_2 do |config| + config.collection = Chouette::StopArea.all + config.key = "name" + config.add_column :lat, attribute: :latitude + end + end + + it "should run all interfaces" do + test_1 = SimpleExporter.new(configuration_name: :test_1, filepath: "tmp/test1.csv") + test_2 = SimpleExporter.new(configuration_name: :test_2, filepath: "tmp/test1.csv") + + expect(test_1).to receive(:export).and_call_original + expect(test_2).to receive(:export).and_call_original + + group = SimpleInterfacesGroup.new "group" + group.add_interface test_1, "Test 1", :export + group.add_interface test_2, "Test 2", :export + group.run + end + end +end diff --git a/spec/models/simple_json_exporter_spec.rb b/spec/models/simple_json_exporter_spec.rb new file mode 100644 index 000000000..4b48dc732 --- /dev/null +++ b/spec/models/simple_json_exporter_spec.rb @@ -0,0 +1,95 @@ +RSpec.describe SimpleJsonExporter do + describe "#define" do + context "with an incomplete configuration" do + it "should raise an error" do + SimpleJsonExporter.define :foo + expect do + SimpleJsonExporter.new(configuration_name: :test).export + end.to raise_error + end + end + context "with a complete configuration" do + before do + SimpleJsonExporter.define :foo do |config| + config.collection = Chouette::StopArea.all + end + end + + it "should define an exporter" do + expect{SimpleJsonExporter.find_configuration(:foo)}.to_not raise_error + expect{SimpleJsonExporter.new(configuration_name: :foo, filepath: "").export}.to_not raise_error + expect{SimpleJsonExporter.find_configuration(:bar)}.to raise_error + expect{SimpleJsonExporter.new(configuration_name: :bar, filepath: "")}.to raise_error + expect{SimpleJsonExporter.new(configuration_name: :bar, filepath: "").export}.to raise_error + expect{SimpleJsonExporter.create(configuration_name: :foo, filepath: "")}.to change{SimpleJsonExporter.count}.by 1 + end + end + + context "when defining the same col twice" do + it "should raise an error" do + expect do + SimpleJsonExporter.define :foo do |config| + config.collection = Chouette::StopArea.all + config.add_field :name + config.add_field :name + end + end.to raise_error + end + end + end + + describe "#export" do + let(:exporter){ importer = SimpleJsonExporter.new(configuration_name: :test, filepath: filepath) } + let(:filepath){ Rails.root + "tmp/" + filename } + let(:filename){ "stop_area.json" } + # let(:stop_area_referential){ create(:stop_area_referential, objectid_format: :stif_netex) } + + context "with one row per item" do + before(:each) do + @stop_area = create :stop_area + @stop_1 = create :stop_point, stop_area: @stop_area + @stop_2 = create :stop_point, stop_area: @stop_area + + SimpleJsonExporter.define :test do |config| + config.collection = ->{ Chouette::StopArea.all } + config.root = "stops" + config.add_field :name + config.add_field :lat, attribute: :latitude + config.add_field :lng, attribute: [:latitude, :to_i, :next] + config.add_field :type, attribute: :area_type + config.add_field :street_name, value: "Lil Exporter" + config.add_node :stop_area_referential do |config| + config.add_field :id, attribute: :id + config.add_field :id_other, value: ->(item){ item.id } + end + + config.add_nodes :stop_points do |config| + config.add_field :id + config.add_node :stop_area do |config| + config.add_field :id + end + end + config.add_field :forty_two, value: 42 + end + end + + it "should export the given file" do + expect{exporter.export verbose: false}.to_not raise_error + expect(exporter.status).to eq "success" + expect(File.exists?(filepath)).to be_truthy + json = JSON.parse File.read(filepath) + row = json["stops"].first + expect(row["name"]).to eq @stop_area.name + expect(row["lat"]).to eq @stop_area.latitude.to_s + expect(row["lng"]).to eq (@stop_area.latitude.to_i + 1) + expect(row["street_name"]).to eq "Lil Exporter" + expect(row["stop_area_referential"]["id"]).to eq @stop_area.stop_area_referential_id + expect(row["stop_area_referential"]["id_other"]).to eq @stop_area.stop_area_referential_id + expect(row["stop_points"][0]["id"]).to eq @stop_1.id + expect(row["stop_points"][0]["stop_area"]["id"]).to eq @stop_area.id + expect(row["stop_points"][1]["id"]).to eq @stop_2.id + expect(row["forty_two"]).to eq 42 + end + end + end +end diff --git a/spec/models/stop_area_referential_spec.rb b/spec/models/stop_area_referential_spec.rb index dd2bdce20..d68b5b809 100644 --- a/spec/models/stop_area_referential_spec.rb +++ b/spec/models/stop_area_referential_spec.rb @@ -8,4 +8,9 @@ RSpec.describe StopAreaReferential, :type => :model do it { is_expected.to have_many(:stop_area_referential_syncs) } it { is_expected.to have_many(:workbenches) } it { should validate_presence_of(:objectid_format) } + it { should allow_value('').for(:registration_number_format) } + it { should allow_value('X').for(:registration_number_format) } + it { should allow_value('XXXXX').for(:registration_number_format) } + it { should_not allow_value('123').for(:registration_number_format) } + it { should_not allow_value('ABC').for(:registration_number_format) } end diff --git a/spec/requests/api/v1/netex_import_spec.rb b/spec/requests/api/v1/netex_import_spec.rb index 8597c1d32..14dac9a25 100644 --- a/spec/requests/api/v1/netex_import_spec.rb +++ b/spec/requests/api/v1/netex_import_spec.rb @@ -1,4 +1,4 @@ -RSpec.describe "NetexImport", type: :request do +RSpec.describe "Import::Netex", type: :request do describe 'POST netex_imports' do @@ -39,7 +39,7 @@ RSpec.describe "NetexImport", type: :request do post_request.(netex_import: legal_attributes) expect( response ).to be_success expect( json_response_body ).to eq( - 'id' => NetexImport.last.id, + 'id' => Import::Netex.last.id, 'referential_id' => Referential.last.id, 'workbench_id' => workbench.id ) @@ -51,7 +51,7 @@ RSpec.describe "NetexImport", type: :request do create(:line, objectid: 'STIF:CODIFLIGNE:Line:C00108', line_referential: workbench.line_referential) create(:line, objectid: 'STIF:CODIFLIGNE:Line:C00109', line_referential: workbench.line_referential) - expect{ post_request.(netex_import: legal_attributes) }.to change{NetexImport.count}.by(1) + expect{ post_request.(netex_import: legal_attributes) }.to change{Import::Netex.count}.by(1) end it 'creates a correct Referential', pending: 'see #5073' do @@ -96,7 +96,7 @@ RSpec.describe "NetexImport", type: :request do end it 'does not create an Import object' do - expect{ post_request.(netex_import: illegal_attributes) }.not_to change{Import.count} + expect{ post_request.(netex_import: illegal_attributes) }.not_to change{Import::Base.count} end it 'might not create a referential' do diff --git a/spec/services/parent_notifier_spec.rb b/spec/services/parent_notifier_spec.rb index ecf508fcd..d2dc6b184 100644 --- a/spec/services/parent_notifier_spec.rb +++ b/spec/services/parent_notifier_spec.rb @@ -20,7 +20,7 @@ RSpec.describe ParentNotifier do expect(netex_import).to receive(:notify_parent) end - ParentNotifier.new(Import).notify_when_finished(netex_imports) + ParentNotifier.new(Import::Base).notify_when_finished(netex_imports) end it "doesn't call #notify_parent if its `notified_parent_at` is set" do @@ -33,7 +33,7 @@ RSpec.describe ParentNotifier do expect(netex_import).not_to receive(:notify_parent) - ParentNotifier.new(Import).notify_when_finished + ParentNotifier.new(Import::Base).notify_when_finished end end @@ -46,8 +46,10 @@ RSpec.describe ParentNotifier do notified_parent_at: nil ) + Import::Base.where(id: netex_import).update_all notified_parent_at: nil + expect( - ParentNotifier.new(Import).objects_pending_notification + ParentNotifier.new(Import::Base).objects_pending_notification ).to eq([netex_import]) end @@ -55,7 +57,7 @@ RSpec.describe ParentNotifier do create(:import, parent: nil) expect( - ParentNotifier.new(Import).objects_pending_notification + ParentNotifier.new(Import::Base).objects_pending_notification ).to be_empty end @@ -70,7 +72,7 @@ RSpec.describe ParentNotifier do end expect( - ParentNotifier.new(Import).objects_pending_notification + ParentNotifier.new(Import::Base).objects_pending_notification ).to be_empty end @@ -83,7 +85,7 @@ RSpec.describe ParentNotifier do ) expect( - ParentNotifier.new(Import).objects_pending_notification + ParentNotifier.new(Import::Base).objects_pending_notification ).to be_empty end end diff --git a/spec/support/permissions.rb b/spec/support/permissions.rb index 95afd6c1c..825e44725 100644 --- a/spec/support/permissions.rb +++ b/spec/support/permissions.rb @@ -17,6 +17,7 @@ module Support connection_links calendars footnotes + exports imports merges journey_patterns diff --git a/spec/support/pundit/pundit_view_policy.rb b/spec/support/pundit/pundit_view_policy.rb index 316ff6718..efe7f0f76 100644 --- a/spec/support/pundit/pundit_view_policy.rb +++ b/spec/support/pundit/pundit_view_policy.rb @@ -5,13 +5,13 @@ module Pundit into.let(:current_referential){ referential || build_stubbed(:referential, organisation: organisation) } into.let(:current_user){ create :user, permissions: permissions, organisation: organisation } into.let(:pundit_user){ UserContext.new(current_user, referential: current_referential) } - into.let(:current_offer_workbench) { create :workbench, organisation: organisation} + into.let(:current_workbench) { create :workbench, organisation: organisation} into.before do allow(view).to receive(:pundit_user) { pundit_user } allow(view).to receive(:current_user) { current_user } allow(view).to receive(:current_organisation).and_return(organisation) - allow(view).to receive(:current_offer_workbench).and_return(current_offer_workbench) - allow(view).to receive(:current_workgroup).and_return(current_offer_workbench.workgroup) + allow(view).to receive(:current_workbench).and_return(current_workbench) + allow(view).to receive(:current_workgroup).and_return(current_workbench.workgroup) allow(view).to receive(:has_feature?){ |f| respond_to?(:features) && features.include?(f)} allow(view).to receive(:user_signed_in?).and_return true allow(view).to receive(:policy) do |instance| diff --git a/spec/views/referentials/show.html.erb_spec.rb b/spec/views/referentials/show.html.erb_spec.rb index a7f37d180..82328cb8e 100644 --- a/spec/views/referentials/show.html.erb_spec.rb +++ b/spec/views/referentials/show.html.erb_spec.rb @@ -11,6 +11,7 @@ describe "referentials/show", type: :view do let(:organisation){ referential.try(:organisation) } let(:permissions){ [] } let(:current_organisation) { organisation } + let(:organisation) { referential.organisation } let(:readonly){ false } before :each do |
