aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock2
-rw-r--r--app/assets/javascripts/es6_browserified/journey_patterns/actions/index.js7
-rw-r--r--app/assets/javascripts/es6_browserified/journey_patterns/components/CreateModal.js2
-rw-r--r--app/assets/javascripts/es6_browserified/journey_patterns/components/JourneyPattern.js8
-rw-r--r--app/assets/javascripts/es6_browserified/journey_patterns/components/JourneyPatterns.js3
-rw-r--r--app/assets/javascripts/es6_browserified/journey_patterns/components/SaveJourneyPattern.js4
-rw-r--r--app/assets/javascripts/es6_browserified/journey_patterns/containers/AddJourneyPattern.js1
-rw-r--r--app/assets/javascripts/es6_browserified/journey_patterns/containers/JourneyPatternList.js1
-rw-r--r--app/assets/javascripts/es6_browserified/journey_patterns/containers/SaveJourneyPattern.js14
-rw-r--r--app/assets/javascripts/es6_browserified/journey_patterns/index.js3
-rw-r--r--app/assets/javascripts/es6_browserified/journey_patterns/reducers/editMode.js12
-rw-r--r--app/assets/javascripts/es6_browserified/journey_patterns/reducers/index.js2
-rw-r--r--app/assets/javascripts/es6_browserified/journey_patterns/reducers/status.js4
-rw-r--r--app/assets/javascripts/es6_browserified/time_tables/actions/index.js77
-rw-r--r--app/assets/javascripts/es6_browserified/time_tables/components/ErrorModal.js7
-rw-r--r--app/assets/javascripts/es6_browserified/time_tables/components/ExceptionsInDay.js4
-rw-r--r--app/assets/javascripts/es6_browserified/time_tables/components/PeriodForm.js3
-rw-r--r--app/assets/javascripts/es6_browserified/time_tables/components/SaveTimetable.js8
-rw-r--r--app/assets/javascripts/es6_browserified/time_tables/components/Timetable.js1
-rw-r--r--app/assets/javascripts/es6_browserified/time_tables/containers/ErrorModal.js1
-rw-r--r--app/assets/javascripts/es6_browserified/time_tables/containers/PeriodForm.js6
-rw-r--r--app/assets/javascripts/es6_browserified/time_tables/containers/SaveTimetable.js4
-rw-r--r--app/assets/javascripts/es6_browserified/time_tables/containers/Timetable.js8
-rw-r--r--app/assets/javascripts/es6_browserified/time_tables/index.js5
-rw-r--r--app/assets/javascripts/es6_browserified/time_tables/reducers/modal.js8
-rw-r--r--app/assets/javascripts/es6_browserified/time_tables/reducers/pagination.js2
-rw-r--r--app/assets/javascripts/es6_browserified/time_tables/reducers/timetable.js13
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/actions/index.js101
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/SaveVehicleJourneys.js10
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/Tools.js34
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourney.js14
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourneys.js3
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/DuplicateVehicleJourney.js67
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/EditVehicleJourney.js5
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/ShiftVehicleJourney.js25
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/select2s/CompanySelect2.js3
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/SaveVehicleJourneys.js14
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Tools.js4
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/VehicleJourneysList.js1
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/DuplicateVehicleJourney.js4
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/EditVehicleJourney.js5
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/index.js15
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/editMode.js12
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/index.js2
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/modal.js3
-rw-r--r--app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/vehicleJourneys.js19
-rw-r--r--app/assets/javascripts/workbench.coffee6
-rw-r--r--app/assets/stylesheets/components/_tables.sass62
-rw-r--r--app/concerns/configurable.rb26
-rw-r--r--app/controllers/api/v1/chouette_controller.rb5
-rw-r--r--app/controllers/api/v1/iboo_controller.rb21
-rw-r--r--app/controllers/api/v1/imports_controller.rb15
-rw-r--r--app/controllers/api/v1/netex_imports_controller.rb55
-rw-r--r--app/controllers/api/v1/workbenches_controller.rb3
-rw-r--r--app/controllers/api_keys_controller.rb32
-rw-r--r--app/controllers/application_controller.rb1
-rw-r--r--app/controllers/autocomplete_time_tables_controller.rb4
-rw-r--r--app/controllers/concerns/control_flow.rb14
-rw-r--r--app/controllers/import_tasks_controller.rb1
-rw-r--r--app/controllers/imports_controller.rb50
-rw-r--r--app/controllers/time_table_combinations_controller.rb4
-rw-r--r--app/controllers/time_tables_controller.rb1
-rw-r--r--app/decorators/api_key_decorator.rb30
-rw-r--r--app/decorators/import_decorator.rb36
-rw-r--r--app/decorators/time_table_decorator.rb14
-rw-r--r--app/helpers/table_builder_helper.rb156
-rw-r--r--app/helpers/table_builder_helper/url.rb7
-rw-r--r--app/models/api/v1/api_key.rb34
-rw-r--r--app/models/chouette/footnote.rb7
-rw-r--r--app/models/chouette/journey_pattern.rb7
-rw-r--r--app/models/chouette/route.rb9
-rw-r--r--app/models/chouette/routing_constraint_zone.rb6
-rw-r--r--app/models/chouette/time_table.rb15
-rw-r--r--app/models/chouette/time_table_date.rb6
-rw-r--r--app/models/chouette/time_table_period.rb6
-rw-r--r--app/models/chouette/vehicle_journey.rb11
-rw-r--r--app/models/chouette/vehicle_journey_at_stop.rb10
-rw-r--r--app/models/clean_up.rb8
-rw-r--r--app/models/concerns/checksum_support.rb29
-rw-r--r--app/models/import.rb44
-rw-r--r--app/models/line_referential_sync.rb12
-rw-r--r--app/models/netex_import.rb3
-rw-r--r--app/models/organisation.rb19
-rw-r--r--app/models/stop_area_referential_sync.rb12
-rw-r--r--app/models/user.rb17
-rw-r--r--app/models/workbench.rb1
-rw-r--r--app/models/workbench_import.rb2
-rw-r--r--app/policies/api_key_policy.rb19
-rw-r--r--app/policies/import_policy.rb7
-rw-r--r--app/policies/time_table_combination_policy.rb12
-rw-r--r--app/services/http_service.rb45
-rw-r--r--app/services/parent_import_notifier.rb15
-rw-r--r--app/services/retry_service.rb54
-rw-r--r--app/services/zip_service.rb55
-rw-r--r--app/views/api/v1/imports/index.rabl3
-rw-r--r--app/views/api/v1/imports/show.rabl6
-rw-r--r--app/views/api/v1/netex_imports/create.json.rabl3
-rw-r--r--app/views/api/v1/workbenches/index.rabl3
-rw-r--r--app/views/api/v1/workbenches/show.rabl3
-rw-r--r--app/views/api_keys/_form.html.slim13
-rw-r--r--app/views/api_keys/edit.html.slim3
-rw-r--r--app/views/api_keys/index.html.slim24
-rw-r--r--app/views/api_keys/new.html.slim3
-rw-r--r--app/views/api_keys/show.html.slim7
-rw-r--r--app/views/imports/_filters.html.slim21
-rw-r--r--app/views/imports/_form.html.slim28
-rw-r--r--app/views/imports/index.html.slim58
-rw-r--r--app/views/imports/new.html.slim14
-rw-r--r--app/views/imports/show.html.slim134
-rw-r--r--app/views/layouts/navigation/_main_nav_left.html.slim10
-rw-r--r--app/views/line_referentials/show.html.slim4
-rw-r--r--app/views/networks/index.html.slim2
-rw-r--r--app/views/networks/show.html.slim2
-rw-r--r--app/views/stop_area_referentials/show.html.slim4
-rw-r--r--app/views/time_table_combinations/_form.html.slim2
-rw-r--r--app/views/time_tables/edit.html.slim3
-rw-r--r--app/workers/workbench_import_worker.rb118
-rw-r--r--config/application.rb2
-rw-r--r--config/deploy.rb2
-rw-r--r--config/environments/development.rb23
-rw-r--r--config/environments/production.rb1
-rw-r--r--config/environments/test.rb1
-rw-r--r--config/initializers/apartment.rb57
-rw-r--r--config/initializers/workbench_import.rb5
-rw-r--r--config/locales/api_keys.en.yml6
-rw-r--r--config/locales/api_keys.fr.yml2
-rw-r--r--config/locales/imports.en.yml9
-rw-r--r--config/locales/imports.fr.yml11
-rw-r--r--config/locales/time_tables.en.yml4
-rw-r--r--config/locales/time_tables.fr.yml4
-rw-r--r--config/routes.rb42
-rw-r--r--config/schedule.rb4
-rw-r--r--db/migrate/20170710125809_add_check_sum.rb13
-rw-r--r--db/migrate/20170710130230_add_check_sum_source.rb13
-rw-r--r--db/migrate/20170715041954_add_parent_type_and_parent_id_to_imports.rb6
-rw-r--r--db/migrate/20170724094628_add_notified_parent_at_to_imports.rb5
-rw-r--r--db/migrate/20170727130705_add_current_step_and_total_steps_to_import.rb6
-rw-r--r--db/migrate/20170802141224_rename_message_attributs_to_message_attributes_everywhere.rb7
-rw-r--r--db/migrate/20170808110333_change_checksum_source_type_to_text.rb17
-rw-r--r--db/migrate/20170816104020_add_creator_to_imports.rb5
-rw-r--r--db/migrate/20170817122914_add_organisation_to_api_keys.rb5
-rw-r--r--db/schema.rb524
-rw-r--r--lib/result.rb37
-rw-r--r--lib/tasks/ci.rake9
-rw-r--r--lib/tasks/generate.rake9
-rw-r--r--lib/tasks/imports.rake6
-rw-r--r--spec/concerns/configurable_spec.rb35
-rw-r--r--spec/controllers/api/v1/iboo_controller_spec.rb12
-rw-r--r--spec/controllers/api/v1/imports_controller_spec.rb38
-rw-r--r--spec/controllers/api/v1/workbenches_controller_spec.rb25
-rw-r--r--spec/controllers/imports_controller_spec.rb1
-rw-r--r--spec/decorators/api_key_decorator_spec.rb4
-rw-r--r--spec/factories/api_keys.rb8
-rw-r--r--spec/factories/chouette_journey_pattern.rb10
-rw-r--r--spec/factories/chouette_routes.rb1
-rw-r--r--spec/factories/chouette_time_table.rb8
-rw-r--r--spec/factories/chouette_time_table_dates.rb5
-rw-r--r--spec/factories/chouette_time_table_periods.rb7
-rw-r--r--spec/factories/chouette_vehicle_journey.rb1
-rw-r--r--spec/factories/chouette_vehicle_journey_at_stop.rb5
-rw-r--r--spec/factories/clean_up_results.rb9
-rw-r--r--spec/factories/import_messages.rb11
-rw-r--r--spec/factories/import_tasks.rb10
-rw-r--r--spec/factories/imports.rb5
-rw-r--r--spec/factories/netex_imports.rb5
-rw-r--r--spec/factories/workbench_imports.rb5
-rw-r--r--spec/fixtures/multiple_references_import.zipbin0 -> 1086 bytes
-rw-r--r--spec/fixtures/neptune.zipbin4904 -> 0 bytes
-rw-r--r--spec/fixtures/nozip.zip1
-rw-r--r--spec/fixtures/single_reference_import.zipbin0 -> 220 bytes
-rw-r--r--spec/javascripts/time_table/actions_spec.js18
-rw-r--r--spec/javascripts/time_table/reducers/modal_spec.js74
-rw-r--r--spec/javascripts/time_table/reducers/timetable_spec.js73
-rw-r--r--spec/javascripts/vehicle_journeys/actions_spec.js27
-rw-r--r--spec/javascripts/vehicle_journeys/reducers/modal_spec.js9
-rw-r--r--spec/javascripts/vehicle_journeys/reducers/vehicle_journeys_spec.js17
-rw-r--r--spec/lib/result_spec.rb20
-rw-r--r--spec/models/api/v1/api_key_spec.rb28
-rw-r--r--spec/models/chouette/footnote_spec.rb19
-rw-r--r--spec/models/chouette/journey_pattern_spec.rb3
-rw-r--r--spec/models/chouette/route/route_base_spec.rb7
-rw-r--r--spec/models/chouette/routing_constraint_zone_spec.rb4
-rw-r--r--spec/models/chouette/time_table_period_spec.rb8
-rw-r--r--spec/models/chouette/time_table_spec.rb246
-rw-r--r--spec/models/chouette/vehicle_journey_at_stop_spec.rb15
-rw-r--r--spec/models/chouette/vehicle_journey_spec.rb5
-rw-r--r--spec/models/import_spec.rb154
-rw-r--r--spec/models/organisation_spec.rb4
-rw-r--r--spec/models/user_spec.rb2
-rw-r--r--spec/policies/api_key_policy_spec.rb28
-rw-r--r--spec/requests/api/v1/netex_import_spec.rb100
-rw-r--r--spec/routing/api/v1/access_links_routes_spec.rb9
-rw-r--r--spec/routing/group_of_lines_spec.rb4
-rw-r--r--spec/services/http_service_spec.rb74
-rw-r--r--spec/services/parent_import_notifier_spec.rb90
-rw-r--r--spec/services/retry_service_spec.rb137
-rw-r--r--spec/services/zip_service/zip_entry_data_spec.rb32
-rw-r--r--spec/services/zip_service/zip_entry_dirs_spec.rb33
-rw-r--r--spec/services/zip_service/zip_output_streams_spec.rb8
-rw-r--r--spec/support/api_key.rb10
-rw-r--r--spec/support/checksum_support.rb53
-rw-r--r--spec/support/fixtures_helper.rb18
-rw-r--r--spec/support/json_helper.rb11
-rw-r--r--spec/support/referential.rb1
-rw-r--r--spec/support/shared_context.rb15
-rw-r--r--spec/support/webmock/helpers.rb18
-rw-r--r--spec/tasks/reflex_rake_spec.rb4
-rw-r--r--spec/workers/stop_area_referential_sync_worker_spec.rb1
-rw-r--r--spec/workers/workbench_import_worker_spec.rb119
210 files changed, 3546 insertions, 1021 deletions
diff --git a/Gemfile b/Gemfile
index f57a9d524..aeb776942 100644
--- a/Gemfile
+++ b/Gemfile
@@ -163,6 +163,7 @@ group :test do
gem 'simplecov', :require => false
gem 'simplecov-rcov', :require => false
gem 'htmlbeautifier'
+ gem 'timecop'
end
group :test, :development, :dev do
diff --git a/Gemfile.lock b/Gemfile.lock
index 2239cf853..d03a26d18 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -525,6 +525,7 @@ GEM
thread (0.2.2)
thread_safe (0.3.6)
tilt (1.4.1)
+ timecop (0.9.1)
transpec (3.3.0)
activesupport (>= 3.0, < 6.0)
astrolabe (~> 1.2)
@@ -669,6 +670,7 @@ DEPENDENCIES
squeel!
teaspoon-jasmine
therubyracer (~> 0.12)
+ timecop
transpec
uglifier (~> 2.7.2)
webmock
diff --git a/app/assets/javascripts/es6_browserified/journey_patterns/actions/index.js b/app/assets/javascripts/es6_browserified/journey_patterns/actions/index.js
index 0ed961f44..3577df2b6 100644
--- a/app/assets/javascripts/es6_browserified/journey_patterns/actions/index.js
+++ b/app/assets/javascripts/es6_browserified/journey_patterns/actions/index.js
@@ -6,6 +6,12 @@ if (!window.Promise) {
}
const actions = {
+ enterEditMode: () => ({
+ type: "ENTER_EDIT_MODE"
+ }),
+ exitEditMode: () => ({
+ type: "EXIT_EDIT_MODE"
+ }),
receiveJourneyPatterns : (json) => ({
type: "RECEIVE_JOURNEY_PATTERNS",
json
@@ -138,6 +144,7 @@ const actions = {
dispatch(actions.updateTotalCount(window.currentItemsLength - json.length))
}
window.currentItemsLength = json.length
+ dispatch(actions.exitEditMode())
dispatch(actions.receiveJourneyPatterns(json))
}
}
diff --git a/app/assets/javascripts/es6_browserified/journey_patterns/components/CreateModal.js b/app/assets/javascripts/es6_browserified/journey_patterns/components/CreateModal.js
index b446e2b37..12871431a 100644
--- a/app/assets/javascripts/es6_browserified/journey_patterns/components/CreateModal.js
+++ b/app/assets/javascripts/es6_browserified/journey_patterns/components/CreateModal.js
@@ -17,7 +17,7 @@ class CreateModal extends Component {
}
render() {
- if(this.props.status.isFetching == true || this.props.status.policy['journey_patterns.update'] == false) {
+ if(this.props.status.isFetching == true || this.props.status.policy['journey_patterns.create'] == false || this.props.editMode == false) {
return false
}
if(this.props.status.fetchSuccess == true) {
diff --git a/app/assets/javascripts/es6_browserified/journey_patterns/components/JourneyPattern.js b/app/assets/javascripts/es6_browserified/journey_patterns/components/JourneyPattern.js
index 286cfc454..377fd0612 100644
--- a/app/assets/javascripts/es6_browserified/journey_patterns/components/JourneyPattern.js
+++ b/app/assets/javascripts/es6_browserified/journey_patterns/components/JourneyPattern.js
@@ -34,7 +34,7 @@ class JourneyPattern extends Component{
type='checkbox'
id={sp.id}
checked={sp.checked}
- disabled={(this.props.value.deletable || this.props.status.policy['journey_patterns.update'] == false) ? 'disabled' : ''}
+ disabled={(this.props.value.deletable || this.props.status.policy['journey_patterns.update'] == false || this.props.editMode == false) ? 'disabled' : ''}
>
</input>
<span className='radio-label'></span>
@@ -78,7 +78,7 @@ class JourneyPattern extends Component{
<span className='fa fa-cog'></span>
</div>
<ul className='dropdown-menu'>
- <li className={(this.props.value.deletable || this.props.status.policy['journey_patterns.update'] == false) ? 'disabled' : ''}>
+ <li className={(this.props.status.policy['journey_patterns.update'] == false || this.props.editMode == false) ? 'disabled' : ''}>
<button
type='button'
onClick={this.props.onOpenEditModal}
@@ -91,10 +91,10 @@ class JourneyPattern extends Component{
<li className={this.props.value.object_id ? '' : 'disabled'}>
{this.vehicleJourneyURL(this.props.value.object_id)}
</li>
- <li className={'delete-action' + ((this.props.status.policy['journey_patterns.update'] == false)? ' disabled' : '')}>
+ <li className={'delete-action' + ((this.props.status.policy['journey_patterns.destroy'] == false || this.props.editMode == false) ? ' disabled' : '')}>
<button
type='button'
- disabled={(this.props.status.policy['journey_patterns.update'] == false)? 'disabled' : ''}
+ disabled={(this.props.status.policy['journey_patterns.destroy'] == false || this.props.editMode == false)? 'disabled' : ''}
onClick={(e) => {
e.preventDefault()
this.props.onDeleteJourneyPattern(this.props.index)}
diff --git a/app/assets/javascripts/es6_browserified/journey_patterns/components/JourneyPatterns.js b/app/assets/javascripts/es6_browserified/journey_patterns/components/JourneyPatterns.js
index 23473ae52..6506b706c 100644
--- a/app/assets/javascripts/es6_browserified/journey_patterns/components/JourneyPatterns.js
+++ b/app/assets/javascripts/es6_browserified/journey_patterns/components/JourneyPatterns.js
@@ -96,7 +96,7 @@ class JourneyPatterns extends Component{
<div className="alert alert-danger mt-sm">
<strong>Erreur : </strong>
{this.props.journeyPatterns.map((jp, index) =>
- jp.errors.map((err, i) => {
+ jp.errors && jp.errors.map((err, i) => {
return (
<ul key={i}>
<li>{err}</li>
@@ -133,6 +133,7 @@ class JourneyPatterns extends Component{
onOpenEditModal= {() => this.props.onOpenEditModal(index, journeyPattern)}
onDeleteJourneyPattern={() => this.props.onDeleteJourneyPattern(index)}
status= {this.props.status}
+ editMode= {this.props.editMode}
/>
)}
</div>
diff --git a/app/assets/javascripts/es6_browserified/journey_patterns/components/SaveJourneyPattern.js b/app/assets/javascripts/es6_browserified/journey_patterns/components/SaveJourneyPattern.js
index 871ba00e1..767dab088 100644
--- a/app/assets/javascripts/es6_browserified/journey_patterns/components/SaveJourneyPattern.js
+++ b/app/assets/javascripts/es6_browserified/journey_patterns/components/SaveJourneyPattern.js
@@ -21,10 +21,10 @@ class SaveJourneyPattern extends Component{
type='button'
onClick={e => {
e.preventDefault()
- actions.submitJourneyPattern(this.props.dispatch, this.props.journeyPatterns)
+ this.props.editMode ? this.props.onSubmitJourneyPattern(this.props.dispatch, this.props.journeyPatterns) : this.props.onEnterEditMode()
}}
>
- Valider
+ {this.props.editMode ? "Valider" : "Editer"}
</button>
</form>
</div>
diff --git a/app/assets/javascripts/es6_browserified/journey_patterns/containers/AddJourneyPattern.js b/app/assets/javascripts/es6_browserified/journey_patterns/containers/AddJourneyPattern.js
index ee13819fd..7aa27754e 100644
--- a/app/assets/javascripts/es6_browserified/journey_patterns/containers/AddJourneyPattern.js
+++ b/app/assets/javascripts/es6_browserified/journey_patterns/containers/AddJourneyPattern.js
@@ -6,6 +6,7 @@ const mapStateToProps = (state) => {
return {
modal: state.modal,
journeyPatterns: state.journeyPatterns,
+ editMode: state.editMode,
status: state.status
}
}
diff --git a/app/assets/javascripts/es6_browserified/journey_patterns/containers/JourneyPatternList.js b/app/assets/javascripts/es6_browserified/journey_patterns/containers/JourneyPatternList.js
index bc2aaf95b..228df3ede 100644
--- a/app/assets/javascripts/es6_browserified/journey_patterns/containers/JourneyPatternList.js
+++ b/app/assets/javascripts/es6_browserified/journey_patterns/containers/JourneyPatternList.js
@@ -6,6 +6,7 @@ const mapStateToProps = (state) => {
return {
journeyPatterns: state.journeyPatterns,
status: state.status,
+ editMode: state.editMode,
stopPointsList: state.stopPointsList
}
}
diff --git a/app/assets/javascripts/es6_browserified/journey_patterns/containers/SaveJourneyPattern.js b/app/assets/javascripts/es6_browserified/journey_patterns/containers/SaveJourneyPattern.js
index 33442c5a0..434264fea 100644
--- a/app/assets/javascripts/es6_browserified/journey_patterns/containers/SaveJourneyPattern.js
+++ b/app/assets/javascripts/es6_browserified/journey_patterns/containers/SaveJourneyPattern.js
@@ -6,11 +6,23 @@ var SaveJourneyPatternComponent = require('../components/SaveJourneyPattern')
const mapStateToProps = (state) => {
return {
journeyPatterns: state.journeyPatterns,
+ editMode: state.editMode,
page: state.pagination.page,
status: state.status
}
}
-const SaveJourneyPattern = connect(mapStateToProps)(SaveJourneyPatternComponent)
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onEnterEditMode: () => {
+ dispatch(actions.enterEditMode())
+ },
+ onSubmitJourneyPattern: (next, state) => {
+ actions.submitJourneyPattern(dispatch, state, next)
+ }
+ }
+}
+
+const SaveJourneyPattern = connect(mapStateToProps, mapDispatchToProps)(SaveJourneyPatternComponent)
module.exports = SaveJourneyPattern
diff --git a/app/assets/javascripts/es6_browserified/journey_patterns/index.js b/app/assets/javascripts/es6_browserified/journey_patterns/index.js
index b06957e0f..ca9efd2d0 100644
--- a/app/assets/javascripts/es6_browserified/journey_patterns/index.js
+++ b/app/assets/javascripts/es6_browserified/journey_patterns/index.js
@@ -12,6 +12,7 @@ var App = require('./components/App')
// var promise = require('redux-promise')
var initialState = {
+ editMode: false,
status: {
policy: window.perms,
fetchSuccess: true,
@@ -35,7 +36,7 @@ var initialState = {
let store = createStore(
journeyPatternsApp,
- initialState
+ initialState,
// applyMiddleware(thunkMiddleware, promise, loggerMiddleware)
)
diff --git a/app/assets/javascripts/es6_browserified/journey_patterns/reducers/editMode.js b/app/assets/javascripts/es6_browserified/journey_patterns/reducers/editMode.js
new file mode 100644
index 000000000..2e8af1aa8
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/journey_patterns/reducers/editMode.js
@@ -0,0 +1,12 @@
+const editMode = (state = {}, action ) => {
+ switch (action.type) {
+ case "ENTER_EDIT_MODE":
+ return true
+ case "EXIT_EDIT_MODE":
+ return false
+ default:
+ return state
+ }
+}
+
+module.exports = editMode
diff --git a/app/assets/javascripts/es6_browserified/journey_patterns/reducers/index.js b/app/assets/javascripts/es6_browserified/journey_patterns/reducers/index.js
index aa35adf0e..a9c28b83e 100644
--- a/app/assets/javascripts/es6_browserified/journey_patterns/reducers/index.js
+++ b/app/assets/javascripts/es6_browserified/journey_patterns/reducers/index.js
@@ -1,4 +1,5 @@
var combineReducers = require('redux').combineReducers
+var editMode = require('./editMode')
var status = require('./status')
var journeyPatterns = require('./journeyPatterns')
var pagination = require('./pagination')
@@ -6,6 +7,7 @@ var modal = require('./modal')
var stopPointsList = require('./stopPointsList')
const journeyPatternsApp = combineReducers({
+ editMode,
status,
journeyPatterns,
pagination,
diff --git a/app/assets/javascripts/es6_browserified/journey_patterns/reducers/status.js b/app/assets/javascripts/es6_browserified/journey_patterns/reducers/status.js
index d7ef12d0b..07bbdc249 100644
--- a/app/assets/javascripts/es6_browserified/journey_patterns/reducers/status.js
+++ b/app/assets/javascripts/es6_browserified/journey_patterns/reducers/status.js
@@ -11,6 +11,10 @@ const status = (state = {}, action) => {
return _.assign({}, state, {fetchSuccess: true, isFetching: false})
case 'RECEIVE_ERRORS':
return _.assign({}, state, {isFetching: false})
+ case 'ENTER_EDIT_MODE':
+ return _.assign({}, state, {editMode: true})
+ case 'EXIT_EDIT_MODE':
+ return _.assign({}, state, {editMode: false})
default:
return state
}
diff --git a/app/assets/javascripts/es6_browserified/time_tables/actions/index.js b/app/assets/javascripts/es6_browserified/time_tables/actions/index.js
index 951664129..61667f5ab 100644
--- a/app/assets/javascripts/es6_browserified/time_tables/actions/index.js
+++ b/app/assets/javascripts/es6_browserified/time_tables/actions/index.js
@@ -96,34 +96,41 @@ const actions = {
closePeriodForm: () => ({
type: 'CLOSE_PERIOD_FORM'
}),
+ resetModalErrors: () => ({
+ type: 'RESET_MODAL_ERRORS'
+ }),
updatePeriodForm: (val, group, selectType) => ({
type: 'UPDATE_PERIOD_FORM',
val,
group,
selectType
}),
- validatePeriodForm: (modalProps, timeTablePeriods, metas) => ({
+ validatePeriodForm: (modalProps, timeTablePeriods, metas, timetableInDates) => ({
type: 'VALIDATE_PERIOD_FORM',
modalProps,
timeTablePeriods,
- metas
+ metas,
+ timetableInDates
}),
- includeDateInPeriod: (index, dayTypes) => ({
+ includeDateInPeriod: (index, dayTypes, date) => ({
type: 'INCLUDE_DATE_IN_PERIOD',
index,
- dayTypes
+ dayTypes,
+ date
}),
- excludeDateFromPeriod: (index, dayTypes) => ({
+ excludeDateFromPeriod: (index, dayTypes, date) => ({
type: 'EXCLUDE_DATE_FROM_PERIOD',
index,
- dayTypes
+ dayTypes,
+ date
}),
openConfirmModal : (callback) => ({
type : 'OPEN_CONFIRM_MODAL',
callback
}),
- showErrorModal: () => ({
- type: 'OPEN_ERROR_MODAL'
+ showErrorModal: (error) => ({
+ type: 'OPEN_ERROR_MODAL',
+ error
}),
closeModal : () => ({
type : 'CLOSE_MODAL'
@@ -161,16 +168,15 @@ const actions = {
// We compare periods & currentDate, to determine if it is included or not
let testDate = false
periods.map((p, i) => {
- if(p.deleted){
- return false
- }
+ if (p.deleted) return false
+
let begin = new Date(p.period_start)
let end = new Date(p.period_end)
if(testDate === false){
if(currentDate >= begin && currentDate <= end) {
testDate = true
- p.include_date = false
+ // p.include_date = false
}
}
})
@@ -187,11 +193,11 @@ const actions = {
})
return improvedCM
},
-
checkConfirmModal: (event, callback, stateChanged, dispatch, metas, timetable) => {
- if(stateChanged === true){
- if(timetable.time_table_periods.length == 0 && _.some(metas.day_types)){
- return actions.showErrorModal()
+ if(stateChanged){
+ const error = actions.errorModalKey(timetable.time_table_periods, metas.day_types)
+ if(error){
+ return actions.showErrorModal(error)
}else{
return actions.openConfirmModal(callback)
}
@@ -203,7 +209,7 @@ const actions = {
formatDate: (props) => {
return props.year + '-' + props.month + '-' + props.day
},
- checkErrorsInPeriods: (start, end, index, periods) => {
+ checkErrorsInPeriods: (start, end, index, periods, days) => {
let error = ''
start = new Date(start)
end = new Date(end)
@@ -215,6 +221,18 @@ const actions = {
})
return error
},
+ checkErrorsInDates: (start, end, in_days) => {
+ let error = ''
+ start = new Date(start)
+ end = new Date(end)
+
+ _.each(in_days, ({date}) => {
+ if (start <= new Date(date) && end >= new Date(date)) {
+ error = 'Une période ne peut chevaucher une date dans un calendrier'
+ }
+ })
+ return error
+ },
fetchTimeTables: (dispatch, nextPage) => {
let urlJSON = window.location.pathname.split('/', 5).join('/')
// console.log(nextPage)
@@ -275,6 +293,31 @@ const actions = {
}
}
})
+ },
+ errorModalKey: (periods, dayTypes) => {
+ const withoutPeriodsWithDaysTypes = _.reject(periods, 'deleted').length == 0 && _.some(dayTypes) && "withoutPeriodsWithDaysTypes"
+ const withPeriodsWithoutDayTypes = _.reject(periods, 'deleted').length > 0 && _.every(dayTypes, dt => dt == false) && "withPeriodsWithoutDayTypes"
+
+ return (withoutPeriodsWithDaysTypes || withPeriodsWithoutDayTypes) && (withoutPeriodsWithDaysTypes ? "withoutPeriodsWithDaysTypes" : "withPeriodsWithoutDayTypes")
+
+ },
+ errorModalMessage: (errorKey) => {
+ switch (errorKey) {
+ case "withoutPeriodsWithDaysTypes":
+ return window.I18n.fr.time_tables.edit.error_modal.withoutPeriodsWithDaysTypes
+ case "withPeriodsWithoutDayTypes":
+ return window.I18n.fr.time_tables.edit.error_modal.withPeriodsWithoutDayTypes
+ default:
+ return errorKey
+
+ }
+ },
+ checkIfTTHasDate: (dates, date) => {
+ if (_.some(dates, date)) {
+ return _.reject(dates, ['date', date.date])
+ } else {
+ return dates.concat(date)
+ }
}
}
diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/ErrorModal.js b/app/assets/javascripts/es6_browserified/time_tables/components/ErrorModal.js
index 31ed256ea..4e8f7e363 100644
--- a/app/assets/javascripts/es6_browserified/time_tables/components/ErrorModal.js
+++ b/app/assets/javascripts/es6_browserified/time_tables/components/ErrorModal.js
@@ -1,18 +1,19 @@
var React = require('react')
var Component = require('react').Component
var PropTypes = require('react').PropTypes
+var errorModalMessage = require('../actions').errorModalMessage
-const ErrorModal = ({dispatch, modal, onModalClose}) => (
+const ErrorModal = ({dispatch, modal, I18n, onModalClose}) => (
<div className={ 'modal fade ' + ((modal.type == 'error') ? 'in' : '') } id='ErrorModal'>
<div className='modal-container'>
<div className='modal-dialog'>
<div className='modal-content'>
<div className='modal-header'>
- <h4 className='modal-title'>Erreur</h4>
+ <h4 className='modal-title'>{window.I18n.fr.time_tables.edit.error_modal.title}</h4>
</div>
<div className='modal-body'>
<div className='mt-md mb-md'>
- <p>Un calendrier d'application ne peut pas avoir de journée(s) d'application sans période(s).</p>
+ <p>{errorModalMessage(modal.modalProps.error)}</p>
</div>
</div>
<div className='modal-footer'>
diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/ExceptionsInDay.js b/app/assets/javascripts/es6_browserified/time_tables/components/ExceptionsInDay.js
index 10b558373..4879e537f 100644
--- a/app/assets/javascripts/es6_browserified/time_tables/components/ExceptionsInDay.js
+++ b/app/assets/javascripts/es6_browserified/time_tables/components/ExceptionsInDay.js
@@ -20,7 +20,7 @@ class ExceptionsInDay extends Component {
data-actiontype='remove'
onClick={(e) => {
$(e.currentTarget).toggleClass('active')
- this.props.onExcludeDateFromPeriod(this.props.index, this.props.metas.day_types)
+ this.props.onExcludeDateFromPeriod(this.props.index, this.props.metas.day_types, this.props.currentDate)
}}
>
<span className='fa fa-times'></span>
@@ -36,7 +36,7 @@ class ExceptionsInDay extends Component {
data-actiontype='add'
onClick={(e) => {
$(e.currentTarget).toggleClass('active')
- this.props.onIncludeDateInPeriod(this.props.index, this.props.metas.day_types)
+ this.props.onIncludeDateInPeriod(this.props.index, this.props.metas.day_types, this.props.currentDate)
}}
>
<span className='fa fa-plus'></span>
diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/PeriodForm.js b/app/assets/javascripts/es6_browserified/time_tables/components/PeriodForm.js
index 028974fc8..3234a3fd7 100644
--- a/app/assets/javascripts/es6_browserified/time_tables/components/PeriodForm.js
+++ b/app/assets/javascripts/es6_browserified/time_tables/components/PeriodForm.js
@@ -1,5 +1,6 @@
var React = require('react')
var PropTypes = require('react').PropTypes
+var _ = require('lodash')
let monthsArray = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre']
const formatNumber = (val) => {
@@ -107,7 +108,7 @@ const PeriodForm = ({modal, timetable, metas, onOpenAddPeriodForm, onClosePeriod
<button
type='button'
className='btn btn-outline-primary mr-sm'
- onClick={() => onValidatePeriodForm(modal.modalProps, timetable.time_table_periods, metas)}
+ onClick={() => onValidatePeriodForm(modal.modalProps, timetable.time_table_periods, metas, _.filter(timetable.time_table_dates, ['in_out', true]))}
>
Valider
</button>
diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/SaveTimetable.js b/app/assets/javascripts/es6_browserified/time_tables/components/SaveTimetable.js
index e8c0aa3ba..779fd8e25 100644
--- a/app/assets/javascripts/es6_browserified/time_tables/components/SaveTimetable.js
+++ b/app/assets/javascripts/es6_browserified/time_tables/components/SaveTimetable.js
@@ -10,6 +10,8 @@ class SaveTimetable extends Component{
}
render() {
+ const error = actions.errorModalKey(this.props.timetable.time_table_periods, this.props.metas.day_types)
+
return (
<div className='row mt-md'>
<div className='col-lg-12 text-right'>
@@ -19,9 +21,9 @@ class SaveTimetable extends Component{
type='button'
onClick={e => {
e.preventDefault()
- if(this.props.timetable.time_table_periods.length == 0 && _.some(this.props.metas.day_types)){
- this.props.onShowErrorModal()
- }else{
+ if (error) {
+ this.props.onShowErrorModal(error)
+ } else {
actions.submitTimetable(this.props.getDispatch(), this.props.timetable, this.props.metas)
}
}}
diff --git a/app/assets/javascripts/es6_browserified/time_tables/components/Timetable.js b/app/assets/javascripts/es6_browserified/time_tables/components/Timetable.js
index d562655b9..3af1a11a4 100644
--- a/app/assets/javascripts/es6_browserified/time_tables/components/Timetable.js
+++ b/app/assets/javascripts/es6_browserified/time_tables/components/Timetable.js
@@ -79,6 +79,7 @@ class Timetable extends Component{
<ExceptionsInDay
index={i}
value={this.props.timetable}
+ currentDate={d.date}
metas={this.props.metas}
blueDaytype={this.props.metas.day_types[d.wday]}
onExcludeDateFromPeriod={this.props.onExcludeDateFromPeriod}
diff --git a/app/assets/javascripts/es6_browserified/time_tables/containers/ErrorModal.js b/app/assets/javascripts/es6_browserified/time_tables/containers/ErrorModal.js
index 16a7d45dd..e0b2c1240 100644
--- a/app/assets/javascripts/es6_browserified/time_tables/containers/ErrorModal.js
+++ b/app/assets/javascripts/es6_browserified/time_tables/containers/ErrorModal.js
@@ -12,6 +12,7 @@ const mapDispatchToProps = (dispatch) => {
return {
onModalClose: () =>{
dispatch(actions.closeModal())
+ dispatch(actions.resetModalErrors())
}
}
}
diff --git a/app/assets/javascripts/es6_browserified/time_tables/containers/PeriodForm.js b/app/assets/javascripts/es6_browserified/time_tables/containers/PeriodForm.js
index 7f2db785a..723a4a7fb 100644
--- a/app/assets/javascripts/es6_browserified/time_tables/containers/PeriodForm.js
+++ b/app/assets/javascripts/es6_browserified/time_tables/containers/PeriodForm.js
@@ -7,7 +7,7 @@ const mapStateToProps = (state) => {
return {
modal: state.modal,
timetable: state.timetable,
- metas: state.metas
+ metas: state.metas,
}
}
@@ -27,8 +27,8 @@ const mapDispatchToProps = (dispatch) => {
val = (val < 10) ? '0' + String(val) : String(val)
dispatch(actions.updatePeriodForm(val, group, 'day'))
},
- onValidatePeriodForm: (modalProps, timeTablePeriods, metas) => {
- dispatch(actions.validatePeriodForm(modalProps, timeTablePeriods, metas))
+ onValidatePeriodForm: (modalProps, timeTablePeriods, metas, timetableInDates) => {
+ dispatch(actions.validatePeriodForm(modalProps, timeTablePeriods, metas, timetableInDates))
}
}
}
diff --git a/app/assets/javascripts/es6_browserified/time_tables/containers/SaveTimetable.js b/app/assets/javascripts/es6_browserified/time_tables/containers/SaveTimetable.js
index b5539e7d8..6287da15b 100644
--- a/app/assets/javascripts/es6_browserified/time_tables/containers/SaveTimetable.js
+++ b/app/assets/javascripts/es6_browserified/time_tables/containers/SaveTimetable.js
@@ -13,8 +13,8 @@ const mapStateToProps = (state) => {
const mapDispatchToProps = (dispatch) => {
return {
- onShowErrorModal: () => {
- dispatch(actions.showErrorModal())
+ onShowErrorModal: (errorKey) => {
+ dispatch(actions.showErrorModal(errorKey))
},
getDispatch: () => {
return dispatch
diff --git a/app/assets/javascripts/es6_browserified/time_tables/containers/Timetable.js b/app/assets/javascripts/es6_browserified/time_tables/containers/Timetable.js
index c6b5fcc6b..639a1e2ab 100644
--- a/app/assets/javascripts/es6_browserified/time_tables/containers/Timetable.js
+++ b/app/assets/javascripts/es6_browserified/time_tables/containers/Timetable.js
@@ -15,11 +15,11 @@ const mapDispatchToProps = (dispatch) => {
onDeletePeriod: (index, dayTypes) =>{
dispatch(actions.deletePeriod(index, dayTypes))
},
- onExcludeDateFromPeriod: (index, dayTypes) => {
- dispatch(actions.excludeDateFromPeriod(index, dayTypes))
+ onExcludeDateFromPeriod: (index, dayTypes, date) => {
+ dispatch(actions.excludeDateFromPeriod(index, dayTypes, date))
},
- onIncludeDateInPeriod: (index, dayTypes) => {
- dispatch(actions.includeDateInPeriod(index, dayTypes))
+ onIncludeDateInPeriod: (index, dayTypes, date) => {
+ dispatch(actions.includeDateInPeriod(index, dayTypes, date))
},
onOpenEditPeriodForm: (period, index) => {
dispatch(actions.openEditPeriodForm(period, index))
diff --git a/app/assets/javascripts/es6_browserified/time_tables/index.js b/app/assets/javascripts/es6_browserified/time_tables/index.js
index 01f8c428e..a91747991 100644
--- a/app/assets/javascripts/es6_browserified/time_tables/index.js
+++ b/app/assets/javascripts/es6_browserified/time_tables/index.js
@@ -22,7 +22,8 @@ var initialState = {
current_month: [],
current_periode_range: '',
periode_range: [],
- time_table_periods: []
+ time_table_periods: [],
+ time_table_dates: []
},
metas: {
comment: '',
@@ -61,7 +62,7 @@ var initialState = {
let store = createStore(
timeTablesApp,
- initialState
+ initialState,
// applyMiddleware(thunkMiddleware, promise, loggerMiddleware)
)
diff --git a/app/assets/javascripts/es6_browserified/time_tables/reducers/modal.js b/app/assets/javascripts/es6_browserified/time_tables/reducers/modal.js
index 69f7b206e..3fe4e43a2 100644
--- a/app/assets/javascripts/es6_browserified/time_tables/reducers/modal.js
+++ b/app/assets/javascripts/es6_browserified/time_tables/reducers/modal.js
@@ -21,7 +21,11 @@ const modal = (state = {}, action) => {
})
case 'OPEN_ERROR_MODAL':
$('#ErrorModal').modal('show')
- return _.assign({}, state, {type: 'error'})
+ newModalProps = _.assign({}, state.modalProps, {error: action.error})
+ return _.assign({}, state, {type: 'error'}, {modalProps: newModalProps})
+ case 'RESET_MODAL_ERRORS':
+ newModalProps = _.assign({}, state.modalProps, {error: ''})
+ return _.assign({}, state, {type: ''}, {modalProps: newModalProps})
case 'CLOSE_PERIOD_FORM':
newModalProps = _.assign({}, state.modalProps, {active: false})
return _.assign({}, state, {modalProps: newModalProps})
@@ -60,7 +64,9 @@ const modal = (state = {}, action) => {
}
let newPeriods = JSON.parse(JSON.stringify(action.timeTablePeriods))
+ let newDays = JSON.parse(JSON.stringify(action.timetableInDates))
let error = actions.checkErrorsInPeriods(period_start, period_end, action.modalProps.index, newPeriods)
+ if (error == '') error = actions.checkErrorsInDates(period_start, period_end, newDays)
newModalProps.error = error
newModalProps.active = (error == '') ? false : true
return _.assign({}, state, {modalProps: newModalProps})
diff --git a/app/assets/javascripts/es6_browserified/time_tables/reducers/pagination.js b/app/assets/javascripts/es6_browserified/time_tables/reducers/pagination.js
index 3d96fb7b7..45fec6b5f 100644
--- a/app/assets/javascripts/es6_browserified/time_tables/reducers/pagination.js
+++ b/app/assets/javascripts/es6_browserified/time_tables/reducers/pagination.js
@@ -26,6 +26,8 @@ const pagination = (state = {}, action) => {
case 'VALIDATE_PERIOD_FORM':
case 'UPDATE_COMMENT':
case 'UPDATE_COLOR':
+ case 'UPDATE_DAY_TYPES':
+ case 'UPDATE_CURRENT_MONTH_FROM_DAYTYPES':
toggleOnConfirmModal('modal')
return _.assign({}, state, {stateChanged: true})
default:
diff --git a/app/assets/javascripts/es6_browserified/time_tables/reducers/timetable.js b/app/assets/javascripts/es6_browserified/time_tables/reducers/timetable.js
index 65cd9231a..64db1ccc1 100644
--- a/app/assets/javascripts/es6_browserified/time_tables/reducers/timetable.js
+++ b/app/assets/javascripts/es6_browserified/time_tables/reducers/timetable.js
@@ -1,6 +1,7 @@
const _ = require('lodash')
var actions = require('../actions')
let newState = {}
+let newDates = []
const timetable = (state = {}, action) => {
switch (action.type) {
@@ -9,7 +10,8 @@ const timetable = (state = {}, action) => {
current_month: action.json.current_month,
current_periode_range: action.json.current_periode_range,
periode_range: action.json.periode_range,
- time_table_periods: action.json.time_table_periods
+ time_table_periods: action.json.time_table_periods,
+ time_table_dates: action.json.time_table_dates
})
return _.assign({}, fetchedState, {current_month: actions.updateSynthesis(fetchedState, actions.strToArrayDayTypes(action.json.day_types))})
case 'RECEIVE_MONTH':
@@ -38,22 +40,24 @@ const timetable = (state = {}, action) => {
newState = _.assign({}, state, {time_table_periods : ttperiods})
return _.assign({}, newState, {current_month: actions.updateSynthesis(newState, action.dayTypes)})
case 'INCLUDE_DATE_IN_PERIOD':
+ newDates = actions.checkIfTTHasDate(state.time_table_dates, {date: action.date, in_out: true})
let newCMi = state.current_month.map((d, i) => {
if(i == action.index){
d.include_date = !d.include_date
}
return d
})
- newState = _.assign({}, state, {current_month: newCMi})
+ newState = _.assign({}, state, {current_month: newCMi, time_table_dates: newDates})
return _.assign({}, newState, {current_month: actions.updateSynthesis(newState, action.dayTypes)})
case 'EXCLUDE_DATE_FROM_PERIOD':
+ newDates = actions.checkIfTTHasDate(state.time_table_dates, {date: action.date, in_out: false})
let newCMe = state.current_month.map((d, i) => {
if(i == action.index){
d.excluded_date = !d.excluded_date
}
return d
})
- newState = _.assign({}, state, {current_month: newCMe})
+ newState = _.assign({}, state, {current_month: newCMe, time_table_dates: newDates})
return _.assign({}, newState, {current_month: actions.updateSynthesis(newState, action.dayTypes)})
case 'UPDATE_CURRENT_MONTH_FROM_DAYTYPES':
return _.assign({}, state, {current_month: actions.updateSynthesis(state, action.dayTypes)})
@@ -64,7 +68,10 @@ const timetable = (state = {}, action) => {
return state
}
let newPeriods = JSON.parse(JSON.stringify(action.timeTablePeriods))
+ let newDays = JSON.parse(JSON.stringify(action.timetableInDates))
let error = actions.checkErrorsInPeriods(period_start, period_end, action.modalProps.index, newPeriods)
+ if (error == '') error = actions.checkErrorsInDates(period_start, period_end, newDays)
+
if(error != ''){
return state
}
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/actions/index.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/actions/index.js
index c30f460d8..2fde0d76f 100644
--- a/app/assets/javascripts/es6_browserified/vehicle_journeys/actions/index.js
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/actions/index.js
@@ -7,6 +7,12 @@ if (!window.Promise) {
var batchActions = require('../batch').batchActions
const actions = {
+ enterEditMode: () => ({
+ type: "ENTER_EDIT_MODE"
+ }),
+ exitEditMode: () => ({
+ type: "EXIT_EDIT_MODE"
+ }),
receiveVehicleJourneys : (json) => ({
type: "RECEIVE_VEHICLE_JOURNEYS",
json
@@ -121,6 +127,9 @@ const actions = {
objectid: selectedCompany.objectid
}
}),
+ unselect2Company: () => ({
+ type: 'UNSELECT_CP_EDIT_MODAL',
+ }),
editVehicleJourney : (data, selectedCompany) => ({
type: 'EDIT_VEHICLEJOURNEY',
data,
@@ -130,13 +139,14 @@ const actions = {
type: 'EDIT_VEHICLEJOURNEY_NOTES',
footnotes
}),
- shiftVehicleJourney : (data) => ({
+ shiftVehicleJourney : (addtionalTime) => ({
type: 'SHIFT_VEHICLEJOURNEY',
- data
+ addtionalTime
}),
- duplicateVehicleJourney : (data, departureDelta) => ({
+ duplicateVehicleJourney : (addtionalTime, duplicateNumber, departureDelta) => ({
type: 'DUPLICATE_VEHICLEJOURNEY',
- data,
+ addtionalTime,
+ duplicateNumber,
departureDelta
}),
deleteVehicleJourneys : () => ({
@@ -371,6 +381,7 @@ const actions = {
dispatch(actions.updateTotalCount(window.currentItemsLength - json.length))
}
window.currentItemsLength = json.length
+ dispatch(actions.exitEditMode())
dispatch(actions.receiveVehicleJourneys(json))
}
}
@@ -384,7 +395,7 @@ const actions = {
},
simplePad: (d) => {
if(d.toString().length == 1){
- return (d < 10) ? '0' + d.toString() : d.toString();
+ return '0' + d.toString()
}else{
return d.toString()
}
@@ -418,8 +429,8 @@ const actions = {
return vjas
},
getDuplicateDelta: (original, newDeparture) => {
- if (original.departure_time.hour != '' && original.departure_time.minute != '' && newDeparture.departure_time.hour != '' && newDeparture.departure_time.minute != ''){
- return (parseInt(newDeparture.departure_time.hour) - parseInt(original.departure_time.hour)) * 60 + (parseInt(newDeparture.departure_time.minute) - parseInt(original.departure_time.minute))
+ if (original.departure_time.hour != '' && original.departure_time.minute != '' && newDeparture.departure_time.hour != undefined && newDeparture.departure_time.minute != undefined){
+ return (newDeparture.departure_time.hour - parseInt(original.departure_time.hour)) * 60 + (newDeparture.departure_time.minute - parseInt(original.departure_time.minute))
}
return 0
},
@@ -431,69 +442,25 @@ const actions = {
vjas.delta = delta
return vjas
},
- checkSchedules: (schedule) => {
- let hours = 0
- let minutes = 0
- if (parseInt(schedule.departure_time.minute) > 59){
- hours = Math.floor(parseInt(schedule.departure_time.minute) / 60)
- minutes = parseInt(schedule.departure_time.minute) % 60
- schedule.departure_time.minute = actions.simplePad(minutes, 'minute')
- schedule.departure_time.hour = parseInt(schedule.departure_time.hour) + hours
- }
- if (parseInt(schedule.arrival_time.minute) > 59){
- hours = Math.floor(parseInt(schedule.arrival_time.minute) / 60)
- minutes = parseInt(schedule.arrival_time.minute) % 60
- schedule.arrival_time.minute = actions.simplePad(minutes, 'minute')
- schedule.arrival_time.hour = parseInt(schedule.arrival_time.hour) + hours
- }
- if (parseInt(schedule.departure_time.minute) < 0){
- hours = Math.floor(parseInt(schedule.departure_time.minute) / 60)
- minutes = (parseInt(schedule.departure_time.minute) % 60) + 60
- if(minutes == 60){
- minutes = 0
- }
- schedule.departure_time.minute = actions.simplePad(minutes, 'minute')
- schedule.departure_time.hour = parseInt(schedule.departure_time.hour) + hours
- }
- if (parseInt(schedule.arrival_time.minute) < 0){
- hours = Math.floor(parseInt(schedule.arrival_time.minute) / 60)
- minutes = (parseInt(schedule.arrival_time.minute) % 60) + 60
- if(minutes == 60){
- minutes = 0
- }
- schedule.arrival_time.minute = actions.simplePad(minutes, 'minute')
- schedule.arrival_time.hour = parseInt(schedule.arrival_time.hour) + hours
- }
+ getShiftedSchedule: ({departure_time, arrival_time}, additional_time) => {
+ // We create dummy dates objects to manipulate time more easily
+ let departureDT = new Date (Date.UTC(2017, 2, 1, parseInt(departure_time.hour), parseInt(departure_time.minute)))
+ let arrivalDT = new Date (Date.UTC(2017, 2, 1, parseInt(arrival_time.hour), parseInt(arrival_time.minute)))
- if(parseInt(schedule.departure_time.hour) > 23){
- schedule.departure_time.hour = '23'
- schedule.departure_time.minute = '59'
- }
- if(parseInt(schedule.arrival_time.hour) > 23){
- schedule.arrival_time.hour = '23'
- schedule.arrival_time.minute = '59'
- }
+ let newDepartureDT = new Date (departureDT.getTime() + additional_time * 60000)
+ let newArrivalDT = new Date (arrivalDT.getTime() + additional_time * 60000)
- if(parseInt(schedule.departure_time.hour) < 0){
- schedule.departure_time.hour = '00'
- schedule.departure_time.minute = '00'
- }
- if(parseInt(schedule.arrival_time.hour) < 0){
- schedule.arrival_time.hour = '00'
- schedule.arrival_time.minute = '00'
+ return {
+ departure_time: {
+ hour: actions.simplePad(newDepartureDT.getUTCHours()),
+ minute: actions.simplePad(newDepartureDT.getUTCMinutes())
+ },
+ arrival_time: {
+ hour: actions.simplePad(newArrivalDT.getUTCHours()),
+ minute: actions.simplePad(newArrivalDT.getUTCMinutes())
+ }
}
-
- schedule.departure_time.hour = actions.simplePad(parseInt(schedule.departure_time.hour), 'hour')
- schedule.arrival_time.hour = actions.simplePad(parseInt(schedule.arrival_time.hour), 'hour')
- // if (parseInt(schedule.departure_time.hour) > 23){
- // schedule.departure_time.hour = parseInt(schedule.departure_time.hour) - 24
- // }
- // if (parseInt(schedule.arrival_time.hour) > 23){
- // schedule.arrival_time.hour = parseInt(schedule.arrival_time.hour) - 24
- // }
- // schedule.departure_time.hour = actions.pad(schedule.departure_time.hour, 'hour')
- // schedule.arrival_time.hour = actions.pad(schedule.arrival_time.hour, 'hour')
- }
+ },
}
module.exports = actions
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/SaveVehicleJourneys.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/SaveVehicleJourneys.js
index 05dda976d..3c45e5758 100644
--- a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/SaveVehicleJourneys.js
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/SaveVehicleJourneys.js
@@ -9,7 +9,7 @@ class SaveVehicleJourneys extends Component{
}
render() {
- if(this.props.filters.policy['vehicle_journeys.update'] == false) {
+ if (this.props.filters.policy['vehicle_journeys.update'] == false) {
return false
}else{
return (
@@ -21,10 +21,10 @@ class SaveVehicleJourneys extends Component{
type='button'
onClick={e => {
e.preventDefault()
- actions.submitVehicleJourneys(this.props.dispatch, this.props.vehicleJourneys)
+ this.props.editMode ? this.props.onSubmitVehicleJourneys(this.props.dispatch, this.props.vehicleJourneys) : this.props.onEnterEditMode()
}}
>
- Valider
+ {this.props.editMode ? "Valider" : "Editer"}
</button>
</form>
</div>
@@ -38,7 +38,9 @@ SaveVehicleJourneys.propTypes = {
vehicleJourneys: PropTypes.array.isRequired,
page: PropTypes.number.isRequired,
status: PropTypes.object.isRequired,
- filters: PropTypes.object.isRequired
+ filters: PropTypes.object.isRequired,
+ onEnterEditMode: PropTypes.func.isRequired,
+ onSubmitVehicleJourneys: PropTypes.func.isRequired
}
module.exports = SaveVehicleJourneys
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/Tools.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/Tools.js
index b417828db..4948e6b1a 100644
--- a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/Tools.js
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/Tools.js
@@ -9,28 +9,34 @@ var NotesEditVehicleJourney = require('../containers/tools/NotesEditVehicleJourn
var TimetablesEditVehicleJourney = require('../containers/tools/TimetablesEditVehicleJourney')
var actions = require('../actions')
-const Tools = ({vehicleJourneys, onCancelSelection}) => {
+const Tools = ({vehicleJourneys, onCancelSelection, filters: {policy}, editMode}) => {
return (
- <div className='select_toolbox'>
- <ul>
- <AddVehicleJourney />
- <DuplicateVehicleJourney />
- <ShiftVehicleJourney />
- <EditVehicleJourney />
- <TimetablesEditVehicleJourney />
- <NotesEditVehicleJourney />
- <DeleteVehicleJourneys />
- </ul>
+ <div>
+ {
+ (policy['vehicle_journeys.create'] && policy['vehicle_journeys.update'] && policy['vehicle_journeys.destroy'] && editMode) &&
+ <div className='select_toolbox'>
+ <ul>
+ <AddVehicleJourney />
+ <DuplicateVehicleJourney />
+ <ShiftVehicleJourney />
+ <EditVehicleJourney />
+ <TimetablesEditVehicleJourney />
+ <NotesEditVehicleJourney />
+ <DeleteVehicleJourneys />
+ </ul>
- <span className='info-msg'>{actions.getSelected(vehicleJourneys).length} course(s) sélectionnée(s)</span>
- <button className='btn btn-xs btn-link pull-right' onClick={onCancelSelection}>Annuler la sélection</button>
+ <span className='info-msg'>{actions.getSelected(vehicleJourneys).length} course(s) sélectionnée(s)</span>
+ <button className='btn btn-xs btn-link pull-right' onClick={onCancelSelection}>Annuler la sélection</button>
+ </div>
+ }
</div>
)
}
Tools.propTypes = {
vehicleJourneys : PropTypes.array.isRequired,
- onCancelSelection: PropTypes.func.isRequired
+ onCancelSelection: PropTypes.func.isRequired,
+ filters: PropTypes.object.isRequired
}
module.exports = Tools
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourney.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourney.js
index f2bd0c3cd..ca6694f61 100644
--- a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourney.js
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourney.js
@@ -58,7 +58,7 @@ class VehicleJourney extends Component {
)}
</div>
- {(this.props.filters.policy['vehicle_journeys.update'] == true) &&
+ {(this.props.filters.policy['vehicle_journeys.update'] == true && this.props.editMode) &&
<div className={(this.props.value.deletable ? 'disabled ' : '') + 'checkbox'}>
<input
id={this.props.index}
@@ -79,13 +79,13 @@ class VehicleJourney extends Component {
<div className={'cellwrap' + (this.cityNameChecker(vj) ? ' headlined' : '')}>
{this.props.filters.toggleArrivals &&
<div data-headline='Arrivée à'>
- <span className={((this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false) ? 'disabled ' : '') + 'input-group time'}>
+ <span className={((this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false || this.props.editMode == false) ? 'disabled ' : '') + 'input-group time'}>
<input
type='number'
min='00'
max='23'
className='form-control'
- disabled={(this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false)}
+ disabled={(this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false || this.props.editMode == false)}
onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', false, false)}}
value={vj.arrival_time['hour']}
/>
@@ -95,7 +95,7 @@ class VehicleJourney extends Component {
min='00'
max='59'
className='form-control'
- disabled={((this.isDisabled(this.props.value.deletable), vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false)}
+ disabled={((this.isDisabled(this.props.value.deletable), vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false || this.props.editMode == false)}
onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'minute', false, false)}}
value={vj.arrival_time['minute']}
/>
@@ -108,13 +108,13 @@ class VehicleJourney extends Component {
}
</div>
<div data-headline='Départ à'>
- <span className={((this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false) ? 'disabled ' : '') + 'input-group time'}>
+ <span className={((this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false || this.props.editMode == false) ? 'disabled ' : '') + 'input-group time'}>
<input
type='number'
min='00'
max='23'
className='form-control'
- disabled={(this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false)}
+ disabled={(this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false || this.props.editMode == false)}
onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', true, this.props.filters.toggleArrivals)}}
value={vj.departure_time['hour']}
/>
@@ -124,7 +124,7 @@ class VehicleJourney extends Component {
min='00'
max='59'
className='form-control'
- disabled={(this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false)}
+ disabled={(this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false || this.props.editMode == false)}
onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, "minute", true, this.props.filters.toggleArrivals)}}
value={vj.departure_time['minute']}
/>
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourneys.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourneys.js
index e8673a25a..8f3f91b25 100644
--- a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourneys.js
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/VehicleJourneys.js
@@ -97,7 +97,7 @@ class VehicleJourneys extends Component{
<div className="alert alert-danger mt-sm">
<strong>Erreur : </strong>
{this.props.vehicleJourneys.map((vj, index) =>
- vj.errors.map((err, i) => {
+ vj.errors && vj.errors.map((err, i) => {
return (
<ul key={i}>
<li>{err}</li>
@@ -131,6 +131,7 @@ class VehicleJourneys extends Component{
value={vj}
key={index}
index={index}
+ editMode={this.props.editMode}
filters={this.props.filters}
onUpdateTime={this.props.onUpdateTime}
onSelectVehicleJourney={this.props.onSelectVehicleJourney}
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/DuplicateVehicleJourney.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/DuplicateVehicleJourney.js
index 34463600a..780b10916 100644
--- a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/DuplicateVehicleJourney.js
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/DuplicateVehicleJourney.js
@@ -7,29 +7,54 @@ var _ = require('lodash')
class DuplicateVehicleJourney extends Component {
constructor(props) {
super(props)
+ this.state = {}
+ this.onFormChange = this.onFormChange.bind(this)
+ }
+
+ componentWillReceiveProps() {
+ if (actions.getSelected(this.props.vehicleJourneys).length > 0) {
+ this.setState((state, props) => {
+ return {
+ duplicate_time_hh: parseInt(this.getDefaultValue('hour')),
+ duplicate_time_mm: parseInt(this.getDefaultValue('minute')),
+ additional_time: 0,
+ duplicate_number: 1
+ }
+ })
+ }
}
handleSubmit() {
if(actions.validateFields(this.refs) == true) {
let newDeparture = {
departure_time : {
- hour: this.refs.duplicate_time_hh.value,
- minute: this.refs.duplicate_time_mm.value
+ hour: this.state.duplicate_time_hh,
+ minute: this.state.duplicate_time_mm
}
}
let val = actions.getDuplicateDelta(_.find(actions.getSelected(this.props.vehicleJourneys)[0].vehicle_journey_at_stops, {'dummy': false}), newDeparture)
- this.refs.additional_time.value = parseInt(this.refs.additional_time.value)
- this.props.onDuplicateVehicleJourney(this.refs, val)
+ this.props.onDuplicateVehicleJourney(this.state.additional_time, this.state.duplicate_number, val)
this.props.onModalClose()
$('#DuplicateVehicleJourneyModal').modal('hide')
}
}
+ onFormChange(e) {
+ let {name, value} = e.target
+ this.setState((state, props) => {
+ return {
+ [name]: parseInt(value)
+ }
+ })
+ }
+
getDefaultValue(type) {
let vjas = _.find(actions.getSelected(this.props.vehicleJourneys)[0].vehicle_journey_at_stops, {'dummy': false})
return vjas.departure_time[type]
}
+
render() {
+
if(this.props.status.isFetching == true) {
return false
}
@@ -65,22 +90,26 @@ class DuplicateVehicleJourney extends Component {
<span className={'input-group time' + (actions.getSelected(this.props.vehicleJourneys).length > 1 ? ' disabled' : '')}>
<input
type='number'
+ name='duplicate_time_hh'
ref='duplicate_time_hh'
min='00'
max='23'
className='form-control'
- defaultValue={this.getDefaultValue('hour')}
- disabled={(actions.getSelected(this.props.vehicleJourneys).length > 1 ? 'disabled' : '')}
+ value={this.state.duplicate_time_hh}
+ onChange={e => this.onFormChange(e)}
+ disabled={actions.getSelected(this.props.vehicleJourneys) && (actions.getSelected(this.props.vehicleJourneys).length > 1 ? 'disabled' : '')}
/>
<span>:</span>
<input
type='number'
+ name='duplicate_time_mm'
ref='duplicate_time_mm'
min='00'
max='59'
className='form-control'
- defaultValue={this.getDefaultValue('minute')}
- disabled={(actions.getSelected(this.props.vehicleJourneys).length > 1 ? 'disabled' : '')}
+ value={this.state.duplicate_time_mm}
+ onChange={e => this.onFormChange(e)}
+ disabled={actions.getSelected(this.props.vehicleJourneys) && (actions.getSelected(this.props.vehicleJourneys).length > 1 ? 'disabled' : '')}
/>
</span>
</span>
@@ -92,11 +121,13 @@ class DuplicateVehicleJourney extends Component {
<input
type='number'
style={{'width': 104}}
+ name='duplicate_number'
ref='duplicate_number'
min='1'
max='20'
- defaultValue='1'
+ value={this.state.duplicate_number}
className='form-control'
+ onChange={e => this.onFormChange(e)}
onKeyDown={(e) => actions.resetValidation(e.currentTarget)}
required
/>
@@ -105,19 +136,21 @@ class DuplicateVehicleJourney extends Component {
<div className='form-group'>
<label className='control-label is-required col-sm-8'>Décalage à partir duquel on créé les courses</label>
- <div className="col-sm-4">
+ <span className="col-sm-4">
<input
type='number'
style={{'width': 104}}
+ name='additional_time'
ref='additional_time'
- min='-59'
- max='59'
- defaultValue='0'
- className='form-control'
+ min='-720'
+ max='720'
+ value={this.state.additional_time}
+ className='form-control disabled'
+ onChange={e => this.onFormChange(e)}
onKeyDown={(e) => actions.resetValidation(e.currentTarget)}
required
- />
- </div>
+ />
+ </span>
</div>
</div>
@@ -131,7 +164,7 @@ class DuplicateVehicleJourney extends Component {
Annuler
</button>
<button
- className='btn btn-primary'
+ className={'btn btn-primary ' + (this.state.additional_time == 0 ? 'disabled' : '')}
type='button'
onClick={this.handleSubmit.bind(this)}
>
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/EditVehicleJourney.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/EditVehicleJourney.js
index d49ea578a..2ff4999c6 100644
--- a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/EditVehicleJourney.js
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/EditVehicleJourney.js
@@ -14,8 +14,10 @@ class EditVehicleJourney extends Component {
var company;
if(this.props.modal.modalProps.selectedCompany) {
company = this.props.modal.modalProps.selectedCompany
- } else {
+ } else if (typeof this.props.modal.modalProps.vehicleJourney.company === Object) {
company = this.props.modal.modalProps.vehicleJourney.company
+ } else {
+ company = undefined
}
this.props.onEditVehicleJourney(this.refs, company)
this.props.onModalClose()
@@ -96,6 +98,7 @@ class EditVehicleJourney extends Component {
<CompanySelect2
company = {this.props.modal.modalProps.vehicleJourney.company}
onSelect2Company = {(e) => this.props.onSelect2Company(e)}
+ onUnselect2Company = {() => this.props.onUnselect2Company()}
/>
</div>
</div>
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/ShiftVehicleJourney.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/ShiftVehicleJourney.js
index 269bb1b8c..dd0bade39 100644
--- a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/ShiftVehicleJourney.js
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/ShiftVehicleJourney.js
@@ -6,16 +6,27 @@ var actions = require('../../actions')
class ShiftVehicleJourney extends Component {
constructor(props) {
super(props)
+ this.state = {
+ additional_time: 0
+ }
}
handleSubmit() {
if(actions.validateFields(this.refs) == true) {
- this.props.onShiftVehicleJourney(this.refs)
+ this.props.onShiftVehicleJourney(this.state.additional_time)
this.props.onModalClose()
$('#ShiftVehicleJourneyModal').modal('hide')
}
}
+ handleAdditionalTimeChange() {
+ this.setState((state, props) => {
+ return {
+ additional_time: parseInt(this.refs.additional_time.value)
+ }
+ })
+ }
+
render() {
if(this.props.status.isFetching == true) {
return false
@@ -53,14 +64,16 @@ class ShiftVehicleJourney extends Component {
<label className='control-label is-required'>Avec un décalage de</label>
<input
type='number'
+ style={{'width': 104}}
ref='additional_time'
- min='-59'
- max='59'
+ min='-720'
+ max='720'
+ value={this.state.additional_time}
className='form-control'
- defaultValue='0'
+ onChange={this.handleAdditionalTimeChange.bind(this)}
onKeyDown={(e) => actions.resetValidation(e.currentTarget)}
required
- />
+ />
</div>
</div>
</div>
@@ -75,7 +88,7 @@ class ShiftVehicleJourney extends Component {
Annuler
</button>
<button
- className='btn btn-primary'
+ className={'btn btn-primary ' + (this.state.additional_time == 0 ? 'disabled' : '')}
type='button'
onClick={this.handleSubmit.bind(this)}
>
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/select2s/CompanySelect2.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/select2s/CompanySelect2.js
index d277be003..c1ce0e92a 100644
--- a/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/select2s/CompanySelect2.js
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/components/tools/select2s/CompanySelect2.js
@@ -20,10 +20,11 @@ class BSelect4 extends React.Component{
data={(this.props.company) ? [this.props.company.name] : undefined}
value={(this.props.company) ? this.props.company.name : undefined}
onSelect={(e) => this.props.onSelect2Company(e) }
+ onUnselect={() => this.props.onUnselect2Company()}
multiple={false}
ref='company_id'
options={{
- allowClear: false,
+ allowClear: true,
theme: 'bootstrap',
width: '100%',
placeholder: 'Filtrer par transporteur...',
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/SaveVehicleJourneys.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/SaveVehicleJourneys.js
index 87bbe5353..c1ce90d38 100644
--- a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/SaveVehicleJourneys.js
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/SaveVehicleJourneys.js
@@ -5,6 +5,7 @@ var SaveVehicleJourneysComponent = require('../components/SaveVehicleJourneys')
const mapStateToProps = (state) => {
return {
+ editMode: state.editMode,
vehicleJourneys: state.vehicleJourneys,
page: state.pagination.page,
status: state.status,
@@ -12,6 +13,17 @@ const mapStateToProps = (state) => {
}
}
-const SaveVehicleJourneys = connect(mapStateToProps)(SaveVehicleJourneysComponent)
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onEnterEditMode: () => {
+ dispatch(actions.enterEditMode())
+ },
+ onSubmitVehicleJourneys: (next, state) => {
+ actions.submitVehicleJourneys(dispatch, state, next)
+ }
+ }
+}
+
+const SaveVehicleJourneys = connect(mapStateToProps, mapDispatchToProps)(SaveVehicleJourneysComponent)
module.exports = SaveVehicleJourneys
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Tools.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Tools.js
index 35f492c98..a4b3056ac 100644
--- a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Tools.js
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/Tools.js
@@ -4,7 +4,9 @@ var actions = require('../actions')
const mapStateToProps = (state) => {
return {
- vehicleJourneys: state.vehicleJourneys
+ vehicleJourneys: state.vehicleJourneys,
+ editMode: state.editMode,
+ filters: state.filters
}
}
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/VehicleJourneysList.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/VehicleJourneysList.js
index 176a68500..f834e4457 100644
--- a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/VehicleJourneysList.js
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/VehicleJourneysList.js
@@ -4,6 +4,7 @@ var VehicleJourneys = require('../components/VehicleJourneys')
const mapStateToProps = (state) => {
return {
+ editMode: state.editMode,
vehicleJourneys: state.vehicleJourneys,
status: state.status,
filters: state.filters,
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/DuplicateVehicleJourney.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/DuplicateVehicleJourney.js
index 224b52a19..70e8fde4d 100644
--- a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/DuplicateVehicleJourney.js
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/DuplicateVehicleJourney.js
@@ -19,8 +19,8 @@ const mapDispatchToProps = (dispatch) => {
onOpenDuplicateModal: () =>{
dispatch(actions.openDuplicateModal())
},
- onDuplicateVehicleJourney: (data, departureDelta) =>{
- dispatch(actions.duplicateVehicleJourney(data, departureDelta))
+ onDuplicateVehicleJourney: (addtionalTime, duplicateNumber, departureDelta) =>{
+ dispatch(actions.duplicateVehicleJourney(addtionalTime, duplicateNumber, departureDelta))
}
}
}
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/EditVehicleJourney.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/EditVehicleJourney.js
index 8f4d43519..ac9772b8a 100644
--- a/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/EditVehicleJourney.js
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/containers/tools/EditVehicleJourney.js
@@ -24,7 +24,10 @@ const mapDispatchToProps = (dispatch) => {
},
onSelect2Company: (e) => {
dispatch(actions.select2Company(e.params.data))
- }
+ },
+ onUnselect2Company: () => {
+ dispatch(actions.unselect2Company())
+ },
}
}
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/index.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/index.js
index 97aa60526..7872899dc 100644
--- a/app/assets/javascripts/es6_browserified/vehicle_journeys/index.js
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/index.js
@@ -8,10 +8,10 @@ var actions = require("./actions")
var enableBatching = require('./batch').enableBatching
// logger, DO NOT REMOVE
-// var applyMiddleware = require('redux').applyMiddleware
-// var createLogger = require('redux-logger')
-// var thunkMiddleware = require('redux-thunk').default
-// var promise = require('redux-promise')
+var applyMiddleware = require('redux').applyMiddleware
+var createLogger = require('redux-logger')
+var thunkMiddleware = require('redux-thunk').default
+var promise = require('redux-promise')
var selectedJP = []
@@ -19,6 +19,7 @@ if (window.journeyPatternId)
selectedJP.push(window.journeyPatternId)
var initialState = {
+ editMode: false,
filters: {
selectedJourneyPatterns : selectedJP,
policy: window.perms,
@@ -85,12 +86,12 @@ if (window.jpOrigin){
initialState.filters.queryString = actions.encodeParams(params)
}
-// const loggerMiddleware = createLogger()
+const loggerMiddleware = createLogger()
let store = createStore(
enableBatching(vehicleJourneysApp),
- initialState
- // applyMiddleware(thunkMiddleware, promise, loggerMiddleware)
+ initialState,
+ applyMiddleware(thunkMiddleware, promise, loggerMiddleware)
)
render(
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/editMode.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/editMode.js
new file mode 100644
index 000000000..2e8af1aa8
--- /dev/null
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/editMode.js
@@ -0,0 +1,12 @@
+const editMode = (state = {}, action ) => {
+ switch (action.type) {
+ case "ENTER_EDIT_MODE":
+ return true
+ case "EXIT_EDIT_MODE":
+ return false
+ default:
+ return state
+ }
+}
+
+module.exports = editMode
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/index.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/index.js
index bd4d7226b..4e0839102 100644
--- a/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/index.js
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/index.js
@@ -4,6 +4,7 @@ var pagination = require('./pagination')
var modal = require('./modal')
var status = require('./status')
var filters = require('./filters')
+var editMode = require('./editMode')
var stopPointsList = require('./stopPointsList')
const vehicleJourneysApp = combineReducers({
@@ -12,6 +13,7 @@ const vehicleJourneysApp = combineReducers({
modal,
status,
filters,
+ editMode,
stopPointsList
})
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/modal.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/modal.js
index 229fd2058..1e5ff4294 100644
--- a/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/modal.js
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/modal.js
@@ -57,6 +57,9 @@ const modal = (state = {}, action) => {
case 'SELECT_CP_EDIT_MODAL':
newModalProps = _.assign({}, state.modalProps, {selectedCompany : action.selectedItem})
return _.assign({}, state, {modalProps: newModalProps})
+ case 'UNSELECT_CP_EDIT_MODAL':
+ newModalProps = _.assign({}, state.modalProps, {selectedCompany : undefined})
+ return _.assign({}, state, {modalProps: newModalProps})
case 'SELECT_TT_CALENDAR_MODAL':
newModalProps = _.assign({}, state.modalProps, {selectedTimetable : action.selectedItem})
return _.assign({}, state, {modalProps: newModalProps})
diff --git a/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/vehicleJourneys.js b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/vehicleJourneys.js
index d463d4b8f..d397e7632 100644
--- a/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/vehicleJourneys.js
+++ b/app/assets/javascripts/es6_browserified/vehicle_journeys/reducers/vehicleJourneys.js
@@ -51,17 +51,8 @@ const vehicleJourney= (state = {}, action, keep) => {
let shiftedArray, shiftedSchedule, shiftedVjas
shiftedArray = state.vehicle_journey_at_stops.map((vjas, i) => {
if (!vjas.dummy){
- shiftedSchedule = {
- departure_time: {
- hour: vjas.departure_time.hour,
- minute: actions.simplePad(parseInt(vjas.departure_time.minute) + parseInt(action.data.additional_time.value))
- },
- arrival_time: {
- hour: vjas.arrival_time.hour,
- minute: actions.simplePad(parseInt(vjas.arrival_time.minute) + parseInt(action.data.additional_time.value))
- }
- }
- actions.checkSchedules(shiftedSchedule)
+ shiftedSchedule = actions.getShiftedSchedule(vjas, action.addtionalTime)
+
shiftedVjas = _.assign({}, state.vehicle_journey_at_stops[i], shiftedSchedule)
vjas = _.assign({}, state.vehicle_journey_at_stops[i], shiftedVjas)
if(!keep){
@@ -181,13 +172,13 @@ const vehicleJourneys = (state = [], action) => {
let dupeVj
let dupes = []
let selectedIndex
- let val = action.data.additional_time.value
+ let val = action.addtionalTime
let departureDelta = action.departureDelta
state.map((vj, i) => {
if(vj.selected){
selectedIndex = i
- for (i = 0; i< action.data.duplicate_number.value; i++){
- action.data.additional_time.value = (parseInt(val) * (i + 1)) + departureDelta
+ for (i = 0; i< action.duplicateNumber; i++){
+ action.addtionalTime = (val * (i + 1)) + departureDelta
dupeVj = vehicleJourney(vj, action, false)
dupeVj.published_journey_name = dupeVj.published_journey_name + '-' + i
dupeVj.selected = false
diff --git a/app/assets/javascripts/workbench.coffee b/app/assets/javascripts/workbench.coffee
index 971462e98..0e9fe62a3 100644
--- a/app/assets/javascripts/workbench.coffee
+++ b/app/assets/javascripts/workbench.coffee
@@ -1,6 +1,6 @@
$(document).on("click", "#referential_filter_btn", (e) ->
dates = [1, 2, 3].reduce (arr, key) ->
- arr.push $("#q_validity_period_begin_gteq_#{key}i").val(), $("#q_validity_period_end_gteq__#{key}i").val()
+ arr.push $("#q_validity_period_begin_gteq_#{key}i").val(), $("#q_validity_period_end_lteq_#{key}i").val()
arr
, []
@@ -8,6 +8,10 @@
noDate = dates.every (date) -> !date
+ console.log("valid dates :", validDate)
+ console.log("no dates :", noDate)
+ console.log(dates)
+
unless (validDate || noDate)
e.preventDefault()
alert(window.I18n.fr.referentials.error_period_filter)
diff --git a/app/assets/stylesheets/components/_tables.sass b/app/assets/stylesheets/components/_tables.sass
index 3fc92d348..b50721f27 100644
--- a/app/assets/stylesheets/components/_tables.sass
+++ b/app/assets/stylesheets/components/_tables.sass
@@ -85,6 +85,67 @@
border-top: 2px solid $darkgrey
margin-top: 15px
+ // Overhead
+ > thead > tr.overhead
+ > th
+ font-size: 1.4rem
+ text-align: center
+
+ &.overheaded-default
+ background-color: rgba($grey, 0.15)
+ background-clip: padding-box
+ border-left: 2px solid rgba($grey, 0.15)
+ border-right: 2px solid rgba($grey, 0.15)
+
+ &.overheaded-danger
+ background-color: $red
+ border-left: 2px solid $red
+ border-right: 2px solid $red
+ color: #fff
+
+ &.overheaded-warning
+ background-color: $orange
+ border-left: 2px solid $orange
+ border-right: 2px solid $orange
+ color: #fff
+
+ &.overheaded-success
+ background-color: $green
+ border-left: 2px solid $green
+ border-right: 2px solid $green
+ color: #fff
+
+ td, th
+ &.overheaded-default
+ border-left: 2px solid rgba($grey, 0.15)
+ border-right: 2px solid rgba($grey, 0.15)
+
+ &.overheaded-danger
+ border-left: 2px solid $red
+ border-right: 2px solid $red
+
+ &.overheaded-warning
+ border-left: 2px solid $orange
+ border-right: 2px solid $orange
+
+ &.overheaded-success
+ border-left: 2px solid $green
+ border-right: 2px solid $green
+
+ tr:last-child
+ td
+ &.overheaded-default
+ border-bottom: 2px solid rgba($grey, 0.15)
+
+ &.overheaded-danger
+ border-bottom: 2px solid $red
+
+ &.overheaded-warning
+ border-bottom: 2px solid $orange
+
+ &.overheaded-success
+ border-bottom: 2px solid $green
+
// Specific for tables displaying stop points
&.has-stoppoints
tbody
@@ -144,7 +205,6 @@
margin-top: -8px
-
// select_toolbox
.select_toolbox
padding: 10px
diff --git a/app/concerns/configurable.rb b/app/concerns/configurable.rb
new file mode 100644
index 000000000..c7d0f1fd9
--- /dev/null
+++ b/app/concerns/configurable.rb
@@ -0,0 +1,26 @@
+module Configurable
+
+ module ClassMethods
+ def config &blk
+ blk ? blk.(configuration) : configuration
+ end
+
+ private
+ def configuration
+ @__configuration__ ||= Rails::Application::Configuration.new
+ end
+ end
+
+ module InstanceMethods
+ private
+
+ def config
+ self.class.config
+ end
+ end
+
+ def self.included(into)
+ into.extend ClassMethods
+ into.send :include, InstanceMethods
+ end
+end
diff --git a/app/controllers/api/v1/chouette_controller.rb b/app/controllers/api/v1/chouette_controller.rb
index 7805074ee..98c2fff05 100644
--- a/app/controllers/api/v1/chouette_controller.rb
+++ b/app/controllers/api/v1/chouette_controller.rb
@@ -7,7 +7,6 @@ module Api
before_action :authenticate
private
-
def authenticate
authenticate_or_request_with_http_token do |token, options|
@referential = Api::V1::ApiKey.referential_from_token(token)
@@ -16,10 +15,10 @@ module Api
switch_referential if @api_key
end
end
+
def switch_referential
Apartment::Tenant.switch!(@api_key.referential.slug)
- end
-
+ end
end
end
end
diff --git a/app/controllers/api/v1/iboo_controller.rb b/app/controllers/api/v1/iboo_controller.rb
new file mode 100644
index 000000000..4db9e9007
--- /dev/null
+++ b/app/controllers/api/v1/iboo_controller.rb
@@ -0,0 +1,21 @@
+class Api::V1::IbooController < Api::V1::ChouetteController
+ protected
+ def begin_of_association_chain
+ @current_organisation
+ end
+
+ private
+ def authenticate
+ authenticate_with_http_basic do |code, token|
+ if organisation = Organisation.find_by(code: code)
+ if organisation.api_keys.exists?(token: token)
+ @current_organisation = organisation
+ end
+ end
+ end
+
+ unless @current_organisation
+ request_http_basic_authentication
+ end
+ end
+end
diff --git a/app/controllers/api/v1/imports_controller.rb b/app/controllers/api/v1/imports_controller.rb
new file mode 100644
index 000000000..6050418d8
--- /dev/null
+++ b/app/controllers/api/v1/imports_controller.rb
@@ -0,0 +1,15 @@
+class Api::V1::ImportsController < Api::V1::IbooController
+ defaults :resource_class => WorkbenchImport
+ belongs_to :workbench
+
+ def create
+ args = workbench_import_params.merge(creator: 'Webservice')
+ @import = parent.workbench_imports.create(args)
+ create!
+ end
+
+ private
+ def workbench_import_params
+ params.require(:workbench_import).permit(:file, :name)
+ end
+end
diff --git a/app/controllers/api/v1/netex_imports_controller.rb b/app/controllers/api/v1/netex_imports_controller.rb
new file mode 100644
index 000000000..8f7c8e67e
--- /dev/null
+++ b/app/controllers/api/v1/netex_imports_controller.rb
@@ -0,0 +1,55 @@
+module Api
+ module V1
+ class NetexImportsController < ChouetteController
+ include ControlFlow
+
+ def create
+ respond_to do | format |
+ format.json(&method(:create_models))
+ end
+ end
+
+
+ private
+
+ def find_workbench
+ @workbench = Workbench.find(netex_import_params['workbench_id'])
+ rescue ActiveRecord::RecordNotFound
+ render json: {errors: {'workbench_id' => 'missing'}}, status: 406
+ finish_action!
+ end
+
+ def create_models
+ find_workbench
+ create_referential
+ create_netex_import
+ end
+
+ def create_netex_import
+ @netex_import = NetexImport.new(netex_import_params.merge(referential_id: @new_referential.id, creator: 'Webservice'))
+ @netex_import.save!
+ rescue ActiveRecord::RecordInvalid
+ render json: {errors: @netex_import.errors}, status: 406
+ finish_action!
+ end
+
+ def create_referential
+ @new_referential =
+ Referential.new(
+ name: netex_import_params['name'],
+ organisation_id: @workbench.organisation_id,
+ workbench_id: @workbench.id)
+ @new_referential.save!
+ rescue ActiveRecord::RecordInvalid
+ render json: {errors: @new_referential.errors}, status: 406
+ finish_action!
+ end
+
+ def netex_import_params
+ params
+ .require('netex_import')
+ .permit(:file, :name, :workbench_id)
+ end
+ end
+ end
+end
diff --git a/app/controllers/api/v1/workbenches_controller.rb b/app/controllers/api/v1/workbenches_controller.rb
new file mode 100644
index 000000000..3c07997ce
--- /dev/null
+++ b/app/controllers/api/v1/workbenches_controller.rb
@@ -0,0 +1,3 @@
+class Api::V1::WorkbenchesController < Api::V1::IbooController
+ defaults :resource_class => Workbench
+end
diff --git a/app/controllers/api_keys_controller.rb b/app/controllers/api_keys_controller.rb
index 35a84da87..7059cf52e 100644
--- a/app/controllers/api_keys_controller.rb
+++ b/app/controllers/api_keys_controller.rb
@@ -1,22 +1,32 @@
-class ApiKeysController < ChouetteController
- defaults :resource_class => Api::V1::ApiKey
-
- belongs_to :referential
+class ApiKeysController < BreadcrumbController
+ defaults resource_class: Api::V1::ApiKey
def create
- create! { referential_path(@referential) }
+ @api_key = Api::V1::ApiKey.new(api_key_params.merge(organisation: current_organisation))
+ create! { organisation_api_keys_path }
+ end
+
+ def index
+ @api_keys = decorate_api_keys(current_organisation.api_keys.paginate(page: params[:page]))
end
+
def update
- update! { referential_path(@referential) }
+ update! { organisation_api_key_path(resource) }
end
+
def destroy
- destroy! { referential_path(@referential) }
+ destroy! { organisation_api_keys_path }
end
private
def api_key_params
- params.require(:api_key).permit( :name )
- end
-
-end
+ params.require(:api_key).permit(:name, :referential_id)
+ end
+ def decorate_api_keys(api_keys)
+ ModelDecorator.decorate(
+ api_keys,
+ with: ApiKeyDecorator,
+ )
+ end
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 8fcaa3b1b..d15aa336d 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -37,6 +37,7 @@ class ApplicationController < ActionController::Base
current_organisation
end
+
# Overwriting the sign_out redirect path method
def after_sign_out_path_for(resource_or_scope)
new_user_session_path
diff --git a/app/controllers/autocomplete_time_tables_controller.rb b/app/controllers/autocomplete_time_tables_controller.rb
index e977a28b0..e5d2b41ae 100644
--- a/app/controllers/autocomplete_time_tables_controller.rb
+++ b/app/controllers/autocomplete_time_tables_controller.rb
@@ -15,9 +15,9 @@ class AutocompleteTimeTablesController < InheritedResources::Base
protected
def select_time_tables
- scope = referential.time_tables
+ scope = referential.time_tables.where("time_tables.id != ?", params[:source_id])
if params[:route_id]
- scope = scope.joins(vehicle_journeys: :route).where( "routes.id IN (#{params[:route_id]})")
+ scope = scope.joins(vehicle_journeys: :route).where( "routes.id IN (#{params[:route_id]}) AND time_tables.id != #{params[:time_table_id]}")
end
scope
end
diff --git a/app/controllers/concerns/control_flow.rb b/app/controllers/concerns/control_flow.rb
new file mode 100644
index 000000000..0f41a1cda
--- /dev/null
+++ b/app/controllers/concerns/control_flow.rb
@@ -0,0 +1,14 @@
+module ControlFlow
+ FinishAction = Class.new RuntimeError
+
+ def self.included into
+ into.rescue_from FinishAction, with: :catch_finish_action
+ end
+
+ # Allow to exit locally inside an action after rendering (especially in error cases)
+ def catch_finish_action; end
+
+ def finish_action! msg = 'finish action'
+ raise FinishAction, msg
+ end
+end
diff --git a/app/controllers/import_tasks_controller.rb b/app/controllers/import_tasks_controller.rb
index 0e3ed6445..cb377ec5a 100644
--- a/app/controllers/import_tasks_controller.rb
+++ b/app/controllers/import_tasks_controller.rb
@@ -1,4 +1,3 @@
-# coding: utf-8
class ImportTasksController < ChouetteController
defaults :resource_class => ImportTask
diff --git a/app/controllers/imports_controller.rb b/app/controllers/imports_controller.rb
index 70c5c1a0d..979c9bfcf 100644
--- a/app/controllers/imports_controller.rb
+++ b/app/controllers/imports_controller.rb
@@ -6,12 +6,24 @@ class ImportsController < BreadcrumbController
def show
show! do
+ @import = @import.decorate(context: {
+ workbench: @workbench
+ })
+
build_breadcrumb :show
end
end
def index
- index! do
+ index! do |format|
+ format.html {
+ if collection.out_of_bounds?
+ redirect_to params.merge(:page => 1)
+ end
+
+ @imports = decorate_imports(@imports)
+ }
+
build_breadcrumb :index
end
end
@@ -34,16 +46,50 @@ class ImportsController < BreadcrumbController
end
end
+ protected
+ def collection
+ @q = parent.imports.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 build_resource
# Manage only NetexImports for the moment
@import ||= NetexImport.new(*resource_params) do |import|
import.workbench = parent
+ import.creator = current_user.name
end
end
def import_params
- params.require(:import).permit(:name, :file, :type, :referential_id)
+ params.require(:import).permit(
+ :name,
+ :file,
+ :type,
+ :referential_id
+ )
+ end
+
+ def sort_column
+ parent.imports.column_names.include?(params[:sort]) ? params[:sort] : 'name'
+ end
+ def sort_direction
+ %w[asc desc].include?(params[:direction]) ? params[:direction] : 'asc'
+ end
+
+ def decorate_imports(imports)
+ ModelDecorator.decorate(
+ imports,
+ with: ImportDecorator,
+ context: {
+ workbench: @workbench
+ }
+ )
end
end
diff --git a/app/controllers/time_table_combinations_controller.rb b/app/controllers/time_table_combinations_controller.rb
index 32f1818b0..ba61a2ea4 100644
--- a/app/controllers/time_table_combinations_controller.rb
+++ b/app/controllers/time_table_combinations_controller.rb
@@ -3,13 +3,17 @@ class TimeTableCombinationsController < ChouetteController
belongs_to :time_table, :parent_class => Chouette::TimeTable
end
+ # include PolicyChecker
+
def new
@combination = TimeTableCombination.new(source_id: parent.id)
+ authorize @combination
@combination.combined_type = 'time_table'
end
def create
@combination = TimeTableCombination.new(params[:time_table_combination].merge(source_id: parent.id))
+ authorize @combination
@combination.valid? ? perform_combination : render(:new)
end
diff --git a/app/controllers/time_tables_controller.rb b/app/controllers/time_tables_controller.rb
index 2ff7a2c3a..0054963c9 100644
--- a/app/controllers/time_tables_controller.rb
+++ b/app/controllers/time_tables_controller.rb
@@ -161,6 +161,7 @@ class TimeTablesController < ChouetteController
private
def ransack_periode scope
return scope unless params[:q]
+ return scope unless params[:q]['end_date_lteq(1i)'].present?
begin_range = flatten_date('start_date_gteq')
end_range = flatten_date('end_date_lteq')
diff --git a/app/decorators/api_key_decorator.rb b/app/decorators/api_key_decorator.rb
new file mode 100644
index 000000000..def3a6a01
--- /dev/null
+++ b/app/decorators/api_key_decorator.rb
@@ -0,0 +1,30 @@
+class ApiKeyDecorator < Draper::Decorator
+ decorates Api::V1::ApiKey
+ delegate_all
+
+
+ def action_links
+ links = []
+
+ links << Link.new(
+ content: h.t('api_keys.actions.show'),
+ href: h.organisation_api_key_path(object),
+ )
+
+ links << Link.new(
+ content: h.t('api_keys.actions.edit'),
+ href: h.edit_organisation_api_key_path(object),
+ )
+
+ if h.policy(object).destroy?
+ links << Link.new(
+ content: h.destroy_link_content,
+ href: h.organisation_api_key_path(object),
+ method: :delete,
+ data: { confirm: h.t('api_keys.actions.destroy_confirm') }
+ )
+ end
+
+ links
+ end
+end
diff --git a/app/decorators/import_decorator.rb b/app/decorators/import_decorator.rb
new file mode 100644
index 000000000..eb6a34a13
--- /dev/null
+++ b/app/decorators/import_decorator.rb
@@ -0,0 +1,36 @@
+class ImportDecorator < Draper::Decorator
+ decorates Import
+
+ delegate_all
+
+ def action_links
+ links = []
+
+ links << Link.new(
+ content: h.t('imports.actions.show'),
+ href: h.workbench_import_path(
+ context[:workbench],
+ object
+ )
+ )
+
+ links << Link.new(
+ content: h.t('imports.actions.download'),
+ href: object.file.url
+ )
+
+ # if h.policy(object).destroy?
+ links << Link.new(
+ content: h.destroy_link_content,
+ href: h.workbench_import_path(
+ context[:workbench],
+ object
+ ),
+ method: :delete,
+ data: { confirm: h.t('imports.actions.destroy_confirm') }
+ )
+
+ links
+ end
+
+end
diff --git a/app/decorators/time_table_decorator.rb b/app/decorators/time_table_decorator.rb
index 526537310..c6eeac176 100644
--- a/app/decorators/time_table_decorator.rb
+++ b/app/decorators/time_table_decorator.rb
@@ -21,13 +21,15 @@ class TimeTableDecorator < Draper::Decorator
)
end
- links << Link.new(
- content: h.t('actions.combine'),
- href: h.new_referential_time_table_time_table_combination_path(
- context[:referential],
- object
+ if h.policy(object).edit?
+ links << Link.new(
+ content: h.t('actions.combine'),
+ href: h.new_referential_time_table_time_table_combination_path(
+ context[:referential],
+ object
+ )
)
- )
+ end
if h.policy(object).duplicate?
links << Link.new(
diff --git a/app/helpers/table_builder_helper.rb b/app/helpers/table_builder_helper.rb
index 375697bec..f15019458 100644
--- a/app/helpers/table_builder_helper.rb
+++ b/app/helpers/table_builder_helper.rb
@@ -42,7 +42,8 @@ require 'table_builder_helper/url'
# ),
# ],
# links: [:show, :edit],
-# cls: 'table has-search'
+# cls: 'table has-search',
+# overhead: [ {title: 'one', width: 1, cls: 'toto'}, {title: 'two <span class="test">Info</span>', width: 2, cls: 'default'} ]
# )
module TableBuilderHelper
# TODO: rename this after migration from `table_builder`
@@ -65,19 +66,36 @@ module TableBuilderHelper
links: [],
# A CSS class to apply to the <table>
- cls: ''
+ cls: '',
+
+ # A set of content, over the th line...
+ overhead: []
)
content_tag :table,
- thead(collection, columns, sortable, selectable, links.any?) +
- tbody(collection, columns, selectable, links),
+ thead(collection, columns, sortable, selectable, links.any?, overhead) +
+ tbody(collection, columns, selectable, links, overhead),
class: cls
end
private
- def thead(collection, columns, sortable, selectable, has_links)
+ def thead(collection, columns, sortable, selectable, has_links, overhead)
content_tag :thead do
- content_tag :tr do
+ # Inserts overhead content if any specified
+ over_head = ''
+
+ unless overhead.empty?
+ over_head = content_tag :tr, class: 'overhead' do
+ oh_cont = []
+
+ overhead.each do |h|
+ oh_cont << content_tag(:th, raw(h[:title]), colspan: h[:width], class: h[:cls])
+ end
+ oh_cont.join.html_safe
+ end
+ end
+
+ main_head = content_tag :tr do
hcont = []
if selectable
@@ -85,25 +103,73 @@ module TableBuilderHelper
end
columns.each do |column|
- hcont << content_tag(:th, build_column_header(
- column,
- sortable,
- collection.model,
- params,
- params[:sort],
- params[:direction]
- ))
+ if overhead.empty?
+ hcont << content_tag(:th, build_column_header(
+ column,
+ sortable,
+ collection.model,
+ params,
+ params[:sort],
+ params[:direction]
+ ))
+
+ else
+ i = columns.index(column)
+
+ if overhead[i].blank?
+ if (i > 0) && (overhead[i - 1][:width] > 1)
+ clsArrayH = overhead[i - 1][:cls].split
+
+ hcont << content_tag(:th, build_column_header(
+ column,
+ sortable,
+ collection.model,
+ params,
+ params[:sort],
+ params[:direction]
+ ), class: td_cls(clsArrayH))
+
+ else
+ hcont << content_tag(:th, build_column_header(
+ column,
+ sortable,
+ collection.model,
+ params,
+ params[:sort],
+ params[:direction]
+ ))
+ end
+
+ else
+ clsArrayH = overhead[i][:cls].split
+
+ hcont << content_tag(:th, build_column_header(
+ column,
+ sortable,
+ collection.model,
+ params,
+ params[:sort],
+ params[:direction]
+ ), class: td_cls(clsArrayH))
+
+ end
+
+ end
end
# Inserts a blank column for the gear menu
- hcont << content_tag(:th, '') if has_links
+ if has_links || collection.last.try(:action_links).try(:any?)
+ hcont << content_tag(:th, '')
+ end
hcont.join.html_safe
end
+
+ (over_head + main_head).html_safe
end
end
- def tbody(collection, columns, selectable, links)
+ def tbody(collection, columns, selectable, links, overhead)
content_tag :tbody do
collection.map do |item|
@@ -126,13 +192,57 @@ module TableBuilderHelper
item,
referential
)
- bcont << content_tag(:td, link_to(value, polymorph_url), title: 'Voir')
+
+ if overhead.empty?
+ bcont << content_tag(:td, link_to(value, polymorph_url), title: 'Voir')
+
+ else
+ i = columns.index(column)
+
+ if overhead[i].blank?
+ if (i > 0) && (overhead[i - 1][:width] > 1)
+ clsArrayAlt = overhead[i - 1][:cls].split
+
+ bcont << content_tag(:td, link_to(value, polymorph_url), title: 'Voir', class: td_cls(clsArrayAlt))
+
+ else
+ bcont << content_tag(:td, link_to(value, polymorph_url), title: 'Voir')
+ end
+
+ else
+ clsArray = overhead[columns.index(column)][:cls].split
+
+ bcont << content_tag(:td, link_to(value, polymorph_url), title: 'Voir', class: td_cls(clsArray))
+ end
+ end
+
else
- bcont << content_tag(:td, value)
+ if overhead.empty?
+ bcont << content_tag(:td, value)
+
+ else
+ i = columns.index(column)
+
+ if overhead[i].blank?
+ if (i > 0) && (overhead[i - 1][:width] > 1)
+ clsArrayAlt = overhead[i - 1][:cls].split
+
+ bcont << content_tag(:td, value, class: td_cls(clsArrayAlt))
+
+ else
+ bcont << content_tag(:td, value)
+ end
+
+ else
+ clsArray = overhead[i][:cls].split
+
+ bcont << content_tag(:td, value, class: td_cls(clsArray))
+ end
+ end
end
end
- if links.any?
+ if links.any? || item.try(:action_links).try(:any?)
bcont << content_tag(
:td,
build_links(item, links),
@@ -146,6 +256,14 @@ module TableBuilderHelper
end
end
+ def td_cls(a)
+ if a.include? 'full-border'
+ a.slice!(a.index('full-border'))
+
+ return a.join(' ')
+ end
+ end
+
def build_links(item, links)
trigger = content_tag(
:div,
diff --git a/app/helpers/table_builder_helper/url.rb b/app/helpers/table_builder_helper/url.rb
index 0894df0fe..f7ba703ae 100644
--- a/app/helpers/table_builder_helper/url.rb
+++ b/app/helpers/table_builder_helper/url.rb
@@ -12,7 +12,12 @@ module TableBuilderHelper
polymorph_url << item.stop_area if item.respond_to? :stop_area
polymorph_url << item if item.respond_to?(:stop_points) || item.is_a?(Chouette::TimeTable)
elsif item.respond_to? :referential
- polymorph_url << item.referential
+ if item.respond_to? :workbench
+ polymorph_url << item.workbench
+ polymorph_url << item
+ else
+ polymorph_url << item.referential
+ end
end
else
polymorph_url << item
diff --git a/app/models/api/v1/api_key.rb b/app/models/api/v1/api_key.rb
index 7390db232..767e65f3a 100644
--- a/app/models/api/v1/api_key.rb
+++ b/app/models/api/v1/api_key.rb
@@ -3,9 +3,32 @@ module Api
class ApiKey < ::ActiveRecord::Base
before_create :generate_access_token
belongs_to :referential, :class_name => '::Referential'
+ belongs_to :organisation, :class_name => '::Organisation'
- def self.model_name
- ActiveModel::Name.new self, Api::V1, self.name.demodulize
+ validates_presence_of :organisation
+
+ class << self
+ def from(referential, name:)
+ find_or_create_by!(name: name, referential: referential)
+ end
+
+ def referential_from_token(token)
+ array = token.split('-')
+ if !array.first.empty? && array.size > 1
+ ::Referential.find array.first
+ end
+ end
+
+ def model_name
+ ActiveModel::Name.new self, Api::V1, self.name.demodulize
+ end
+
+ def organisation_from_token(token)
+ array = token.split('-')
+ if !array[1].empty? && array.size > 1
+ ::Organisation.find array[1]
+ end
+ end
end
def eql?(other)
@@ -13,16 +36,11 @@ module Api
other.token == self.token
end
- def self.referential_from_token(token)
- array = token.split('-')
- return nil unless array.size==2
- ::Referential.find( array.first)
- end
private
def generate_access_token
begin
- self.token = "#{referential.id}-#{SecureRandom.hex}"
+ self.token = "#{referential_id}-#{organisation_id}-#{SecureRandom.hex}"
end while self.class.exists?(:token => self.token)
end
end
diff --git a/app/models/chouette/footnote.rb b/app/models/chouette/footnote.rb
index de427b249..1664faf23 100644
--- a/app/models/chouette/footnote.rb
+++ b/app/models/chouette/footnote.rb
@@ -1,6 +1,13 @@
class Chouette::Footnote < Chouette::ActiveRecord
+ include ChecksumSupport
+
belongs_to :line, inverse_of: :footnotes
has_and_belongs_to_many :vehicle_journeys, :class_name => 'Chouette::VehicleJourney'
validates_presence_of :line
+
+ def checksum_attributes
+ attrs = ['code', 'label']
+ self.slice(*attrs).values
+ end
end
diff --git a/app/models/chouette/journey_pattern.rb b/app/models/chouette/journey_pattern.rb
index f238d7339..fa5fba26d 100644
--- a/app/models/chouette/journey_pattern.rb
+++ b/app/models/chouette/journey_pattern.rb
@@ -1,4 +1,5 @@
class Chouette::JourneyPattern < Chouette::TridentActiveRecord
+ include ChecksumSupport
include JourneyPatternRestrictions
# FIXME http://jira.codehaus.org/browse/JRUBY-6358
self.primary_key = "id"
@@ -20,6 +21,12 @@ class Chouette::JourneyPattern < Chouette::TridentActiveRecord
attr_accessor :control_checked
after_update :control_route_sections, :unless => "control_checked"
+ def checksum_attributes
+ values = self.slice(*['name', 'published_name', 'registration_number']).values
+ values << self.stop_points.map(&:stop_area).map(&:user_objectid)
+ values.flatten
+ end
+
def self.state_update route, state
transaction do
state.each do |item|
diff --git a/app/models/chouette/route.rb b/app/models/chouette/route.rb
index 76905bf2b..6774e8a86 100644
--- a/app/models/chouette/route.rb
+++ b/app/models/chouette/route.rb
@@ -1,5 +1,6 @@
class Chouette::Route < Chouette::TridentActiveRecord
include RouteRestrictions
+ include ChecksumSupport
extend Enumerize
extend ActiveModel::Naming
@@ -99,6 +100,14 @@ class Chouette::Route < Chouette::TridentActiveRecord
end
end
+ def checksum_attributes
+ values = self.slice(*['name', 'published_name', 'wayback']).values
+ values.tap do |attrs|
+ attrs << self.stop_points.map{|sp| "#{sp.stop_area.user_objectid}#{sp.for_boarding}#{sp.for_alighting}" }.join
+ attrs << self.routing_constraint_zones.map(&:checksum)
+ end
+ end
+
def geometry
points = stop_areas.map(&:to_lat_lng).compact.map do |loc|
[loc.lng, loc.lat]
diff --git a/app/models/chouette/routing_constraint_zone.rb b/app/models/chouette/routing_constraint_zone.rb
index a649b0b8e..9931748b2 100644
--- a/app/models/chouette/routing_constraint_zone.rb
+++ b/app/models/chouette/routing_constraint_zone.rb
@@ -1,4 +1,6 @@
class Chouette::RoutingConstraintZone < Chouette::TridentActiveRecord
+ include ChecksumSupport
+
belongs_to :route
has_array_of :stop_points, class_name: 'Chouette::StopPoint'
@@ -15,6 +17,10 @@ class Chouette::RoutingConstraintZone < Chouette::TridentActiveRecord
.order("routes.name #{direction}")
end
+ def checksum_attributes
+ self.stop_points.map(&:stop_area).map(&:user_objectid)
+ end
+
def stop_points_belong_to_route
errors.add(:stop_point_ids, I18n.t('activerecord.errors.models.routing_constraint_zone.attributes.stop_points.stop_points_not_from_route')) unless stop_points.all? { |sp| route.stop_points.include? sp }
end
diff --git a/app/models/chouette/time_table.rb b/app/models/chouette/time_table.rb
index 1753cbed5..3f56f6a1d 100644
--- a/app/models/chouette/time_table.rb
+++ b/app/models/chouette/time_table.rb
@@ -1,4 +1,5 @@
class Chouette::TimeTable < Chouette::TridentActiveRecord
+ include ChecksumSupport
include TimeTableRestrictions
# FIXME http://jira.codehaus.org/browse/JRUBY-6358
self.primary_key = "id"
@@ -26,6 +27,14 @@ class Chouette::TimeTable < Chouette::TridentActiveRecord
after_save :save_shortcuts
+ def checksum_attributes
+ [].tap do |attrs|
+ attrs << self.int_day_types
+ attrs << self.dates.map(&:checksum).map(&:to_s).sort
+ attrs << self.periods.map(&:checksum).map(&:to_s).sort
+ end
+ end
+
def self.object_id_key
"Timetable"
end
@@ -478,7 +487,7 @@ class Chouette::TimeTable < Chouette::TridentActiveRecord
def merge!(another_tt)
transaction do
days = [].tap do |array|
- array.push(*self.included_days_in_dates_and_periods, *another_tt.effective_days)
+ array.push(*self.effective_days, *another_tt.effective_days)
array.uniq!
end
@@ -507,7 +516,7 @@ class Chouette::TimeTable < Chouette::TridentActiveRecord
def intersect!(another_tt)
transaction do
days = [].tap do |array|
- array.push(*self.included_days_in_dates_and_periods)
+ array.push(*self.effective_days)
array.delete_if {|day| !another_tt.effective_days.include?(day) }
array.uniq!
end
@@ -527,7 +536,7 @@ class Chouette::TimeTable < Chouette::TridentActiveRecord
def disjoin!(another_tt)
transaction do
days = [].tap do |array|
- array.push(*self.included_days_in_dates_and_periods)
+ array.push(*self.effective_days)
array.delete_if {|day| another_tt.effective_days.include?(day) }
array.uniq!
end
diff --git a/app/models/chouette/time_table_date.rb b/app/models/chouette/time_table_date.rb
index b881c9a5d..1893eae91 100644
--- a/app/models/chouette/time_table_date.rb
+++ b/app/models/chouette/time_table_date.rb
@@ -1,4 +1,6 @@
class Chouette::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
@@ -12,5 +14,9 @@ class Chouette::TimeTableDate < Chouette::ActiveRecord
ActiveModel::Name.new Chouette::TimeTableDate, Chouette, "TimeTableDate"
end
+ def checksum_attributes
+ attrs = ['date', 'in_out']
+ self.slice(*attrs).values
+ end
end
diff --git a/app/models/chouette/time_table_period.rb b/app/models/chouette/time_table_period.rb
index 6d3486bb6..ed136f3b9 100644
--- a/app/models/chouette/time_table_period.rb
+++ b/app/models/chouette/time_table_period.rb
@@ -1,4 +1,6 @@
class Chouette::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
@@ -7,6 +9,10 @@ class Chouette::TimeTablePeriod < Chouette::ActiveRecord
validate :start_must_be_before_end
+ def checksum_attributes
+ attrs = ['period_start', 'period_end']
+ self.slice(*attrs).values
+ end
def self.model_name
ActiveModel::Name.new Chouette::TimeTablePeriod, Chouette, "TimeTablePeriod"
diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb
index 5e86a4897..d5ca58959 100644
--- a/app/models/chouette/vehicle_journey.rb
+++ b/app/models/chouette/vehicle_journey.rb
@@ -1,5 +1,6 @@
module Chouette
class VehicleJourney < TridentActiveRecord
+ include ChecksumSupport
include VehicleJourneyRestrictions
include StifTransportModeEnumerations
# FIXME http://jira.codehaus.org/browse/JRUBY-6358
@@ -55,6 +56,16 @@ module Chouette
end
end
+ def checksum_attributes
+ [].tap do |attrs|
+ attrs << self.published_journey_name
+ attrs << self.published_journey_identifier
+ attrs << self.try(:company).try(:objectid).try(:local_id)
+ attrs << self.footnotes.map(&:checksum).sort
+ attrs << self.vehicle_journey_at_stops.map(&:checksum).sort
+ end
+ end
+
def set_default_values
if number.nil?
self.number = 0
diff --git a/app/models/chouette/vehicle_journey_at_stop.rb b/app/models/chouette/vehicle_journey_at_stop.rb
index 35d94aa75..156cc761f 100644
--- a/app/models/chouette/vehicle_journey_at_stop.rb
+++ b/app/models/chouette/vehicle_journey_at_stop.rb
@@ -2,6 +2,7 @@ module Chouette
class VehicleJourneyAtStop < ActiveRecord
include ForBoardingEnumerations
include ForAlightingEnumerations
+ include ChecksumSupport
DAY_OFFSET_MAX = 1
@@ -66,6 +67,13 @@ module Chouette
offset < 0 || offset > DAY_OFFSET_MAX
end
-
+ def checksum_attributes
+ [].tap do |attrs|
+ attrs << self.departure_time.try(:to_s, :time)
+ attrs << self.arrival_time.try(:to_s, :time)
+ attrs << self.departure_day_offset.to_s
+ attrs << self.arrival_day_offset.to_s
+ end
+ end
end
end
diff --git a/app/models/clean_up.rb b/app/models/clean_up.rb
index b1135a155..7aab7f32e 100644
--- a/app/models/clean_up.rb
+++ b/app/models/clean_up.rb
@@ -153,13 +153,13 @@ class CleanUp < ActiveRecord::Base
update_attribute(:started_at, Time.now)
end
- def log_successful message_attributs
+ def log_successful message_attributes
update_attribute(:ended_at, Time.now)
- CleanUpResult.create(clean_up: self, message_key: :successfull, message_attributs: message_attributs)
+ CleanUpResult.create(clean_up: self, message_key: :successfull, message_attributes: message_attributes)
end
- def log_failed message_attributs
+ def log_failed message_attributes
update_attribute(:ended_at, Time.now)
- CleanUpResult.create(clean_up: self, message_key: :failed, message_attributs: message_attributs)
+ CleanUpResult.create(clean_up: self, message_key: :failed, message_attributes: message_attributes)
end
end
diff --git a/app/models/concerns/checksum_support.rb b/app/models/concerns/checksum_support.rb
new file mode 100644
index 000000000..c95e23bcf
--- /dev/null
+++ b/app/models/concerns/checksum_support.rb
@@ -0,0 +1,29 @@
+module ChecksumSupport
+ extend ActiveSupport::Concern
+ SEPARATOR = '|'
+ VALUE_FOR_NIL_ATTRIBUTE = '-'
+
+ included do
+ before_save :set_current_checksum_source, :update_checksum
+ end
+
+ def checksum_attributes
+ self.attributes.values
+ end
+
+ def current_checksum_source
+ source = self.checksum_attributes.map{ |x| x unless x.try(:empty?) }
+ source = source.map{ |x| x || VALUE_FOR_NIL_ATTRIBUTE }
+ source.map(&:to_s).join(SEPARATOR)
+ end
+
+ def set_current_checksum_source
+ self.checksum_source = self.current_checksum_source
+ end
+
+ def update_checksum
+ if self.checksum_source_changed?
+ self.checksum = Digest::SHA256.new.hexdigest(self.checksum_source)
+ end
+ end
+end
diff --git a/app/models/import.rb b/app/models/import.rb
index d0736ab0b..b34ca2b48 100644
--- a/app/models/import.rb
+++ b/app/models/import.rb
@@ -3,13 +3,55 @@ class Import < ActiveRecord::Base
belongs_to :workbench
belongs_to :referential
+ belongs_to :parent, polymorphic: true
+
extend Enumerize
enumerize :status, in: %i(new pending successful failed running aborted canceled)
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 self.failing_statuses
+ symbols_with_indifferent_access(%i(failed aborted canceled))
+ end
+
+ def self.finished_statuses
+ symbols_with_indifferent_access(%i(successful failed aborted canceled))
+ end
+
+ def notify_parent
+ parent.child_change(self)
+ update(notified_parent_at: DateTime.now)
+ end
- before_create do
+ def child_change(child)
+ return if self.class.finished_statuses.include?(status)
+
+ if self.class.failing_statuses.include?(child.status)
+ return update(status: 'failed')
+ end
+
+ update(status: 'successful') if ready?
+ end
+
+ def ready?
+ current_step == total_steps
+ end
+
+ private
+
+ def initialize_fields
self.token_download = SecureRandom.urlsafe_base64
self.status = Import.status.new
end
+
+ def self.symbols_with_indifferent_access(array)
+ array.flat_map { |symbol| [symbol, symbol.to_s] }
+ end
end
diff --git a/app/models/line_referential_sync.rb b/app/models/line_referential_sync.rb
index 6730ddd73..75c1e48a2 100644
--- a/app/models/line_referential_sync.rb
+++ b/app/models/line_referential_sync.rb
@@ -40,11 +40,11 @@ class LineReferentialSync < ActiveRecord::Base
end
end
- def create_sync_message criticity, key, message_attributs = {}
+ def create_sync_message criticity, key, message_attributes = {}
params = {
criticity: criticity,
message_key: key,
- message_attributs: message_attributs
+ message_attributes: message_attributes
}
line_referential_sync_messages.create params
end
@@ -54,13 +54,13 @@ class LineReferentialSync < ActiveRecord::Base
create_sync_message :info, :pending
end
- def log_successful message_attributs
+ def log_successful message_attributes
update_attribute(:ended_at, Time.now)
- create_sync_message :info, :successful, message_attributs
+ create_sync_message :info, :successful, message_attributes
end
- def log_failed message_attributs
+ def log_failed message_attributes
update_attribute(:ended_at, Time.now)
- create_sync_message :error, :failed, message_attributs
+ create_sync_message :error, :failed, message_attributes
end
end
diff --git a/app/models/netex_import.rb b/app/models/netex_import.rb
index de5b84537..575cef816 100644
--- a/app/models/netex_import.rb
+++ b/app/models/netex_import.rb
@@ -2,12 +2,13 @@ require 'net/http'
class NetexImport < Import
after_commit :launch_java_import
+
def launch_java_import
logger.warn "Call iev get #{Rails.configuration.iev_url}/boiv_iev/referentials/importer/new?id=#{id}"
begin
Net::HTTP.get(URI("#{Rails.configuration.iev_url}/boiv_iev/referentials/importer/new?id=#{id}"))
rescue Exception => e
- logger.error "IEV server error : e.message"
+ logger.error "IEV server error : #{e.message}"
logger.error e.backtrace.inspect
end
end
diff --git a/app/models/organisation.rb b/app/models/organisation.rb
index d0742bda6..895ca03d9 100644
--- a/app/models/organisation.rb
+++ b/app/models/organisation.rb
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
class Organisation < ActiveRecord::Base
include DataFormatEnumerations
@@ -14,6 +13,7 @@ class Organisation < ActiveRecord::Base
has_many :workbenches
has_many :calendars
+ has_many :api_keys, class_name: 'Api::V1::ApiKey'
validates_presence_of :name
validates_uniqueness_of :code
@@ -26,19 +26,12 @@ class Organisation < ActiveRecord::Base
def self.portail_api_request
conf = Rails.application.config.try(:stif_portail_api)
- raise 'Rails.application.config.stif_portail_api settings is not defined' unless conf
+ raise 'Rails.application.config.stif_portail_api configuration is not defined' unless conf
- conn = Faraday.new(:url => conf[:url]) do |c|
- c.headers['Authorization'] = "Token token=\"#{conf[:key]}\""
- c.adapter Faraday.default_adapter
- end
-
- resp = conn.get '/api/v1/organizations'
- if resp.status == 200
- JSON.parse resp.body
- else
- raise "Error on api request status : #{resp.status} => #{resp.body}"
- end
+ HTTPService.get_json_resource(
+ host: conf[:url],
+ path: '/api/v1/organizations',
+ token: conf[:key])
end
def self.sync_update code, name, scope
diff --git a/app/models/stop_area_referential_sync.rb b/app/models/stop_area_referential_sync.rb
index 0e32df4d2..e6cf2ecbc 100644
--- a/app/models/stop_area_referential_sync.rb
+++ b/app/models/stop_area_referential_sync.rb
@@ -40,11 +40,11 @@ class StopAreaReferentialSync < ActiveRecord::Base
end
end
- def create_sync_message criticity, key, message_attributs = {}
+ def create_sync_message criticity, key, message_attributes = {}
params = {
criticity: criticity,
message_key: key,
- message_attributs: message_attributs
+ message_attributes: message_attributes
}
stop_area_referential_sync_messages.create params
end
@@ -54,13 +54,13 @@ class StopAreaReferentialSync < ActiveRecord::Base
create_sync_message :info, :pending
end
- def log_successful message_attributs
+ def log_successful message_attributes
update_attribute(:ended_at, Time.now)
- create_sync_message :info, :successful, message_attributs
+ create_sync_message :info, :successful, message_attributes
end
- def log_failed message_attributs
+ def log_failed message_attributes
update_attribute(:ended_at, Time.now)
- create_sync_message :error, :failed, message_attributs
+ create_sync_message :error, :failed, message_attributes
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index c2aa14bda..37d35209a 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -41,19 +41,12 @@ class User < ActiveRecord::Base
def self.portail_api_request
conf = Rails.application.config.try(:stif_portail_api)
- raise 'Rails.application.config.stif_portail_api settings is not defined' unless conf
+ raise 'Rails.application.config.stif_portail_api configuration is not defined' unless conf
- conn = Faraday.new(:url => conf[:url]) do |c|
- c.headers['Authorization'] = %{Token token="#{conf[:key]}"}
- c.adapter Faraday.default_adapter
- end
-
- resp = conn.get '/api/v1/users'
- if resp.status == 200
- JSON.parse resp.body
- else
- raise "Error on api request status : #{resp.status} => #{resp.body}"
- end
+ HTTPService.get_json_resource(
+ host: conf[:url],
+ path: '/api/v1/users',
+ token: conf[:key])
end
def self.portail_sync
diff --git a/app/models/workbench.rb b/app/models/workbench.rb
index 4023c221b..c37cba858 100644
--- a/app/models/workbench.rb
+++ b/app/models/workbench.rb
@@ -9,6 +9,7 @@ class Workbench < ActiveRecord::Base
has_many :group_of_lines, through: :line_referential
has_many :stop_areas, through: :stop_area_referential
has_many :imports
+ has_many :workbench_imports
validates :name, presence: true
validates :organisation, presence: true
diff --git a/app/models/workbench_import.rb b/app/models/workbench_import.rb
new file mode 100644
index 000000000..9323bd4b5
--- /dev/null
+++ b/app/models/workbench_import.rb
@@ -0,0 +1,2 @@
+class WorkbenchImport < Import
+end
diff --git a/app/policies/api_key_policy.rb b/app/policies/api_key_policy.rb
new file mode 100644
index 000000000..7b4c22e33
--- /dev/null
+++ b/app/policies/api_key_policy.rb
@@ -0,0 +1,19 @@
+class ApiKeyPolicy < ApplicationPolicy
+ class Scope < Scope
+ def resolve
+ scope
+ end
+ end
+
+ def destroy?
+ organisation_match? && user.has_permission?('api_keys.destroy')
+ end
+
+ def create?
+ organisation_match? && user.has_permission?('api_keys.create')
+ end
+
+ def update?
+ organisation_match? && user.has_permission?('api_keys.update')
+ end
+end
diff --git a/app/policies/import_policy.rb b/app/policies/import_policy.rb
new file mode 100644
index 000000000..9e1d99a66
--- /dev/null
+++ b/app/policies/import_policy.rb
@@ -0,0 +1,7 @@
+class ImportPolicy < ApplicationPolicy
+ class Scope < Scope
+ def resolve
+ scope
+ end
+ end
+end
diff --git a/app/policies/time_table_combination_policy.rb b/app/policies/time_table_combination_policy.rb
new file mode 100644
index 000000000..daa6808e4
--- /dev/null
+++ b/app/policies/time_table_combination_policy.rb
@@ -0,0 +1,12 @@
+class TimeTableCombinationPolicy < ApplicationPolicy
+
+ class Scope < Scope
+ def resolve
+ scope
+ end
+ end
+
+ def create?
+ !archived? && organisation_match? && user.has_permission?('time_tables.update')
+ end
+end
diff --git a/app/services/http_service.rb b/app/services/http_service.rb
new file mode 100644
index 000000000..ae7d0e413
--- /dev/null
+++ b/app/services/http_service.rb
@@ -0,0 +1,45 @@
+module HTTPService extend self
+
+ Timeout = Faraday::TimeoutError
+
+ def get_resource(host:, path:, token: nil, params: {})
+ Faraday.new(url: host) do |c|
+ c.headers['Authorization'] = "Token token=#{token.inspect}" if token
+ c.adapter Faraday.default_adapter
+
+ return c.get path, params
+ end
+ end
+
+ def get_json_resource(host:, path:, token: nil, params: {})
+ # Stupid Ruby!!! (I mean I just **need** Pattern Matching, maybe I need to write it myself :O)
+ resp = get_resource(host: host, path: path, token: token, params: params)
+ if resp.status == 200
+ return JSON.parse(resp.body)
+ else
+ raise "Error on api request status : #{resp.status} => #{resp.body}"
+ end
+ end
+
+ # host: 'http://localhost:3000',
+ # path: '/api/v1/netex_imports.json',
+ # token: '13-74009c36638f587c9eafb1ce46e95585',
+ # params: { netex_import: {referential_id: 13, workbench_id: 1}},
+ # upload: {file: [StringIO.new('howdy'), 'application/zip', 'greeting']})
+ def post_resource(host:, path:, token: nil, params: {}, upload: nil)
+ Faraday.new(url: host) do |c|
+ c.headers['Authorization'] = "Token token=#{token.inspect}" if token
+ c.request :multipart
+ c.request :url_encoded
+ c.adapter Faraday.default_adapter
+
+ if upload
+ name = upload.keys.first
+ value, mime_type, as_name = upload.values.first
+ params.update( name => Faraday::UploadIO.new(value, mime_type, as_name ) )
+ end
+
+ return c.post path, params
+ end
+ end
+end
diff --git a/app/services/parent_import_notifier.rb b/app/services/parent_import_notifier.rb
new file mode 100644
index 000000000..47e6755e4
--- /dev/null
+++ b/app/services/parent_import_notifier.rb
@@ -0,0 +1,15 @@
+class ParentImportNotifier
+ def self.notify_when_finished(imports = nil)
+ imports ||= imports_pending_notification
+ imports.each(&:notify_parent)
+ end
+
+ def self.imports_pending_notification
+ Import
+ .where(
+ notified_parent_at: nil,
+ status: Import.finished_statuses
+ )
+ .where.not(parent: nil)
+ end
+end
diff --git a/app/services/retry_service.rb b/app/services/retry_service.rb
new file mode 100644
index 000000000..21b1def36
--- /dev/null
+++ b/app/services/retry_service.rb
@@ -0,0 +1,54 @@
+require 'result'
+
+class RetryService
+
+ Retry = Class.new(RuntimeError)
+
+ # @param@ delays:
+ # An array of delays that are used to retry after a sleep of the indicated
+ # value in case of failed exceutions.
+ # Once this array is exhausted the executen fails permanently
+ #
+ # @param@ rescue_from:
+ # During execution all the excpetions from this array +plus RetryService::Retry+ are rescued from and
+ # trigger just another retry after a `sleep` as indicated above.
+ #
+ # @param@ block:
+ # This optional code is excuted before each retry, it is passed the result of the failed attempt, thus
+ # an `Exception` and the number of execution already tried.
+ def initialize( delays: [], rescue_from: [], &blk )
+ @intervals = delays
+ @registered_exceptions = Array(rescue_from) << Retry
+ @failure_callback = blk
+ end
+
+ # @param@ blk:
+ # The code to be executed it will be retried goverened by the `delay` passed into the initializer
+ # as described there in case it fails with one of the predefined exceptions or `RetryService::Retry`
+ #
+ # Eventually it will return a `Result` object.
+ def execute &blk
+ result = execute_protected blk
+ return result if result.ok?
+ @intervals.each_with_index do | interval, retry_count |
+ sleep interval
+ @failure_callback.try(:call, result.value, retry_count + 1)
+ result = execute_protected blk
+ return result if result.ok?
+ end
+ result
+ end
+
+
+ private
+
+ def execute_protected blk
+ Result.ok(blk.())
+ rescue Exception => e
+ if @registered_exceptions.any?{ |re| e.is_a? re }
+ Result.error(e)
+ else
+ raise
+ end
+ end
+end
diff --git a/app/services/zip_service.rb b/app/services/zip_service.rb
new file mode 100644
index 000000000..778bfd06d
--- /dev/null
+++ b/app/services/zip_service.rb
@@ -0,0 +1,55 @@
+class ZipService
+
+ attr_reader :current_entry, :zip_data
+
+ def initialize data
+ @zip_data = data
+ @current_entry = nil
+ end
+
+ class << self
+ def convert_entries entries
+ -> output_stream do
+ entries.each do |e|
+ output_stream.put_next_entry e.name
+ output_stream.write e.get_input_stream.read
+ end
+ end
+ end
+
+ def entries input_stream
+ Enumerator.new do |enum|
+ loop{ enum << input_stream.get_next_entry }
+ end.lazy.take_while{ |e| e }
+ end
+ end
+
+ def entry_groups
+ self.class.entries(input_stream).group_by(&method(:entry_key))
+ end
+
+ def entry_group_streams
+ entry_groups.map(&method(:make_stream)).to_h
+ end
+
+ def entry_key entry
+ entry.name.split('/', -1)[-2]
+ end
+
+ def make_stream pair
+ name, entries = pair
+ [name, make_stream_from( entries )]
+ end
+
+ def make_stream_from entries
+ Zip::OutputStream.write_buffer(&self.class.convert_entries(entries))
+ end
+
+ def next_entry
+ @current_entry = input_stream.get_next_entry
+ end
+
+ def input_stream
+ @__input_stream__ ||= Zip::InputStream.open(StringIO.new(zip_data))
+ end
+end
diff --git a/app/views/api/v1/imports/index.rabl b/app/views/api/v1/imports/index.rabl
new file mode 100644
index 000000000..e8cfd101e
--- /dev/null
+++ b/app/views/api/v1/imports/index.rabl
@@ -0,0 +1,3 @@
+collection @imports
+
+extends "api/v1/imports/show"
diff --git a/app/views/api/v1/imports/show.rabl b/app/views/api/v1/imports/show.rabl
new file mode 100644
index 000000000..180894cb8
--- /dev/null
+++ b/app/views/api/v1/imports/show.rabl
@@ -0,0 +1,6 @@
+object @import
+
+attributes :id, :name, :status
+node :referential_ids do |i|
+ i.workbench.referentials.map(&:id)
+end
diff --git a/app/views/api/v1/netex_imports/create.json.rabl b/app/views/api/v1/netex_imports/create.json.rabl
new file mode 100644
index 000000000..f37703349
--- /dev/null
+++ b/app/views/api/v1/netex_imports/create.json.rabl
@@ -0,0 +1,3 @@
+
+object @netex_import
+attributes :id, :workbench_id, :referential_id
diff --git a/app/views/api/v1/workbenches/index.rabl b/app/views/api/v1/workbenches/index.rabl
new file mode 100644
index 000000000..2f0bf5fee
--- /dev/null
+++ b/app/views/api/v1/workbenches/index.rabl
@@ -0,0 +1,3 @@
+collection @workbenches
+
+extends "api/v1/workbenches/show"
diff --git a/app/views/api/v1/workbenches/show.rabl b/app/views/api/v1/workbenches/show.rabl
new file mode 100644
index 000000000..d43727809
--- /dev/null
+++ b/app/views/api/v1/workbenches/show.rabl
@@ -0,0 +1,3 @@
+object @workbench
+
+attributes :id, :name
diff --git a/app/views/api_keys/_form.html.slim b/app/views/api_keys/_form.html.slim
index 74b806677..f3ebf3fe1 100644
--- a/app/views/api_keys/_form.html.slim
+++ b/app/views/api_keys/_form.html.slim
@@ -1,10 +1,7 @@
-= semantic_form_for [@referential, @api_key] do |form|
- = form.inputs do
- = form.input :name
-
+= simple_form_for @api_key, url: action_url do |f|
+ = f.input :name
- unless @api_key.new_record?
- = form.input :token, :input_html => { :readonly => true }
+ = f.input :token, :input_html => { readonly: true }
- = form.actions do
- = form.action :submit, as: :button
- = form.action :cancel, as: :link \ No newline at end of file
+ = f.association :referential
+ = f.button :submit, 'submit', class: 'btn-primary'
diff --git a/app/views/api_keys/edit.html.slim b/app/views/api_keys/edit.html.slim
index 110f0775d..e47deddf7 100644
--- a/app/views/api_keys/edit.html.slim
+++ b/app/views/api_keys/edit.html.slim
@@ -1,3 +1,2 @@
= title_tag t('api_keys.edit.title')
-
-== render 'form' \ No newline at end of file
+== render partial: 'form', locals: {action_url: organisation_api_key_path}
diff --git a/app/views/api_keys/index.html.slim b/app/views/api_keys/index.html.slim
new file mode 100644
index 000000000..fc8d95c7a
--- /dev/null
+++ b/app/views/api_keys/index.html.slim
@@ -0,0 +1,24 @@
+- header_params = ['map-marker',
+ t('.title'),
+ '']
+- header_params << link_to(t('actions.add'), new_organisation_api_key_path, class: 'btn btn-default') if policy(Api::V1::ApiKey).create?
+= pageheader(*header_params) do
+
+
+- if @api_keys.any?
+ .row
+ .col-lg-12
+ = table_builder_2 @api_keys,
+ [ \
+ TableBuilderHelper::Column.new( \
+ key: :name, \
+ attribute: 'name' \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :token, \
+ attribute: 'token' \
+ ), \
+ ],
+ cls: 'table has-search'
+
+ = new_pagination @api_keys, 'pull-right'
diff --git a/app/views/api_keys/new.html.slim b/app/views/api_keys/new.html.slim
index f7b1dd99b..291c9f8a6 100644
--- a/app/views/api_keys/new.html.slim
+++ b/app/views/api_keys/new.html.slim
@@ -1,3 +1,2 @@
= title_tag t('api_keys.new.title')
-
-== render "form" \ No newline at end of file
+== render partial: 'form', locals: {action_url: organisation_api_keys_path}
diff --git a/app/views/api_keys/show.html.slim b/app/views/api_keys/show.html.slim
index b65717408..de30ac125 100644
--- a/app/views/api_keys/show.html.slim
+++ b/app/views/api_keys/show.html.slim
@@ -12,7 +12,6 @@
- content_for :sidebar do
ul.actions
- li = link_to t('api_keys.actions.new'), new_referential_api_key_path(@referential), class: "add"
- li = link_to t('api_keys.actions.edit'), edit_referential_api_key_path(@referential, @api_key), class: "edit"
- li = link_to t('api_keys.actions.destroy'), referential_api_key_path(@referential, @api_key), :method => :delete, :data => {:confirm => t('api_keys.actions.destroy_confirm')}, class: "remove"
- br \ No newline at end of file
+ li = link_to t('api_keys.actions.edit'), edit_organisation_api_key_path(@api_key), class: "edit"
+ li = link_to t('api_keys.actions.destroy'), organisation_api_key_path(@api_key), :method => :delete, :data => {:confirm => t('api_keys.actions.destroy_confirm')}, class: "remove"
+ br
diff --git a/app/views/imports/_filters.html.slim b/app/views/imports/_filters.html.slim
new file mode 100644
index 000000000..99fcb0232
--- /dev/null
+++ b/app/views/imports/_filters.html.slim
@@ -0,0 +1,21 @@
+= search_form_for @q, url: workbench_imports_path(@workbench), html: { method: :get, class: 'form form-filter' } do |f|
+ .ffg-row
+ .input-group.search_bar
+ = f.search_field :name_or_creator_cont, class: 'form-control', placeholder: t('imports.filters.name_or_creator_cont')
+ span.input-group-btn
+ button.btn.btn-default#search_btn type='submit'
+ span.fa.fa-search
+
+ .ffg-row
+ .form-group.togglable
+ = f.label Import.human_attribute_name(:status), required: false, class: 'control-label'
+ = f.input :status_eq_any, collection: @imports.map(&:status).uniq.compact, as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + l + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}
+
+ .form-group.togglable
+ = f.label Import.human_attribute_name(:started_at), required: false, class: 'control-label'
+ .filter_menu
+ = f.input :started_at_eq, as: :date, label: false, wrapper_html: { class: 'date smart_date filter_menu-item' }, include_blank: true
+
+ .actions
+ = link_to t('actions.erase'), workbench_imports_path(@workbench), class: 'btn btn-link'
+ = f.submit t('actions.filter'), class: 'btn btn-default'
diff --git a/app/views/imports/_form.html.slim b/app/views/imports/_form.html.slim
index b795e908f..3ec22415f 100644
--- a/app/views/imports/_form.html.slim
+++ b/app/views/imports/_form.html.slim
@@ -1,6 +1,22 @@
-= simple_form_for import, as: :import, url: workbench_imports_path(workbench) do |f|
- = f.input :name
- = f.input :file
- = f.association :referential, collection: workbench.referentials
- = f.input :type, as: :hidden
- = f.button :submit
+= simple_form_for import, as: :import, url: workbench_imports_path(workbench), html: {class: 'form-horizontal', id: 'wb_import_form'}, wrapper: :horizontal_form do |form|
+
+ .row
+ .col-lg-12
+ = form.input :name
+
+ .row
+ .col-lg-12
+ .form-group
+ = form.label :file, t('activerecord.attributes.import.resources'), class: 'control-label col-sm-4 col-xs-5'
+ .col-sm-8.col-xs-7
+ = form.input_field :file, label: false, class: 'form-control'
+
+ .separator
+
+ .row
+ .col-lg-12
+ = form.association :referential, collection: workbench.referentials, input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': t('imports.filters.referential') }, label: t('activerecord.attributes.import.references_type'), label_method: :name, wrapper_html: { class: 'select2ed'}
+ = form.input :type, as: :hidden
+
+
+ = form.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'wb_import_form'
diff --git a/app/views/imports/index.html.slim b/app/views/imports/index.html.slim
index 6e2d49f73..79e464490 100644
--- a/app/views/imports/index.html.slim
+++ b/app/views/imports/index.html.slim
@@ -1,12 +1,46 @@
-= title_tag t('.title')
-- @imports.each do |import|
- .import
- li = link_to import.name, workbench_import_path(@workbench, import)
- li = import.referential.name if import.referential
- li = link_to import.file.file.filename, import.file.url, target: :_blank
- hr
-
-.warning = t('.warning')
-- content_for :sidebar do
- ul.actions
- li = link_to t('imports.actions.new'), new_workbench_import_path(workbench_id: @workbench), class: 'add'
+/ PageHeader
+= pageheader 'map-marker',
+ t('.title'),
+ '',
+ '',
+ link_to(t('imports.actions.new'), new_workbench_import_path(workbench_id: @workbench), class: 'btn btn-primary')
+
+/ PageContent
+.page_content
+ .container-fluid
+ - if params[:q].present? or @imports.any?
+ .row
+ .col-lg-12
+ = render 'filters'
+
+ - if @imports.any?
+ .row
+ .col-lg-12
+ = table_builder_2 @imports,
+ [ \
+ TableBuilderHelper::Column.new( \
+ key: :status, \
+ attribute: 'status' \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :started_at, \
+ attribute: 'started_at' \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :name, \
+ attribute: 'name' \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :creator, \
+ attribute: 'creator' \
+ ) \
+ ],
+ links: [],
+ cls: 'table has-search'
+
+ = new_pagination @imports, 'pull-right'
+
+ - unless @imports.any?
+ .row.mt-xs
+ .col-lg-12
+ = replacement_msg t('imports.search_no_results')
diff --git a/app/views/imports/new.html.slim b/app/views/imports/new.html.slim
index 55b655a85..2a18c41f2 100644
--- a/app/views/imports/new.html.slim
+++ b/app/views/imports/new.html.slim
@@ -1,4 +1,10 @@
-= title_tag t('.title')
-.row
- .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-8.col-sm-offset-2
- = render 'form', import: @import, workbench: @workbench
+/ PageHeader
+= pageheader 'map-marker',
+ t('.title')
+
+/ PageContent
+.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', import: @import, workbench: @workbench
diff --git a/app/views/imports/show.html.slim b/app/views/imports/show.html.slim
index b40e11ea4..65e3f33ff 100644
--- a/app/views/imports/show.html.slim
+++ b/app/views/imports/show.html.slim
@@ -1,14 +1,126 @@
-.title.row
- .col-md-8
- = title_tag job_status_title(@import)
+/ PageHeader
+= pageheader 'map-marker',
+ @import.name,
+ '',
+ t('last_update', time: l(@import.updated_at, format: :short)) do
+
+ / Below is secundary actions & optional contents (filters, ...)
+ .row
+ .col-lg-12.text-right.mb-sm
+ - @import.action_links.each do |link|
+ = link_to link.href,
+ method: link.method,
+ data: link.data,
+ class: 'btn btn-primary' do
+ = link.content
-.import_show
- .links
- = link_to font_awesome_classic_tag("fa-file-#{@import.file.file.extension}-o") + t("imports.show.imported_file"), @import.file.url
+/ PageContent
+.page_content
+ .container-fluid
+ .row
+ .col-lg-6.col-md-6.col-sm-12.col-xs-12
+ = definition_list t('metadatas'), { 'Récupération des données' => '-', "Nom de l'archive" => @import.try(:file_identifier)}
-- content_for :sidebar do
- ul.actions
- li
- = link_to t('imports.actions.destroy'), workbench_import_path(@workbench, @import.id), method: :delete, data: {confirm: t('imports.actions.destroy_confirm')}, class: 'remove'
+ / .row
+ / .col-lg-12
+ / = table_builder_2 Import.all,
+ / [ \
+ / TableBuilderHelper::Column.new( \
+ / key: :status, \
+ / attribute: 'status' \
+ / ), \
+ / TableBuilderHelper::Column.new( \
+ / key: :started_at, \
+ / attribute: 'started_at' \
+ / ), \
+ / TableBuilderHelper::Column.new( \
+ / key: :name, \
+ / attribute: 'name' \
+ / ), \
+ / TableBuilderHelper::Column.new( \
+ / key: :creator, \
+ / attribute: 'creator' \
+ / ) \
+ / ],
+ / links: [],
+ / cls: 'table',
+ / overhead: [ \
+ / {}, \
+ / { \
+ / title: 'Lorem ipsum dolor sit amet', \
+ / width: 1, \
+ / cls: 'overheaded-danger full-border' \
+ / }, { \
+ / title: 'Toto <span title="Lorem ipsum..." class="fa fa-lg fa-info-circle text-info"></span>', \
+ / width: 2, \
+ / cls: 'overheaded-default' \
+ / } \
+ / ]
- = history_tag(@import)
+ .row
+ .col-lg-12
+ / TMP static table
+ table.table
+ thead
+ tr
+ th Nom du jeu de données
+ th Conformité Netex
+ th Contrôle STIF
+ th Contrôle organisation
+ th
+
+ tbody
+ tr
+ td Nom JDD #1
+ td.text-center
+ span.fa.fa-circle.text-success
+ td.text-center
+ span.fa.fa-circle.text-danger
+ td.text-center -
+ td.actions
+ .btn-group
+ .btn.dropdown-toggle data-toggle='dropdown'
+ span.fa.fa-cog
+
+ ul.dropdown-menu
+ li
+ = link_to 'Rapport de contrôle STIF', '#'
+ li
+ = link_to 'Rapport de contrôle Orga.', '#'
+
+ tr
+ td Nom JDD #2
+ td.text-center
+ span.fa.fa-circle.text-warning
+ td.text-center
+ span.fa.fa-circle.text-warning
+ td.text-center -
+ td.actions
+ .btn-group
+ .btn.dropdown-toggle data-toggle='dropdown'
+ span.fa.fa-cog
+
+ ul.dropdown-menu
+ li
+ = link_to 'Rapport de contrôle STIF', '#'
+ li
+ = link_to 'Rapport de contrôle Orga.', '#'
+
+ tr
+ td Nom JDD #3
+ td.text-center
+ span.fa.fa-circle.text-danger
+ td.text-center
+ span.fa.fa-circle.text-danger
+
+ td.text-center -
+ td.actions
+ .btn-group
+ .btn.dropdown-toggle data-toggle='dropdown'
+ span.fa.fa-cog
+
+ ul.dropdown-menu
+ li
+ = link_to 'Rapport de contrôle STIF', '#'
+ li
+ = link_to 'Rapport de contrôle Orga.', '#'
diff --git a/app/views/layouts/navigation/_main_nav_left.html.slim b/app/views/layouts/navigation/_main_nav_left.html.slim
index 8e82ac528..74442692d 100644
--- a/app/views/layouts/navigation/_main_nav_left.html.slim
+++ b/app/views/layouts/navigation/_main_nav_left.html.slim
@@ -33,8 +33,14 @@
.list-group
= link_to '#', class: "list-group-item #{params[:controller] == 'workbenches' ? 'active' : ''}" do
span Jeux de données
- = link_to '#', class: 'list-group-item' do
- span Import
+
+ - if @workbench
+ = link_to workbench_imports_path(@workbench), class: "list-group-item #{(params[:controller] == 'imports') ? 'active' : ''}" do
+ span Import
+ - else
+ = link_to '#', class: 'list-group-item disabled' do
+ span Import
+
= link_to calendars_path, class: 'list-group-item' do
span Modèles de calendrier
= link_to '#', class: 'list-group-item' do
diff --git a/app/views/line_referentials/show.html.slim b/app/views/line_referentials/show.html.slim
index 95c2c02b0..e2381e7e9 100644
--- a/app/views/line_referentials/show.html.slim
+++ b/app/views/line_referentials/show.html.slim
@@ -44,6 +44,6 @@
td.text-center
.fa.fa-circle class="text-#{criticity_class(log.criticity)}"
td
- - data = log.message_attributs.symbolize_keys!
+ - data = log.message_attributes.symbolize_keys!
- data[:processing_time] = distance_of_time_in_words(data[:processing_time].to_i)
- = t("line_referential_sync.message.#{log.message_key}", log.message_attributs.symbolize_keys!).html_safe
+ = t("line_referential_sync.message.#{log.message_key}", log.message_attributes.symbolize_keys!).html_safe
diff --git a/app/views/networks/index.html.slim b/app/views/networks/index.html.slim
index 4c1f9783c..1478c3e09 100644
--- a/app/views/networks/index.html.slim
+++ b/app/views/networks/index.html.slim
@@ -40,6 +40,6 @@
= new_pagination @networks, 'pull-right'
- unless @networks.any?
- .row
+ .row.mt-xs
.col-lg-12
= replacement_msg t('networks.search_no_results')
diff --git a/app/views/networks/show.html.slim b/app/views/networks/show.html.slim
index 09edbad2e..5726daa2b 100644
--- a/app/views/networks/show.html.slim
+++ b/app/views/networks/show.html.slim
@@ -1,7 +1,7 @@
/ PageHeader
= pageheader 'map-marker',
@network.name,
- 'Lorem ipsum dolor sit amet',
+ '',
t('last_update', time: l(@network.updated_at, format: :short)) do
/ Below is secundary actions & optional contents (filters, ...)
diff --git a/app/views/stop_area_referentials/show.html.slim b/app/views/stop_area_referentials/show.html.slim
index 56ecbf6da..0fca15fff 100644
--- a/app/views/stop_area_referentials/show.html.slim
+++ b/app/views/stop_area_referentials/show.html.slim
@@ -37,6 +37,6 @@
td.text-center
.fa.fa-circle class="text-#{criticity_class(log.criticity)}"
td
- - data = log.message_attributs.symbolize_keys!
+ - data = log.message_attributes.symbolize_keys!
- data[:processing_time] = distance_of_time_in_words(data[:processing_time].to_i)
- = t("stop_area_referential_sync.message.#{log.message_key}", log.message_attributs.symbolize_keys!).html_safe
+ = t("stop_area_referential_sync.message.#{log.message_key}", log.message_attributes.symbolize_keys!).html_safe
diff --git a/app/views/time_table_combinations/_form.html.slim b/app/views/time_table_combinations/_form.html.slim
index b4f818828..581f00457 100644
--- a/app/views/time_table_combinations/_form.html.slim
+++ b/app/views/time_table_combinations/_form.html.slim
@@ -7,7 +7,7 @@
abbr title='Champ requis' *
= f.input :combined_type, as: :boolean, checked_value: 'time_table', unchecked_value: 'calendar', required: false, label: content_tag(:span, t("time_table_combinations.combined_type.#{@combination.combined_type}"), class: 'switch-label', data: { checkedValue: 'Calendriers', uncheckedValue: 'Modèles de calendriers' }), wrapper_html: { class: 'col-sm-8 col-xs-7' }
- = f.input :time_table_id, as: :select, input_html: {class: 'tt_combination_target', style: "width: 100%", data: { 'select2-ajax': 'true', 'select2ed-placeholder': 'Indiquez un calendrier...', term: 'comment_or_objectid_cont', url: referential_autocomplete_time_tables_path(@referential, format: :json)}}, wrapper_html: {class: @combination.combined_type != 'time_table' ? 'hidden' : ''}
+ = f.input :time_table_id, as: :select, input_html: {class: 'tt_combination_target', style: "width: 100%", data: { 'select2-ajax': 'true', 'select2ed-placeholder': 'Indiquez un calendrier...', term: 'comment_or_objectid_cont', url: referential_autocomplete_time_tables_path(@referential, format: :json, :source_id => @combination.source_id)}}, wrapper_html: {class: @combination.combined_type != 'time_table' ? 'hidden' : ''}
= f.input :calendar_id, as: :select, input_html: { class: 'tt_combination_target', style: "width: 100%", data: { 'select2-ajax': 'true', 'select2ed-placeholder': 'Indiquez un modèle de calendrier...', term: 'name_cont', url: autocomplete_calendars_path}}, wrapper_html: {class: @combination.combined_type != 'calendar' ? 'hidden' : ''}
diff --git a/app/views/time_tables/edit.html.slim b/app/views/time_tables/edit.html.slim
index f129cd63a..a2dfb90f9 100644
--- a/app/views/time_tables/edit.html.slim
+++ b/app/views/time_tables/edit.html.slim
@@ -10,6 +10,7 @@
#periods
= javascript_tag do
- | window.actionType = "#{raw params[:action]}"
+ | window.actionType = "#{raw params[:action]}";
+ | window.I18n = #{(I18n.backend.send(:translations).to_json).html_safe};
= javascript_include_tag 'es6_browserified/time_tables/index.js'
diff --git a/app/workers/workbench_import_worker.rb b/app/workers/workbench_import_worker.rb
new file mode 100644
index 000000000..7f77b46dc
--- /dev/null
+++ b/app/workers/workbench_import_worker.rb
@@ -0,0 +1,118 @@
+class WorkbenchImportWorker
+ include Sidekiq::Worker
+ include Rails.application.routes.url_helpers
+ include Configurable
+
+ RETRY_DELAYS = [3, 5, 8]
+
+ # Workers
+ # =======
+
+ def perform(import_id)
+ @workbench_import = WorkbenchImport.find(import_id)
+ @response = nil
+ @workbench_import.update_attributes(status: 'running')
+ downloaded = download
+ zip_service = ZipService.new(downloaded)
+ upload zip_service
+ end
+
+ def download
+ logger.info "HTTP GET #{import_url}"
+ @zipfile_data = HTTPService.get_resource(
+ host: import_host,
+ path: import_path,
+ params: {token: @workbench_import.token_download}).body
+ end
+
+ def execute_post eg_name, eg_stream
+ logger.info "HTTP POST #{export_url} (for #{complete_entry_group_name(eg_name)})"
+ HTTPService.post_resource(
+ host: export_host,
+ path: export_path,
+ token: token(eg_name),
+ params: params,
+ upload: {file: [eg_stream, 'application/zip', eg_name]})
+ end
+
+ def log_failure reason, count
+ logger.warn "HTTP POST failed with #{reason}, count = #{count}, response=#{@response}"
+ end
+
+ def try_again
+ raise RetryService::Retry
+ end
+
+ def try_upload_entry_group eg_name, eg_stream
+ result = execute_post eg_name, eg_stream
+ return result if result && result.status < 400
+ @response = result.body
+ try_again
+ end
+
+ def upload zip_service
+ entry_group_streams = zip_service.entry_group_streams
+ @workbench_import.update_attributes total_steps: entry_group_streams.size
+ entry_group_streams.each_with_index(&method(:upload_entry_group))
+ rescue StopIteration
+ @workbench_import.update_attributes( current_step: entry_group_streams.size, status: 'failed' )
+ end
+
+ def upload_entry_group entry_pair, element_count
+ @workbench_import.update_attributes( current_step: element_count.succ )
+ retry_service = RetryService.new(
+ delays: RETRY_DELAYS,
+ rescue_from: [HTTPService::Timeout],
+ &method(:log_failure))
+ status = retry_service.execute(&upload_entry_group_proc(entry_pair))
+ raise StopIteration unless status.ok?
+ end
+
+ def upload_entry_group_proc entry_pair
+ eg_name, eg_stream = entry_pair
+ # This should be fn.try_upload_entry_group(eg_name, eg_stream) ;(
+ -> do
+ try_upload_entry_group(eg_name, eg_stream)
+ end
+ end
+
+
+
+ # Queries
+ # =======
+
+ def complete_entry_group_name entry_group_name
+ [@workbench_import.name, entry_group_name].join("--")
+ end
+
+ def token entry_group_name
+ Api::V1::ApiKey.from(@workbench_import.referential, name: complete_entry_group_name(entry_group_name)).token
+ end
+
+ # Constants
+ # =========
+
+ def export_host
+ Rails.application.config.rails_host
+ end
+ def export_path
+ api_v1_netex_imports_path(format: :json)
+ end
+ def export_url
+ @__export_url__ ||= File.join(export_host, export_path)
+ end
+
+ def import_host
+ Rails.application.config.rails_host
+ end
+ def import_path
+ @__import_path__ ||= download_workbench_import_path(@workbench_import.workbench, @workbench_import)
+ end
+ def import_url
+ @__import_url__ ||= File.join(import_host, import_path)
+ end
+
+ def params
+ @__params__ ||= { netex_import: { referential_id: @workbench_import.referential_id, workbench_id: @workbench_import.workbench_id } }
+ end
+end
diff --git a/config/application.rb b/config/application.rb
index 910ddd983..05a9752b6 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -14,7 +14,7 @@ module ChouetteIhm
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
- config.autoload_paths << config.root.join("lib")
+ config.autoload_paths << config.root.join('lib')
# custom exception pages
config.exceptions_app = self.routes
diff --git a/config/deploy.rb b/config/deploy.rb
index 4ab888e92..fdd0b1d1d 100644
--- a/config/deploy.rb
+++ b/config/deploy.rb
@@ -24,7 +24,7 @@ require "bundler/capistrano"
require 'whenever/capistrano'
require 'capistrano/npm'
-set :npm_options, '--production --silent --no-progress'
+set :npm_options, '--production --no-progress'
after 'deploy:finalize_update', 'npm:install'
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 59cb9eefa..56773d81e 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -61,15 +61,17 @@ Rails.application.configure do
config.reflex_api_url = "https://pprod.reflex.stif.info/ws/reflex/V1/service=getData"
config.codifligne_api_url = "https://pprod.codifligne.stif.info/rest/v1/lc/getlist"
- # config.chouette_authentication_settings = {
- # type: "database"
- # }
- config.chouette_authentication_settings = {
- type: "cas",
- cas_server: "http://stif-portail-dev.af83.priv/sessions"
- }
- config.stif_portail_api =
- {
+ if Rails.env.development? && ENV['NO_VPN']
+ config.chouette_authentication_settings = {
+ type: "database"
+ }
+ else
+ config.chouette_authentication_settings = {
+ type: "cas",
+ cas_server: "http://stif-portail-dev.af83.priv/sessions"
+ }
+ end
+ config.stif_portail_api = {
key: "Ohphie1Voo6the5hohpi",
url: "http://stif-portail-dev.af83.priv"
}
@@ -80,7 +82,8 @@ Rails.application.configure do
config.portal_url = "http://stif-boiv-staging.af83.priv"
# IEV url
- config.iev_url = "localhost:8080"
+ config.iev_url = ENV.fetch('IEV_URL', 'http://localhost:8080')
+ config.rails_host = ENV.fetch('RAILS_HOST', 'http://localhost:3000')
# file to data for demo
config.demo_data = "tmp/demo.zip"
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 19fc23024..794c8bd88 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -141,6 +141,7 @@ Rails.application.configure do
# IEV
config.iev_url = "http://worker-server:8080"
+ config.rails_host = ENV.fetch('RAILS_HOST')
# Set node env for browserify-rails
config.browserify_rails.node_env = "production"
diff --git a/config/environments/test.rb b/config/environments/test.rb
index a6db12006..b3312be4a 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -62,6 +62,7 @@ Rails.application.configure do
# Reflex api url
config.reflex_api_url = "https://195.46.215.128/ws/reflex/V1/service=getData"
+ config.rails_host = "http://www.example.com"
# file to data for demo
config.demo_data = "tmp/demo.zip"
diff --git a/config/initializers/apartment.rb b/config/initializers/apartment.rb
index 29ce6564f..e1e86449c 100644
--- a/config/initializers/apartment.rb
+++ b/config/initializers/apartment.rb
@@ -18,34 +18,35 @@ Apartment.configure do |config|
# config.excluded_models = %w{Tenant}
#
config.excluded_models = [
- "Referential",
- "ReferentialMetadata",
- "Organisation",
- "User",
- "Api::V1::ApiKey",
- "RuleParameterSet",
- "StopAreaReferential",
- "StopAreaReferentialMembership",
- "StopAreaReferentialSync",
- "StopAreaReferentialSyncMessage",
- "Chouette::StopArea",
- "LineReferential",
- "LineReferentialMembership",
- "LineReferentialSync",
- "LineReferentialSyncMessage",
- "Chouette::Line",
- "Chouette::GroupOfLine",
- "Chouette::Company",
- "Chouette::Network",
- "ReferentialCloning",
- "Workbench",
- "CleanUp",
- "CleanUpResult",
- "Calendar",
- "Import",
- "NetexImport",
- "ImportMessage",
- "ImportResource"
+ 'Referential',
+ 'ReferentialMetadata',
+ 'Organisation',
+ 'User',
+ 'Api::V1::ApiKey',
+ 'RuleParameterSet',
+ 'StopAreaReferential',
+ 'StopAreaReferentialMembership',
+ 'StopAreaReferentialSync',
+ 'StopAreaReferentialSyncMessage',
+ 'Chouette::StopArea',
+ 'LineReferential',
+ 'LineReferentialMembership',
+ 'LineReferentialSync',
+ 'LineReferentialSyncMessage',
+ 'Chouette::Line',
+ 'Chouette::GroupOfLine',
+ 'Chouette::Company',
+ 'Chouette::Network',
+ 'ReferentialCloning',
+ 'Workbench',
+ 'CleanUp',
+ 'CleanUpResult',
+ 'Calendar',
+ 'Import',
+ 'NetexImport',
+ 'WorkbenchImport',
+ 'ImportMessage',
+ 'ImportResource'
]
# use postgres schemas?
diff --git a/config/initializers/workbench_import.rb b/config/initializers/workbench_import.rb
new file mode 100644
index 000000000..89ddd72ef
--- /dev/null
+++ b/config/initializers/workbench_import.rb
@@ -0,0 +1,5 @@
+WorkbenchImportWorker.config do | config |
+ config.dir = ENV.fetch('WORKBENCH_IMPORT_DIR'){ Rails.root.join 'tmp/workbench_import' }
+
+ FileUtils.mkdir_p config.dir
+end
diff --git a/config/locales/api_keys.en.yml b/config/locales/api_keys.en.yml
index 221fa6eef..1480c8e55 100644
--- a/config/locales/api_keys.en.yml
+++ b/config/locales/api_keys.en.yml
@@ -7,12 +7,14 @@ en:
destroy_confirm: "Are you sure you want destroy this api key?"
show:
title: "Api key"
+ index:
+ title: Api key
new:
title: "Add a new api key"
edit:
title: "Update api key"
- activerecord:
- models:
+ activerecord:
+ models:
api_key: "Api Key"
attributes:
api_key:
diff --git a/config/locales/api_keys.fr.yml b/config/locales/api_keys.fr.yml
index 445367c72..20af91a49 100644
--- a/config/locales/api_keys.fr.yml
+++ b/config/locales/api_keys.fr.yml
@@ -7,6 +7,8 @@ fr:
destroy_confirm: "Etes vous sûr de vouloir détruire la clé d'accès API ?"
show:
title: "Clé d'accès API"
+ index:
+ title: Clé d'accès API
new:
title: "Ajouter une clé d'accès API"
edit:
diff --git a/config/locales/imports.en.yml b/config/locales/imports.en.yml
index b20f0f1da..b92b8843f 100644
--- a/config/locales/imports.en.yml
+++ b/config/locales/imports.en.yml
@@ -1,7 +1,13 @@
en:
imports:
+ search_no_results: "No import matching your query"
+ filters:
+ referential: "Select data space..."
+ name_or_creator_cont: "Select an import or creator name..."
actions:
new: "New import"
+ show: "Import report"
+ download: "Download original file"
destroy: "Destroy"
destroy_confirm: "Are you sure you want destroy this import?"
index:
@@ -50,7 +56,10 @@ en:
import:
resources: "File to import"
created_at: "Created on"
+ started_at: Started at
+ name: Name
status: "Status"
+ creator: "Creator"
references_type: "Data to be imported"
no_save: "No save"
rule_parameter_set_id: "Rule parameter set for compliance check"
diff --git a/config/locales/imports.fr.yml b/config/locales/imports.fr.yml
index 933025c82..f7bf8c178 100644
--- a/config/locales/imports.fr.yml
+++ b/config/locales/imports.fr.yml
@@ -1,7 +1,13 @@
fr:
imports:
+ search_no_results: "Aucun import ne correspond à votre recherche"
+ filters:
+ referential: "Sélectionnez un jeu de données..."
+ name_or_creator_cont: "Indiquez un nom d'import ou d'opérateur..."
actions:
new: "Nouvel import"
+ show: "Rapport d'import"
+ download: "Téléch. fichier source"
destroy: "Supprimer cet import"
destroy_confirm: "Etes vous sûr de supprimer cet import ?"
index:
@@ -50,7 +56,10 @@ fr:
import:
resources: "Fichier à importer"
created_at: "Créé le"
- status: "Status"
+ started_at: Démarrage
+ name: "Nom de l'import"
+ status: "Etat"
+ creator: "Opérateur"
no_save: "Pas de sauvegarde"
references_type: "Données à importer"
rule_parameter_set_id: "Jeu de paramètres pour validation"
diff --git a/config/locales/time_tables.en.yml b/config/locales/time_tables.en.yml
index ed2f9758e..d67e30edb 100644
--- a/config/locales/time_tables.en.yml
+++ b/config/locales/time_tables.en.yml
@@ -29,6 +29,10 @@ en:
title: "Duplicate timetable"
edit:
title: "Update timetable %{time_table}"
+ error_modal:
+ title: "Error"
+ withoutPeriodsWithDaysTypes: "A timetable can't have day type(s) without period(s)."
+ withPeriodsWithoutDayTypes: "A tiemetable can't have period(s) swithout day type(s)."
show:
title: "Timetable %{time_table}"
dates: "Application dates"
diff --git a/config/locales/time_tables.fr.yml b/config/locales/time_tables.fr.yml
index cf6888d0f..06d1d59e8 100644
--- a/config/locales/time_tables.fr.yml
+++ b/config/locales/time_tables.fr.yml
@@ -29,6 +29,10 @@ fr:
title: "Dupliquer un calendrier"
edit:
title: "Editer le calendrier %{time_table}"
+ error_modal:
+ title: "Erreur"
+ withoutPeriodsWithDaysTypes: "Un calendrier d'application ne peut pas avoir de journée(s) d'application sans période(s)."
+ withPeriodsWithoutDayTypes: "Un calendrier d'application ne peut pas avoir de période(s) sans journée(s) d'application."
show:
title: Calendrier %{time_table}
dates: "Dates d'application"
diff --git a/config/routes.rb b/config/routes.rb
index 28c092e6a..bf1c1cb74 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -32,29 +32,34 @@ ChouetteIhm::Application.routes.draw do
namespace :api do
namespace :v1 do
- resources :time_tables, :only => [:index, :show]
- resources :connection_links, :only => [:index, :show]
- resources :companies, :only => [:index, :show]
- resources :networks, :only => [:index, :show]
- resources :stop_areas, :only => [:index, :show]
- resources :group_of_lines, :only => [:index, :show]
- resources :access_points, :only => [:index, :show]
- resources :access_links, :only => [:index, :show]
- resources :lines, :only => [:index, :show] do
- resources :journey_patterns, :only => [:index, :show]
- resources :routes, :only => [:index, :show] do
- resources :vehicle_journeys, :only => [:index, :show]
- resources :journey_patterns, :only => [:index, :show]
- resources :stop_areas, :only => [:index, :show]
+ resources :workbenches, only: [:index, :show] do
+ resources :imports, only: [:index, :show, :create]
+ end
+ resources :access_links, only: [:index, :show]
+ resources :access_points, only: [:index, :show]
+ resources :connection_links, only: [:index, :show]
+ resources :companies, only: [:index, :show]
+ resources :group_of_lines, only: [:index, :show]
+ resources :netex_imports, only: :create
+ resources :journey_patterns, only: :show
+ resources :lines, only: [:index, :show] do
+ resources :journey_patterns, only: [:index, :show]
+ resources :routes, only: [:index, :show] do
+ resources :vehicle_journeys, only: [:index, :show]
+ resources :journey_patterns, only: [:index, :show]
+ resources :stop_areas, only: [:index, :show]
end
end
- resources :routes, :only => :show
- resources :journey_patterns, :only => :show
- resources :vehicle_journeys, :only => :show
+ resources :networks, only: [:index, :show]
+ resources :routes, only: :show
+ resources :stop_areas, only: [:index, :show]
+ resources :time_tables, only: [:index, :show]
+ resources :vehicle_journeys, only: :show
end
end
resource :organisation, :only => [:show, :edit, :update] do
+ resources :api_keys
resources :users
resources :rule_parameter_sets
end
@@ -77,7 +82,6 @@ ChouetteIhm::Application.routes.draw do
end
resources :referentials, except: :index do
- resources :api_keys
resources :autocomplete_stop_areas, only: [:show, :index] do
get 'around', on: :member
end
@@ -180,7 +184,7 @@ ChouetteIhm::Application.routes.draw do
resources :timebands
resources :access_points do
- resources :access_links
+ resources :access_links
end
resources :stop_areas, controller: "referential_stop_areas" do
diff --git a/config/schedule.rb b/config/schedule.rb
index 83c4d7388..8aa21076f 100644
--- a/config/schedule.rb
+++ b/config/schedule.rb
@@ -38,3 +38,7 @@ end
every :day, :at => '4:00 am' do
rake "codifligne:sync"
end
+
+every 5.minutes do
+ rake "import:notify_parent"
+end
diff --git a/db/migrate/20170710125809_add_check_sum.rb b/db/migrate/20170710125809_add_check_sum.rb
new file mode 100644
index 000000000..b91ddb74d
--- /dev/null
+++ b/db/migrate/20170710125809_add_check_sum.rb
@@ -0,0 +1,13 @@
+class AddCheckSum < ActiveRecord::Migration
+ def change
+ add_column :vehicle_journey_at_stops, :checksum, :string
+ add_column :footnotes, :checksum, :string
+ add_column :routing_constraint_zones, :checksum, :string
+ add_column :routes, :checksum, :string
+ add_column :journey_patterns, :checksum, :string
+ add_column :vehicle_journeys, :checksum, :string
+ add_column :time_table_dates, :checksum, :string
+ add_column :time_table_periods, :checksum, :string
+ add_column :time_tables, :checksum, :string
+ end
+end
diff --git a/db/migrate/20170710130230_add_check_sum_source.rb b/db/migrate/20170710130230_add_check_sum_source.rb
new file mode 100644
index 000000000..b8e36e954
--- /dev/null
+++ b/db/migrate/20170710130230_add_check_sum_source.rb
@@ -0,0 +1,13 @@
+class AddCheckSumSource < ActiveRecord::Migration
+ def change
+ add_column :vehicle_journey_at_stops, :checksum_source, :string
+ add_column :footnotes, :checksum_source, :string
+ add_column :routing_constraint_zones, :checksum_source, :string
+ add_column :routes, :checksum_source, :string
+ add_column :journey_patterns, :checksum_source, :string
+ add_column :vehicle_journeys, :checksum_source, :string
+ add_column :time_table_dates, :checksum_source, :string
+ add_column :time_table_periods, :checksum_source, :string
+ add_column :time_tables, :checksum_source, :string
+ end
+end
diff --git a/db/migrate/20170715041954_add_parent_type_and_parent_id_to_imports.rb b/db/migrate/20170715041954_add_parent_type_and_parent_id_to_imports.rb
new file mode 100644
index 000000000..96c1c2a59
--- /dev/null
+++ b/db/migrate/20170715041954_add_parent_type_and_parent_id_to_imports.rb
@@ -0,0 +1,6 @@
+class AddParentTypeAndParentIdToImports < ActiveRecord::Migration
+ def change
+ add_column :imports, :parent_id, :bigint
+ add_column :imports, :parent_type, :string
+ end
+end
diff --git a/db/migrate/20170724094628_add_notified_parent_at_to_imports.rb b/db/migrate/20170724094628_add_notified_parent_at_to_imports.rb
new file mode 100644
index 000000000..7c6484cfc
--- /dev/null
+++ b/db/migrate/20170724094628_add_notified_parent_at_to_imports.rb
@@ -0,0 +1,5 @@
+class AddNotifiedParentAtToImports < ActiveRecord::Migration
+ def change
+ add_column :imports, :notified_parent_at, :datetime
+ end
+end
diff --git a/db/migrate/20170727130705_add_current_step_and_total_steps_to_import.rb b/db/migrate/20170727130705_add_current_step_and_total_steps_to_import.rb
new file mode 100644
index 000000000..b31e86f17
--- /dev/null
+++ b/db/migrate/20170727130705_add_current_step_and_total_steps_to_import.rb
@@ -0,0 +1,6 @@
+class AddCurrentStepAndTotalStepsToImport < ActiveRecord::Migration
+ def change
+ add_column :imports, :current_step, :integer, default: 0
+ add_column :imports, :total_steps, :integer, default: 0
+ end
+end
diff --git a/db/migrate/20170802141224_rename_message_attributs_to_message_attributes_everywhere.rb b/db/migrate/20170802141224_rename_message_attributs_to_message_attributes_everywhere.rb
new file mode 100644
index 000000000..f80292b14
--- /dev/null
+++ b/db/migrate/20170802141224_rename_message_attributs_to_message_attributes_everywhere.rb
@@ -0,0 +1,7 @@
+class RenameMessageAttributsToMessageAttributesEverywhere < ActiveRecord::Migration
+ def change
+ rename_column :import_messages, :message_attributs, :message_attributes
+ rename_column :line_referential_sync_messages, :message_attributs, :message_attributes
+ rename_column :stop_area_referential_sync_messages, :message_attributs, :message_attributes
+ end
+end
diff --git a/db/migrate/20170808110333_change_checksum_source_type_to_text.rb b/db/migrate/20170808110333_change_checksum_source_type_to_text.rb
new file mode 100644
index 000000000..731b2a19d
--- /dev/null
+++ b/db/migrate/20170808110333_change_checksum_source_type_to_text.rb
@@ -0,0 +1,17 @@
+class ChangeChecksumSourceTypeToText < ActiveRecord::Migration
+ def tables
+ [:vehicle_journey_at_stops, :footnotes, :routing_constraint_zones, :routes, :journey_patterns, :vehicle_journeys, :time_table_dates, :time_table_periods, :time_tables]
+ end
+
+ def up
+ self.tables.each do |table|
+ change_column table, :checksum_source, :text
+ end
+ end
+
+ def down
+ self.tables.each do |table|
+ change_column table, :checksum_source, :string
+ end
+ end
+end
diff --git a/db/migrate/20170816104020_add_creator_to_imports.rb b/db/migrate/20170816104020_add_creator_to_imports.rb
new file mode 100644
index 000000000..5fb808451
--- /dev/null
+++ b/db/migrate/20170816104020_add_creator_to_imports.rb
@@ -0,0 +1,5 @@
+class AddCreatorToImports < ActiveRecord::Migration
+ def change
+ add_column :imports, :creator, :string
+ end
+end
diff --git a/db/migrate/20170817122914_add_organisation_to_api_keys.rb b/db/migrate/20170817122914_add_organisation_to_api_keys.rb
new file mode 100644
index 000000000..14c742c87
--- /dev/null
+++ b/db/migrate/20170817122914_add_organisation_to_api_keys.rb
@@ -0,0 +1,5 @@
+class AddOrganisationToApiKeys < ActiveRecord::Migration
+ def change
+ add_reference :api_keys, :organisation, index: true, foreign_key: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index e64e5c04a..b03200b6a 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170607141317) do
+ActiveRecord::Schema.define(version: 20170817122914) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -21,12 +21,12 @@ ActiveRecord::Schema.define(version: 20170607141317) do
create_table "access_links", id: :bigserial, force: :cascade do |t|
t.integer "access_point_id", limit: 8
t.integer "stop_area_id", limit: 8
- t.string "objectid", null: false
+ t.string "objectid", limit: 255, null: false
t.integer "object_version", limit: 8
- t.string "creator_id"
- t.string "name"
- t.string "comment"
- t.decimal "link_distance", precision: 19, scale: 2
+ t.string "creator_id", limit: 255
+ t.string "name", limit: 255
+ t.string "comment", limit: 255
+ t.decimal "link_distance", precision: 19, scale: 2
t.boolean "lift_availability"
t.boolean "mobility_restricted_suitability"
t.boolean "stairs_availability"
@@ -34,9 +34,9 @@ ActiveRecord::Schema.define(version: 20170607141317) do
t.time "frequent_traveller_duration"
t.time "occasional_traveller_duration"
t.time "mobility_restricted_traveller_duration"
- t.string "link_type"
+ t.string "link_type", limit: 255
t.integer "int_user_needs"
- t.string "link_orientation"
+ t.string "link_orientation", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
end
@@ -44,26 +44,26 @@ ActiveRecord::Schema.define(version: 20170607141317) do
add_index "access_links", ["objectid"], name: "access_links_objectid_key", unique: true, using: :btree
create_table "access_points", id: :bigserial, force: :cascade do |t|
- t.string "objectid"
+ t.string "objectid", limit: 255
t.integer "object_version", limit: 8
- t.string "creator_id"
- t.string "name"
- t.string "comment"
- t.decimal "longitude", precision: 19, scale: 16
- t.decimal "latitude", precision: 19, scale: 16
- t.string "long_lat_type"
- t.string "country_code"
- t.string "street_name"
- t.string "contained_in"
+ t.string "creator_id", limit: 255
+ t.string "name", limit: 255
+ t.string "comment", limit: 255
+ t.decimal "longitude", precision: 19, scale: 16
+ t.decimal "latitude", precision: 19, scale: 16
+ t.string "long_lat_type", limit: 255
+ t.string "country_code", limit: 255
+ t.string "street_name", limit: 255
+ t.string "contained_in", limit: 255
t.time "openning_time"
t.time "closing_time"
- t.string "access_type"
+ t.string "access_type", limit: 255
t.boolean "lift_availability"
t.boolean "mobility_restricted_suitability"
t.boolean "stairs_availability"
t.integer "stop_area_id", limit: 8
- t.string "zip_code"
- t.string "city_name"
+ t.string "zip_code", limit: 255
+ t.string "city_name", limit: 255
t.text "import_xml"
t.datetime "created_at"
t.datetime "updated_at"
@@ -72,19 +72,22 @@ ActiveRecord::Schema.define(version: 20170607141317) do
add_index "access_points", ["objectid"], name: "access_points_objectid_key", unique: true, using: :btree
create_table "api_keys", id: :bigserial, force: :cascade do |t|
- t.integer "referential_id", limit: 8
- t.string "token"
- t.string "name"
+ t.integer "referential_id", limit: 8
+ t.string "token", limit: 255
+ t.string "name", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
+ t.integer "organisation_id"
end
+ add_index "api_keys", ["organisation_id"], name: "index_api_keys_on_organisation_id", using: :btree
+
create_table "calendars", id: :bigserial, force: :cascade do |t|
- t.string "name"
- t.string "short_name"
- t.daterange "date_ranges", array: true
- t.date "dates", array: true
- t.boolean "shared", default: false
+ t.string "name", limit: 255
+ t.string "short_name", limit: 255
+ t.daterange "date_ranges", array: true
+ t.date "dates", array: true
+ t.boolean "shared", default: false
t.integer "organisation_id", limit: 8
t.datetime "created_at"
t.datetime "updated_at"
@@ -94,7 +97,7 @@ ActiveRecord::Schema.define(version: 20170607141317) do
add_index "calendars", ["short_name"], name: "index_calendars_on_short_name", unique: true, using: :btree
create_table "clean_up_results", id: :bigserial, force: :cascade do |t|
- t.string "message_key"
+ t.string "message_key", limit: 255
t.hstore "message_attributs"
t.integer "clean_up_id", limit: 8
t.datetime "created_at"
@@ -104,7 +107,7 @@ ActiveRecord::Schema.define(version: 20170607141317) do
add_index "clean_up_results", ["clean_up_id"], name: "index_clean_up_results_on_clean_up_id", using: :btree
create_table "clean_ups", id: :bigserial, force: :cascade do |t|
- t.string "status"
+ t.string "status", limit: 255
t.datetime "started_at"
t.datetime "ended_at"
t.integer "referential_id", limit: 8
@@ -118,20 +121,20 @@ ActiveRecord::Schema.define(version: 20170607141317) do
add_index "clean_ups", ["referential_id"], name: "index_clean_ups_on_referential_id", using: :btree
create_table "companies", id: :bigserial, force: :cascade do |t|
- t.string "objectid", null: false
+ t.string "objectid", limit: 255, null: false
t.integer "object_version", limit: 8
- t.string "creator_id"
- t.string "name"
- t.string "short_name"
- t.string "organizational_unit"
- t.string "operating_department_name"
- t.string "code"
- t.string "phone"
- t.string "fax"
- t.string "email"
- t.string "registration_number"
- t.string "url"
- t.string "time_zone"
+ t.string "creator_id", limit: 255
+ t.string "name", limit: 255
+ t.string "short_name", limit: 255
+ t.string "organizational_unit", limit: 255
+ t.string "operating_department_name", limit: 255
+ t.string "code", limit: 255
+ t.string "phone", limit: 255
+ t.string "fax", limit: 255
+ t.string "email", limit: 255
+ t.string "registration_number", limit: 255
+ t.string "url", limit: 255
+ t.string "time_zone", limit: 255
t.integer "line_referential_id", limit: 8
t.text "import_xml"
t.datetime "created_at"
@@ -145,13 +148,13 @@ ActiveRecord::Schema.define(version: 20170607141317) do
create_table "connection_links", id: :bigserial, force: :cascade do |t|
t.integer "departure_id", limit: 8
t.integer "arrival_id", limit: 8
- t.string "objectid", null: false
+ t.string "objectid", limit: 255, null: false
t.integer "object_version", limit: 8
- t.string "creator_id"
- t.string "name"
- t.string "comment"
- t.decimal "link_distance", precision: 19, scale: 2
- t.string "link_type"
+ t.string "creator_id", limit: 255
+ t.string "name", limit: 255
+ t.string "comment", limit: 255
+ t.decimal "link_distance", precision: 19, scale: 2
+ t.string "link_type", limit: 255
t.time "default_duration"
t.time "frequent_traveller_duration"
t.time "occasional_traveller_duration"
@@ -166,15 +169,31 @@ ActiveRecord::Schema.define(version: 20170607141317) do
add_index "connection_links", ["objectid"], name: "connection_links_objectid_key", unique: true, using: :btree
+ create_table "delayed_jobs", id: :bigserial, force: :cascade do |t|
+ t.integer "priority", default: 0
+ t.integer "attempts", default: 0
+ t.text "handler"
+ t.text "last_error"
+ t.datetime "run_at"
+ t.datetime "locked_at"
+ t.datetime "failed_at"
+ t.string "locked_by", limit: 255
+ t.string "queue", limit: 255
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree
+
create_table "exports", id: :bigserial, force: :cascade do |t|
t.integer "referential_id", limit: 8
- t.string "status"
- t.string "type"
- t.string "options"
+ t.string "status", limit: 255
+ t.string "type", limit: 255
+ t.string "options", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
- t.string "references_type"
- t.string "reference_ids"
+ t.string "references_type", limit: 255
+ t.string "reference_ids", limit: 255
end
add_index "exports", ["referential_id"], name: "index_exports_on_referential_id", using: :btree
@@ -184,23 +203,23 @@ ActiveRecord::Schema.define(version: 20170607141317) do
t.integer "line_id", limit: 8
t.integer "connection_link_id", limit: 8
t.integer "stop_point_id", limit: 8
- t.string "objectid", null: false
+ t.string "objectid", limit: 255, null: false
t.integer "object_version", limit: 8
t.datetime "creation_time"
- t.string "creator_id"
- t.string "name"
- t.string "comment"
- t.string "description"
+ t.string "creator_id", limit: 255
+ t.string "name", limit: 255
+ t.string "comment", limit: 255
+ t.string "description", limit: 255
t.boolean "free_access"
- t.decimal "longitude", precision: 19, scale: 16
- t.decimal "latitude", precision: 19, scale: 16
- t.string "long_lat_type"
- t.decimal "x", precision: 19, scale: 2
- t.decimal "y", precision: 19, scale: 2
- t.string "projection_type"
- t.string "country_code"
- t.string "street_name"
- t.string "contained_in"
+ t.decimal "longitude", precision: 19, scale: 16
+ t.decimal "latitude", precision: 19, scale: 16
+ t.string "long_lat_type", limit: 255
+ t.decimal "x", precision: 19, scale: 2
+ t.decimal "y", precision: 19, scale: 2
+ t.string "projection_type", limit: 255
+ t.string "country_code", limit: 255
+ t.string "street_name", limit: 255
+ t.string "contained_in", limit: 255
end
add_index "facilities", ["objectid"], name: "facilities_objectid_key", unique: true, using: :btree
@@ -211,11 +230,13 @@ ActiveRecord::Schema.define(version: 20170607141317) do
end
create_table "footnotes", id: :bigserial, force: :cascade do |t|
- t.integer "line_id", limit: 8
- t.string "code"
- t.string "label"
+ t.integer "line_id", limit: 8
+ t.string "code", limit: 255
+ t.string "label", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
+ t.string "checksum"
+ t.text "checksum_source"
end
create_table "footnotes_vehicle_journeys", id: false, force: :cascade do |t|
@@ -224,12 +245,12 @@ ActiveRecord::Schema.define(version: 20170607141317) do
end
create_table "group_of_lines", id: :bigserial, force: :cascade do |t|
- t.string "objectid", null: false
+ t.string "objectid", limit: 255, null: false
t.integer "object_version", limit: 8
- t.string "creator_id"
- t.string "name"
- t.string "comment"
- t.string "registration_number"
+ t.string "creator_id", limit: 255
+ t.string "name", limit: 255
+ t.string "comment", limit: 255
+ t.string "registration_number", limit: 255
t.integer "line_referential_id", limit: 8
t.text "import_xml"
t.datetime "created_at"
@@ -246,8 +267,8 @@ ActiveRecord::Schema.define(version: 20170607141317) do
create_table "import_messages", id: :bigserial, force: :cascade do |t|
t.integer "criticity"
- t.string "message_key"
- t.hstore "message_attributs"
+ t.string "message_key", limit: 255
+ t.hstore "message_attributes"
t.integer "import_id", limit: 8
t.integer "resource_id", limit: 8
t.datetime "created_at"
@@ -260,31 +281,37 @@ ActiveRecord::Schema.define(version: 20170607141317) do
create_table "import_resources", id: :bigserial, force: :cascade do |t|
t.integer "import_id", limit: 8
- t.string "status"
+ t.string "status", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
- t.string "type"
- t.string "reference"
- t.string "name"
+ t.string "type", limit: 255
+ t.string "reference", limit: 255
+ t.string "name", limit: 255
t.hstore "metrics"
end
add_index "import_resources", ["import_id"], name: "index_import_resources_on_import_id", using: :btree
create_table "imports", id: :bigserial, force: :cascade do |t|
- t.string "status"
- t.string "current_step_id"
+ t.string "status", limit: 255
+ t.string "current_step_id", limit: 255
t.float "current_step_progress"
t.integer "workbench_id", limit: 8
t.integer "referential_id", limit: 8
- t.string "name"
+ t.string "name", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
- t.string "file"
+ t.string "file", limit: 255
t.datetime "started_at"
t.datetime "ended_at"
- t.string "token_download"
+ t.string "token_download", limit: 255
t.string "type", limit: 255
+ 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 "imports", ["referential_id"], name: "index_imports_on_referential_id", using: :btree
@@ -318,18 +345,20 @@ ActiveRecord::Schema.define(version: 20170607141317) do
create_table "journey_patterns", id: :bigserial, force: :cascade do |t|
t.integer "route_id", limit: 8
- t.string "objectid", null: false
+ t.string "objectid", limit: 255, null: false
t.integer "object_version", limit: 8
- t.string "creator_id"
- t.string "name"
- t.string "comment"
- t.string "registration_number"
- t.string "published_name"
+ t.string "creator_id", limit: 255
+ t.string "name", limit: 255
+ t.string "comment", limit: 255
+ t.string "registration_number", limit: 255
+ t.string "published_name", limit: 255
t.integer "departure_stop_point_id", limit: 8
t.integer "arrival_stop_point_id", limit: 8
- t.integer "section_status", default: 0, null: false
+ t.integer "section_status", default: 0, null: false
t.datetime "created_at"
t.datetime "updated_at"
+ t.string "checksum"
+ t.text "checksum_source"
end
add_index "journey_patterns", ["objectid"], name: "journey_patterns_objectid_key", unique: true, using: :btree
@@ -349,8 +378,8 @@ ActiveRecord::Schema.define(version: 20170607141317) do
create_table "line_referential_sync_messages", id: :bigserial, force: :cascade do |t|
t.integer "criticity"
- t.string "message_key"
- t.hstore "message_attributs"
+ t.string "message_key", limit: 255
+ t.hstore "message_attributes"
t.integer "line_referential_sync_id", limit: 8
t.datetime "created_at"
t.datetime "updated_at"
@@ -364,42 +393,42 @@ ActiveRecord::Schema.define(version: 20170607141317) do
t.datetime "updated_at"
t.datetime "started_at"
t.datetime "ended_at"
- t.string "status"
+ t.string "status", limit: 255
end
add_index "line_referential_syncs", ["line_referential_id"], name: "index_line_referential_syncs_on_line_referential_id", using: :btree
create_table "line_referentials", id: :bigserial, force: :cascade do |t|
- t.string "name"
+ t.string "name", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
- t.integer "sync_interval", default: 1
+ t.integer "sync_interval", default: 1
end
create_table "lines", id: :bigserial, force: :cascade do |t|
t.integer "network_id", limit: 8
t.integer "company_id", limit: 8
- t.string "objectid", null: false
+ t.string "objectid", limit: 255, null: false
t.integer "object_version", limit: 8
- t.string "creator_id"
- t.string "name"
- t.string "number"
- t.string "published_name"
- t.string "transport_mode"
- t.string "registration_number"
- t.string "comment"
+ t.string "creator_id", limit: 255
+ t.string "name", limit: 255
+ t.string "number", limit: 255
+ t.string "published_name", limit: 255
+ t.string "transport_mode", limit: 255
+ t.string "registration_number", limit: 255
+ t.string "comment", limit: 255
t.boolean "mobility_restricted_suitability"
t.integer "int_user_needs"
t.boolean "flexible_service"
- t.string "url"
+ t.string "url", limit: 255
t.string "color", limit: 6
t.string "text_color", limit: 6
- t.string "stable_id"
+ t.string "stable_id", limit: 255
t.integer "line_referential_id", limit: 8
- t.boolean "deactivated", default: false
+ t.boolean "deactivated", default: false
t.text "import_xml"
- t.string "transport_submode"
- t.integer "secondary_company_ids", limit: 8, array: true
+ t.string "transport_submode", limit: 255
+ t.integer "secondary_company_ids", limit: 8, array: true
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "seasonal"
@@ -411,17 +440,17 @@ ActiveRecord::Schema.define(version: 20170607141317) do
add_index "lines", ["secondary_company_ids"], name: "index_lines_on_secondary_company_ids", using: :gin
create_table "networks", id: :bigserial, force: :cascade do |t|
- t.string "objectid", null: false
+ t.string "objectid", limit: 255, null: false
t.integer "object_version", limit: 8
- t.string "creator_id"
+ t.string "creator_id", limit: 255
t.date "version_date"
- t.string "description"
- t.string "name"
- t.string "registration_number"
- t.string "source_name"
- t.string "source_type"
- t.string "source_identifier"
- t.string "comment"
+ t.string "description", limit: 255
+ t.string "name", limit: 255
+ t.string "registration_number", limit: 255
+ t.string "source_name", limit: 255
+ t.string "source_type", limit: 255
+ t.string "source_identifier", limit: 255
+ t.string "comment", limit: 255
t.text "import_xml"
t.integer "line_referential_id", limit: 8
t.datetime "created_at"
@@ -433,11 +462,11 @@ ActiveRecord::Schema.define(version: 20170607141317) do
add_index "networks", ["registration_number"], name: "networks_registration_number_key", using: :btree
create_table "organisations", id: :bigserial, force: :cascade do |t|
- t.string "name"
+ t.string "name", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
- t.string "data_format", default: "neptune"
- t.string "code"
+ t.string "data_format", limit: 255, default: "neptune"
+ t.string "code", limit: 255
t.datetime "synced_at"
t.hstore "sso_attributes"
end
@@ -448,12 +477,12 @@ ActiveRecord::Schema.define(version: 20170607141317) do
t.integer "start_of_link_id", limit: 8
t.integer "end_of_link_id", limit: 8
t.integer "route_id", limit: 8
- t.string "objectid", null: false
+ t.string "objectid", limit: 255, null: false
t.integer "object_version", limit: 8
- t.string "creator_id"
- t.string "name"
- t.string "comment"
- t.decimal "link_distance", precision: 19, scale: 2
+ t.string "creator_id", limit: 255
+ t.string "name", limit: 255
+ t.string "comment", limit: 255
+ t.decimal "link_distance", precision: 19, scale: 2
t.datetime "created_at"
t.datetime "updated_at"
end
@@ -461,7 +490,7 @@ ActiveRecord::Schema.define(version: 20170607141317) do
add_index "pt_links", ["objectid"], name: "pt_links_objectid_key", unique: true, using: :btree
create_table "referential_clonings", id: :bigserial, force: :cascade do |t|
- t.string "status"
+ t.string "status", limit: 255
t.datetime "started_at"
t.datetime "ended_at"
t.integer "source_referential_id", limit: 8
@@ -482,30 +511,30 @@ ActiveRecord::Schema.define(version: 20170607141317) do
t.daterange "periodes", array: true
end
- add_index "referential_metadata", ["line_ids"], name: "index_referential_metadata_on_line_ids", using: :gin
+ add_index "referential_metadata", ["line_ids"], name: "index_referential_metadata_on_line_ids", using: :btree
add_index "referential_metadata", ["referential_id"], name: "index_referential_metadata_on_referential_id", using: :btree
add_index "referential_metadata", ["referential_source_id"], name: "index_referential_metadata_on_referential_source_id", using: :btree
create_table "referentials", id: :bigserial, force: :cascade do |t|
- t.string "name"
- t.string "slug"
+ t.string "name", limit: 255
+ t.string "slug", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
- t.string "prefix"
- t.string "projection_type"
- t.string "time_zone"
- t.string "bounds"
+ t.string "prefix", limit: 255
+ t.string "projection_type", limit: 255
+ t.string "time_zone", limit: 255
+ t.string "bounds", limit: 255
t.integer "organisation_id", limit: 8
t.text "geographical_bounds"
t.integer "user_id", limit: 8
- t.string "user_name"
- t.string "data_format"
+ t.string "user_name", limit: 255
+ t.string "data_format", limit: 255
t.integer "line_referential_id", limit: 8
t.integer "stop_area_referential_id", limit: 8
t.integer "workbench_id", limit: 8
t.datetime "archived_at"
t.integer "created_from_id", limit: 8
- t.boolean "ready", default: false
+ t.boolean "ready", default: false
end
add_index "referentials", ["created_from_id"], name: "index_referentials_on_created_from_id", using: :btree
@@ -513,44 +542,48 @@ ActiveRecord::Schema.define(version: 20170607141317) do
create_table "route_sections", id: :bigserial, force: :cascade do |t|
t.integer "departure_id", limit: 8
t.integer "arrival_id", limit: 8
- t.geometry "input_geometry", limit: {:srid=>4326, :type=>"line_string"}
- t.geometry "processed_geometry", limit: {:srid=>4326, :type=>"line_string"}
- t.string "objectid", null: false
+ t.string "objectid", limit: 255, null: false
t.integer "object_version", limit: 8
- t.string "creator_id"
+ t.string "creator_id", limit: 255
t.float "distance"
t.boolean "no_processing"
+ t.geometry "input_geometry", limit: {:srid=>4326, :type=>"line_string"}
+ t.geometry "processed_geometry", limit: {:srid=>4326, :type=>"line_string"}
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "routes", id: :bigserial, force: :cascade do |t|
t.integer "line_id", limit: 8
- t.string "objectid", null: false
+ t.string "objectid", limit: 255, null: false
t.integer "object_version", limit: 8
- t.string "creator_id"
- t.string "name"
- t.string "comment"
+ t.string "creator_id", limit: 255
+ t.string "name", limit: 255
+ t.string "comment", limit: 255
t.integer "opposite_route_id", limit: 8
- t.string "published_name"
- t.string "number"
- t.string "direction"
- t.string "wayback"
+ t.string "published_name", limit: 255
+ t.string "number", limit: 255
+ t.string "direction", limit: 255
+ t.string "wayback", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
+ t.string "checksum"
+ t.text "checksum_source"
end
add_index "routes", ["objectid"], name: "routes_objectid_key", unique: true, using: :btree
create_table "routing_constraint_zones", id: :bigserial, force: :cascade do |t|
- t.string "name"
+ t.string "name", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
- t.string "objectid", null: false
- t.integer "object_version", limit: 8
- t.string "creator_id"
- t.integer "route_id", limit: 8
- t.integer "stop_point_ids", limit: 8, array: true
+ t.string "objectid", limit: 255, null: false
+ t.integer "object_version", limit: 8
+ t.string "creator_id", limit: 255
+ t.integer "route_id", limit: 8
+ t.integer "stop_point_ids", limit: 8, array: true
+ t.string "checksum"
+ t.text "checksum_source"
end
create_table "routing_constraints_lines", id: false, force: :cascade do |t|
@@ -560,7 +593,7 @@ ActiveRecord::Schema.define(version: 20170607141317) do
create_table "rule_parameter_sets", id: :bigserial, force: :cascade do |t|
t.text "parameters"
- t.string "name"
+ t.string "name", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
t.integer "organisation_id", limit: 8
@@ -574,8 +607,8 @@ ActiveRecord::Schema.define(version: 20170607141317) do
create_table "stop_area_referential_sync_messages", id: :bigserial, force: :cascade do |t|
t.integer "criticity"
- t.string "message_key"
- t.hstore "message_attributs"
+ t.string "message_key", limit: 255
+ t.hstore "message_attributes"
t.integer "stop_area_referential_sync_id", limit: 8
t.datetime "created_at"
t.datetime "updated_at"
@@ -589,43 +622,43 @@ ActiveRecord::Schema.define(version: 20170607141317) do
t.datetime "updated_at"
t.datetime "ended_at"
t.datetime "started_at"
- t.string "status"
+ t.string "status", limit: 255
end
add_index "stop_area_referential_syncs", ["stop_area_referential_id"], name: "index_stop_area_referential_syncs_on_stop_area_referential_id", using: :btree
create_table "stop_area_referentials", id: :bigserial, force: :cascade do |t|
- t.string "name"
+ t.string "name", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "stop_areas", id: :bigserial, force: :cascade do |t|
t.integer "parent_id", limit: 8
- t.string "objectid", null: false
+ t.string "objectid", limit: 255, null: false
t.integer "object_version", limit: 8
- t.string "creator_id"
- t.string "name"
- t.string "comment"
- t.string "area_type"
- t.string "registration_number"
- t.string "nearest_topic_name"
+ t.string "creator_id", limit: 255
+ t.string "name", limit: 255
+ t.string "comment", limit: 255
+ t.string "area_type", limit: 255
+ t.string "registration_number", limit: 255
+ t.string "nearest_topic_name", limit: 255
t.integer "fare_code"
t.decimal "longitude", precision: 19, scale: 16
t.decimal "latitude", precision: 19, scale: 16
- t.string "long_lat_type"
- t.string "country_code"
- t.string "street_name"
+ t.string "long_lat_type", limit: 255
+ t.string "country_code", limit: 255
+ t.string "street_name", limit: 255
t.boolean "mobility_restricted_suitability"
t.boolean "stairs_availability"
t.boolean "lift_availability"
t.integer "int_user_needs"
- t.string "zip_code"
- t.string "city_name"
- t.string "url"
- t.string "time_zone"
+ t.string "zip_code", limit: 255
+ t.string "city_name", limit: 255
+ t.string "url", limit: 255
+ t.string "time_zone", limit: 255
t.integer "stop_area_referential_id", limit: 8
- t.string "status"
+ t.string "status", limit: 255
t.text "import_xml"
t.datetime "deleted_at"
t.datetime "created_at"
@@ -646,12 +679,12 @@ ActiveRecord::Schema.define(version: 20170607141317) do
create_table "stop_points", id: :bigserial, force: :cascade do |t|
t.integer "route_id", limit: 8
t.integer "stop_area_id", limit: 8
- t.string "objectid", null: false
+ t.string "objectid", limit: 255, null: false
t.integer "object_version", limit: 8
- t.string "creator_id"
+ t.string "creator_id", limit: 255
t.integer "position"
- t.string "for_boarding"
- t.string "for_alighting"
+ t.string "for_boarding", limit: 255
+ t.string "for_alighting", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
end
@@ -661,9 +694,9 @@ ActiveRecord::Schema.define(version: 20170607141317) do
create_table "taggings", id: :bigserial, force: :cascade do |t|
t.integer "tag_id", limit: 8
t.integer "taggable_id", limit: 8
- t.string "taggable_type"
+ t.string "taggable_type", limit: 255
t.integer "tagger_id", limit: 8
- t.string "tagger_type"
+ t.string "tagger_type", limit: 255
t.string "context", limit: 128
t.datetime "created_at"
end
@@ -672,36 +705,40 @@ ActiveRecord::Schema.define(version: 20170607141317) do
add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree
create_table "tags", id: :bigserial, force: :cascade do |t|
- t.string "name"
- t.integer "taggings_count", default: 0
+ t.string "name", limit: 255
+ t.integer "taggings_count", default: 0
end
add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree
create_table "time_table_dates", id: :bigserial, force: :cascade do |t|
- t.integer "time_table_id", limit: 8, null: false
+ t.integer "time_table_id", limit: 8, null: false
t.date "date"
- t.integer "position", null: false
+ t.integer "position", null: false
t.boolean "in_out"
+ t.string "checksum"
+ t.text "checksum_source"
end
add_index "time_table_dates", ["time_table_id"], name: "index_time_table_dates_on_time_table_id", using: :btree
create_table "time_table_periods", id: :bigserial, force: :cascade do |t|
- t.integer "time_table_id", limit: 8, null: false
+ t.integer "time_table_id", limit: 8, null: false
t.date "period_start"
t.date "period_end"
- t.integer "position", null: false
+ t.integer "position", null: false
+ t.string "checksum"
+ t.text "checksum_source"
end
add_index "time_table_periods", ["time_table_id"], name: "index_time_table_periods_on_time_table_id", using: :btree
create_table "time_tables", id: :bigserial, force: :cascade do |t|
- t.string "objectid", null: false
+ t.string "objectid", limit: 255, null: false
t.integer "object_version", limit: 8, default: 1
- t.string "creator_id"
- t.string "version"
- t.string "comment"
+ t.string "creator_id", limit: 255
+ t.string "version", limit: 255
+ t.string "comment", limit: 255
t.integer "int_day_types", default: 0
t.date "start_date"
t.date "end_date"
@@ -710,6 +747,8 @@ ActiveRecord::Schema.define(version: 20170607141317) do
t.datetime "updated_at"
t.string "color", limit: 255
t.integer "created_from_id"
+ t.string "checksum"
+ t.text "checksum_source"
end
add_index "time_tables", ["calendar_id"], name: "index_time_tables_on_calendar_id", using: :btree
@@ -725,49 +764,49 @@ ActiveRecord::Schema.define(version: 20170607141317) do
add_index "time_tables_vehicle_journeys", ["vehicle_journey_id"], name: "index_time_tables_vehicle_journeys_on_vehicle_journey_id", using: :btree
create_table "timebands", id: :bigserial, force: :cascade do |t|
- t.string "objectid", null: false
+ t.string "objectid", limit: 255, null: false
t.integer "object_version", limit: 8
- t.string "creator_id"
- t.string "name"
- t.time "start_time", null: false
- t.time "end_time", null: false
+ t.string "creator_id", limit: 255
+ t.string "name", limit: 255
+ t.time "start_time", null: false
+ t.time "end_time", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", id: :bigserial, force: :cascade do |t|
- t.string "email", default: "", null: false
- t.string "encrypted_password", default: ""
- t.string "reset_password_token"
+ t.string "email", limit: 255, default: "", null: false
+ t.string "encrypted_password", limit: 255, default: ""
+ t.string "reset_password_token", limit: 255
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
- t.integer "sign_in_count", default: 0
+ t.integer "sign_in_count", default: 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
- t.string "current_sign_in_ip"
- t.string "last_sign_in_ip"
+ t.string "current_sign_in_ip", limit: 255
+ t.string "last_sign_in_ip", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
t.integer "organisation_id", limit: 8
- t.string "name"
- t.string "confirmation_token"
+ t.string "name", limit: 255
+ t.string "confirmation_token", limit: 255
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
- t.string "unconfirmed_email"
- t.integer "failed_attempts", default: 0
- t.string "unlock_token"
+ t.string "unconfirmed_email", limit: 255
+ t.integer "failed_attempts", default: 0
+ t.string "unlock_token", limit: 255
t.datetime "locked_at"
- t.string "authentication_token"
- t.string "invitation_token"
+ t.string "authentication_token", limit: 255
+ t.string "invitation_token", limit: 255
t.datetime "invitation_sent_at"
t.datetime "invitation_accepted_at"
t.integer "invitation_limit"
t.integer "invited_by_id", limit: 8
- t.string "invited_by_type"
+ t.string "invited_by_type", limit: 255
t.datetime "invitation_created_at"
- t.string "username"
+ t.string "username", limit: 255
t.datetime "synced_at"
- t.string "permissions", array: true
+ t.string "permissions", limit: 255, array: true
end
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
@@ -778,14 +817,16 @@ ActiveRecord::Schema.define(version: 20170607141317) do
create_table "vehicle_journey_at_stops", id: :bigserial, force: :cascade do |t|
t.integer "vehicle_journey_id", limit: 8
t.integer "stop_point_id", limit: 8
- t.string "connecting_service_id"
- t.string "boarding_alighting_possibility"
+ t.string "connecting_service_id", limit: 255
+ t.string "boarding_alighting_possibility", limit: 255
t.time "arrival_time"
t.time "departure_time"
- t.string "for_boarding"
- t.string "for_alighting"
- t.integer "departure_day_offset", default: 0
- t.integer "arrival_day_offset", default: 0
+ t.string "for_boarding", limit: 255
+ t.string "for_alighting", limit: 255
+ t.integer "departure_day_offset", default: 0
+ t.integer "arrival_day_offset", default: 0
+ t.string "checksum"
+ t.text "checksum_source"
end
add_index "vehicle_journey_at_stops", ["stop_point_id"], name: "index_vehicle_journey_at_stops_on_stop_pointid", using: :btree
@@ -795,29 +836,31 @@ ActiveRecord::Schema.define(version: 20170607141317) do
t.integer "route_id", limit: 8
t.integer "journey_pattern_id", limit: 8
t.integer "company_id", limit: 8
- t.string "objectid", null: false
+ t.string "objectid", limit: 255, null: false
t.integer "object_version", limit: 8
- t.string "creator_id"
- t.string "comment"
- t.string "status_value"
- t.string "transport_mode"
- t.string "published_journey_name"
- t.string "published_journey_identifier"
- t.string "facility"
- t.string "vehicle_type_identifier"
+ t.string "creator_id", limit: 255
+ t.string "comment", limit: 255
+ t.string "status_value", limit: 255
+ t.string "transport_mode", limit: 255
+ t.string "published_journey_name", limit: 255
+ t.string "published_journey_identifier", limit: 255
+ t.string "facility", limit: 255
+ t.string "vehicle_type_identifier", limit: 255
t.integer "number", limit: 8
t.boolean "mobility_restricted_suitability"
t.boolean "flexible_service"
- t.integer "journey_category", default: 0, null: false
+ t.integer "journey_category", default: 0, null: false
t.datetime "created_at"
t.datetime "updated_at"
+ t.string "checksum"
+ t.text "checksum_source"
end
add_index "vehicle_journeys", ["objectid"], name: "vehicle_journeys_objectid_key", unique: true, using: :btree
add_index "vehicle_journeys", ["route_id"], name: "index_vehicle_journeys_on_route_id", using: :btree
create_table "workbenches", id: :bigserial, force: :cascade do |t|
- t.string "name"
+ t.string "name", limit: 255
t.integer "organisation_id", limit: 8
t.datetime "created_at"
t.datetime "updated_at"
@@ -829,20 +872,19 @@ ActiveRecord::Schema.define(version: 20170607141317) do
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_foreign_key "access_links", "access_points", name: "aclk_acpt_fkey"
+ add_foreign_key "access_links", "access_points", name: "aclk_acpt_fkey", on_delete: :cascade
+ add_foreign_key "api_keys", "organisations"
add_foreign_key "group_of_lines_lines", "group_of_lines", name: "groupofline_group_fkey", on_delete: :cascade
- add_foreign_key "journey_frequencies", "timebands", on_delete: :nullify
- add_foreign_key "journey_frequencies", "vehicle_journeys", on_delete: :nullify
- add_foreign_key "journey_pattern_sections", "journey_patterns", on_delete: :cascade
- add_foreign_key "journey_pattern_sections", "route_sections", on_delete: :cascade
+ add_foreign_key "journey_frequencies", "timebands", name: "journey_frequencies_timeband_id_fk", on_delete: :nullify
+ add_foreign_key "journey_frequencies", "vehicle_journeys", name: "journey_frequencies_vehicle_journey_id_fk", on_delete: :nullify
+ add_foreign_key "journey_pattern_sections", "journey_patterns", name: "journey_pattern_sections_journey_pattern_id_fk", on_delete: :cascade
+ add_foreign_key "journey_pattern_sections", "route_sections", name: "journey_pattern_sections_route_section_id_fk", on_delete: :cascade
add_foreign_key "journey_patterns", "routes", name: "jp_route_fkey", on_delete: :cascade
add_foreign_key "journey_patterns", "stop_points", column: "arrival_stop_point_id", name: "arrival_point_fkey", on_delete: :nullify
add_foreign_key "journey_patterns", "stop_points", column: "departure_stop_point_id", name: "departure_point_fkey", on_delete: :nullify
add_foreign_key "journey_patterns_stop_points", "journey_patterns", name: "jpsp_jp_fkey", on_delete: :cascade
add_foreign_key "journey_patterns_stop_points", "stop_points", name: "jpsp_stoppoint_fkey", on_delete: :cascade
- add_foreign_key "route_sections", "stop_areas", column: "arrival_id"
- add_foreign_key "route_sections", "stop_areas", column: "departure_id"
- add_foreign_key "routes", "routes", column: "opposite_route_id", name: "route_opposite_route_fkey"
+ add_foreign_key "routes", "routes", column: "opposite_route_id", name: "route_opposite_route_fkey", on_delete: :nullify
add_foreign_key "stop_areas", "stop_areas", column: "parent_id", name: "area_parent_fkey", on_delete: :nullify
add_foreign_key "stop_areas_stop_areas", "stop_areas", column: "child_id", name: "stoparea_child_fkey", on_delete: :cascade
add_foreign_key "stop_areas_stop_areas", "stop_areas", column: "parent_id", name: "stoparea_parent_fkey", on_delete: :cascade
diff --git a/lib/result.rb b/lib/result.rb
new file mode 100644
index 000000000..96e03d323
--- /dev/null
+++ b/lib/result.rb
@@ -0,0 +1,37 @@
+# A value wrapper adding status information to any value
+# Status can be :ok or :error, we are thusly implementing
+# what is expressed in Elixir/Erlang as result tuples and
+# in Haskell as `Data.Either`
+class Result
+
+ attr_reader :status, :value
+
+ class << self
+ def ok value
+ make :ok, value
+ end
+ def error value
+ make :error, value
+ end
+
+ def new *args
+ raise NoMethodError, "No default constructor for #{self}"
+ end
+
+ private
+ def make status, value
+ allocate.tap do | o |
+ o.instance_exec do
+ @status = status
+ @value = value
+ end
+ end
+ end
+ end
+
+ def ok?; status == :ok end
+
+ def == other
+ other.kind_of?(self.class) && other.status == status && other.value == value
+ end
+end
diff --git a/lib/tasks/ci.rake b/lib/tasks/ci.rake
index 658e4e04e..90e47560e 100644
--- a/lib/tasks/ci.rake
+++ b/lib/tasks/ci.rake
@@ -3,7 +3,7 @@ namespace :ci do
task :setup do
cp "config/database/jenkins.yml", "config/database.yml"
sh "RAILS_ENV=test rake db:migrate"
- sh "npm install"
+ sh "npm --production --no-progress install"
end
def git_branch
@@ -27,11 +27,14 @@ namespace :ci do
sh "bundle exec bundle-audit check --update"
end
+ task :spec do
+ sh "bundle exec rspec --profile"
+ end
+
task :teaspoon do
sh "RAILS_ENV=test bundle exec rake teaspoon"
end
-
desc "Deploy after CI"
task :deploy do
sh "cap #{deploy_env} deploy:migrations"
@@ -44,4 +47,4 @@ namespace :ci do
end
desc "Run continuous integration tasks (spec, ...)"
-task :ci => ["ci:setup", "spec", "ci:teaspoon", "cucumber", "ci:check_security", "ci:deploy", "ci:clean"]
+task :ci => ["ci:setup", "ci:spec", "ci:teaspoon", "cucumber", "ci:check_security", "ci:deploy", "ci:clean"]
diff --git a/lib/tasks/generate.rake b/lib/tasks/generate.rake
index f02a75cc2..a9b1a3454 100644
--- a/lib/tasks/generate.rake
+++ b/lib/tasks/generate.rake
@@ -2,8 +2,15 @@ namespace :generate do
desc "Create model diagrams for Chouette"
task :model_diagram => :environment do
- sh "bundle exec rake erd only='Calendar,Referential,Chouette::Line,Chouette::Route,Chouette::JourneyPattern,Chouette::VehicleJourney,Chouette::VehicleJourneyAtStop,Chouette::TimeTable,Chouette::TimeTableDate,Chouette::TimeTablePeriod,Chouette::Footnote,Chouette::Network,Chouette::Company,Chouette::StopPoint,Chouette::StopArea' filename='offer_datas' title='Offer Datas'"
sh "bundle exec rake erd only='Organisation,Referential,User,Workbench' filename='organisation' title='Organisation'"
+ sh "bundle exec rake erd only='Calendar,Referential,Chouette::Line,Chouette::Route,Chouette::JourneyPattern,Chouette::VehicleJourney,Chouette::VehicleJourneyAtStop,Chouette::TimeTable,Chouette::TimeTableDate,Chouette::TimeTablePeriod,Chouette::Footnote,Chouette::Network,Chouette::Company,Chouette::StopPoint,Chouette::StopArea' filename='offer_datas' title='Offer Datas'"
+ sh "bundle exec rake erd only='Organisation,StopAreaReferential,StopAreaReferentialSync,StopAreaReferentialSyncMessage,StopAreaReferentialMembership,LineReferential,LineReferentialSync,LineReferentialSyncMessage,LineReferentialMembership' filename='referentiels_externes' title='Référentiels externes'"
+ sh "bundle exec rake erd only='NetexImport,Import,WorkbenchImport,ImportResource,ImportMessage' filename='import' title='Import'"
+ #sh "bundle exec rake erd only='' filename='validation' title='Validation'"
+ #sh "bundle exec rake erd only='VehicleJourney,VehicleJourneyExport' filename='export' title='Export'"
+ #sh "bundle exec rake erd only='' filename='intégration' title='Integration'"
+ #sh "bundle exec rake erd only='' filename='fusion' title='Fusion'"
+ #sh "bundle exec rake erd only='' filename='publication' title='Publication'"
end
end
diff --git a/lib/tasks/imports.rake b/lib/tasks/imports.rake
new file mode 100644
index 000000000..6bc84acc8
--- /dev/null
+++ b/lib/tasks/imports.rake
@@ -0,0 +1,6 @@
+namespace :import do
+ desc "Notify parent imports when children finish"
+ task notify_parent: :environment do
+ ParentImportNotifier.notify_when_finished
+ end
+end
diff --git a/spec/concerns/configurable_spec.rb b/spec/concerns/configurable_spec.rb
new file mode 100644
index 000000000..330241b72
--- /dev/null
+++ b/spec/concerns/configurable_spec.rb
@@ -0,0 +1,35 @@
+RSpec.describe Configurable do
+
+ subject do
+ Class.new do
+ include Configurable
+ end
+ end
+
+ let( :something ){ double('something') }
+
+ it 'can be configured' do
+ expect{ subject.config.anything }.to raise_error(NoMethodError)
+
+ subject.config.something = something
+
+ expect( subject.config.something ).to eq(something)
+ # Instances delegate to the class
+ expect( subject.new.send(:config).something ).to eq(something)
+ # **All** instances delegate to the class
+ expect( subject.new.send(:config).something ).to eq(something)
+ end
+
+ it 'can be configured with a block' do
+
+ subject.config do | c |
+ c.something = something
+ end
+
+ expect( subject.config.something ).to eq(something)
+ # Instances delegate to the class
+ expect( subject.new.send(:config).something ).to eq(something)
+ # **All** instances delegate to the class
+ expect( subject.new.send(:config).something ).to eq(something)
+ end
+end
diff --git a/spec/controllers/api/v1/iboo_controller_spec.rb b/spec/controllers/api/v1/iboo_controller_spec.rb
new file mode 100644
index 000000000..64a929d1a
--- /dev/null
+++ b/spec/controllers/api/v1/iboo_controller_spec.rb
@@ -0,0 +1,12 @@
+require 'rails_helper'
+
+RSpec.describe Api::V1::IbooController, type: :controller do
+ context '#authenticate' do
+ include_context 'iboo authenticated api user'
+
+ it 'should set current organisation' do
+ controller.send(:authenticate)
+ expect(assigns(:current_organisation)).to eq api_key.organisation
+ end
+ end
+end
diff --git a/spec/controllers/api/v1/imports_controller_spec.rb b/spec/controllers/api/v1/imports_controller_spec.rb
new file mode 100644
index 000000000..266b25486
--- /dev/null
+++ b/spec/controllers/api/v1/imports_controller_spec.rb
@@ -0,0 +1,38 @@
+require 'rails_helper'
+
+RSpec.describe Api::V1::ImportsController, type: :controller do
+ let(:workbench) { create :workbench, organisation: organisation }
+
+ context 'unauthenticated' do
+ include_context 'iboo wrong authorisation api user'
+
+ describe 'GET #index' do
+ it 'should not be successful' do
+ get :index, workbench_id: workbench.id
+ expect(response).not_to be_success
+ end
+ end
+ end
+
+ context 'authenticated' do
+ include_context 'iboo authenticated api user'
+
+ describe 'GET #index' do
+ it 'should be successful' do
+ get :index, workbench_id: workbench.id
+ expect(response).to be_success
+ end
+ end
+
+ describe 'POST #create' do
+ let(:file) { fixture_file_upload('multiple_references_import.zip') }
+
+ it 'should be successful' do
+ expect {
+ post :create, workbench_id: workbench.id, workbench_import: {file: file, creator: 'test'}, format: :json
+ }.to change{WorkbenchImport.count}.by(1)
+ expect(response).to be_success
+ end
+ end
+ end
+end
diff --git a/spec/controllers/api/v1/workbenches_controller_spec.rb b/spec/controllers/api/v1/workbenches_controller_spec.rb
new file mode 100644
index 000000000..7780da142
--- /dev/null
+++ b/spec/controllers/api/v1/workbenches_controller_spec.rb
@@ -0,0 +1,25 @@
+require 'rails_helper'
+
+RSpec.describe Api::V1::WorkbenchesController, type: :controller do
+ context 'unauthenticated' do
+ include_context 'iboo wrong authorisation api user'
+
+ describe 'GET #index' do
+ it 'should not be successful' do
+ get :index
+ expect(response).not_to be_success
+ end
+ end
+ end
+
+ context 'authenticated' do
+ include_context 'iboo authenticated api user'
+
+ describe 'GET #index' do
+ it 'should be successful' do
+ get :index
+ expect(response).to be_success
+ end
+ end
+ end
+end
diff --git a/spec/controllers/imports_controller_spec.rb b/spec/controllers/imports_controller_spec.rb
index 7b575ab61..f07190496 100644
--- a/spec/controllers/imports_controller_spec.rb
+++ b/spec/controllers/imports_controller_spec.rb
@@ -15,6 +15,7 @@ RSpec.describe ImportsController, :type => :controller do
it 'should be successful' do
get :download, workbench_id: workbench.id, id: import.id, token: import.token_download
expect(response).to be_success
+ expect( response.body ).to eq(import.file.read)
end
end
end
diff --git a/spec/decorators/api_key_decorator_spec.rb b/spec/decorators/api_key_decorator_spec.rb
new file mode 100644
index 000000000..9451a3974
--- /dev/null
+++ b/spec/decorators/api_key_decorator_spec.rb
@@ -0,0 +1,4 @@
+require 'spec_helper'
+
+describe ApiKeyDecorator do
+end
diff --git a/spec/factories/api_keys.rb b/spec/factories/api_keys.rb
new file mode 100644
index 000000000..963938c64
--- /dev/null
+++ b/spec/factories/api_keys.rb
@@ -0,0 +1,8 @@
+FactoryGirl.define do
+ factory :api_key, class: Api::V1::ApiKey do
+ name { SecureRandom.urlsafe_base64 }
+ token { "#{referential_id}-#{organisation_id}-#{SecureRandom.hex}" }
+ referential
+ organisation
+ end
+end
diff --git a/spec/factories/chouette_journey_pattern.rb b/spec/factories/chouette_journey_pattern.rb
index bf55b286f..62241f313 100644
--- a/spec/factories/chouette_journey_pattern.rb
+++ b/spec/factories/chouette_journey_pattern.rb
@@ -1,14 +1,14 @@
FactoryGirl.define do
-
+
factory :journey_pattern_common, :class => Chouette::JourneyPattern do
sequence(:name) { |n| "jp name #{n}" }
sequence(:published_name) { |n| "jp publishedname #{n}" }
sequence(:comment) { |n| "jp comment #{n}" }
sequence(:registration_number) { |n| "jp registration_number #{n}" }
sequence(:objectid) { |n| "test:JourneyPattern:#{n}" }
-
+
association :route, :factory => :route
-
+
factory :journey_pattern do
after(:create) do |j|
j.stop_point_ids = j.route.stop_points.map(&:id)
@@ -16,7 +16,7 @@ FactoryGirl.define do
j.arrival_stop_point_id = j.route.stop_points.last.id
end
end
-
+
factory :journey_pattern_odd do
after(:create) do |j|
j.stop_point_ids = j.route.stop_points.select { |sp| sp.position%2==0}.map(&:id)
@@ -24,7 +24,7 @@ FactoryGirl.define do
j.arrival_stop_point_id = j.stop_point_ids.last
end
end
-
+
factory :journey_pattern_even do
after(:create) do |j|
j.stop_point_ids = j.route.stop_points.select { |sp| sp.position%2==1}.map(&:id)
diff --git a/spec/factories/chouette_routes.rb b/spec/factories/chouette_routes.rb
index c1a9423c5..a707bcbf6 100644
--- a/spec/factories/chouette_routes.rb
+++ b/spec/factories/chouette_routes.rb
@@ -18,6 +18,7 @@ FactoryGirl.define do
after(:create) do |route, evaluator|
create_list(:stop_point, evaluator.stop_points_count, route: route)
+ route.reload
end
factory :route_with_journey_patterns do
diff --git a/spec/factories/chouette_time_table.rb b/spec/factories/chouette_time_table.rb
index 6480df79d..b410d4ab8 100644
--- a/spec/factories/chouette_time_table.rb
+++ b/spec/factories/chouette_time_table.rb
@@ -1,12 +1,4 @@
FactoryGirl.define do
-
- factory :time_table_date, :class => Chouette::TimeTableDate do
- association :time_table, :factory => :time_table
- end
-
- factory :time_table_period, :class => Chouette::TimeTablePeriod do
- end
-
factory :time_table, :class => Chouette::TimeTable do
sequence(:comment) { |n| "Timetable #{n}" }
sequence(:objectid) { |n| "test:Timetable:#{n}" }
diff --git a/spec/factories/chouette_time_table_dates.rb b/spec/factories/chouette_time_table_dates.rb
new file mode 100644
index 000000000..62fdb3917
--- /dev/null
+++ b/spec/factories/chouette_time_table_dates.rb
@@ -0,0 +1,5 @@
+FactoryGirl.define do
+ factory :time_table_date, class: Chouette::TimeTableDate do
+ association :time_table
+ end
+end
diff --git a/spec/factories/chouette_time_table_periods.rb b/spec/factories/chouette_time_table_periods.rb
new file mode 100644
index 000000000..66640bbcc
--- /dev/null
+++ b/spec/factories/chouette_time_table_periods.rb
@@ -0,0 +1,7 @@
+FactoryGirl.define do
+ factory :time_table_period, class: Chouette::TimeTablePeriod do
+ association :time_table
+ period_start { Date.today }
+ period_end { Date.today + 1.month }
+ end
+end
diff --git a/spec/factories/chouette_vehicle_journey.rb b/spec/factories/chouette_vehicle_journey.rb
index e7ecb79ac..d1e00cd1d 100644
--- a/spec/factories/chouette_vehicle_journey.rb
+++ b/spec/factories/chouette_vehicle_journey.rb
@@ -11,6 +11,7 @@ FactoryGirl.define do
end
factory :vehicle_journey do
+ association :company, factory: :company
transient do
stop_arrival_time '01:00:00'
stop_departure_time '03:00:00'
diff --git a/spec/factories/chouette_vehicle_journey_at_stop.rb b/spec/factories/chouette_vehicle_journey_at_stop.rb
index c452a1317..831e347d4 100644
--- a/spec/factories/chouette_vehicle_journey_at_stop.rb
+++ b/spec/factories/chouette_vehicle_journey_at_stop.rb
@@ -1,8 +1,9 @@
FactoryGirl.define do
-
factory :vehicle_journey_at_stop, :class => Chouette::VehicleJourneyAtStop do
association :vehicle_journey, :factory => :vehicle_journey
+ departure_day_offset { 0 }
+ departure_time { Time.now }
+ arrival_time { Time.now - 1.hour }
end
-
end
diff --git a/spec/factories/clean_up_results.rb b/spec/factories/clean_up_results.rb
deleted file mode 100644
index 6d3818eff..000000000
--- a/spec/factories/clean_up_results.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-FactoryGirl.define do
- factory :clean_up_result do
- criticity 1
-message_key "MyString"
-message_attributs ""
-cleanup nil
- end
-
-end
diff --git a/spec/factories/import_messages.rb b/spec/factories/import_messages.rb
deleted file mode 100644
index 1101107d2..000000000
--- a/spec/factories/import_messages.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-FactoryGirl.define do
- factory :import_message do
- criticity 1
- message_key "MyString"
- message_attributs ""
- import nil
- resource nil
- resource_attributes {}
- end
-
-end
diff --git a/spec/factories/import_tasks.rb b/spec/factories/import_tasks.rb
deleted file mode 100644
index 9ca6db899..000000000
--- a/spec/factories/import_tasks.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-FactoryGirl.define do
- factory :import_task do |f|
- user_name "dummy"
- user_id 123
- no_save false
- format "Neptune"
- resources { Rack::Test::UploadedFile.new 'spec/fixtures/neptune.zip', 'application/zip', false }
- referential { Referential.find_by_slug("first") }
- end
-end
diff --git a/spec/factories/imports.rb b/spec/factories/imports.rb
index fc8668606..2c53106c3 100644
--- a/spec/factories/imports.rb
+++ b/spec/factories/imports.rb
@@ -9,5 +9,10 @@ FactoryGirl.define do
status :new
started_at nil
ended_at nil
+ creator 'rspec'
+
+ after(:build) do |import|
+ import.class.skip_callback(:create, :before, :initialize_fields)
+ end
end
end
diff --git a/spec/factories/netex_imports.rb b/spec/factories/netex_imports.rb
new file mode 100644
index 000000000..057e47730
--- /dev/null
+++ b/spec/factories/netex_imports.rb
@@ -0,0 +1,5 @@
+FactoryGirl.define do
+ factory :netex_import, class: NetexImport, parent: :import do
+ file { File.open(Rails.root.join('spec', 'fixtures', 'terminated_job.json')) }
+ end
+end
diff --git a/spec/factories/workbench_imports.rb b/spec/factories/workbench_imports.rb
new file mode 100644
index 000000000..5cdcfd15f
--- /dev/null
+++ b/spec/factories/workbench_imports.rb
@@ -0,0 +1,5 @@
+FactoryGirl.define do
+ factory :workbench_import, class: WorkbenchImport, parent: :import do
+ file { File.open(Rails.root.join('spec', 'fixtures', 'terminated_job.json')) }
+ end
+end
diff --git a/spec/fixtures/multiple_references_import.zip b/spec/fixtures/multiple_references_import.zip
new file mode 100644
index 000000000..28ddff198
--- /dev/null
+++ b/spec/fixtures/multiple_references_import.zip
Binary files differ
diff --git a/spec/fixtures/neptune.zip b/spec/fixtures/neptune.zip
deleted file mode 100644
index 86b688b51..000000000
--- a/spec/fixtures/neptune.zip
+++ /dev/null
Binary files differ
diff --git a/spec/fixtures/nozip.zip b/spec/fixtures/nozip.zip
new file mode 100644
index 000000000..505bd213a
--- /dev/null
+++ b/spec/fixtures/nozip.zip
@@ -0,0 +1 @@
+no zip file
diff --git a/spec/fixtures/single_reference_import.zip b/spec/fixtures/single_reference_import.zip
new file mode 100644
index 000000000..4aee23614
--- /dev/null
+++ b/spec/fixtures/single_reference_import.zip
Binary files differ
diff --git a/spec/javascripts/time_table/actions_spec.js b/spec/javascripts/time_table/actions_spec.js
index 9756e797f..3e0c38c4b 100644
--- a/spec/javascripts/time_table/actions_spec.js
+++ b/spec/javascripts/time_table/actions_spec.js
@@ -157,33 +157,39 @@ describe('actions', () => {
let modalProps = {}
let timeTablePeriods = []
let metas = {}
+ let timetableInDates = []
const expectedAction = {
type: 'VALIDATE_PERIOD_FORM',
modalProps,
timeTablePeriods,
- metas
+ metas,
+ timetableInDates
}
- expect(actions.validatePeriodForm(modalProps, timeTablePeriods, metas)).toEqual(expectedAction)
+ expect(actions.validatePeriodForm(modalProps, timeTablePeriods, metas, timetableInDates)).toEqual(expectedAction)
})
it('should create an action to include date in period', () => {
let index = 1
+ let date = actions.formatDate(new Date)
const expectedAction = {
type: 'INCLUDE_DATE_IN_PERIOD',
index,
- dayTypes
+ dayTypes,
+ date
}
- expect(actions.includeDateInPeriod(index, dayTypes)).toEqual(expectedAction)
+ expect(actions.includeDateInPeriod(index, dayTypes, date)).toEqual(expectedAction)
})
it('should create an action to exclude date from period', () => {
let index = 1
+ let date = actions.formatDate(new Date)
const expectedAction = {
type: 'EXCLUDE_DATE_FROM_PERIOD',
index,
- dayTypes
+ dayTypes,
+ date
}
- expect(actions.excludeDateFromPeriod(index, dayTypes)).toEqual(expectedAction)
+ expect(actions.excludeDateFromPeriod(index, dayTypes, date)).toEqual(expectedAction)
})
it('should create an action to open confirm modal', () => {
diff --git a/spec/javascripts/time_table/reducers/modal_spec.js b/spec/javascripts/time_table/reducers/modal_spec.js
index 4246027b8..160f3955f 100644
--- a/spec/javascripts/time_table/reducers/modal_spec.js
+++ b/spec/javascripts/time_table/reducers/modal_spec.js
@@ -170,12 +170,14 @@ describe('modal reducer', () => {
}
let ttperiods = []
+ let ttdates = []
expect(
modalReducer(state, {
type: 'VALIDATE_PERIOD_FORM',
modalProps : modProps,
- timeTablePeriods: ttperiods
+ timeTablePeriods: ttperiods,
+ timetableInDates: ttdates
})
).toEqual(Object.assign({}, state, {modalProps: newModalProps}))
})
@@ -222,6 +224,8 @@ describe('modal reducer', () => {
{id: 265, period_start: '2017-05-14', period_end: '2017-05-24'}
]
+ let ttdates2 = []
+
let newModalProps2 = {
active: true,
begin: {
@@ -242,8 +246,74 @@ describe('modal reducer', () => {
modalReducer(state2, {
type: 'VALIDATE_PERIOD_FORM',
modalProps : modProps2,
- timeTablePeriods: ttperiods2
+ timeTablePeriods: ttperiods2,
+ timetableInDates: ttdates2
})
).toEqual(Object.assign({}, state2, {modalProps: newModalProps2}))
})
+
+ it('should handle VALIDATE_PERIOD_FORM and throw error if period overlaps date', () => {
+ let state3 = {
+ confirmModal: {},
+ modalProps: {
+ active: false,
+ begin: {
+ day: '01',
+ month: '08',
+ year: '2017'
+ },
+ end: {
+ day: '09',
+ month: '08',
+ year: '2017'
+ },
+ index: false,
+ error: ''
+ },
+ type: ''
+ }
+ let modProps3 = {
+ active: false,
+ begin: {
+ day: '01',
+ month: '08',
+ year: '2017'
+ },
+ end: {
+ day: '09',
+ month: '08',
+ year: '2017'
+ },
+ index: false,
+ error: ''
+ }
+ let ttperiods3 = []
+
+ let ttdates3 = [{date: "2017-08-04", include_date: true}]
+
+ let newModalProps3 = {
+ active: true,
+ begin: {
+ day: '01',
+ month: '08',
+ year: '2017'
+ },
+ end: {
+ day: '09',
+ month: '08',
+ year: '2017'
+ },
+ index: false,
+ error: "Une période ne peut chevaucher une date dans un calendrier"
+ }
+
+ expect(
+ modalReducer(state3, {
+ type: 'VALIDATE_PERIOD_FORM',
+ modalProps : modProps3,
+ timeTablePeriods: ttperiods3,
+ timetableInDates: ttdates3
+ })
+ ).toEqual(Object.assign({}, state3, {modalProps: newModalProps3}))
+ })
})
diff --git a/spec/javascripts/time_table/reducers/timetable_spec.js b/spec/javascripts/time_table/reducers/timetable_spec.js
index 0b418a52e..805a29b5f 100644
--- a/spec/javascripts/time_table/reducers/timetable_spec.js
+++ b/spec/javascripts/time_table/reducers/timetable_spec.js
@@ -12,12 +12,15 @@ let current_month = [{"day":"lundi","date":"2017-05-01","wday":1,"wnumber":"18",
let newCurrentMonth = [{"day":"lundi","date":"2017-05-01","wday":1,"wnumber":"18","mday":1,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"mardi","date":"2017-05-02","wday":2,"wnumber":"18","mday":2,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"mercredi","date":"2017-05-03","wday":3,"wnumber":"18","mday":3,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"jeudi","date":"2017-05-04","wday":4,"wnumber":"18","mday":4,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"vendredi","date":"2017-05-05","wday":5,"wnumber":"18","mday":5,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"samedi","date":"2017-05-06","wday":6,"wnumber":"18","mday":6,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"dimanche","date":"2017-05-07","wday":0,"wnumber":"18","mday":7,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"lundi","date":"2017-05-08","wday":1,"wnumber":"19","mday":8,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"mardi","date":"2017-05-09","wday":2,"wnumber":"19","mday":9,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"mercredi","date":"2017-05-10","wday":3,"wnumber":"19","mday":10,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"jeudi","date":"2017-05-11","wday":4,"wnumber":"19","mday":11,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"vendredi","date":"2017-05-12","wday":5,"wnumber":"19","mday":12,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"samedi","date":"2017-05-13","wday":6,"wnumber":"19","mday":13,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"dimanche","date":"2017-05-14","wday":0,"wnumber":"19","mday":14,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"lundi","date":"2017-05-15","wday":1,"wnumber":"20","mday":15,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"mardi","date":"2017-05-16","wday":2,"wnumber":"20","mday":16,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"mercredi","date":"2017-05-17","wday":3,"wnumber":"20","mday":17,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"jeudi","date":"2017-05-18","wday":4,"wnumber":"20","mday":18,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"vendredi","date":"2017-05-19","wday":5,"wnumber":"20","mday":19,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"samedi","date":"2017-05-20","wday":6,"wnumber":"20","mday":20,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"dimanche","date":"2017-05-21","wday":0,"wnumber":"20","mday":21,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"lundi","date":"2017-05-22","wday":1,"wnumber":"21","mday":22,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"mardi","date":"2017-05-23","wday":2,"wnumber":"21","mday":23,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"mercredi","date":"2017-05-24","wday":3,"wnumber":"21","mday":24,"include_date":false,"excluded_date":false,"in_periods":true},{"day":"jeudi","date":"2017-05-25","wday":4,"wnumber":"21","mday":25,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"vendredi","date":"2017-05-26","wday":5,"wnumber":"21","mday":26,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"samedi","date":"2017-05-27","wday":6,"wnumber":"21","mday":27,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"dimanche","date":"2017-05-28","wday":0,"wnumber":"21","mday":28,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"lundi","date":"2017-05-29","wday":1,"wnumber":"22","mday":29,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"mardi","date":"2017-05-30","wday":2,"wnumber":"22","mday":30,"include_date":false,"excluded_date":false,"in_periods":false},{"day":"mercredi","date":"2017-05-31","wday":3,"wnumber":"22","mday":31,"include_date":false,"excluded_date":false,"in_periods":false}]
+let time_table_dates = []
+
let json = {
current_month: current_month,
current_periode_range: current_periode_range,
periode_range: periode_range,
time_table_periods: time_table_periods,
- day_types: strDayTypes
+ day_types: strDayTypes,
+ time_table_dates: time_table_dates
}
describe('timetable reducer with empty state', () => {
@@ -26,7 +29,8 @@ describe('timetable reducer with empty state', () => {
current_month: [],
current_periode_range: "",
periode_range: [],
- time_table_periods: []
+ time_table_periods: [],
+ time_table_dates: []
}
})
@@ -42,6 +46,7 @@ describe('timetable reducer with empty state', () => {
current_periode_range: current_periode_range,
periode_range: periode_range,
time_table_periods: time_table_periods,
+ time_table_dates: time_table_dates
}
expect(
timetableReducer(state, {
@@ -59,6 +64,7 @@ describe('timetable reducer with filled state', () => {
current_periode_range: current_periode_range,
periode_range: periode_range,
time_table_periods: time_table_periods,
+ time_table_dates: time_table_dates
}
})
@@ -130,45 +136,79 @@ describe('timetable reducer with filled state', () => {
).toEqual(state)
})
- it('should handle INCLUDE_DATE_IN_PERIOD', () => {
+ it('should handle INCLUDE_DATE_IN_PERIOD and add in_day if TT doesnt have it', () => {
+ let newDates = state.time_table_dates.concat({date: "2017-05-05", in_out: true})
+ let newState = Object.assign({}, state, {time_table_dates: newDates})
state.current_month[4].include_date = true
expect(
timetableReducer(state, {
type: 'INCLUDE_DATE_IN_PERIOD',
index: 4,
- dayTypes: arrDayTypes
+ dayTypes: arrDayTypes,
+ date: "2017-05-05"
})
- ).toEqual(state)
+ ).toEqual(newState)
+ })
+
+ it('should handle INCLUDE_DATE_IN_PERIOD and remove in_day if TT has it', () => {
+ state.current_month[4].include_date = true
+ state.time_table_dates.push({date: "2017-05-05", in_out: true})
+ let newState = Object.assign({}, state, {time_table_dates: []})
+ expect(
+ timetableReducer(state, {
+ type: 'INCLUDE_DATE_IN_PERIOD',
+ index: 4,
+ dayTypes: arrDayTypes,
+ date: "2017-05-05"
+ })
+ ).toEqual(newState)
})
- it('should handle EXCLUDE_DATE_FROM_PERIOD', () => {
+ it('should handle EXCLUDE_DATE_FROM_PERIOD and add out_day if TT doesnt have it', () => {
+ let newDates = state.time_table_dates.concat({date: "2017-05-01", in_out: false})
+ let newState = Object.assign({}, state, {time_table_dates: newDates})
state.current_month[0].excluded_date = true
expect(
timetableReducer(state, {
type: 'EXCLUDE_DATE_FROM_PERIOD',
index: 0,
- dayTypes: arrDayTypes
+ dayTypes: arrDayTypes,
+ date: "2017-05-01"
})
- ).toEqual(state)
+ ).toEqual(newState)
+ })
+
+ it('should handle EXCLUDE_DATE_FROM_PERIOD and remove out_day if TT has it', () => {
+ state.time_table_dates = [{date: "2017-05-01", in_out: false}]
+ state.current_month[0].excluded_date = true
+ let newState = Object.assign({}, state, {time_table_dates: []})
+ expect(
+ timetableReducer(state, {
+ type: 'EXCLUDE_DATE_FROM_PERIOD',
+ index: 0,
+ dayTypes: arrDayTypes,
+ date: "2017-05-01"
+ })
+ ).toEqual(newState)
})
- it('should handle VALIDATE_PERIOD_FORM', () => {
- state.current_month[13].in_periods = false
- state.time_table_periods[4].period_start = '2017-05-15'
+ it('should handle VALIDATE_PERIOD_FORM and add period if modalProps index = false', () => {
+ let newPeriods = state.time_table_periods.concat({"period_start": "2018-05-15", "period_end": "2018-05-24"})
+ let newState = Object.assign({}, state, {time_table_periods: newPeriods})
let modalProps = {
active: false,
begin: {
day: '15',
month: '05',
- year: '2017'
+ year: '2018'
},
end: {
day: '24',
month: '05',
- year: '2017'
+ year: '2018'
},
error: '',
- index: 4
+ index: false
}
expect(
timetableReducer(state, {
@@ -177,8 +217,9 @@ describe('timetable reducer with filled state', () => {
timeTablePeriods: state.time_table_periods,
metas: {
day_types: arrDayTypes
- }
+ },
+ timetableInDates: state.time_table_dates.filter(d => d.in_out == true)
})
- ).toEqual(state)
+ ).toEqual(newState)
})
})
diff --git a/spec/javascripts/vehicle_journeys/actions_spec.js b/spec/javascripts/vehicle_journeys/actions_spec.js
index d96baf8ef..707ae22cb 100644
--- a/spec/javascripts/vehicle_journeys/actions_spec.js
+++ b/spec/javascripts/vehicle_journeys/actions_spec.js
@@ -165,12 +165,12 @@ describe('when updating vjas time', () => {
})
describe('when clicking on validate button inside shifting modal', () => {
it('should create an action to shift a vehiclejourney schedule', () => {
- const data = {}
+ const addtionalTime = 0
const expectedAction = {
type: 'SHIFT_VEHICLEJOURNEY',
- data
+ addtionalTime
}
- expect(actions.shiftVehicleJourney(data)).toEqual(expectedAction)
+ expect(actions.shiftVehicleJourney(addtionalTime)).toEqual(expectedAction)
})
})
describe('when clicking on validate button inside editing modal', () => {
@@ -187,14 +187,16 @@ describe('when clicking on validate button inside editing modal', () => {
})
describe('when clicking on validate button inside duplicating modal', () => {
it('should create an action to duplicate a vehiclejourney schedule', () => {
- const data = {}
+ const addtionalTime = 0
const departureDelta = 0
+ const duplicateNumber = 1
const expectedAction = {
type: 'DUPLICATE_VEHICLEJOURNEY',
- data,
+ addtionalTime,
+ duplicateNumber,
departureDelta
}
- expect(actions.duplicateVehicleJourney(data, departureDelta)).toEqual(expectedAction)
+ expect(actions.duplicateVehicleJourney(addtionalTime, duplicateNumber, departureDelta)).toEqual(expectedAction)
})
})
describe('when clicking on edit notes modal', () => {
@@ -432,3 +434,16 @@ describe('when using select2 to pick a company', () => {
expect(actions.select2Company(selectedCompany)).toEqual(expectedAction)
})
})
+describe('when using select2 to unselect a company', () => {
+ it('should create an action to unselect a company inside modal', () => {
+ let selectedCompany = {
+ id: 1,
+ objectid: 2,
+ name: 'test',
+ }
+ const expectedAction = {
+ type: 'UNSELECT_CP_EDIT_MODAL'
+ }
+ expect(actions.unselect2Company()).toEqual(expectedAction)
+ })
+})
diff --git a/spec/javascripts/vehicle_journeys/reducers/modal_spec.js b/spec/javascripts/vehicle_journeys/reducers/modal_spec.js
index c016812da..4530b5ee7 100644
--- a/spec/javascripts/vehicle_journeys/reducers/modal_spec.js
+++ b/spec/javascripts/vehicle_journeys/reducers/modal_spec.js
@@ -167,4 +167,13 @@ describe('modal reducer', () => {
})
).toEqual(Object.assign({}, state, {modalProps: newModalProps}))
})
+
+ it('should handle UNSELECT_CP_EDIT_MODAL', () => {
+ let newModalProps = {selectedCompany : undefined}
+ expect(
+ modalReducer(state, {
+ type: 'UNSELECT_CP_EDIT_MODAL'
+ })
+ ).toEqual(Object.assign({}, state, {modalProps: newModalProps}))
+ })
})
diff --git a/spec/javascripts/vehicle_journeys/reducers/vehicle_journeys_spec.js b/spec/javascripts/vehicle_journeys/reducers/vehicle_journeys_spec.js
index 620e2ffdd..3b2137a2a 100644
--- a/spec/javascripts/vehicle_journeys/reducers/vehicle_journeys_spec.js
+++ b/spec/javascripts/vehicle_journeys/reducers/vehicle_journeys_spec.js
@@ -198,15 +198,12 @@ describe('vehicleJourneys reducer', () => {
},
stop_area_object_id : "FR:92024:ZDE:420553:STIF"
}]
- let fakeData = {
- objectid: {value : '11'},
- additional_time: {value: '5'}
- }
+ let addtionalTime = 5
let newVJ = Object.assign({}, state[0], {vehicle_journey_at_stops: newVJAS})
expect(
vjReducer(state, {
type: 'SHIFT_VEHICLEJOURNEY',
- data: fakeData
+ addtionalTime
})
).toEqual([newVJ, state[1]])
})
@@ -225,17 +222,17 @@ describe('vehicleJourneys reducer', () => {
stop_area_object_id : "FR:92024:ZDE:420553:STIF"
}]
let departureDelta = 1
- let fakeData = {
- duplicate_number: {value : 1},
- additional_time: {value: '5'}
- }
+ let addtionalTime = 5
+ let duplicateNumber = 1
+
let newVJ = Object.assign({}, state[0], {vehicle_journey_at_stops: newVJAS, selected: false})
newVJ.published_journey_name = state[0].published_journey_name + '-0'
delete newVJ['objectid']
expect(
vjReducer(state, {
type: 'DUPLICATE_VEHICLEJOURNEY',
- data: fakeData,
+ addtionalTime,
+ duplicateNumber,
departureDelta
})
).toEqual([state[0], newVJ, state[1]])
diff --git a/spec/lib/result_spec.rb b/spec/lib/result_spec.rb
new file mode 100644
index 000000000..949de163c
--- /dev/null
+++ b/spec/lib/result_spec.rb
@@ -0,0 +1,20 @@
+RSpec.describe Result do
+
+ context 'is a wrapper of a value' do
+ it { expect( described_class.ok('hello').value ).to eq('hello') }
+ it { expect( described_class.error('hello').value ).to eq('hello') }
+ end
+
+ context 'it has status information' do
+ it { expect( described_class.ok('hello') ).to be_ok }
+ it { expect( described_class.ok('hello').status ).to eq(:ok) }
+
+ it { expect( described_class.error('hello') ).not_to be_ok }
+ it { expect( described_class.error('hello').status ).to eq(:error) }
+ end
+
+ context 'nil is just another value' do
+ it { expect( described_class.ok(nil) ).to be_ok }
+ it { expect( described_class.ok(nil).value ).to be_nil }
+ end
+end
diff --git a/spec/models/api/v1/api_key_spec.rb b/spec/models/api/v1/api_key_spec.rb
index eb8826c0e..b700429d3 100644
--- a/spec/models/api/v1/api_key_spec.rb
+++ b/spec/models/api/v1/api_key_spec.rb
@@ -1,13 +1,25 @@
-require 'spec_helper'
+require 'rails_helper'
-describe Api::V1::ApiKey, :type => :model do
- let!(:referential){create(:referential)}
- subject { Api::V1::ApiKey.create( :name => "test", :referential => referential)}
+RSpec.describe Api::V1::ApiKey, type: :model do
+ subject { create(:api_key) }
- it "test" do
- expect(subject).to be_valid
- expect(subject.referential).to eq(referential)
+ it { should validate_presence_of :organisation }
+ it 'should have a valid factory' do
+ expect(build(:api_key)).to be_valid
+ end
+
+ describe '#referential_from_token' do
+ it 'should return referential' do
+ referential = Api::V1::ApiKey.referential_from_token(subject.token)
+ expect(referential).to eq(subject.referential)
+ end
end
-end
+ describe '#organisation_from_token' do
+ it 'should return organisation' do
+ organisation = Api::V1::ApiKey.organisation_from_token(subject.token)
+ expect(organisation).to eq(subject.organisation)
+ end
+ end
+end
diff --git a/spec/models/chouette/footnote_spec.rb b/spec/models/chouette/footnote_spec.rb
index 5c09e3931..98d751499 100644
--- a/spec/models/chouette/footnote_spec.rb
+++ b/spec/models/chouette/footnote_spec.rb
@@ -1,9 +1,22 @@
require 'spec_helper'
-describe Chouette::Footnote do
-
- subject { build(:footnote) }
+describe Chouette::Footnote, type: :model do
+ let(:footnote) { create(:footnote) }
it { should validate_presence_of :line }
+ describe 'checksum' do
+ it_behaves_like 'checksum support', :footnote
+
+ context '#checksum_attributes' do
+ it 'should return code and label' do
+ expected = [footnote.code, footnote.label]
+ expect(footnote.checksum_attributes).to include(*expected)
+ end
+
+ it 'should not return other atrributes' do
+ expect(footnote.checksum_attributes).to_not include(footnote.updated_at)
+ end
+ end
+ end
end
diff --git a/spec/models/chouette/journey_pattern_spec.rb b/spec/models/chouette/journey_pattern_spec.rb
index 26d220056..047022ade 100644
--- a/spec/models/chouette/journey_pattern_spec.rb
+++ b/spec/models/chouette/journey_pattern_spec.rb
@@ -1,6 +1,9 @@
require 'spec_helper'
describe Chouette::JourneyPattern, :type => :model do
+ describe 'checksum' do
+ it_behaves_like 'checksum support', :journey_pattern
+ end
# context 'validate minimum stop_points size' do
# let(:journey_pattern) { create :journey_pattern }
diff --git a/spec/models/chouette/route/route_base_spec.rb b/spec/models/chouette/route/route_base_spec.rb
index 08f201022..c93b311ff 100644
--- a/spec/models/chouette/route/route_base_spec.rb
+++ b/spec/models/chouette/route/route_base_spec.rb
@@ -1,6 +1,9 @@
RSpec.describe Chouette::Route, :type => :model do
subject { create(:route) }
+ describe 'checksum' do
+ it_behaves_like 'checksum support', :route
+ end
describe '#objectid' do
subject { super().objectid }
@@ -62,8 +65,4 @@ RSpec.describe Chouette::Route, :type => :model do
end
end
end
-
end
-
-
-
diff --git a/spec/models/chouette/routing_constraint_zone_spec.rb b/spec/models/chouette/routing_constraint_zone_spec.rb
index 0165c369d..054cfb9e6 100644
--- a/spec/models/chouette/routing_constraint_zone_spec.rb
+++ b/spec/models/chouette/routing_constraint_zone_spec.rb
@@ -9,6 +9,10 @@ describe Chouette::RoutingConstraintZone, type: :model do
# shoulda matcher to validate length of array ?
xit { is_expected.to validate_length_of(:stop_point_ids).is_at_least(2) }
+ describe 'checksum' do
+ it_behaves_like 'checksum support', :routing_constraint_zone
+ end
+
describe 'validations' do
it 'validates the presence of route_id' do
expect {
diff --git a/spec/models/chouette/time_table_period_spec.rb b/spec/models/chouette/time_table_period_spec.rb
index 07dc602cb..cc1a3ae09 100644
--- a/spec/models/chouette/time_table_period_spec.rb
+++ b/spec/models/chouette/time_table_period_spec.rb
@@ -4,11 +4,15 @@ describe Chouette::TimeTablePeriod, :type => :model do
let!(:time_table) { create(:time_table)}
subject { create(:time_table_period ,:time_table => time_table, :period_start => Date.new(2014,6,30), :period_end => Date.new(2014,7,6) ) }
- let!(:p2) {create(:time_table_period ,:time_table => time_table, :period_start => Date.new(2014,7,6), :period_end => Date.new(2014,7,14) ) }
+ let!(:p2) {create(:time_table_period ,:time_table => time_table, :period_start => Date.new(2014,7,6), :period_end => Date.new(2014,7,14) ) }
it { is_expected.to validate_presence_of :period_start }
it { is_expected.to validate_presence_of :period_end }
-
+
+ describe 'checksum' do
+ it_behaves_like 'checksum support', :time_table_period
+ end
+
describe "#overlap" do
context "when periods intersect, " do
it "should detect period overlap" do
diff --git a/spec/models/chouette/time_table_spec.rb b/spec/models/chouette/time_table_spec.rb
index f13e13d52..c4eaeaaf0 100644
--- a/spec/models/chouette/time_table_spec.rb
+++ b/spec/models/chouette/time_table_spec.rb
@@ -840,252 +840,15 @@ end
:period_end => Date.new(2013, 05, 30))
expect(time_table.intersects([ Date.new(2013, 05, 27), Date.new(2013, 05, 28)])).to eq([ Date.new(2013, 05, 27), Date.new(2013, 05, 28)])
end
-
-
- end
-
- describe "#include_day?" do
- it "should return true if a date equal day" do
- time_table = Chouette::TimeTable.create!(:comment => "Test", :objectid => "test:Timetable:1")
- time_table.dates << Chouette::TimeTableDate.new( :date => Date.today, :in_out => true)
- expect(time_table.include_day?(Date.today)).to eq(true)
- end
-
- it "should return true if a period include day" do
- time_table = Chouette::TimeTable.create!(:comment => "Test", :objectid => "test:Timetable:1", :int_day_types => 12) # Day type monday and tuesday
- time_table.periods << Chouette::TimeTablePeriod.new(
- :period_start => Date.new(2013, 05, 27),
- :period_end => Date.new(2013, 05, 29))
- expect(time_table.include_day?( Date.new(2013, 05, 27))).to eq(true)
- end
- end
-
- describe "#include_in_dates?" do
- it "should return true if a date equal day" do
- time_table = Chouette::TimeTable.create!(:comment => "Test", :objectid => "test:Timetable:1")
- time_table.dates << Chouette::TimeTableDate.new( :date => Date.today, :in_out => true)
- expect(time_table.include_in_dates?(Date.today)).to eq(true)
- end
-
- it "should return false if a period include day but that is exclued" do
- time_table = Chouette::TimeTable.create!(:comment => "Test", :objectid => "test:Timetable:1", :int_day_types => 12) # Day type monday and tuesday
- excluded_date = Date.new(2013, 05, 27)
- time_table.dates << Chouette::TimeTableDate.new( :date => excluded_date, :in_out => false)
- expect(time_table.include_in_dates?( excluded_date)).to be_falsey
- end
- end
-
- describe "#include_in_periods?" do
- it "should return true if a period include day" do
- time_table = Chouette::TimeTable.create!(:comment => "Test", :objectid => "test:Timetable:1", :int_day_types => 4)
- time_table.periods << Chouette::TimeTablePeriod.new(
- :period_start => Date.new(2012, 1, 1),
- :period_end => Date.new(2012, 01, 30))
- expect(time_table.include_in_periods?(Date.new(2012, 1, 2))).to eq(true)
- end
-
- it "should return false if a period include day but that is exclued" do
- time_table = Chouette::TimeTable.create!(:comment => "Test", :objectid => "test:Timetable:1", :int_day_types => 12) # Day type monday and tuesday
- excluded_date = Date.new(2013, 05, 27)
- time_table.dates << Chouette::TimeTableDate.new( :date => excluded_date, :in_out => false)
- time_table.periods << Chouette::TimeTablePeriod.new(
- :period_start => Date.new(2013, 05, 27),
- :period_end => Date.new(2013, 05, 29))
- expect(time_table.include_in_periods?( excluded_date)).to be_falsey
- end
- end
-
- describe "#include_in_overlap_dates?" do
- it "should return true if a day is included in overlap dates" do
- time_table = Chouette::TimeTable.create!(:comment => "Test", :objectid => "test:Timetable:1", :int_day_types => 4)
- time_table.periods << Chouette::TimeTablePeriod.new(
- :period_start => Date.new(2012, 1, 1),
- :period_end => Date.new(2012, 01, 30))
- time_table.dates << Chouette::TimeTableDate.new( :date => Date.new(2012, 1, 2), :in_out => true)
- expect(time_table.include_in_overlap_dates?(Date.new(2012, 1, 2))).to eq(true)
- end
- it "should return false if the day is excluded" do
- time_table = Chouette::TimeTable.create!(:comment => "Test", :objectid => "test:Timetable:1", :int_day_types => 4)
- time_table.periods << Chouette::TimeTablePeriod.new(
- :period_start => Date.new(2012, 1, 1),
- :period_end => Date.new(2012, 01, 30))
- time_table.dates << Chouette::TimeTableDate.new( :date => Date.new(2012, 1, 2), :in_out => false)
- expect(time_table.include_in_overlap_dates?(Date.new(2012, 1, 2))).to be_falsey
- end
- end
-
- describe "#dates" do
- it "should have with position 0" do
- expect(subject.dates.first.position).to eq(0)
- end
- context "when first date has been removed" do
- before do
- subject.dates.first.destroy
- end
- it "should begin with position 0" do
- expect(subject.dates.first.position).to eq(0)
- end
- end
- end
- describe "#validity_out_between?" do
- let(:empty_tm) {build(:time_table)}
- it "should be false if empty calendar" do
- expect(empty_tm.validity_out_between?( Date.today, Date.today + 7.day)).to be_falsey
- end
- it "should be true if caldendar is out during start_date and end_date period" do
- start_date = subject.bounding_dates.max - 2.day
- end_date = subject.bounding_dates.max + 2.day
- expect(subject.validity_out_between?( start_date, end_date)).to be_truthy
- end
- it "should be false if calendar is out on start date" do
- start_date = subject.bounding_dates.max
- end_date = subject.bounding_dates.max + 2.day
- expect(subject.validity_out_between?( start_date, end_date)).to be_falsey
- end
- it "should be false if calendar is out on end date" do
- start_date = subject.bounding_dates.max - 2.day
- end_date = subject.bounding_dates.max
- expect(subject.validity_out_between?( start_date, end_date)).to be_truthy
- end
- it "should be false if calendar is out after start_date" do
- start_date = subject.bounding_dates.max + 2.day
- end_date = subject.bounding_dates.max + 4.day
- expect(subject.validity_out_between?( start_date, end_date)).to be_falsey
- end
- end
- describe "#validity_out_from_on?" do
- let(:empty_tm) {build(:time_table)}
- it "should be false if empty calendar" do
- expect(empty_tm.validity_out_from_on?( Date.today)).to be_falsey
- end
- it "should be true if caldendar ends on expected date" do
- expected_date = subject.bounding_dates.max
- expect(subject.validity_out_from_on?( expected_date)).to be_truthy
- end
- it "should be true if calendar ends before expected date" do
- expected_date = subject.bounding_dates.max + 30.day
- expect(subject.validity_out_from_on?( expected_date)).to be_truthy
- end
- it "should be false if calendars ends after expected date" do
- expected_date = subject.bounding_dates.max - 30.day
- expect(subject.validity_out_from_on?( expected_date)).to be_falsey
- end
- end
- describe "#bounding_dates" do
- context "when timetable contains only periods" do
- before do
- subject.dates = []
- subject.save
- end
- it "should retreive periods.period_start.min and periods.period_end.max" do
- expect(subject.bounding_dates.min).to eq(subject.periods.map(&:period_start).min)
- expect(subject.bounding_dates.max).to eq(subject.periods.map(&:period_end).max)
- end
- end
- context "when timetable contains only dates" do
- before do
- subject.periods = []
- subject.save
- end
- it "should retreive dates.min and dates.max" do
- expect(subject.bounding_dates.min).to eq(subject.dates.map(&:date).min)
- expect(subject.bounding_dates.max).to eq(subject.dates.map(&:date).max)
- end
- end
- it "should contains min date" do
- min_date = subject.bounding_dates.min
- subject.dates.each do |tm_date|
- expect(min_date <= tm_date.date).to be_truthy
- end
- subject.periods.each do |tm_period|
- expect(min_date <= tm_period.period_start).to be_truthy
- end
-
- end
- it "should contains max date" do
- max_date = subject.bounding_dates.max
- subject.dates.each do |tm_date|
- expect(tm_date.date <= max_date).to be_truthy
- end
- subject.periods.each do |tm_period|
- expect(tm_period.period_end <= max_date).to be_truthy
- end
-
- end
- end
- describe "#periods" do
- it "should begin with position 0" do
- expect(subject.periods.first.position).to eq(0)
- end
- context "when first period has been removed" do
- before do
- subject.periods.first.destroy
- end
- it "should begin with position 0" do
- expect(subject.periods.first.position).to eq(0)
- end
- end
- it "should have period_start before period_end" do
- period = Chouette::TimeTablePeriod.new
- period.period_start = Date.today
- period.period_end = Date.today + 10
- expect(period.valid?).to be_truthy
- end
- it "should not have period_start after period_end" do
- period = Chouette::TimeTablePeriod.new
- period.period_start = Date.today
- period.period_end = Date.today - 10
- expect(period.valid?).to be_falsey
- end
- it "should not have period_start equal to period_end" do
- period = Chouette::TimeTablePeriod.new
- period.period_start = Date.today
- period.period_end = Date.today
- expect(period.valid?).to be_falsey
- end
end
- describe "#effective_days_of_periods" do
- before do
- subject.periods.clear
- subject.periods << Chouette::TimeTablePeriod.new(
- :period_start => Date.new(2014, 6, 30),
- :period_end => Date.new(2014, 7, 6))
- subject.int_day_types = 4|8|16
- end
- it "should return monday to wednesday" do
- expect(subject.effective_days_of_periods.size).to eq(3)
- expect(subject.effective_days_of_periods[0]).to eq(Date.new(2014, 6, 30))
- expect(subject.effective_days_of_periods[1]).to eq(Date.new(2014, 7, 1))
- expect(subject.effective_days_of_periods[2]).to eq(Date.new(2014, 7, 2))
- end
- it "should return thursday" do
- expect(subject.effective_days_of_periods(Chouette::TimeTable.valid_days(32)).size).to eq(1)
- expect(subject.effective_days_of_periods(Chouette::TimeTable.valid_days(32))[0]).to eq(Date.new(2014, 7, 3))
- end
-
- end
+ # it { is_expected.to validate_presence_of :comment }
+ # it { is_expected.to validate_uniqueness_of :objectid }
- describe "#included_days" do
- before do
- subject.dates.clear
- subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,16), :in_out => true)
- subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,17), :in_out => false)
- subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,18), :in_out => true)
- subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,19), :in_out => false)
- subject.dates << Chouette::TimeTableDate.new( :date => Date.new(2014,7,20), :in_out => true)
- end
- it "should return 3 dates" do
- days = subject.included_days
- expect(days.size).to eq(3)
- expect(days[0]).to eq(Date.new(2014, 7, 16))
- expect(days[1]).to eq(Date.new(2014,7, 18))
- expect(days[2]).to eq(Date.new(2014, 7,20))
- end
+ describe 'checksum' do
+ it_behaves_like 'checksum support', :time_table
end
-
-
describe "#excluded_days" do
before do
subject.dates.clear
@@ -1223,5 +986,4 @@ end
expect(subject.tag_list.size).to eq(2)
end
end
-
end
diff --git a/spec/models/chouette/vehicle_journey_at_stop_spec.rb b/spec/models/chouette/vehicle_journey_at_stop_spec.rb
index d999ed1a8..4f9d12730 100644
--- a/spec/models/chouette/vehicle_journey_at_stop_spec.rb
+++ b/spec/models/chouette/vehicle_journey_at_stop_spec.rb
@@ -1,6 +1,21 @@
require 'spec_helper'
RSpec.describe Chouette::VehicleJourneyAtStop, type: :model do
+ describe 'checksum' do
+ let(:at_stop) { build_stubbed(:vehicle_journey_at_stop) }
+
+ it_behaves_like 'checksum support', :vehicle_journey_at_stop
+
+ context '#checksum_attributes' do
+ it 'should return attributes' do
+ expected = [at_stop.departure_time.to_s(:time), at_stop.arrival_time.to_s(:time)]
+ expected << at_stop.departure_day_offset.to_s
+ expected << at_stop.arrival_day_offset.to_s
+ expect(at_stop.checksum_attributes).to include(*expected)
+ end
+ end
+ end
+
describe "#day_offset_outside_range?" do
let (:at_stop) { build_stubbed(:vehicle_journey_at_stop) }
diff --git a/spec/models/chouette/vehicle_journey_spec.rb b/spec/models/chouette/vehicle_journey_spec.rb
index 3c04a77cc..52f2ab42d 100644
--- a/spec/models/chouette/vehicle_journey_spec.rb
+++ b/spec/models/chouette/vehicle_journey_spec.rb
@@ -17,8 +17,13 @@ describe Chouette::VehicleJourney, :type => :model do
expect(vehicle_journey).to be_valid
end
+ describe 'checksum' do
+ it_behaves_like 'checksum support', :vehicle_journey
+ end
+
describe "vjas_departure_time_must_be_before_next_stop_arrival_time",
skip: "Validation currently commented out because it interferes with day offsets" do
+
let(:vehicle_journey) { create :vehicle_journey }
let(:vjas) { vehicle_journey.vehicle_journey_at_stops }
diff --git a/spec/models/import_spec.rb b/spec/models/import_spec.rb
index a2855d086..76f945871 100644
--- a/spec/models/import_spec.rb
+++ b/spec/models/import_spec.rb
@@ -1,10 +1,158 @@
-require 'rails_helper'
-
RSpec.describe Import, :type => :model do
+
it { should belong_to(:referential) }
it { should belong_to(:workbench) }
+ it { should belong_to(:parent) }
- it { should enumerize(:status).in(:new, :pending, :successful, :failed, :canceled, :running, :aborted ) }
+ it { should enumerize(:status).in("aborted", "canceled", "failed", "new", "pending", "running", "successful") }
it { should validate_presence_of(:file) }
+ it { should validate_presence_of(:workbench) }
+ it { should validate_presence_of(:creator) }
+
+ let(:workbench_import) { build_stubbed(:workbench_import) }
+ let(:workbench_import_with_completed_steps) do
+ workbench_import = build_stubbed(
+ :workbench_import,
+ total_steps: 2,
+ current_step: 2
+ )
+ end
+
+ let(:netex_import) do
+ netex_import = build_stubbed(
+ :netex_import,
+ parent: workbench_import
+ )
+ end
+
+ describe "#notify_parent" do
+ it "must call #child_change on its parent" do
+ allow(netex_import).to receive(:update)
+
+ expect(workbench_import).to receive(:child_change).with(netex_import)
+
+ 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
+ )
+
+ netex_import.notify_parent
+ end
+ end
+ end
+
+ describe "#child_change" do
+ shared_examples(
+ "updates :status to failed when child status indicates failure"
+ ) do |failure_status|
+ it "updates :status to failed when child status indicates failure" do
+ allow(workbench_import).to receive(:update)
+
+ netex_import = build_stubbed(
+ :netex_import,
+ parent: workbench_import,
+ status: failure_status
+ )
+
+ expect(workbench_import).to receive(:update).with(status: 'failed')
+
+ workbench_import.child_change(netex_import)
+ end
+ end
+
+ include_examples(
+ "updates :status to failed when child status indicates failure",
+ "failed"
+ )
+ include_examples(
+ "updates :status to failed when child status indicates failure",
+ "aborted"
+ )
+ include_examples(
+ "updates :status to failed when child status indicates failure",
+ "canceled"
+ )
+
+ it "updates :status to successful when #ready?" do
+ expect(workbench_import).to receive(:update).with(status: 'successful')
+
+ workbench_import.child_change(netex_import)
+ end
+
+ it "updates :status to failed when #ready? and child is failed" do
+ netex_import = build_stubbed(
+ :netex_import,
+ parent: workbench_import,
+ status: :failed
+ )
+
+ expect(workbench_import).to receive(:update).with(status: 'failed')
+
+ workbench_import.child_change(netex_import)
+ end
+
+ shared_examples(
+ "doesn't update :status if parent import status is finished"
+ ) do |finished_status|
+ it "doesn't update :status if parent import status is finished" do
+ workbench_import = build_stubbed(
+ :workbench_import,
+ total_steps: 2,
+ current_step: 2,
+ status: finished_status
+ )
+ child = double('Import')
+
+ expect(workbench_import).not_to receive(:update)
+
+ workbench_import.child_change(child)
+ end
+ end
+
+ include_examples(
+ "doesn't update :status if parent import status is finished",
+ "successful"
+ )
+ include_examples(
+ "doesn't update :status if parent import status is finished",
+ "failed"
+ )
+ include_examples(
+ "doesn't update :status if parent import status is finished",
+ "aborted"
+ )
+ include_examples(
+ "doesn't update :status if parent import status is finished",
+ "canceled"
+ )
+ end
+
+ describe "#ready?" do
+ it "returns true if #current_step == #total_steps" do
+ import = build_stubbed(
+ :import,
+ total_steps: 4,
+ current_step: 4
+ )
+
+ expect(import.ready?).to be true
+ end
+
+ it "returns false if #current_step != #total_steps" do
+ import = build_stubbed(
+ :import,
+ total_steps: 6,
+ current_step: 3
+ )
+
+ expect(import.ready?).to be false
+ end
+ end
end
diff --git a/spec/models/organisation_spec.rb b/spec/models/organisation_spec.rb
index 527f71015..b16324a56 100644
--- a/spec/models/organisation_spec.rb
+++ b/spec/models/organisation_spec.rb
@@ -1,5 +1,3 @@
-require 'spec_helper'
-
describe Organisation, :type => :model do
it { should validate_presence_of(:name) }
it { should validate_uniqueness_of(:code) }
@@ -17,7 +15,7 @@ describe Organisation, :type => :model do
let(:conf) { Rails.application.config.stif_portail_api }
before :each do
stub_request(:get, "#{conf[:url]}/api/v1/organizations").
- with(headers: { 'Authorization' => "Token token=\"#{conf[:key]}\"" }).
+ with(stub_headers(authorization_token: conf[:key])).
to_return(body: File.open(File.join(Rails.root, 'spec', 'fixtures', 'organizations.json')), status: 200)
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 3a9ae37e9..51ccfccd3 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -67,7 +67,7 @@ RSpec.describe User, :type => :model do
let(:conf) { Rails.application.config.stif_portail_api }
before :each do
stub_request(:get, "#{conf[:url]}/api/v1/users").
- with(headers: { 'Authorization' => "Token token=\"#{conf[:key]}\"" }).
+ with(stub_headers(authorization_token: conf[:key])).
to_return(body: File.open(File.join(Rails.root, 'spec', 'fixtures', 'users.json')), status: 200)
end
diff --git a/spec/policies/api_key_policy_spec.rb b/spec/policies/api_key_policy_spec.rb
new file mode 100644
index 000000000..5b9d59fa3
--- /dev/null
+++ b/spec/policies/api_key_policy_spec.rb
@@ -0,0 +1,28 @@
+require 'rails_helper'
+
+RSpec.describe ApiKeyPolicy do
+
+ let(:user) { User.new }
+
+ subject { described_class }
+
+ permissions ".scope" do
+ pending "add some examples to (or delete) #{__FILE__}"
+ end
+
+ permissions :show? do
+ pending "add some examples to (or delete) #{__FILE__}"
+ end
+
+ permissions :create? do
+ pending "add some examples to (or delete) #{__FILE__}"
+ end
+
+ permissions :update? do
+ pending "add some examples to (or delete) #{__FILE__}"
+ end
+
+ permissions :destroy? do
+ pending "add some examples to (or delete) #{__FILE__}"
+ end
+end
diff --git a/spec/requests/api/v1/netex_import_spec.rb b/spec/requests/api/v1/netex_import_spec.rb
new file mode 100644
index 000000000..fd5f6d497
--- /dev/null
+++ b/spec/requests/api/v1/netex_import_spec.rb
@@ -0,0 +1,100 @@
+RSpec.describe "NetexImport", type: :request do
+
+ describe 'POST netex_imports' do
+
+ let( :referential ){ create :referential }
+ let( :workbench ){ referential.workbench }
+
+
+ let( :file_path ){ fixtures_path 'single_reference_import.zip' }
+ let( :file ){ fixture_file_upload( file_path ) }
+
+ let( :post_request ) do
+ -> (attributes) do
+ post api_v1_netex_imports_path(format: :json),
+ attributes,
+ authorization
+ end
+ end
+
+ let( :legal_attributes ) do
+ {
+ name: 'hello world',
+ file: file,
+ workbench_id: workbench.id
+ }
+ end
+
+
+ context 'with correct credentials and correct request' do
+ let( :authorization ){ authorization_token_header( get_api_key.token ) }
+
+ it 'succeeds' do
+ post_request.(netex_import: legal_attributes)
+ expect( response ).to be_success
+ expect( json_response_body ).to eq(
+ 'id' => NetexImport.last.id,
+ 'referential_id' => Referential.last.id,
+ 'workbench_id' => workbench.id
+ )
+ end
+
+ it 'creates a NetexImport object in the DB' do
+ expect{ post_request.(netex_import: legal_attributes) }.to change{NetexImport.count}.by(1)
+ end
+
+ it 'creates a correct Referential' do
+ legal_attributes # force object creation for correct to change behavior
+ expect{post_request.(netex_import: legal_attributes)}.to change{Referential.count}.by(1)
+ Referential.last.tap do | ref |
+ expect( ref.workbench_id ).to eq(workbench.id)
+ expect( ref.organisation_id ).to eq(workbench.organisation_id)
+ end
+ end
+ end
+
+
+ context 'with incorrect credentials and correct request' do
+ let( :authorization ){ authorization_token_header( "#{referential.id}-incorrect_token") }
+
+ it 'does not create any DB object and does not succeed' do
+ legal_attributes # force object creation for correct to change behavior
+ expect{ post_request.(netex_import: legal_attributes) }.not_to change{Referential.count}
+ expect( response.status ).to eq(401)
+ end
+
+ end
+
+ context 'with correct credentials and incorrect request' do
+ let( :authorization ){ authorization_token_header( get_api_key.token ) }
+
+ shared_examples_for 'illegal attributes' do |bad_attribute, illegal_value=nil, referential_count: 0|
+ context "missing #{bad_attribute}" do
+ let!( :illegal_attributes ){ legal_attributes.merge( bad_attribute => illegal_value ) }
+ it 'does not succeed' do
+ post_request.(netex_import: illegal_attributes)
+ expect( response.status ).to eq(406)
+ expect( json_response_body['errors'][bad_attribute.to_s] ).not_to be_empty
+ end
+
+ it 'does not create an Import object' do
+ expect{ post_request.(netex_import: illegal_attributes) }.not_to change{Import.count}
+ end
+
+ it 'might create a referential' do
+ expect{ post_request.(netex_import: illegal_attributes) }.to change{Referential.count}.by(referential_count)
+ end
+ end
+ end
+
+ it_behaves_like 'illegal attributes', :file, referential_count: 1
+ it_behaves_like 'illegal attributes', :workbench_id
+ context 'name already taken' do
+ before do
+ create :referential, name: 'already taken'
+ end
+ it_behaves_like 'illegal attributes', :name, 'already taken'
+ end
+ end
+ end
+end
diff --git a/spec/routing/api/v1/access_links_routes_spec.rb b/spec/routing/api/v1/access_links_routes_spec.rb
new file mode 100644
index 000000000..9164d3f05
--- /dev/null
+++ b/spec/routing/api/v1/access_links_routes_spec.rb
@@ -0,0 +1,9 @@
+RSpec.describe Api::V1::AccessLinksController, type: :controller do
+
+ it 'routes to index' do
+ expect( get: '/api/v1/access_links' ).to route_to(
+ controller: 'api/v1/access_links',
+ action: 'index'
+ )
+ end
+end
diff --git a/spec/routing/group_of_lines_spec.rb b/spec/routing/group_of_lines_spec.rb
index 2a7262893..01ebeefe4 100644
--- a/spec/routing/group_of_lines_spec.rb
+++ b/spec/routing/group_of_lines_spec.rb
@@ -1,6 +1,4 @@
-require 'spec_helper'
-
-describe GroupOfLinesController do
+RSpec.describe GroupOfLinesController do
describe "routing" do
it "not recognize #routes" do
expect(get( "/line_referentials/1/group_of_lines/2/routes")).not_to route_to(
diff --git a/spec/services/http_service_spec.rb b/spec/services/http_service_spec.rb
new file mode 100644
index 000000000..8c8af480c
--- /dev/null
+++ b/spec/services/http_service_spec.rb
@@ -0,0 +1,74 @@
+RSpec.describe HTTPService do
+
+ subject{ described_class }
+
+ %i{host params path result}.each do |param|
+ let(param){ double(param) }
+ end
+ let( :token ){ SecureRandom.hex }
+
+ let( :faraday_connection ){ double('faraday_connection') }
+ let( :headers ){ {} }
+
+
+ context 'get_resource' do
+ let( :params ){ double('params') }
+
+ it 'sets authorization and returns result' do
+ expect(Faraday).to receive(:new).with(url: host).and_yield(faraday_connection)
+ expect(faraday_connection).to receive(:adapter).with(Faraday.default_adapter)
+ expect(faraday_connection).to receive(:headers).and_return headers
+ expect(faraday_connection).to receive(:get).with(path, params).and_return(result)
+
+ expect(subject.get_resource(host: host, path: path, token: token, params: params)).to eq(result)
+ expect(headers['Authorization']).to eq( "Token token=#{token.inspect}" )
+ end
+ end
+
+ context 'post_resource' do
+ %i{as_name mime_type name upload_io value}.each do | param |
+ let( param ){ double(param) }
+ end
+
+ let( :upload_list ){ [value, mime_type, as_name] }
+
+ it 'sets authorization and posts data' do
+ expect(Faraday::UploadIO).to receive(:new).with(*upload_list).and_return upload_io
+ expect(params).to receive(:update).with(name => upload_io)
+
+ expect(Faraday).to receive(:new).with(url: host).and_yield(faraday_connection)
+ expect(faraday_connection).to receive(:adapter).with(Faraday.default_adapter)
+ expect(faraday_connection).to receive(:headers).and_return headers
+ expect(faraday_connection).to receive(:request).with(:multipart)
+ expect(faraday_connection).to receive(:request).with(:url_encoded)
+
+ expect(faraday_connection).to receive(:post).with(path, params).and_return(result)
+
+ expect(subject.post_resource(
+ host: host,
+ path: path,
+ token: token,
+ params: params,
+ upload: {name => upload_list} )).to eq(result)
+ expect(headers['Authorization']).to eq( "Token token=#{token.inspect}" )
+ end
+
+ end
+
+ context 'get_json_resource' do
+
+ let( :content ){ SecureRandom.hex }
+
+ it 'delegates an parses the response' do
+ expect_it.to receive(:get_resource)
+ .with(host: host, path: path, token: token, params: params)
+ .and_return(double(body: {content: content}.to_json, status: 200))
+
+ expect( subject.get_json_resource(
+ host: host,
+ path: path,
+ token: token,
+ params: params) ).to eq('content' => content)
+ end
+ end
+end
diff --git a/spec/services/parent_import_notifier_spec.rb b/spec/services/parent_import_notifier_spec.rb
new file mode 100644
index 000000000..3ab505f88
--- /dev/null
+++ b/spec/services/parent_import_notifier_spec.rb
@@ -0,0 +1,90 @@
+RSpec.describe ParentImportNotifier do
+ let(:workbench_import) { create(:workbench_import) }
+
+ describe ".notify_when_finished" do
+ it "calls #notify_parent on finished imports" do
+ workbench_import = build_stubbed(:workbench_import)
+
+ finished_statuses = [:failed, :successful, :aborted, :canceled]
+ netex_imports = []
+
+ finished_statuses.each do |status|
+ netex_imports << build_stubbed(
+ :netex_import,
+ parent: workbench_import,
+ status: status
+ )
+ end
+
+ netex_imports.each do |netex_import|
+ expect(netex_import).to receive(:notify_parent)
+ end
+
+ ParentImportNotifier.notify_when_finished(netex_imports)
+ end
+
+ it "doesn't call #notify_parent if its `notified_parent_at` is set" do
+ netex_import = create(
+ :netex_import,
+ parent: workbench_import,
+ status: :failed,
+ notified_parent_at: DateTime.now
+ )
+
+ expect(netex_import).not_to receive(:notify_parent)
+
+ ParentImportNotifier.notify_when_finished
+ end
+ end
+
+ describe ".imports_pending_notification" do
+ it "includes imports with a parent and `notified_parent_at` unset" do
+ netex_import = create(
+ :netex_import,
+ parent: workbench_import,
+ status: :successful,
+ notified_parent_at: nil
+ )
+
+ expect(
+ ParentImportNotifier.imports_pending_notification
+ ).to eq([netex_import])
+ end
+
+ it "doesn't include imports without a parent" do
+ create(:import, parent: nil)
+
+ expect(
+ ParentImportNotifier.imports_pending_notification
+ ).to be_empty
+ end
+
+ it "doesn't include imports that aren't finished" do
+ [:new, :pending, :running].each do |status|
+ create(
+ :netex_import,
+ parent: workbench_import,
+ status: status,
+ notified_parent_at: nil
+ )
+ end
+
+ expect(
+ ParentImportNotifier.imports_pending_notification
+ ).to be_empty
+ end
+
+ it "doesn't include imports that have already notified their parent" do
+ create(
+ :netex_import,
+ parent: workbench_import,
+ status: :successful,
+ notified_parent_at: DateTime.now
+ )
+
+ expect(
+ ParentImportNotifier.imports_pending_notification
+ ).to be_empty
+ end
+ end
+end
diff --git a/spec/services/retry_service_spec.rb b/spec/services/retry_service_spec.rb
new file mode 100644
index 000000000..bb3416373
--- /dev/null
+++ b/spec/services/retry_service_spec.rb
@@ -0,0 +1,137 @@
+RSpec.describe RetryService do
+ subject { described_class.new delays: [2, 3], rescue_from: [NameError, ArgumentError] }
+
+ context 'no retry necessary' do
+ before do
+ expect( subject ).not_to receive(:sleep)
+ end
+
+ it 'returns an ok result' do
+ expect( subject.execute { 42 } ).to eq(Result.ok(42))
+ end
+ it 'does not fail on nil' do
+ expect( subject.execute { nil } ).to eq(Result.ok(nil))
+ end
+
+ it 'fails wihout retries if raising un unregistered exception' do
+ expect{ subject.execute{ raise KeyError } }.to raise_error(KeyError)
+ end
+
+ end
+
+ context 'all retries fail' do
+ before do
+ expect( subject ).to receive(:sleep).with(2)
+ expect( subject ).to receive(:sleep).with(3)
+ end
+ it 'fails after raising a registered exception n times' do
+ result = subject.execute{ raise ArgumentError }
+ expect( result.status ).to eq(:error)
+ expect( result.value ).to be_kind_of(ArgumentError)
+ end
+ it 'fails with an explicit try again (automatically registered exception)' do
+ result = subject.execute{ raise RetryService::Retry }
+ expect( result.status ).to eq(:error)
+ expect( result.value ).to be_kind_of(RetryService::Retry)
+ end
+ end
+
+ context "if at first you don't succeed" do
+ before do
+ @count = 0
+ expect( subject ).to receive(:sleep).with(2)
+ end
+
+ it 'succeeds the second time' do
+ expect( subject.execute{ succeed_later(ArgumentError){ 42 } } ).to eq(Result.ok(42))
+ end
+
+ it 'succeeds the second time with try again (automatically registered exception)' do
+ expect( subject.execute{ succeed_later(RetryService::Retry){ 42 } } ).to eq(Result.ok(42))
+ end
+ end
+
+ context 'last chance' do
+ before do
+ @count = 0
+ expect( subject ).to receive(:sleep).with(2)
+ expect( subject ).to receive(:sleep).with(3)
+ end
+ it 'succeeds the third time with try again (automatically registered exception)' do
+ result = subject.execute{ succeed_later(RetryService::Retry, count: 2){ 42 } }
+ expect( result ).to eq( Result.ok(42) )
+ end
+ end
+
+ context 'failure callback once' do
+ subject do
+ described_class.new delays: [2, 3], rescue_from: [NameError, ArgumentError] do |reason, count|
+ @reason=reason
+ @callback_count=count
+ @failures += 1
+ end
+ end
+
+ before do
+ @failures = 0
+ @count = 0
+ expect( subject ).to receive(:sleep).with(2)
+ end
+
+ it 'succeeds the second time and calls the failure_callback once' do
+ subject.execute{ succeed_later(RetryService::Retry){ 42 } }
+ expect( @failures ).to eq(1)
+ end
+ it '... and the failure is passed into the callback' do
+ subject.execute{ succeed_later(RetryService::Retry){ 42 } }
+ expect( @reason ).to be_a(RetryService::Retry)
+ expect( @callback_count ).to eq(1)
+ end
+ end
+
+ context 'failure callback twice' do
+ subject do
+ described_class.new delays: [2, 3], rescue_from: [NameError, ArgumentError] do |_reason, _count|
+ @failures += 1
+ end
+ end
+
+ before do
+ @failures = 0
+ @count = 0
+ expect( subject ).to receive(:sleep).with(2)
+ expect( subject ).to receive(:sleep).with(3)
+ end
+
+ it 'succeeds the third time and calls the failure_callback twice' do
+ subject.execute{ succeed_later(NameError, count: 2){ 42 } }
+ expect( @failures ).to eq(2)
+ end
+ end
+
+ context 'failure callback in constructor' do
+ subject do
+ described_class.new(delays: [1, 2], &method(:add2failures))
+ end
+ before do
+ @failures = []
+ @count = 0
+ expect( subject ).to receive(:sleep).with(1)
+ expect( subject ).to receive(:sleep).with(2)
+ end
+ it 'succeeds the second time and calls the failure_callback once' do
+ subject.execute{ succeed_later(RetryService::Retry, count: 2){ 42 } }
+ expect( @failures ).to eq([1,2])
+ end
+ end
+
+ def add2failures( e, c)
+ @failures << c
+ end
+
+ def succeed_later error, count: 1, &blk
+ return blk.() unless @count < count
+ @count += 1
+ raise error, 'error'
+ end
+end
diff --git a/spec/services/zip_service/zip_entry_data_spec.rb b/spec/services/zip_service/zip_entry_data_spec.rb
new file mode 100644
index 000000000..2a7226eb4
--- /dev/null
+++ b/spec/services/zip_service/zip_entry_data_spec.rb
@@ -0,0 +1,32 @@
+RSpec.describe ZipService do
+
+ subject{ described_class.new(read_fixture('multiple_references_import.zip')) }
+
+ it 'can group all entries' do
+ expect( subject.entry_groups.keys ).to eq(%w{ref1 ref2})
+ end
+
+ context 'creates correct zip data for each subdir' do
+ it 'e.g. reference1' do
+ reference1_stream = subject.entry_group_streams['ref1']
+ control_stream = Zip::InputStream.open( reference1_stream )
+ control_entries = described_class.entries(control_stream)
+ expect( control_entries.map{ |e| [e.name, e.get_input_stream.read]}.force ).to eq([
+ ["multiref/ref1/", ""],
+ ["multiref/ref1/datum-1", "multi-ref1-datum1\n"],
+ ["multiref/ref1/datum-2", "multi-ref1-datum2\n"]
+ ])
+ end
+ it 'e.g. reference2' do
+ reference2_stream = subject.entry_group_streams['ref2']
+ control_stream = Zip::InputStream.open( reference2_stream )
+ control_entries = described_class.entries(control_stream)
+ expect( control_entries.map{ |e| [e.name, e.get_input_stream.read]}.force ).to eq([
+ ["multiref/ref2/", ""],
+ ["multiref/ref2/datum-1", "multi-ref2-datum1\n"],
+ ["multiref/ref2/datum-2", "multi-ref2-datum2\n"]
+ ])
+ end
+ end
+
+end
diff --git a/spec/services/zip_service/zip_entry_dirs_spec.rb b/spec/services/zip_service/zip_entry_dirs_spec.rb
new file mode 100644
index 000000000..8ca1b0f1a
--- /dev/null
+++ b/spec/services/zip_service/zip_entry_dirs_spec.rb
@@ -0,0 +1,33 @@
+RSpec.describe ZipService do
+
+ let( :zip_service ){ described_class }
+
+ let( :zip_data ){ File.read zip_file }
+
+ shared_examples_for 'a correct zip entry reader' do
+ it 'gets all entries of the zip file' do
+ expect( zip_service.new(zip_data).entry_groups.keys ).to eq(expected)
+ end
+ end
+
+ context 'single entry' do
+ let( :zip_file ){ fixtures_path 'multiple_references_import.zip' }
+ let( :expected ){ %w{ref1 ref2} }
+
+ it_behaves_like 'a correct zip entry reader'
+ end
+
+ context 'more entries' do
+ let( :zip_file ){ fixtures_path 'single_reference_import.zip' }
+ let( :expected ){ %w{ref} }
+
+ it_behaves_like 'a correct zip entry reader'
+ end
+
+ context 'illegal file' do
+ let( :zip_file ){ fixtures_path 'nozip.zip' }
+ let( :expected ){ [] }
+
+ it_behaves_like 'a correct zip entry reader'
+ end
+end
diff --git a/spec/services/zip_service/zip_output_streams_spec.rb b/spec/services/zip_service/zip_output_streams_spec.rb
new file mode 100644
index 000000000..742f9b996
--- /dev/null
+++ b/spec/services/zip_service/zip_output_streams_spec.rb
@@ -0,0 +1,8 @@
+RSpec.describe ZipService do
+
+ subject{ described_class.new(read_fixture('multiple_references_import.zip')) }
+
+ it "exposes its size" do
+ expect( subject.entry_group_streams.size ).to eq(2)
+ end
+end
diff --git a/spec/support/api_key.rb b/spec/support/api_key.rb
index 9353fac15..cc08cd7f1 100644
--- a/spec/support/api_key.rb
+++ b/spec/support/api_key.rb
@@ -1,20 +1,28 @@
module ApiKeyHelper
+ def authorization_token_header(key)
+ {'Authorization' => "Token token=#{key}"}
+ end
+
def get_api_key
- Api::V1::ApiKey.first_or_create( :referential_id => referential.id, :name => "test")
+ Api::V1::ApiKey.first_or_create(referential: referential, organisation: organisation)
end
+
def config_formatted_request_with_authorization( format)
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Token.encode_credentials( get_api_key.token)
request.accept = format
end
+
def config_formatted_request_with_dummy_authorization( format)
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Token.encode_credentials( "dummy")
request.accept = format
end
+
def config_formatted_request_without_authorization( format)
request.env['HTTP_AUTHORIZATION'] = nil
request.accept = format
end
+
def json_xml_format?
request.accept == "application/json" || request.accept == "application/xml"
end
diff --git a/spec/support/checksum_support.rb b/spec/support/checksum_support.rb
new file mode 100644
index 000000000..14ea3c55e
--- /dev/null
+++ b/spec/support/checksum_support.rb
@@ -0,0 +1,53 @@
+shared_examples 'checksum support' do |factory_name|
+ let(:instance) { create(factory_name) }
+
+ describe '#current_checksum_source' do
+ let(:attributes) { ['code_value', 'label_value'] }
+ let(:seperator) { ChecksumSupport::SEPARATOR }
+ let(:nil_value) { ChecksumSupport::VALUE_FOR_NIL_ATTRIBUTE }
+
+ before do
+ allow_any_instance_of(instance.class).to receive(:checksum_attributes).and_return(attributes)
+ end
+
+ it 'should separate attribute by seperator' do
+ expect(instance.current_checksum_source).to eq("code_value#{seperator}label_value")
+ end
+
+ context 'nil value' do
+ let(:attributes) { ['code_value', nil] }
+
+ it 'should replace nil attributes by default value' do
+ source = "code_value#{seperator}#{nil_value}"
+ expect(instance.current_checksum_source).to eq(source)
+ end
+ end
+
+ context 'empty array' do
+ let(:attributes) { ['code_value', []] }
+
+ it 'should convert to nil' do
+ source = "code_value#{seperator}#{nil_value}"
+ expect(instance.current_checksum_source).to eq(source)
+ end
+ end
+ end
+
+ it 'should save checksum on create' do
+ expect(instance.checksum).to_not be_nil
+ end
+
+ it 'should save checksum_source' do
+ expect(instance.checksum_source).to_not be_nil
+ end
+
+ it 'should trigger set_current_checksum_source on save' do
+ expect(instance).to receive(:set_current_checksum_source)
+ instance.save
+ end
+
+ it 'should trigger update_checksum on save' do
+ expect(instance).to receive(:update_checksum)
+ instance.save
+ end
+end
diff --git a/spec/support/fixtures_helper.rb b/spec/support/fixtures_helper.rb
new file mode 100644
index 000000000..20963261b
--- /dev/null
+++ b/spec/support/fixtures_helper.rb
@@ -0,0 +1,18 @@
+module Support
+ module FixturesHelper
+ def fixtures_path *segments
+ Rails.root.join( fixture_path, *segments )
+ end
+
+ def open_fixture *segments
+ File.open(fixtures_path(*segments))
+ end
+ def read_fixture *segments
+ File.read(fixtures_path(*segments))
+ end
+ end
+end
+
+RSpec.configure do |c|
+ c.include Support::FixturesHelper
+end
diff --git a/spec/support/json_helper.rb b/spec/support/json_helper.rb
new file mode 100644
index 000000000..a383981a0
--- /dev/null
+++ b/spec/support/json_helper.rb
@@ -0,0 +1,11 @@
+module Support
+ module JsonHelper
+ def json_response_body
+ JSON.parse(response.body)
+ end
+ end
+end
+
+RSpec.configure do | config |
+ config.include Support::JsonHelper, type: :request
+end
diff --git a/spec/support/referential.rb b/spec/support/referential.rb
index 57b510f69..c431856b8 100644
--- a/spec/support/referential.rb
+++ b/spec/support/referential.rb
@@ -12,6 +12,7 @@ module ReferentialHelper
base.class_eval do
extend ClassMethods
alias_method :referential, :first_referential
+ alias_method :organisation, :first_organisation
end
end
diff --git a/spec/support/shared_context.rb b/spec/support/shared_context.rb
new file mode 100644
index 000000000..e9b0025a2
--- /dev/null
+++ b/spec/support/shared_context.rb
@@ -0,0 +1,15 @@
+shared_context 'iboo authenticated api user' do
+ let(:api_key) { create(:api_key, organisation: organisation) }
+
+ before do
+ request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(api_key.organisation.code, api_key.token)
+ end
+end
+
+shared_context 'iboo wrong authorisation api user' do
+ let(:api_key) { create(:api_key, organisation: organisation) }
+
+ before do
+ request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('fake code', api_key.token)
+ end
+end
diff --git a/spec/support/webmock/helpers.rb b/spec/support/webmock/helpers.rb
new file mode 100644
index 000000000..fc6c77850
--- /dev/null
+++ b/spec/support/webmock/helpers.rb
@@ -0,0 +1,18 @@
+module Support
+ module Webmock
+ module Helpers
+ def stub_headers(*args)
+ {headers: make_headers(*args)}
+ end
+
+ def make_headers(headers={}, authorization_token:)
+ headers.merge('Authorization' => "Token token=#{authorization_token.inspect}")
+ end
+ end
+ end
+end
+
+RSpec.configure do | conf |
+ conf.include Support::Webmock::Helpers, type: :model
+ conf.include Support::Webmock::Helpers, type: :worker
+end
diff --git a/spec/tasks/reflex_rake_spec.rb b/spec/tasks/reflex_rake_spec.rb
index 04c5886aa..6ece223d2 100644
--- a/spec/tasks/reflex_rake_spec.rb
+++ b/spec/tasks/reflex_rake_spec.rb
@@ -5,7 +5,7 @@ describe 'reflex:sync' do
before(:each) do
['getOP', 'getOR'].each do |method|
stub_request(:get, "#{Rails.application.config.reflex_api_url}/?format=xml&idRefa=0&method=#{method}").
- to_return(body: File.open("#{fixture_path}/reflex.zip"), status: 200)
+ to_return(body: open_fixture('reflex.zip'), status: 200)
end
stop_area_ref = create(:stop_area_referential, name: 'Reflex')
@@ -43,7 +43,7 @@ describe 'reflex:sync' do
before(:each) do
['getOP', 'getOR'].each do |method|
stub_request(:get, "#{Rails.application.config.reflex_api_url}/?format=xml&idRefa=0&method=#{method}").
- to_return(body: File.open("#{fixture_path}/reflex_updated.zip"), status: 200)
+ to_return(body: open_fixture('reflex_updated.zip'), status: 200)
end
Stif::ReflexSynchronization.synchronize
end
diff --git a/spec/workers/stop_area_referential_sync_worker_spec.rb b/spec/workers/stop_area_referential_sync_worker_spec.rb
index 48b64e55e..50c7cf45f 100644
--- a/spec/workers/stop_area_referential_sync_worker_spec.rb
+++ b/spec/workers/stop_area_referential_sync_worker_spec.rb
@@ -1,4 +1,3 @@
-require 'rails_helper'
RSpec.describe StopAreaReferentialSyncWorker, type: :worker do
let!(:stop_area_referential_sync) { create :stop_area_referential_sync }
diff --git a/spec/workers/workbench_import_worker_spec.rb b/spec/workers/workbench_import_worker_spec.rb
new file mode 100644
index 000000000..b719cbb98
--- /dev/null
+++ b/spec/workers/workbench_import_worker_spec.rb
@@ -0,0 +1,119 @@
+RSpec.describe WorkbenchImportWorker, type: [:worker, :request] do
+
+ let( :worker ) { described_class.new }
+ let( :import ){ build_stubbed :import, token_download: download_token, file: zip_file }
+
+ let( :workbench ){ import.workbench }
+ let( :referential ){ import.referential }
+ let( :api_key ){ build_stubbed :api_key, referential: referential, token: "#{referential.id}-#{SecureRandom.hex}" }
+ let( :params ) do
+ { netex_import:
+ { referential_id: referential.id, workbench_id: workbench.id }
+ }
+ end
+
+ # http://www.example.com/workbenches/:workbench_id/imports/:id/download
+ let( :host ){ Rails.configuration.rails_host }
+ let( :path ){ download_workbench_import_path(workbench, import) }
+
+ let( :downloaded_zip ){ double("downloaded zip") }
+ let( :download_zip_response ){ OpenStruct.new( body: downloaded_zip ) }
+ let( :download_token ){ SecureRandom.urlsafe_base64 }
+
+
+ let( :upload_path ) { api_v1_netex_imports_path(format: :json) }
+
+ let( :entry_group_streams ) do
+ entry_count.times.map{ |i| double( "entry group stream #{i}" ) }
+ end
+ let( :entry_groups ) do
+ entry_count.times.map do | i |
+ {"entry_group_name#{i}" => entry_group_streams[i] }
+ end
+ end
+
+ let( :zip_service ){ double("zip service") }
+ let( :zip_file ){ open_fixture('multiple_references_import.zip') }
+
+ let( :post_response_ok ){ double(status: 201, body: "{}") }
+
+ before do
+ # Silence Logger
+ allow_any_instance_of(Logger).to receive(:info)
+ allow_any_instance_of(Logger).to receive(:warn)
+
+ # That should be `build_stubbed's` job, no?
+ allow(Import).to receive(:find).with(import.id).and_return(import)
+
+ allow(Api::V1::ApiKey).to receive(:from).and_return(api_key)
+ allow(ZipService).to receive(:new).with(downloaded_zip).and_return zip_service
+ expect(zip_service).to receive(:entry_group_streams).and_return(entry_groups)
+ expect( import ).to receive(:update_attributes).with(status: 'running')
+ end
+
+
+ context 'multireferential zipfile, no errors' do
+ let( :entry_count ){ 2 }
+
+ it 'downloads a zip file, cuts it, and uploads all pieces' do
+
+ expect(HTTPService).to receive(:get_resource)
+ .with(host: host, path: path, params: {token: download_token})
+ .and_return( download_zip_response )
+
+ entry_groups.each do | entry_group_name, entry_group_stream |
+ mock_post entry_group_name, entry_group_stream, post_response_ok
+ end
+
+ expect( import ).to receive(:update_attributes).with(total_steps: 2)
+ expect( import ).to receive(:update_attributes).with(current_step: 1)
+ expect( import ).to receive(:update_attributes).with(current_step: 2)
+
+ worker.perform import.id
+
+ end
+ end
+
+ context 'multireferential zipfile with error' do
+ let( :entry_count ){ 3 }
+ let( :post_response_failure ){ double(status: 406, body: {error: 'What was you thinking'}) }
+
+ it 'downloads a zip file, cuts it, and uploads some pieces' do
+ expect(HTTPService).to receive(:get_resource)
+ .with(host: host, path: path, params: {token: download_token})
+ .and_return( download_zip_response )
+
+ # First entry_group succeeds
+ entry_groups[0..0].each do | entry_group_name, entry_group_stream |
+ mock_post entry_group_name, entry_group_stream, post_response_ok
+ end
+
+ # Second entry_group fails (M I S E R A B L Y)
+ entry_groups[1..1].each do | entry_group_name, entry_group_stream |
+ mock_post entry_group_name, entry_group_stream, post_response_failure
+ WorkbenchImportWorker::RETRY_DELAYS.each do | delay |
+ mock_post entry_group_name, entry_group_stream, post_response_failure
+ expect_any_instance_of(RetryService).to receive(:sleep).with(delay)
+ end
+ end
+
+ expect( import ).to receive(:update_attributes).with(total_steps: 3)
+ expect( import ).to receive(:update_attributes).with(current_step: 1)
+ expect( import ).to receive(:update_attributes).with(current_step: 2)
+ expect( import ).to receive(:update_attributes).with(current_step: 3, status: 'failed')
+
+ worker.perform import.id
+
+ end
+ end
+
+ def mock_post entry_group_name, entry_group_stream, response
+ expect( HTTPService ).to receive(:post_resource)
+ .with(host: host,
+ path: upload_path,
+ token: api_key.token,
+ params: params,
+ upload: {file: [entry_group_stream, 'application/zip', entry_group_name]})
+ .and_return(response)
+ end
+end