aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Gemfile.lock2
-rw-r--r--INSTALL.md146
-rw-r--r--app/assets/javascripts/forms.coffee2
-rw-r--r--app/assets/javascripts/main_menu.coffee5
-rw-r--r--app/assets/stylesheets/OpenLayers/custom.sass58
-rw-r--r--app/assets/stylesheets/components/_modals.sass6
-rw-r--r--app/assets/stylesheets/components/_referential_overview.sass15
-rw-r--r--app/assets/stylesheets/modules/_vj_collection.sass29
-rw-r--r--app/controllers/api/v1/imports_controller.rb4
-rw-r--r--app/controllers/api/v1/internals/netex_imports_controller.rb8
-rw-r--r--app/controllers/api/v1/netex_imports_controller.rb2
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/controllers/autocomplete_lines_controller.rb17
-rw-r--r--app/controllers/calendars_controller.rb11
-rw-r--r--app/controllers/compliance_check_sets_controller.rb2
-rw-r--r--app/controllers/concerns/iev_interfaces.rb69
-rw-r--r--app/controllers/export_tasks_controller.rb87
-rw-r--r--app/controllers/exports_controller.rb94
-rw-r--r--app/controllers/import_messages_controller.rb10
-rw-r--r--app/controllers/import_resources_controller.rb4
-rw-r--r--app/controllers/import_tasks_controller.rb69
-rw-r--r--app/controllers/imports_controller.rb62
-rw-r--r--app/controllers/referentials_controller.rb8
-rw-r--r--app/controllers/statuses_controller.rb2
-rw-r--r--app/controllers/stop_areas_controller.rb25
-rw-r--r--app/controllers/workbenches_controller.rb11
-rw-r--r--app/decorators/export_decorator.rb21
-rw-r--r--app/decorators/import_decorator.rb2
-rw-r--r--app/decorators/stop_point_decorator.rb21
-rw-r--r--app/helpers/compliance_check_resources_helper.rb4
-rw-r--r--app/helpers/compliance_control_sets_helper.rb2
-rw-r--r--app/helpers/exports_helper.rb27
-rw-r--r--app/helpers/stop_areas_helper.rb36
-rw-r--r--app/helpers/table_builder_helper.rb2
-rw-r--r--app/helpers/table_builder_helper/column.rb20
-rw-r--r--app/javascript/helpers/routes_map.coffee130
-rw-r--r--app/javascript/packs/exports/new.js3
-rw-r--r--app/javascript/packs/referential_lines/show.js2
-rw-r--r--app/javascript/vehicle_journeys/actions/index.js12
-rw-r--r--app/javascript/vehicle_journeys/components/VehicleJourney.js37
-rw-r--r--app/javascript/vehicle_journeys/components/VehicleJourneys.js83
-rw-r--r--app/javascript/vehicle_journeys/components/tools/CreateModal.js8
-rw-r--r--app/javascript/vehicle_journeys/containers/VehicleJourneysList.js4
-rw-r--r--app/javascript/vehicle_journeys/reducers/vehicleJourneys.js9
-rw-r--r--app/models/chouette/access_link.rb2
-rw-r--r--app/models/chouette/access_point.rb2
-rw-r--r--app/models/chouette/connection_link.rb2
-rw-r--r--app/models/chouette/group_of_line.rb2
-rw-r--r--app/models/chouette/journey_pattern.rb15
-rw-r--r--app/models/chouette/line.rb12
-rw-r--r--app/models/chouette/network.rb2
-rw-r--r--app/models/chouette/pt_link.rb2
-rw-r--r--app/models/chouette/purchase_window.rb7
-rw-r--r--app/models/chouette/route.rb2
-rw-r--r--app/models/chouette/stop_area.rb56
-rw-r--r--app/models/chouette/stop_point.rb2
-rw-r--r--app/models/chouette/time_table.rb2
-rw-r--r--app/models/chouette/time_table_date.rb1
-rw-r--r--app/models/chouette/time_table_period.rb1
-rw-r--r--app/models/chouette/timeband.rb1
-rw-r--r--app/models/chouette/vehicle_journey.rb34
-rw-r--r--app/models/chouette/vehicle_journey_at_stop.rb33
-rw-r--r--app/models/chouette/vehicle_journey_at_stops_day_offset.rb33
-rw-r--r--app/models/compliance_check_message_export.rb4
-rw-r--r--app/models/compliance_check_resource.rb2
-rw-r--r--app/models/compliance_check_set.rb44
-rw-r--r--app/models/concerns/application_days_support.rb47
-rw-r--r--app/models/concerns/custom_fields_support.rb24
-rw-r--r--app/models/concerns/iev_interfaces/message.rb9
-rw-r--r--app/models/concerns/iev_interfaces/resource.rb9
-rw-r--r--app/models/concerns/iev_interfaces/task.rb119
-rw-r--r--app/models/custom_field.rb76
-rw-r--r--app/models/dashboard.rb1
-rw-r--r--app/models/export.rb53
-rw-r--r--app/models/export/base.rb89
-rw-r--r--app/models/export/message.rb8
-rw-r--r--app/models/export/netex.rb22
-rw-r--r--app/models/export/referential_companies.rb92
-rw-r--r--app/models/export/resource.rb8
-rw-r--r--app/models/export/workgroup.rb9
-rw-r--r--app/models/export_log_message.rb42
-rw-r--r--app/models/export_report.rb8
-rw-r--r--app/models/export_service.rb23
-rw-r--r--app/models/export_task.rb119
-rw-r--r--app/models/gtfs_export.rb47
-rw-r--r--app/models/gtfs_import.rb36
-rw-r--r--app/models/hub_export.rb24
-rw-r--r--app/models/import.rb103
-rw-r--r--app/models/import/base.rb45
-rw-r--r--app/models/import/gtfs.rb (renamed from app/models/netex_import.rb)4
-rw-r--r--app/models/import/message.rb8
-rw-r--r--app/models/import/message_export.rb (renamed from app/models/import_message_export.rb)8
-rw-r--r--app/models/import/netex.rb24
-rw-r--r--app/models/import/resource.rb8
-rw-r--r--app/models/import/workbench.rb (renamed from app/models/workbench_import.rb)2
-rw-r--r--app/models/import_message.rb8
-rw-r--r--app/models/import_report.rb8
-rw-r--r--app/models/import_resource.rb11
-rw-r--r--app/models/import_service.rb23
-rw-r--r--app/models/import_task.rb141
-rw-r--r--app/models/kml_export.rb24
-rw-r--r--app/models/line_referential.rb1
-rw-r--r--app/models/merge.rb1
-rw-r--r--app/models/neptune_export.rb27
-rw-r--r--app/models/neptune_import.rb19
-rw-r--r--app/models/netex_export.rb24
-rw-r--r--app/models/organisation.rb2
-rw-r--r--app/models/simple_exporter.rb182
-rw-r--r--app/models/simple_importer.rb305
-rw-r--r--app/models/simple_interface.rb372
-rw-r--r--app/models/simple_interfaces_group.rb76
-rw-r--r--app/models/simple_json_exporter.rb183
-rw-r--r--app/models/stop_area_referential.rb29
-rw-r--r--app/models/workbench.rb9
-rw-r--r--app/models/workgroup.rb2
-rw-r--r--app/policies/export_policy.rb15
-rw-r--r--app/policies/workbench_policy.rb11
-rw-r--r--app/services/referential_overview.rb6
-rw-r--r--app/uploaders/import_uploader.rb2
-rw-r--r--app/views/autocomplete_lines/index.rabl8
-rw-r--r--app/views/calendars/_form_advanced.html.slim2
-rw-r--r--app/views/compliance_check_sets/index.html.slim7
-rw-r--r--app/views/compliance_checks/show.html.slim2
-rw-r--r--app/views/compliance_control_sets/_filters.html.slim2
-rw-r--r--app/views/compliance_controls/show.html.slim2
-rw-r--r--app/views/dashboards/_dashboard.html.slim8
-rw-r--r--app/views/exports/_export.html.slim20
-rw-r--r--app/views/exports/_exports.html.slim9
-rw-r--r--app/views/exports/_form.html.slim16
-rw-r--r--app/views/exports/index.html.slim48
-rw-r--r--app/views/exports/index.js.slim1
-rw-r--r--app/views/exports/new.html.slim7
-rw-r--r--app/views/exports/show.html.slim65
-rw-r--r--app/views/imports/_import_messages.html.slim8
-rw-r--r--app/views/imports/index.html.slim10
-rw-r--r--app/views/imports/show.html.slim2
-rw-r--r--app/views/layouts/navigation/_main_nav_left_content_stif.html.slim2
-rw-r--r--app/views/layouts/navigation/_page_header.html.slim3
-rw-r--r--app/views/lines/show.html.slim10
-rw-r--r--app/views/referential_lines/show.html.slim2
-rw-r--r--app/views/referential_vehicle_journeys/_filters.html.slim11
-rw-r--r--app/views/referentials/_form.html.slim14
-rw-r--r--app/views/referentials/_overview.html.slim9
-rw-r--r--app/views/shared/iev_interfaces/_filters.html.slim (renamed from app/views/imports/_filters.html.slim)4
-rw-r--r--app/views/shared/iev_interfaces/_messages.html.slim14
-rw-r--r--app/views/stif/dashboards/_dashboard.html.slim2
-rw-r--r--app/views/stop_areas/_filters.html.slim26
-rw-r--r--app/views/stop_areas/_form.html.slim4
-rw-r--r--app/views/stop_areas/index.html.slim4
-rw-r--r--app/views/stop_areas/show.html.slim2
-rw-r--r--app/views/vehicle_journeys/show.rabl2
-rw-r--r--app/views/workbenches/_form.html.slim9
-rw-r--r--app/views/workbenches/edit.html.slim8
-rw-r--r--app/views/workbenches/show.html.slim2
-rw-r--r--app/workers/simple_export_worker.rb10
-rw-r--r--app/workers/workbench_import_worker.rb4
-rw-r--r--app/workers/workgroup_export_worker.rb38
-rw-r--r--config/breadcrumbs.rb12
-rw-r--r--config/database/ci.yml11
-rw-r--r--config/database/jenkins.yml8
-rw-r--r--config/environments/development.rb9
-rw-r--r--config/environments/production.rb8
-rw-r--r--config/initializers/apartment.rb80
-rw-r--r--config/initializers/exporters.rb6
-rw-r--r--config/initializers/sidekiq.rb4
-rw-r--r--config/initializers/stif.rb4
-rw-r--r--config/locales/compliance_controls.fr.yml2
-rw-r--r--config/locales/export_messages.en.yml3
-rw-r--r--config/locales/export_messages.fr.yml3
-rw-r--r--config/locales/exports.en.yml80
-rw-r--r--config/locales/exports.fr.yml84
-rw-r--r--config/locales/footnotes.fr.yml4
-rw-r--r--config/locales/import_messages.en.yml4
-rw-r--r--config/locales/import_resources.en.yml20
-rw-r--r--config/locales/import_resources.fr.yml22
-rw-r--r--config/locales/import_tasks.en.yml52
-rw-r--r--config/locales/import_tasks.fr.yml53
-rw-r--r--config/locales/imports.en.yml13
-rw-r--r--config/locales/imports.fr.yml20
-rw-r--r--config/locales/journey_patterns.fr.yml2
-rw-r--r--config/locales/routing_constraint_zones.fr.yml6
-rw-r--r--config/locales/stop_areas.en.yml18
-rw-r--r--config/locales/stop_areas.fr.yml19
-rw-r--r--config/locales/vehicle_journeys.fr.yml2
-rw-r--r--config/locales/workbenches.en.yml15
-rw-r--r--config/locales/workbenches.fr.yml15
-rw-r--r--config/middlewares/cachesettings.rb20
-rw-r--r--config/routes.rb12
-rw-r--r--db/migrate/20171106111448_update_import_message_criticity_type_to_string.rb4
-rw-r--r--db/migrate/20180208174834_add_attributes_to_workbench.rb9
-rw-r--r--db/migrate/20180226074739_add_registration_number_format_to_stop_area_referentials.rb5
-rw-r--r--db/migrate/20180301142531_create_simple_exporters.rb7
-rw-r--r--db/migrate/20180306135204_clean_former_exports.rb5
-rw-r--r--db/migrate/20180306152953_update_imports_names.rb13
-rw-r--r--db/migrate/20180307071448_create_new_exports.rb56
-rw-r--r--db/migrate/20180307202627_add_confirmed_at_to_stop_areas.rb5
-rw-r--r--db/migrate/20180308063549_update_stop_areas_confirmed_at_attribute.rb9
-rw-r--r--db/migrate/20180308095116_add_options_to_exports.rb5
-rw-r--r--db/schema.rb25
-rw-r--r--lib/model_attribute.rb18
-rw-r--r--lib/stif/dashboard.rb4
-rw-r--r--lib/stif/permission_translator.rb1
-rw-r--r--lib/tasks/ci.rake26
-rw-r--r--lib/tasks/exports.rake98
-rw-r--r--lib/tasks/helpers/simple_interfaces.rb11
-rw-r--r--lib/tasks/imports.rake92
-rw-r--r--script/launch-cron2
-rw-r--r--spec/controllers/api/v1/imports_controller_spec.rb2
-rw-r--r--spec/controllers/autocomplete_lines_controller_spec.rb67
-rw-r--r--spec/controllers/exports_controller_spec.rb99
-rw-r--r--spec/controllers/line_referentials_controller_spec.rb4
-rw-r--r--spec/controllers/lines_controller_spec.rb8
-rw-r--r--spec/controllers/referential_vehicle_journeys_controller_spec.rb12
-rw-r--r--spec/controllers/referentials_controller_spec.rb36
-rw-r--r--spec/controllers/stop_area_referentials_controller_spec.rb4
-rw-r--r--spec/controllers/stop_areas_controller_spec.rb8
-rw-r--r--spec/controllers/vehicle_journey_imports_controller_spec.rb5
-rw-r--r--spec/factories/custom_fields.rb2
-rw-r--r--spec/factories/exports.rb5
-rw-r--r--spec/factories/exports/export_messages.rb7
-rw-r--r--spec/factories/exports/export_resources.rb9
-rw-r--r--spec/factories/exports/exports.rb34
-rw-r--r--spec/factories/exports/netex_exports.rb7
-rw-r--r--spec/factories/exports/workgroup_exports.rb5
-rw-r--r--spec/factories/imports/import_messages.rb (renamed from spec/factories/import_messages.rb)2
-rw-r--r--spec/factories/imports/import_resources.rb (renamed from spec/factories/import_resources.rb)2
-rw-r--r--spec/factories/imports/imports.rb (renamed from spec/factories/imports.rb)4
-rw-r--r--spec/factories/imports/netex_imports.rb (renamed from spec/factories/netex_imports.rb)4
-rw-r--r--spec/factories/imports/workbench_imports.rb (renamed from spec/factories/workbench_imports.rb)2
-rw-r--r--spec/features/compliance_control_sets_spec.rb15
-rw-r--r--spec/features/stop_areas_spec.rb40
-rw-r--r--spec/helpers/table_builder_helper_spec.rb47
-rw-r--r--spec/javascript/vehicle_journeys/actions_spec.js13
-rw-r--r--spec/javascript/vehicle_journeys/reducers/vehicleJourneys_spec.js2
-rw-r--r--spec/lib/model_attribute_spec.rb6
-rw-r--r--spec/models/chouette/journey_pattern_spec.rb17
-rw-r--r--spec/models/chouette/stop_area_spec.rb76
-rw-r--r--spec/models/chouette/vehicle_journey_at_stop_spec.rb6
-rw-r--r--spec/models/chouette/vehicle_journey_at_stops_day_offset_spec.rb71
-rw-r--r--spec/models/chouette/vehicle_journey_spec.rb5
-rw-r--r--spec/models/compliance_check_set_spec.rb7
-rw-r--r--spec/models/custom_field_spec.rb42
-rw-r--r--spec/models/export/export_message_spec.rb7
-rw-r--r--spec/models/export/export_resource_spec.rb19
-rw-r--r--spec/models/export/export_spec.rb239
-rw-r--r--spec/models/export/netex_export_spec.rb19
-rw-r--r--spec/models/export/workgroup_export_spec.rb10
-rw-r--r--spec/models/export_log_message_spec.rb16
-rw-r--r--spec/models/export_spec.rb66
-rw-r--r--spec/models/export_task_spec.rb8
-rw-r--r--spec/models/gtfs_export_spec.rb62
-rw-r--r--spec/models/gtfs_import_spec.rb2
-rw-r--r--spec/models/import/import_message_spec.rb (renamed from spec/models/import_message_spec.rb)2
-rw-r--r--spec/models/import/import_resource_spec.rb (renamed from spec/models/import_resource_spec.rb)2
-rw-r--r--spec/models/import/import_spec.rb (renamed from spec/models/import_spec.rb)31
-rw-r--r--spec/models/import/netex_import_spec.rb41
-rw-r--r--spec/models/import_service_spec.rb19
-rw-r--r--spec/models/import_task_spec.rb196
-rw-r--r--spec/models/merge_spec.rb18
-rw-r--r--spec/models/netex_export_spec.rb16
-rw-r--r--spec/models/netex_import_spec.rb39
-rw-r--r--spec/models/referential_spec.rb25
-rw-r--r--spec/models/simple_exporter_spec.rb118
-rw-r--r--spec/models/simple_importer_spec.rb16
-rw-r--r--spec/models/simple_interfaces_group_spec.rb31
-rw-r--r--spec/models/simple_json_exporter_spec.rb95
-rw-r--r--spec/models/stop_area_referential_spec.rb5
-rw-r--r--spec/models/workgroup_spec.rb2
-rw-r--r--spec/requests/api/v1/netex_import_spec.rb8
-rw-r--r--spec/services/parent_notifier_spec.rb14
-rw-r--r--spec/support/permissions.rb1
-rw-r--r--spec/support/pundit/pundit_view_policy.rb6
-rw-r--r--spec/views/referentials/show.html.erb_spec.rb1
274 files changed, 4522 insertions, 2673 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/
diff --git a/Gemfile.lock b/Gemfile.lock
index 090886f3d..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)
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_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/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 4f8fe7a25..e9a071177 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', '')}_#{Date.today.to_s}.csv"
+ send_data Import::MessageExport.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..1535fd171 100644
--- a/app/controllers/import_resources_controller.rb
+++ b/app/controllers/import_resources_controller.rb
@@ -1,7 +1,7 @@
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
+ belongs_to :import, :parent_class => Import::Base
def index
index! do |format|
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/stop_areas_controller.rb b/app/controllers/stop_areas_controller.rb
index 41a1a8c6d..c77500132 100644
--- a/app/controllers/stop_areas_controller.rb
+++ b/app/controllers/stop_areas_controller.rb
@@ -53,6 +53,7 @@ class StopAreasController < ChouetteController
index! do |format|
format.html {
+ # binding.pry
if collection.out_of_bounds?
redirect_to params.merge(:page => 1)
end
@@ -133,7 +134,9 @@ class StopAreasController < ChouetteController
end
def collection
- @q = parent.present? ? parent.stop_areas.search(params[:q]) : referential.stop_areas.search(params[:q])
+ scope = parent.present? ? parent.stop_areas : referential.stop_areas
+ scope = ransack_status(scope)
+ @q = scope.search(params[:q])
if sort_column && sort_direction
@stop_areas ||=
@@ -202,8 +205,28 @@ class StopAreasController < ChouetteController
:waiting_time,
:zip_code,
:kind,
+ :status,
localized_names: Chouette::StopArea::AVAILABLE_LOCALIZATIONS
)
end
+ # Fake ransack filter
+ def ransack_status scope
+ return scope unless params[:q].try(:[], :status)
+ return scope if params[:q][:status].values.uniq.length == 1
+
+ @status = {
+ in_creation: params[:q][:status]['in_creation'] == 'true',
+ confirmed: params[:q][:status]['confirmed'] == 'true',
+ deactivated: params[:q][:status]['deactivated'] == 'true',
+ }
+
+ scope = Chouette::StopArea.where(
+ "confirmed_at #{@status[:confirmed] ? "IS NOT NULL" : "IS NULL"}
+ AND deleted_at #{@status[:deactivated] ? "IS NOT NULL" : "IS NULL"}"
+ )
+
+ params[:q].delete :status
+ scope
+ end
end
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/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..1c9d974a1 100644
--- a/app/helpers/stop_areas_helper.rb
+++ b/app/helpers/stop_areas_helper.rb
@@ -54,4 +54,40 @@ 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
+
+ def stop_area_status(stop_area)
+ if stop_area.activated?
+ content_tag(:span, nil, class: 'fa fa-check-circle fa-lg text-success') +
+ t('activerecord.attributes.stop_area.confirmed')
+ elsif stop_area.deactivated?
+ content_tag(:span, nil, class: 'fa fa-exclamation-circle fa-lg text-danger') +
+ t('activerecord.attributes.stop_area.deactivated')
+ else
+ content_tag(:span, nil, class: 'fa fa-pencil fa-lg text-info') +
+ t('activerecord.attributes.stop_area.in_creation')
+ end
+ end
+
+ def stop_area_status_options
+ Chouette::StopArea.statuses.map do |status|
+ [ t(status, scope: 'activerecord.attributes.stop_area'), status ]
+ end
+ 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 e67753e4b..e00e9b1b0 100644
--- a/app/javascript/vehicle_journeys/actions/index.js
+++ b/app/javascript/vehicle_journeys/actions/index.js
@@ -203,24 +203,24 @@ 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 : () => ({
type: 'TOGGLE_ARRIVALS',
}),
- updateTime : (val, subIndex, index, timeUnit, isDeparture, isArrivalsToggled) => ({
+ updateTime : (val, subIndex, index, timeUnit, isDeparture, isArrivalsToggled, enforceConsistency=false) => ({
type: 'UPDATE_TIME',
val,
subIndex,
index,
timeUnit,
isDeparture,
- isArrivalsToggled
+ isArrivalsToggled,
+ enforceConsistency
}),
resetStateFilters: () => ({
type: 'RESET_FILTERS'
@@ -487,10 +487,10 @@ const actions = {
vjas.delta = delta
return vjas
},
- adjustSchedule: (action, schedule) => {
+ adjustSchedule: (action, schedule, enforceConsistency=false) => {
// we enforce that the departure time remains after the arrival time
actions.getDelta(schedule)
- if(schedule.delta < 0){
+ if(enforceConsistency && schedule.delta < 0){
if(action.isDeparture){
schedule.arrival_time = schedule.departure_time
}
diff --git a/app/javascript/vehicle_journeys/components/VehicleJourney.js b/app/javascript/vehicle_journeys/components/VehicleJourney.js
index 7db0cee1c..a667bf8aa 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'>
@@ -132,6 +155,7 @@ export default class VehicleJourney extends Component {
disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false}
readOnly={!this.props.editMode && !vj.dummy}
onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', false, false)}}
+ onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', false, false, true)}}
value={vj.arrival_time['hour']}
/>
<span>:</span>
@@ -143,6 +167,7 @@ export default class VehicleJourney extends Component {
disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false}
readOnly={!this.props.editMode && !vj.dummy}
onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'minute', false, false)}}
+ onBlur={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'minute', false, false, true)}}
value={vj.arrival_time['minute']}
/>
</span>
@@ -150,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")}>
@@ -163,6 +188,7 @@ export default class VehicleJourney extends Component {
disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false}
readOnly={!this.props.editMode && !vj.dummy}
onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', true, this.props.filters.toggleArrivals)}}
+ onBlur={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', true, this.props.filters.toggleArrivals, true)}}
value={vj.departure_time['hour']}
/>
<span>:</span>
@@ -174,13 +200,11 @@ export default class VehicleJourney extends Component {
disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false}
readOnly={!this.props.editMode && !vj.dummy}
onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, "minute", true, this.props.filters.toggleArrivals)}}
+ onBlur={(e) => {this.props.onUpdateTime(e, i, this.props.index, "minute", true, this.props.filters.toggleArrivals, true)}}
value={vj.departure_time['minute']}
/>
- </span>
- </div>
- {vj.errors && <div className="errors">
- {vj.errors}
- </div>}
+ </span>
+ </div>
</div>
</div>
)}
@@ -197,4 +221,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/containers/VehicleJourneysList.js b/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js
index 76d1c3a78..81d603fd4 100644
--- a/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js
+++ b/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js
@@ -20,8 +20,8 @@ const mapDispatchToProps = (dispatch) => {
dispatch(actions.fetchingApi())
actions.fetchVehicleJourneys(dispatch, undefined, undefined, filters.queryString, routeUrl)
},
- onUpdateTime: (e, subIndex, index, timeUnit, isDeparture, isArrivalsToggled) => {
- dispatch(actions.updateTime(e.target.value, subIndex, index, timeUnit, isDeparture, isArrivalsToggled))
+ onUpdateTime: (e, subIndex, index, timeUnit, isDeparture, isArrivalsToggled, enforceConsistency=false) => {
+ dispatch(actions.updateTime(e.target.value, subIndex, index, timeUnit, isDeparture, isArrivalsToggled, enforceConsistency))
},
onSelectVehicleJourney: (index) => {
dispatch(actions.selectVehicleJourney(index))
diff --git a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js
index 8705b3cf2..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"}
@@ -148,11 +153,11 @@ const vehicleJourney= (state = {}, action, keep) => {
newSchedule.departure_time[action.timeUnit] = actions.pad(action.val, action.timeUnit)
if(!action.isArrivalsToggled)
newSchedule.arrival_time[action.timeUnit] = newSchedule.departure_time[action.timeUnit]
- newSchedule = actions.adjustSchedule(action, newSchedule)
+ newSchedule = actions.adjustSchedule(action, newSchedule, action.enforceConsistency)
return _.assign({}, state.vehicle_journey_at_stops[action.subIndex], {arrival_time: newSchedule.arrival_time, departure_time: newSchedule.departure_time, delta: newSchedule.delta})
}else{
newSchedule.arrival_time[action.timeUnit] = actions.pad(action.val, action.timeUnit)
- newSchedule = actions.adjustSchedule(action, newSchedule)
+ newSchedule = actions.adjustSchedule(action, newSchedule, action.enforceConsistency)
return _.assign({}, state.vehicle_journey_at_stops[action.subIndex], {arrival_time: newSchedule.arrival_time, departure_time: newSchedule.departure_time, delta: newSchedule.delta})
}
}else{
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..1918c90d1 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
@@ -362,37 +383,66 @@ module Chouette
end
def activated?
- deleted_at.nil?
+ deleted_at.nil? && confirmed_at
end
def deactivated?
- !activated?
+ deleted_at && confirmed_at.nil?
end
def activate
+ self.confirmed_at = Time.now
self.deleted_at = nil
end
def deactivate
+ self.confirmed_at = nil
self.deleted_at = Time.now
end
def activate!
+ update_attribute :confirmed_at, Time.now
update_attribute :deleted_at, nil
end
def deactivate!
+ update_attribute :confirmed_at, nil
update_attribute :deleted_at, Time.now
end
+ def status
+ return :deleted if deleted_at
+ return :confirmed if confirmed_at
+
+ :in_creation
+ end
+
+ def status=(status)
+ case status&.to_sym
+ when :deleted
+ deactivate
+ when :confirmed
+ activate
+ when :in_creation
+ self.confirmed_at = self.deleted_at = nil
+ end
+ end
+
+ def self.statuses
+ %i{in_creation confirmed deleted}
+ end
+
def time_zone_offset
return 0 unless time_zone.present?
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..b3987060a 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 }
@@ -63,7 +62,7 @@ module Chouette
scope :with_ordered_stop_area_ids, ->(first, second){
if first.present? && second.present?
joins(journey_pattern: :stop_points).
- joins('INNER JOIN "journey_patterns" ON "journey_patterns"."id" = "vehicle_journeys"."journey_pattern_id" INNER JOIN "journey_patterns_stop_points" ON "journey_patterns_stop_points"."journey_pattern_id" = "journey_patterns"."id" INNER JOIN "stop_points" as "second_stop_points" ON "stop_points"."id" = "journey_patterns_stop_points"."stop_point_id"').
+ joins('INNER JOIN "journey_patterns" ON "journey_patterns"."id" = "vehicle_journeys"."journey_pattern_id" INNER JOIN "journey_patterns_stop_points" ON "journey_patterns_stop_points"."journey_pattern_id" = "journey_patterns"."id" INNER JOIN "stop_points" as "second_stop_points" ON "second_stop_points"."id" = "journey_patterns_stop_points"."stop_point_id"').
where('stop_points.stop_area_id = ?', first).
where('second_stop_points.stop_area_id = ? and stop_points.position < second_stop_points.position', second)
else
@@ -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_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/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/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_message_export.rb b/app/models/import/message_export.rb
index 05f8a2cc7..7a7add08c 100644
--- a/app/models/import_message_export.rb
+++ b/app/models/import/message_export.rb
@@ -2,7 +2,7 @@
require "csv"
require "zip"
-class ImportMessageExport
+class Import::MessageExport
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
@@ -26,12 +26,14 @@ class ImportMessageExport
end
def to_csv(options = {})
- CSV.generate(options) do |csv|
+ csv_string = 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") ]
+ csv << [import_message.criticity, import_message.message_attributes['test_id'], 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
+ # 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/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..5bd011039
--- /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: "Import::Message", 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_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/line_referential.rb b/app/models/line_referential.rb
index 15b2f6276..0d2ed39b1 100644
--- a/app/models/line_referential.rb
+++ b/app/models/line_referential.rb
@@ -10,6 +10,7 @@ class LineReferential < ActiveRecord::Base
has_many :networks, class_name: 'Chouette::Network'
has_many :line_referential_syncs, -> { order created_at: :desc }
has_many :workbenches
+ has_one :workgroup
def add_member(organisation, options = {})
attributes = options.merge organisation: organisation
diff --git a/app/models/merge.rb b/app/models/merge.rb
index d42d882ac..e72c794fe 100644
--- a/app/models/merge.rb
+++ b/app/models/merge.rb
@@ -159,7 +159,6 @@ class Merge < ActiveRecord::Base
route_id: nil,
objectid: objectid,
)
-
new_route.stop_points.build attributes
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..a9d3cc9b1 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
@@ -6,6 +8,7 @@ class StopAreaReferential < ActiveRecord::Base
has_many :stop_areas, class_name: 'Chouette::StopArea'
has_many :stop_area_referential_syncs, -> {order created_at: :desc}
has_many :workbenches
+ has_one :workgroup
def add_member(organisation, options = {})
attributes = options.merge organisation: organisation
@@ -15,4 +18,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/models/workgroup.rb b/app/models/workgroup.rb
index 3af20ae23..708225a2a 100644
--- a/app/models/workgroup.rb
+++ b/app/models/workgroup.rb
@@ -11,6 +11,8 @@ class Workgroup < ActiveRecord::Base
validates_presence_of :line_referential_id
validates_presence_of :stop_area_referential_id
+ validates_uniqueness_of :stop_area_referential_id
+ validates_uniqueness_of :line_referential_id
has_many :custom_fields
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
index 8dd699c65..3b3861e0c 100644
--- a/app/views/compliance_checks/show.html.slim
+++ b/app/views/compliance_checks/show.html.slim
@@ -10,4 +10,4 @@
- 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') => I18n.t("enumerize.transport_submode.#{resource.compliance_check_block.transport_submode}")
+ 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/show.html.slim b/app/views/compliance_controls/show.html.slim
index 6e7a45d12..643237676 100644
--- a/app/views/compliance_controls/show.html.slim
+++ b/app/views/compliance_controls/show.html.slim
@@ -10,4 +10,4 @@
- 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/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/_filters.html.slim b/app/views/stop_areas/_filters.html.slim
index 00369d3ed..a32638567 100644
--- a/app/views/stop_areas/_filters.html.slim
+++ b/app/views/stop_areas/_filters.html.slim
@@ -13,6 +13,32 @@
.form-group.togglable class=filter_item_class(params[:q], :area_type_eq_any)
= f.label Chouette::StopArea.human_attribute_name(:area_type), required: false, class: 'control-label'
= f.input :area_type_eq_any, checked: params[:q] && params[:q][:area_type_eq_any], collection: Chouette::AreaType.options, as: :check_boxes, label: false, label_method: lambda{|w| ("<span>" + w[0] + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' }
+
+ .form-group.togglable class=filter_item_class(params[:q], :status)
+ = f.label Chouette::StopArea.human_attribute_name(:state), required: false, class: 'control-label'
+ .form-group.checkbox_list
+ = f.simple_fields_for :status do |p|
+ = p.input :in_creation,
+ label: ("<span>#{t('activerecord.attributes.stop_area.in_creation')}<span class='fa fa-pencil text-info'></span></span>").html_safe,
+ as: :boolean,
+ wrapper_html: { class: 'checkbox-wrapper' },
+ checked_value: true,
+ unchecked_value: false,
+ input_html: { checked: @status.try(:[], :in_creation) }
+ = p.input :confirmed,
+ label: ("<span>#{t('activerecord.attributes.stop_area.confirmed')}<span class='fa fa-check-circle text-success'></span></span>").html_safe,
+ as: :boolean,
+ wrapper_html: { class: 'checkbox-wrapper' },
+ checked_value: true,
+ unchecked_value: false,
+ input_html: { checked: @status.try(:[], :confirmed) }
+ = p.input :deactivated,
+ label: ("<span>#{t('activerecord.attributes.stop_area.deleted')}<span class='fa fa-exclamation-circle text-danger'></span></span>").html_safe,
+ as: :boolean,
+ wrapper_html: { class: 'checkbox-wrapper' },
+ checked_value: true,
+ unchecked_value: false,
+ input_html: { checked: @status.try(:[], :deactivated) }
.actions
= link_to 'Effacer', @workbench, class: 'btn btn-link'
diff --git a/app/views/stop_areas/_form.html.slim b/app/views/stop_areas/_form.html.slim
index bb1fbe1e9..d6682ef70 100644
--- a/app/views/stop_areas/_form.html.slim
+++ b/app/views/stop_areas/_form.html.slim
@@ -27,6 +27,8 @@
.slave data-master="[name='stop_area[kind]']" data-value=kind
= f.input :area_type, as: :select, :input_html => {id: kind, :disabled => !@stop_area.new_record?}, :collection => Chouette::AreaType.options(kind), :include_blank => false, disabled: !@stop_area.new_record?
+ = f.input :status, as: :select, :collection => stop_area_status_options, :include_blank => false
+
.location_info
h3 = t("stop_areas.stop_area.localisation")
@@ -48,7 +50,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/index.html.slim b/app/views/stop_areas/index.html.slim
index 71c7f995c..587efbdaa 100644
--- a/app/views/stop_areas/index.html.slim
+++ b/app/views/stop_areas/index.html.slim
@@ -32,8 +32,8 @@
attribute: 'registration_number' \
), \
TableBuilderHelper::Column.new( \
- key: :deleted_at, \
- attribute: Proc.new { |s| line_status(s.deleted_at) } \
+ name: t('activerecord.attributes.stop_area.state'), \
+ attribute: Proc.new { |s| stop_area_status(s) } \
), \
TableBuilderHelper::Column.new( \
key: :zip_code, \
diff --git a/app/views/stop_areas/show.html.slim b/app/views/stop_areas/show.html.slim
index a4e14a272..a6147b86d 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_status(@stop_area),
@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 adcbb0b6f..e57cbc4f2 100644
--- a/config/breadcrumbs.rb
+++ b/config/breadcrumbs.rb
@@ -111,13 +111,23 @@ 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)
+ link I18n.t('import.resources.index.title'), workbench_import_import_resources_path(import.workbench, import.parent)
parent :import, import.workbench, import.parent
end
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_controls.fr.yml b/config/locales/compliance_controls.fr.yml
index 44d01a973..78b92451f 100644
--- a/config/locales/compliance_controls.fr.yml
+++ b/config/locales/compliance_controls.fr.yml
@@ -191,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/footnotes.fr.yml b/config/locales/footnotes.fr.yml
index 692098046..5169cfc11 100644
--- a/config/locales/footnotes.fr.yml
+++ b/config/locales/footnotes.fr.yml
@@ -15,6 +15,6 @@ fr:
other: "notes"
attributes:
footnote:
- code: "numéro"
+ code: "titre"
checksum: Signature métier
- label: "ligne de texte"
+ label: "texte"
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..386039319 100644
--- a/config/locales/import_resources.en.yml
+++ b/config/locales/import_resources.en.yml
@@ -1,11 +1,14 @@
en:
+ import:
+ resources: &resources
+ index:
+ title: "NeTEx conformity"
+ table_state: "%{lines_imported} line(s) imported on %{lines_in_zipfile} presents in zipfile"
+ table_title: "Satus of anlyzed files"
+ table_explanation: "When calendriers.xml and/or commun.xml are not imported, then all lines file are not processed."
+ metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a"
import_resources:
- index:
- title: "NeTEx conformity"
- table_state: "%{lines_imported} line(s) imported on %{lines_in_zipfile} presents in zipfile"
- table_title: "Satus of anlyzed files"
- table_explanation: "When calendriers.xml and/or commun.xml are not imported, then all lines file are not processed."
- metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a"
+ <<: *resources
activerecord:
models:
import_resource:
@@ -14,5 +17,6 @@ en:
other: "netex conformities"
attributes:
import:
- name: "Filename"
- status: "Status"
+ resource:
+ name: "Filename"
+ status: "Status"
diff --git a/config/locales/import_resources.fr.yml b/config/locales/import_resources.fr.yml
index a271ae1ca..50fb7f1ca 100644
--- a/config/locales/import_resources.fr.yml
+++ b/config/locales/import_resources.fr.yml
@@ -1,11 +1,14 @@
fr:
+ import:
+ resources: &resources
+ index:
+ title: "Rapport de conformité NeTEx"
+ table_state: "%{lines_imported} ligne(s) importée(s) sur %{lines_in_zipfile} présente(s) dans l'archive"
+ table_title: "Etat des fichiers analysés"
+ table_explanation: "Dans le cas ou le(s) fichiers calendriers.xml et/ou commun.xml sont dans un état non importé, alors tous les fichiers lignes sont automatiquement dans un état non traité."
+ metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a"
import_resources:
- index:
- title: "Rapport de conformité NeTEx"
- table_state: "%{lines_imported} ligne(s) importée(s) sur %{lines_in_zipfile} présente(s) dans l'archive"
- table_title: "Etat des fichiers analysés"
- table_explanation: "Dans le cas ou le(s) fichiers calendriers.xml et/ou commun.xml sont dans un état non importé, alors tous les fichiers lignes sont automatiquement dans un état non traité."
- metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a"
+ <<: *resources
activerecord:
models:
import_resource:
@@ -13,6 +16,7 @@ fr:
one: "rapport de conformité Netex"
other: "rapports de conformité Netex"
attributes:
- import_resource:
- name: "Fichier"
- status: "Etat"
+ import:
+ resource:
+ name: "Fichier"
+ status: "Etat"
diff --git a/config/locales/import_tasks.en.yml b/config/locales/import_tasks.en.yml
deleted file mode 100644
index 34f7e6998..000000000
--- a/config/locales/import_tasks.en.yml
+++ /dev/null
@@ -1,52 +0,0 @@
-en:
- import_tasks:
- actions:
- new: "New import"
- new:
- title: "New import"
- all: "All"
- flash: "Import task on queue, refresh page to see progression"
- activemodel:
- models:
- import_task:
- zero: "import"
- one: "import"
- other: "imports"
- neptune_import:
- zero: "import"
- one: "Neptune import"
- other: "imports"
- gtfs_import:
- zero: "import"
- one: "GTFS import"
- other: "imports"
- netex_import:
- zero: "import"
- one: "NeTEx import"
- other: "imports"
- attributes:
- import_task:
- name: "Import name"
- no_save: "No save"
- resources: "File to import"
- references_type: "Data to be imported"
- 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"
- errors:
- models:
- import_task:
- attributes:
- resources:
- maximum_file_size: "File should smaller than %{maximum_file_size} (%{file_size} sent)"
- invalid_mime_type: "File doesn't use the expected format (%{mime_type} sent)"
- formtastic:
- titles:
- import_task:
- max_distance_for_commercial: "Maximal distance to merge homonymous stops in commercial stop in meter"
- max_distance_for_connection_link: "Maximal distance to link stops by connection link stop in meter"
- ignore_last_word: "ignore last word on stop name in homonymous detection (inappliable when just one word occurs)"
- ignore_end_chars: "ignore some chars at the end of stop names in homonymous detection"
- references_type: "Filter on stop areas import only GTFS stops and transfers files, these may contain extra attributes"
diff --git a/config/locales/import_tasks.fr.yml b/config/locales/import_tasks.fr.yml
deleted file mode 100644
index 002ca03cb..000000000
--- a/config/locales/import_tasks.fr.yml
+++ /dev/null
@@ -1,53 +0,0 @@
-fr:
- import_tasks:
- actions:
- new: "Nouvel import"
- new:
- title: "Nouvel import"
- all: "Tout"
- flash: "La demande d'import est mise en file d'attente, veuillez rafraichir régulièrement la page pour en suivre la progression"
- activemodel:
- models:
- import_task:
- zero: "import"
- one: "import"
- other: "imports"
- neptune_import:
- zero: "import"
- one: "import Neptune"
- other: "imports"
- gtfs_import:
- zero: "import"
- one: "import GTFS"
- other: "imports"
- netex_import:
- zero: "import"
- one: "import NeTEx"
- other: "imports"
- attributes:
- import_task:
- name: "Nom de l'import"
- no_save: "Pas de sauvegarde"
- resources: "Fichier à importer"
- references_type: "Données à importer"
- 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"
- errors:
- models:
- import_task:
- attributes:
- resources:
- maximum_file_size: "Le fichier ne peut dépasser %{maximum_file_size} (%{file_size} soumis)"
- invalid_mime_type: "Le fichier n'utilise pas le format attendu (%{mime_type} soumis)"
-
- formtastic:
- titles:
- import_task:
- max_distance_for_commercial: "Distance maximale entre deux arrêts homonymes pour créer les zones d'arrêt (en mètre)"
- max_distance_for_connection_link: "Distance maximale entre deux arrêts pour créer les correspondances (en mètre)"
- ignore_last_word: "Ignorer le dernier mot pour détecter l'homonymie des noms d'arrêt (inapplicable quand le nom ne comporte qu'un mot)"
- ignore_end_chars: "Ignorer les n derniers caractères du nom de l'arrêt pour détecter l'homonymie"
- references_type: "Le filtre sur arrêts importe uniquement les fichiers GTFS stops et transfers gtfs, ceux-ci pouvant contenir des attributs supplémentaires"
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/journey_patterns.fr.yml b/config/locales/journey_patterns.fr.yml
index fb69f4190..8f1dc1e41 100644
--- a/config/locales/journey_patterns.fr.yml
+++ b/config/locales/journey_patterns.fr.yml
@@ -42,7 +42,7 @@ fr:
published_name: "Nom public"
section_status: "Status section"
comment: "Commentaire"
- registration_number: "Numéro d'enregistrement"
+ registration_number: "Code mission"
stop_point_ids: "Sélection des arrêts desservis"
objectid: "Identifiant Neptune"
object_version: "Version"
diff --git a/config/locales/routing_constraint_zones.fr.yml b/config/locales/routing_constraint_zones.fr.yml
index 024dd3288..d4b97fff2 100644
--- a/config/locales/routing_constraint_zones.fr.yml
+++ b/config/locales/routing_constraint_zones.fr.yml
@@ -2,9 +2,9 @@ fr:
activerecord:
models:
routing_constraint_zone:
- zero: zone de contrainte
- one: zone de contrainte
- other: zone de contraintes
+ zero: ITL
+ one: ITL
+ other: ITLs
attributes:
routing_constraint_zone:
checksum: Signature métier
diff --git a/config/locales/stop_areas.en.yml b/config/locales/stop_areas.en.yml
index c1ced1094..389f70c0c 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"
@@ -103,8 +110,11 @@ en:
name: "Name"
registration_number: "Registration number"
published_name: "Published name"
- deleted: "Deleted"
- deleted_at: "Deleted at"
+ in_creation: "In creation"
+ confirmed: "Activated"
+ confirmed_at: "Activated at"
+ deleted: "Deactivated"
+ deleted_at: "Deactivated at"
comment: "Description"
stop_area_type: "Area type"
area_type: "Area type"
@@ -136,14 +146,14 @@ en:
coordinates: "Coordinates (lat,lng) WGS84"
zip_code: "Zip code"
city_name: "City"
- 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..aee112be7 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"
@@ -105,8 +112,12 @@ fr:
kind: "Catégorie"
registration_number: "Numéro d'enregistrement"
published_name: "Nom public"
- deleted: "Supprimé"
- deleted_at: "Activé"
+ in_creation: "En création"
+ confirmed: "Actif"
+ confirmed_at: "Activé le"
+ deleted: "Désactivé"
+ deactivated: "Désactivé"
+ deleted_at: "Désactivé le"
comment: "Commentaire"
stop_area_type: "Type d'arrêt"
area_type: "Type d'arrêt"
@@ -138,14 +149,14 @@ fr:
coordinates: "Coordonnées (lat,lng) WGS84"
zip_code: "Code postal"
city_name: "Commune"
- 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/vehicle_journeys.fr.yml b/config/locales/vehicle_journeys.fr.yml
index a43fa4580..a1eb5b3f7 100644
--- a/config/locales/vehicle_journeys.fr.yml
+++ b/config/locales/vehicle_journeys.fr.yml
@@ -122,7 +122,7 @@ fr:
object_version: "Version"
objectid: "Identifiant Neptune"
on_demand_fs: "Service à la demande"
- published_journey_identifier: "Identifiant public"
+ published_journey_identifier: "Numéro de train"
published_journey_name: "Nom public"
purchase_window: "Disponibilité commerciale"
regular_fs: "Service régulier"
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/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/20180307202627_add_confirmed_at_to_stop_areas.rb b/db/migrate/20180307202627_add_confirmed_at_to_stop_areas.rb
new file mode 100644
index 000000000..602d3afdc
--- /dev/null
+++ b/db/migrate/20180307202627_add_confirmed_at_to_stop_areas.rb
@@ -0,0 +1,5 @@
+class AddConfirmedAtToStopAreas < ActiveRecord::Migration
+ def change
+ add_column :stop_areas, :confirmed_at, :datetime
+ end
+end
diff --git a/db/migrate/20180308063549_update_stop_areas_confirmed_at_attribute.rb b/db/migrate/20180308063549_update_stop_areas_confirmed_at_attribute.rb
new file mode 100644
index 000000000..7bc0471f7
--- /dev/null
+++ b/db/migrate/20180308063549_update_stop_areas_confirmed_at_attribute.rb
@@ -0,0 +1,9 @@
+class UpdateStopAreasConfirmedAtAttribute < ActiveRecord::Migration
+ def up
+ Chouette::StopArea.where(deleted_at: nil).update_all(confirmed_at: Time.now)
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ 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 045fc658d..2f9ffa840 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,8 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20180227151937) 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 "postgis"
@@ -90,9 +89,9 @@ ActiveRecord::Schema.define(version: 20180227151937) do
t.integer "organisation_id", limit: 8
t.datetime "created_at"
t.datetime "updated_at"
+ t.integer "workgroup_id", limit: 8
t.integer "int_day_types"
t.date "excluded_dates", array: true
- t.integer "workgroup_id", limit: 8
end
add_index "calendars", ["organisation_id"], name: "index_calendars_on_organisation_id", using: :btree
@@ -421,9 +420,9 @@ ActiveRecord::Schema.define(version: 20180227151937) 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 +724,12 @@ ActiveRecord::Schema.define(version: 20180227151937) 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|
@@ -800,6 +800,7 @@ ActiveRecord::Schema.define(version: 20180227151937) do
t.integer "waiting_time"
t.string "kind"
t.jsonb "localized_names"
+ t.datetime "confirmed_at"
end
add_index "stop_areas", ["name"], name: "index_stop_areas_on_name", using: :btree
@@ -1007,17 +1008,21 @@ ActiveRecord::Schema.define(version: 20180227151937) 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/model_attribute.rb b/lib/model_attribute.rb
index 872c00152..4880d2490 100644
--- a/lib/model_attribute.rb
+++ b/lib/model_attribute.rb
@@ -64,27 +64,16 @@ class ModelAttribute
# Chouette::Route
define :route, :name, :string
define :route, :published_name, :string
- define :route, :comment, :string
- define :route, :number, :string
- define :route, :direction, :string
define :route, :wayback, :string
# Chouette::JourneyPattern
define :journey_pattern, :name, :string
define :journey_pattern, :published_name, :string
- define :journey_pattern, :comment, :string
define :journey_pattern, :registration_number, :string
- define :journey_pattern, :section_status, :integer
# Chouette::VehicleJourney
- define :vehicle_journey, :comment, :string
- define :vehicle_journey, :status_value, :string
- define :vehicle_journey, :transport_mode, :string
- define :vehicle_journey, :facility, :string
define :vehicle_journey, :published_journey_name, :string
define :vehicle_journey, :published_journey_identifier, :string
- define :vehicle_journey, :vehicle_type_identifier, :string
- define :vehicle_journey, :number, :integer
define :vehicle_journey, :mobility_restricted_suitability, :boolean
define :vehicle_journey, :flexible_service, :boolean
@@ -92,13 +81,6 @@ class ModelAttribute
define :footnote, :code, :string
define :footnote, :label, :string
- # Chouette::TimeTable
- define :time_table, :version, :string
- define :time_table, :comment, :string
- define :time_table, :start_date, :date
- define :time_table, :end_date, :date
- define :time_table, :color, :string
-
# Chouette::RoutingConstraintZone
define :routing_constraint_zone, :name, :string
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/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/referential_vehicle_journeys_controller_spec.rb b/spec/controllers/referential_vehicle_journeys_controller_spec.rb
index 50230dd9e..6bf4c7553 100644
--- a/spec/controllers/referential_vehicle_journeys_controller_spec.rb
+++ b/spec/controllers/referential_vehicle_journeys_controller_spec.rb
@@ -80,6 +80,18 @@ RSpec.describe ReferentialVehicleJourneysController, type: :controller do
expect(assigns[:vehicle_journeys]).to_not include(vehicle_journey_2)
end
end
+
+ context "with both stops one being unused" do
+ let(:from_area_id){ vehicle_journey_1.stop_areas.first.id }
+ let(:to_area_id){
+ stop_area = create :stop_area
+ create :stop_point, stop_area: stop_area, route: vehicle_journey_1.route
+ stop_area
+ }
+ it "should apply filters" do
+ expect(assigns[:vehicle_journeys]).to eq []
+ end
+ end
end
end
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/features/stop_areas_spec.rb b/spec/features/stop_areas_spec.rb
index 668eb2fa3..02e853999 100644
--- a/spec/features/stop_areas_spec.rb
+++ b/spec/features/stop_areas_spec.rb
@@ -30,6 +30,46 @@ describe "StopAreas", :type => :feature do
expect(page).to have_content(stop_areas.first.name)
expect(page).not_to have_content(stop_areas.last.name)
end
+
+ context 'filtering by status' do
+ before do
+ stop_areas.first.activate!
+ stop_areas.last.deactivate!
+ end
+
+ describe 'updated stop areas in before block' do
+
+ it 'supports displaying only stop areas in creation' do
+ find("#q_status_in_creation").set(true)
+ click_button 'search-btn'
+ expect(page).not_to have_content(stop_areas.first.name)
+ expect(page).not_to have_content(stop_areas.last.name)
+ end
+
+ it 'supports displaying only confirmed stop areas' do
+ find("#q_status_confirmed").set(true)
+ click_button 'search-btn'
+ expect(page).to have_content(stop_areas.first.name)
+ expect(page).not_to have_content(stop_areas.last.name)
+ end
+
+ it 'supports displaying only deactivated stop areas' do
+ find("#q_status_deactivated").set(true)
+ click_button 'search-btn'
+ expect(page).not_to have_content(stop_areas.first.name)
+ expect(page).to have_content(stop_areas.last.name)
+ end
+
+ it 'should display all stop areas if all filters are checked' do
+ find("#q_status_in_creation").set(true)
+ find("#q_status_confirmed").set(true)
+ find("#q_status_deactivated").set(true)
+ click_button 'search-btn'
+ expect(page).to have_content(stop_areas.first.name)
+ expect(page).to have_content(stop_areas.last.name)
+ end
+ end
+ end
end
end
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/actions_spec.js b/spec/javascript/vehicle_journeys/actions_spec.js
index 9710d833c..bfbb4fb36 100644
--- a/spec/javascript/vehicle_journeys/actions_spec.js
+++ b/spec/javascript/vehicle_journeys/actions_spec.js
@@ -194,7 +194,7 @@ describe('when toggling arrivals', () => {
})
describe('when updating vjas time', () => {
it('should create an action to update time', () => {
- const val = 33, subIndex = 0, index = 0, timeUnit = 'minute', isDeparture = true, isArrivalsToggled = true
+ const val = 33, subIndex = 0, index = 0, timeUnit = 'minute', isDeparture = true, isArrivalsToggled = true, enforceConsistency = false
const expectedAction = {
type: 'UPDATE_TIME',
val,
@@ -202,7 +202,8 @@ describe('when updating vjas time', () => {
index,
timeUnit,
isDeparture,
- isArrivalsToggled
+ isArrivalsToggled,
+ enforceConsistency
}
expect(actions.updateTime(val, subIndex, index, timeUnit, isDeparture, isArrivalsToggled)).toEqual(expectedAction)
})
@@ -640,7 +641,7 @@ describe('actions.adjustSchedule', () => {
}
})
it('should do nothing', () => {
- expect(actions.adjustSchedule(action, schedule)).toEqual(schedule)
+ expect(actions.adjustSchedule(action, schedule, true)).toEqual(schedule)
})
}),
context('with a delta < 0', () => {
@@ -662,7 +663,7 @@ describe('actions.adjustSchedule', () => {
arrival_time: departure_time,
delta: 0
}
- expect(actions.adjustSchedule(action, schedule)).toEqual(expected)
+ expect(actions.adjustSchedule(action, schedule, true)).toEqual(expected)
})
})
}),
@@ -676,7 +677,7 @@ describe('actions.adjustSchedule', () => {
}
})
it('should do nothing', () => {
- expect(actions.adjustSchedule(action, schedule)).toEqual(schedule)
+ expect(actions.adjustSchedule(action, schedule, true)).toEqual(schedule)
})
}),
context('with a delta < 0', () => {
@@ -698,7 +699,7 @@ describe('actions.adjustSchedule', () => {
arrival_time: arrival_time,
delta: 0
}
- expect(actions.adjustSchedule(action, schedule)).toEqual(expected)
+ expect(actions.adjustSchedule(action, schedule, true)).toEqual(expected)
})
})
})
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/lib/model_attribute_spec.rb b/spec/lib/model_attribute_spec.rb
index cdba87a90..6da740dcb 100644
--- a/spec/lib/model_attribute_spec.rb
+++ b/spec/lib/model_attribute_spec.rb
@@ -22,12 +22,10 @@ RSpec.describe ModelAttribute do
it "returns the list of classes of ModelAttributes in .all" do
ModelAttribute.define(:route, :name, :string)
ModelAttribute.define(:journey_pattern, :name, :string)
- ModelAttribute.define(:time_table, :start_date, :date)
expect(ModelAttribute.classes).to match_array([
'Route',
'JourneyPattern',
- 'TimeTable'
])
end
end
@@ -68,13 +66,13 @@ RSpec.describe ModelAttribute do
it "returns all ModelAttributes for a given class" do
ModelAttribute.define(:route, :name, :string)
ModelAttribute.define(:route, :published_name, :string)
- ModelAttribute.define(:route, :direction, :string)
+ ModelAttribute.define(:route, :wayback, :string)
ModelAttribute.define(:journey_pattern, :name, :string)
expect(ModelAttribute.methods_by_class(:route)).to match_array([
ModelAttribute.new(:route, :name, :string),
ModelAttribute.new(:route, :published_name, :string),
- ModelAttribute.new(:route, :direction, :string)
+ ModelAttribute.new(:route, :wayback, :string)
])
end
end
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/merge_spec.rb b/spec/models/merge_spec.rb
index 92f8f74b1..95181a80e 100644
--- a/spec/models/merge_spec.rb
+++ b/spec/models/merge_spec.rb
@@ -20,6 +20,7 @@ RSpec.describe Merge do
metadatas: [referential_metadata]
factor = 1
+ stop_points_positions = {}
referential.switch do
line_referential.lines.each do |line|
@@ -30,12 +31,17 @@ RSpec.describe Merge do
end
referential.routes.each do |route|
+ route.stop_points.each do |sp|
+ sp.set_list_position 0
+ end
+ route.reload.update_checksum!
factor.times do
FactoryGirl.create :journey_pattern, route: route, stop_points: route.stop_points.sample(3)
end
end
referential.journey_patterns.each do |journey_pattern|
+ stop_points_positions[journey_pattern.name] = Hash[*journey_pattern.stop_points.map{|sp| [sp.stop_area_id, sp.position]}.flatten]
factor.times do
FactoryGirl.create :vehicle_journey, journey_pattern: journey_pattern, company: company
end
@@ -53,6 +59,18 @@ RSpec.describe Merge do
merge = Merge.create!(workbench: referential.workbench, referentials: [referential, referential])
merge.merge!
+
+ output = merge.output.current
+ output.switch
+
+ # Let's check stop_point positions are respected
+ # This should be enforced by the checksum preservation though
+ output.journey_patterns.each do |journey_pattern|
+ journey_pattern.stop_points.each do |sp|
+ expect(sp.position).to eq stop_points_positions[journey_pattern.name][sp.stop_area_id]
+ end
+ 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..6e8620d94 100644
--- a/spec/models/simple_importer_spec.rb
+++ b/spec/models/simple_importer_spec.rb
@@ -1,11 +1,13 @@
+# coding: utf-8
RSpec.describe SimpleImporter do
describe "#define" 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 +20,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
@@ -43,6 +46,7 @@ RSpec.describe SimpleImporter do
config.add_column :street_name
config.add_column :stop_area_referential, value: stop_area_referential
config.add_value :kind, :commercial
+ config.add_value :status, :confirmed
end
end
@@ -139,8 +143,8 @@ RSpec.describe SimpleImporter do
end
context "with a custom behaviour" do
- let!(:present){ create :stop_area, name: "Nom du Stop", stop_area_referential: stop_area_referential }
- let!(:missing){ create :stop_area, name: "Another", stop_area_referential: stop_area_referential }
+ let!(:present){ create :stop_area, name: "Nom du Stop", stop_area_referential: stop_area_referential, status: :confirmed }
+ let!(:missing){ create :stop_area, name: "Another", stop_area_referential: stop_area_referential, status: :confirmed }
before(:each){
importer.configure do |config|
config.before do |importer|
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/models/workgroup_spec.rb b/spec/models/workgroup_spec.rb
index ac8d3fc98..97fff3d86 100644
--- a/spec/models/workgroup_spec.rb
+++ b/spec/models/workgroup_spec.rb
@@ -6,6 +6,8 @@ RSpec.describe Workgroup, type: :model do
it{ should have_many(:workbenches) }
it{ should validate_uniqueness_of(:name) }
+ it{ should validate_uniqueness_of(:stop_area_referential_id) }
+ it{ should validate_uniqueness_of(:line_referential_id) }
it 'is not valid without a stop_area_referential' do
workgroup.stop_area_referential_id = nil
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