diff options
182 files changed, 2543 insertions, 2012 deletions
diff --git a/Gemfile.lock b/Gemfile.lock index 090886f3d..251de0e1a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -332,7 +332,7 @@ GEM net-ssh (4.1.0) net-ssh-gateway (2.0.0) net-ssh (>= 4.0.0) - newrelic_rpm (4.0.0.332) + newrelic_rpm (4.8.0.341) nokogiri (1.8.2) mini_portile2 (~> 2.3.0) open4 (1.3.4) diff --git a/app/assets/javascripts/forms.coffee b/app/assets/javascripts/forms.coffee index b7ae3c6ca..9543220d0 100644 --- a/app/assets/javascripts/forms.coffee +++ b/app/assets/javascripts/forms.coffee @@ -25,7 +25,7 @@ isEdge = !isIE && !!window.StyleMedia if $('.page-action').children('.formSubmitr').length > 0 $('.page-action').children('.formSubmitr').remove() - $('.formSubmitr').appendTo('.page-action') + $('.formSubmitr').appendTo('.page-action').addClass('sticky-action') if isIE || isEdge $('.formSubmitr').off() diff --git a/app/assets/stylesheets/OpenLayers/custom.sass b/app/assets/stylesheets/OpenLayers/custom.sass index 013c056d6..0675b0ba6 100644 --- a/app/assets/stylesheets/OpenLayers/custom.sass +++ b/app/assets/stylesheets/OpenLayers/custom.sass @@ -28,6 +28,7 @@ font-size: 0.6em &:hover text-decoration: none + font-weight: bold &.active background: $blue diff --git a/app/assets/stylesheets/modules/_vj_collection.sass b/app/assets/stylesheets/modules/_vj_collection.sass index d9079daa2..3ff0828ea 100644 --- a/app/assets/stylesheets/modules/_vj_collection.sass +++ b/app/assets/stylesheets/modules/_vj_collection.sass @@ -88,10 +88,12 @@ .table-2entries .t2e-head - .detailed-timetables + .detailed-timetables, .detailed-purchase-windows + &:after + left: 0 .fa margin-right: 5px - .detailed-timetables-bt + .detailed-timetables-bt, .detailed-purchase-windows-bt text-decoration: none .fa margin-right: 5px @@ -117,7 +119,7 @@ top: 50% margin-top: -8px - .detailed-timetables + .detailed-timetables, .detailed-purchase-windows padding-top: 10px text-align: left margin-bottom: -5px @@ -129,6 +131,13 @@ a text-decoration: none border: none + white-space: nowrap + overflow: hidden + text-overflow: ellipsis + display: inline-block + margin: 0 + line-height: 1em + max-width: 100% &:before position: absolute left: 0px @@ -147,7 +156,19 @@ padding-top: 8px font-weight: bold - .t2e-item-list .detailed-timetables > div + .detailed-purchase-windows + margin-bottom: 12px + position: relative + &:after + position: absolute + left: -8px + bottom: 0 + right: -8px + content: "" + border-top: 1px solid $lightgrey + + .t2e-item-list .detailed-timetables > div, + .t2e-item-list .detailed-purchase-windows > div, border-left: none &:after top: 50% diff --git a/app/controllers/api/v1/imports_controller.rb b/app/controllers/api/v1/imports_controller.rb index 3d7f4ca79..dc2df0697 100644 --- a/app/controllers/api/v1/imports_controller.rb +++ b/app/controllers/api/v1/imports_controller.rb @@ -1,11 +1,11 @@ class Api::V1::ImportsController < Api::V1::IbooController - defaults :resource_class => WorkbenchImport + defaults :resource_class => Import::Workbench belongs_to :workbench def create args = workbench_import_params.merge(creator: 'Webservice') @import = parent.workbench_imports.create(args) - if @import.valid? + if @import.valid? create! else render json: { status: "error", messages: @import.errors.full_messages } diff --git a/app/controllers/api/v1/internals/netex_imports_controller.rb b/app/controllers/api/v1/internals/netex_imports_controller.rb index c8e33f7b8..c2b7b20cc 100644 --- a/app/controllers/api/v1/internals/netex_imports_controller.rb +++ b/app/controllers/api/v1/internals/netex_imports_controller.rb @@ -25,13 +25,13 @@ module Api private def find_netex_import - @netex_import = NetexImport.find(params[:id]) + @netex_import = Import::Netex.find(params[:id]) rescue ActiveRecord::RecordNotFound render json: { - status: "error", + status: "error", message: "Record not found" } - finish_action! + finish_action! end def find_workbench @@ -52,7 +52,7 @@ module Api attributes = attributes.merge referential_id: @new_referential.id - @netex_import = NetexImport.new attributes + @netex_import = Import::Netex.new attributes @netex_import.save! unless @netex_import.referential diff --git a/app/controllers/api/v1/netex_imports_controller.rb b/app/controllers/api/v1/netex_imports_controller.rb index d86c1fcd8..2654fa088 100644 --- a/app/controllers/api/v1/netex_imports_controller.rb +++ b/app/controllers/api/v1/netex_imports_controller.rb @@ -34,7 +34,7 @@ module Api attributes = attributes.merge referential_id: @new_referential.id - @netex_import = NetexImport.new attributes + @netex_import = Import::Netex.new attributes @netex_import.save! unless @netex_import.referential diff --git a/app/controllers/concerns/iev_interfaces.rb b/app/controllers/concerns/iev_interfaces.rb new file mode 100644 index 000000000..aa4d3fe6a --- /dev/null +++ b/app/controllers/concerns/iev_interfaces.rb @@ -0,0 +1,69 @@ +module IevInterfaces + extend ActiveSupport::Concern + + included do + before_action only: [:index] { set_date_time_params("started_at", DateTime) } + before_action :ransack_status_params, only: [:index] + respond_to :html + belongs_to :workbench + end + + def show + show! do + instance_variable_set "@#{collection_name.singularize}", resource.decorate(context: { + workbench: @workbench + }) + end + end + + def index + index! do |format| + format.html { + if collection.out_of_bounds? + redirect_to params.merge(:page => 1) + end + collection = decorate_collection(collection) + } + end + end + + protected + + def collection + scope = parent.send(collection_name).where(parent_id: nil) + if index_model.name.demodulize != "Base" + scope = scope.where(type: index_model.name) + end + + scope = self.ransack_period_range(scope: scope, error_message: t("#{collection_name}.filters.error_period_filter"), query: :where_started_at_in) + + @q = scope.search(params[:q]) + + unless instance_variable_get "@#{collection_name}" + coll = if sort_column && sort_direction + @q.result(distinct: true).order(sort_column + ' ' + sort_direction).paginate(page: params[:page], per_page: 10) + else + @q.result(distinct: true).order(:name).paginate(page: params[:page], per_page: 10) + end + instance_variable_set "@#{collection_name}", decorate_collection(coll) + end + instance_variable_get "@#{collection_name}" + end + + private + def ransack_status_params + if params[:q] + return params[:q].delete(:status_eq_any) if params[:q][:status_eq_any].empty? || ( (resource_class.status.values & params[:q][:status_eq_any]).length >= 4 ) + params[:q][:status_eq_any].push("new", "running") if params[:q][:status_eq_any].include?("pending") + params[:q][:status_eq_any].push("aborted", "canceled") if params[:q][:status_eq_any].include?("failed") + end + end + + def sort_column + parent.imports.column_names.include?(params[:sort]) ? params[:sort] : 'created_at' + end + + def sort_direction + %w[asc desc].include?(params[:direction]) ? params[:direction] : 'desc' + end +end diff --git a/app/controllers/export_tasks_controller.rb b/app/controllers/export_tasks_controller.rb deleted file mode 100644 index b889c1882..000000000 --- a/app/controllers/export_tasks_controller.rb +++ /dev/null @@ -1,87 +0,0 @@ -class ExportTasksController < ChouetteController - include ReferentialSupport - defaults :resource_class => ExportTask - - respond_to :html, :only => [:new, :create] - respond_to :js, :only => [:new, :create] - belongs_to :referential - - def new - @available_exports = available_exports - begin - new! - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t(error.locale_for_error) - redirect_to referential_path(@referential) - end - end - - def create - @available_exports = available_exports - begin - create! do |success, failure| - success.html { redirect_to referential_exports_path(@referential) } - end - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t(error.locale_for_error) - redirect_to referential_path(@referential) - end - end - - def references - references_type = params[:filter].pluralize - references = @referential.send(references_type).where("name ilike ?", "%#{params[:q]}%").select("id, name") - respond_to do |format| - format.json do - render :json => references.collect { |child| { :id => child.id, :name => child.name } } - end - end - end - - protected - - def available_exports - export_task_parameters = params[:export_task] - if export_task_parameters.present? - @available_exports = [ - export_task_parameters[:data_format] == "neptune" ? build_resource : NeptuneExport.new(:referential_id => @referential.id ), - export_task_parameters[:data_format] == "netex" ? build_resource : NetexExport.new(:referential_id => @referential.id ), - export_task_parameters[:data_format] == "gtfs" ? build_resource : GtfsExport.new(:referential_id => @referential.id ), - export_task_parameters[:data_format] == "hub" ? build_resource : HubExport.new(:referential_id => @referential.id ), - export_task_parameters[:data_format] == "kml" ? build_resource : KmlExport.new(:referential_id => @referential.id ) - ] - else - @available_exports = [ - NeptuneExport.new(:referential_id => @referential.id ), - NetexExport.new(:referential_id => @referential.id ), - GtfsExport.new(:referential_id => @referential.id ), - HubExport.new(:referential_id => @referential.id ), - KmlExport.new(:referential_id => @referential.id ) - ] - end - end - - def build_resource - @export_task ||= if params[:export_task].present? - export_task_parameters = params[:export_task] - case export_task_parameters[:data_format] - when "neptune" - NeptuneExport.new(export_task_parameters) - when "netex" - NetexExport.new(export_task_parameters) - when "gtfs" - GtfsExport.new(export_task_parameters) - when "hub" - HubExport.new(export_task_parameters) - when "kml" - KmlExport.new(export_task_parameters) - end - else - NeptuneExport.new - end - - end - -end diff --git a/app/controllers/exports_controller.rb b/app/controllers/exports_controller.rb index ccc163e34..a5282a514 100644 --- a/app/controllers/exports_controller.rb +++ b/app/controllers/exports_controller.rb @@ -1,74 +1,50 @@ -require 'will_paginate/array' -require 'open-uri' - class ExportsController < ChouetteController - include ReferentialSupport - defaults :resource_class => Export - - respond_to :html, :only => [:show, :index, :destroy, :exported_file] - respond_to :js, :only => [:index] - belongs_to :referential - - def index - begin - index! - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t(error.locale_for_error) - redirect_to referential_path(@referential) + include PolicyChecker + include RansackDateFilter + include IevInterfaces + skip_before_action :authenticate_user!, only: [:upload] + defaults resource_class: Export::Base, collection_name: 'exports', instance_name: 'export' + + def upload + if params[:token] == resource.token_upload + resource.file = params[:file] + resource.save! + redirect_to [resource.workbench, resource] + else + user_not_authorized end end - def show - begin - show! - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t(error.locale_for_error) - redirect_to referential_path(@referential) - end - end + private - def destroy - begin - destroy! - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t(error.locale_for_error) - redirect_to referential_path(@referential) - end + def index_model + Export::Base end - def exported_file - # WARNING : files under 10kb in size get treated as StringIO by OpenUri - # http://stackoverflow.com/questions/10496874/why-does-openuri-treat-files-under-10kb-in-size-as-stringio - OpenURI::Buffer.send :remove_const, 'StringMax' if OpenURI::Buffer.const_defined?('StringMax') - OpenURI::Buffer.const_set 'StringMax', 0 - begin - send_file open(resource.file_path), { :type => "application/#{resource.filename_extension}", :disposition => "attachment", :filename => resource.filename } - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t(error.locale_for_error) - redirect_to referential_path(@referential) + def build_resource + Export::Base.force_load_descendants if Rails.env.development? + @export ||= Export::Base.new(*resource_params) do |export| + export.workbench = parent + export.creator = current_user.name end + @export end - protected - - def export_service - ExportService.new(@referential) - end - - def resource - @export ||= export_service.find( params[:id] ) - @line_items = @export.report.line_items - if @line_items.size > 500 - @line_items = @line_items.paginate(page: params[:page], per_page: 20) + def export_params + permitted_keys = %i(name type referential_id) + export_class = params[:export] && params[:export][:type] && params[:export][:type].safe_constantize + if export_class + permitted_keys += export_class.options.keys end - @export + params.require(:export).permit(permitted_keys) end - def collection - @exports ||= export_service.all.sort_by{ |export| export.created_at }.reverse.paginate(:page => params[:page]) + def decorate_collection(exports) + ExportDecorator.decorate( + exports, + context: { + workbench: @workbench + } + ) end end diff --git a/app/controllers/import_messages_controller.rb b/app/controllers/import_messages_controller.rb index 4f8fe7a25..e9a071177 100644 --- a/app/controllers/import_messages_controller.rb +++ b/app/controllers/import_messages_controller.rb @@ -1,15 +1,15 @@ class ImportMessagesController < ChouetteController - defaults resource_class: ImportMessage, collection_name: 'import_messages', instance_name: 'import_message' + defaults resource_class: Import::Message, collection_name: 'import_messages', instance_name: 'import_message' respond_to :csv - belongs_to :import, :parent_class => Import do - belongs_to :import_resource, :parent_class => ImportResource + belongs_to :import, :parent_class => Import::Base do + belongs_to :import_resource, :parent_class => Import::Resource end def index index! do |format| format.csv { - send_data ImportMessageExport.new(:import_messages => @import_messages).to_csv(:col_sep => "\;", :quote_char=>'"', force_quotes: true) , :filename => "import_errors_#{@import_resource.name.gsub('.xml', '')}_#{Date.today.to_s}.csv" + send_data Import::MessageExport.new(:import_messages => @import_messages).to_csv(:col_sep => "\;", :quote_char=>'"', force_quotes: true) , :filename => "import_errors_#{@import_resource.name.gsub('.xml', '')}_#{Date.today.to_s}.csv" } end end @@ -20,7 +20,7 @@ class ImportMessagesController < ChouetteController end def parent - @import_resource ||= ImportResource.find(params[:import_resource_id]) + @import_resource ||= Import::Resource.find(params[:import_resource_id]) end end diff --git a/app/controllers/import_resources_controller.rb b/app/controllers/import_resources_controller.rb index ea78394a1..1535fd171 100644 --- a/app/controllers/import_resources_controller.rb +++ b/app/controllers/import_resources_controller.rb @@ -1,7 +1,7 @@ class ImportResourcesController < ChouetteController - defaults resource_class: ImportResource, collection_name: 'import_resources', instance_name: 'import_resource' + defaults resource_class: Import::Resource, collection_name: 'import_resources', instance_name: 'import_resource' respond_to :html - belongs_to :import + belongs_to :import, :parent_class => Import::Base def index index! do |format| diff --git a/app/controllers/import_tasks_controller.rb b/app/controllers/import_tasks_controller.rb deleted file mode 100644 index 1a349087d..000000000 --- a/app/controllers/import_tasks_controller.rb +++ /dev/null @@ -1,69 +0,0 @@ -class ImportTasksController < ChouetteController - include ReferentialSupport - defaults :resource_class => ImportTask - - respond_to :html, :only => [:new, :create] - respond_to :js, :only => [:new, :create] - belongs_to :referential - - def new - @available_imports = available_imports - begin - new! - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t('iev.exception.default') - redirect_to referential_path(@referential) - end - end - - def create - @available_imports = available_imports - begin - create! do |success, failure| - success.html { redirect_to referential_imports_path(@referential) } - end - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t('iev.exception.default') - redirect_to referential_path(@referential) - end - end - - protected - - def available_imports - import_task_parameters = params[:import_task] - - if import_task_parameters.present? - @available_imports = [ - import_task_parameters[:data_format] == "neptune" ? build_resource : NeptuneImport.new(:referential_id => @referential.id ), - import_task_parameters[:data_format] == "netex" ? build_resource : NetexImport.new(:referential_id => @referential.id ), - import_task_parameters[:data_format] == "gtfs" ? build_resource : GtfsImport.new(:referential_id => @referential.id ) - ] - else - @available_imports = [ - NeptuneImport.new(:referential_id => @referential.id ), - NetexImport.new(:referential_id => @referential.id ), - GtfsImport.new(:referential_id => @referential.id ) - ] - end - end - - def build_resource - @import_task ||= if params[:import_task].present? - import_task_parameters = params[:import_task] - case import_task_parameters[:data_format] - when "neptune" - NeptuneImport.new(import_task_parameters) - when "netex" - @import_task = NetexImport.new(import_task_parameters) - when "gtfs" - @import_task = GtfsImport.new(import_task_parameters) - end - else - @import_task = NeptuneImport.new - end - end - -end diff --git a/app/controllers/imports_controller.rb b/app/controllers/imports_controller.rb index 7a999d657..8d7a723a0 100644 --- a/app/controllers/imports_controller.rb +++ b/app/controllers/imports_controller.rb @@ -1,31 +1,9 @@ class ImportsController < ChouetteController include PolicyChecker include RansackDateFilter - before_action only: [:index] { set_date_time_params("started_at", DateTime) } + include IevInterfaces skip_before_action :authenticate_user!, only: [:download] - defaults resource_class: Import, collection_name: 'imports', instance_name: 'import' - before_action :ransack_status_params, only: [:index] - respond_to :html - belongs_to :workbench - - def show - show! do - @import = @import.decorate(context: { - workbench: @workbench - }) - end - end - - def index - index! do |format| - format.html { - if collection.out_of_bounds? - redirect_to params.merge(:page => 1) - end - @imports = decorate_imports(@imports) - } - end - end + defaults resource_class: Import::Base, collection_name: 'imports', instance_name: 'import' def download if params[:token] == resource.token_download @@ -35,33 +13,14 @@ class ImportsController < ChouetteController end end - protected - def collection - scope = parent.imports.where(type: "WorkbenchImport") - - scope = self.ransack_period_range(scope: scope, error_message: t('imports.filters.error_period_filter'), query: :where_started_at_in) - - @q = scope.search(params[:q]) - - if sort_column && sort_direction - @imports ||= @q.result(distinct: true).order(sort_column + ' ' + sort_direction).paginate(page: params[:page], per_page: 10) - else - @imports ||= @q.result(distinct: true).order(:name).paginate(page: params[:page], per_page: 10) - end - end - private - def ransack_status_params - if params[:q] - return params[:q].delete(:status_eq_any) if params[:q][:status_eq_any].empty? || ( (Import.status.values & params[:q][:status_eq_any]).length >= 4 ) - params[:q][:status_eq_any].push("new", "running") if params[:q][:status_eq_any].include?("pending") - params[:q][:status_eq_any].push("aborted", "canceled") if params[:q][:status_eq_any].include?("failed") - end + def index_model + Import::Workbench end - + def build_resource - @import ||= WorkbenchImport.new(*resource_params) do |import| + @import ||= Import::Workbench.new(*resource_params) do |import| import.workbench = parent import.creator = current_user.name end @@ -76,14 +35,7 @@ class ImportsController < ChouetteController ) end - def sort_column - parent.imports.column_names.include?(params[:sort]) ? params[:sort] : 'created_at' - end - def sort_direction - %w[asc desc].include?(params[:direction]) ? params[:direction] : 'desc' - end - - def decorate_imports(imports) + def decorate_collection(imports) ImportDecorator.decorate( imports, context: { diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index e38a92982..152da4fa2 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -5,7 +5,7 @@ class StatusesController < ChouetteController status = { referentials_blocked: Referential.blocked.count, - imports_blocked: Import.blocked.count, + imports_blocked: Import::Base.blocked.count, compliance_check_sets_blocked: ComplianceCheckSet.blocked.count } status[:status] = global_status status diff --git a/app/decorators/export_decorator.rb b/app/decorators/export_decorator.rb new file mode 100644 index 000000000..26682f383 --- /dev/null +++ b/app/decorators/export_decorator.rb @@ -0,0 +1,21 @@ +class ExportDecorator < AF83::Decorator + decorates Export::Base + + set_scope { context[:workbench] } + + define_instance_method :export_status_css_class do + cls ='' + cls = 'overheaded-success' if object.status == 'successful' + cls = 'overheaded-warning' if object.status == 'warning' + cls = 'overheaded-danger' if %w[failed aborted canceled].include? object.status + cls + end + + create_action_link do |l| + l.content t('exports.actions.new') + end + + with_instance_decorator do |instance_decorator| + instance_decorator.show_action_link + end +end diff --git a/app/decorators/import_decorator.rb b/app/decorators/import_decorator.rb index 1964365ae..a20dfbc42 100644 --- a/app/decorators/import_decorator.rb +++ b/app/decorators/import_decorator.rb @@ -1,5 +1,5 @@ class ImportDecorator < AF83::Decorator - decorates Import + decorates Import::Base set_scope { context[:workbench] } diff --git a/app/decorators/stop_point_decorator.rb b/app/decorators/stop_point_decorator.rb index e777e2b56..4ff5bce9c 100644 --- a/app/decorators/stop_point_decorator.rb +++ b/app/decorators/stop_point_decorator.rb @@ -10,26 +10,5 @@ class StopPointDecorator < AF83::Decorator ) end end - - instance_decorator.edit_action_link do |l| - l.content h.t('stop_points.actions.edit') - l.href do - h.edit_stop_area_referential_stop_area_path( - object.stop_area.stop_area_referential, - object.stop_area - ) - end - end - - instance_decorator.destroy_action_link do |l| - l.content h.destroy_link_content('stop_points.actions.destroy') - l.href do - h.referential_stop_area_path( - object.referential, - object.stop_area - ) - end - l.data confirm: h.t('stop_points.actions.destroy_confirm') - end end end diff --git a/app/helpers/compliance_check_resources_helper.rb b/app/helpers/compliance_check_resources_helper.rb index 95cabed88..19b152e8d 100644 --- a/app/helpers/compliance_check_resources_helper.rb +++ b/app/helpers/compliance_check_resources_helper.rb @@ -4,8 +4,8 @@ module ComplianceCheckResourcesHelper def compliance_check_resource_status(status) cls = '' cls = 'success' if status == 'OK' - cls = 'warning' if status == 'WARNING' - cls = 'danger' if %w[ERROR IGNORED].include? status + cls = 'warning' if %w[WARNING IGNORED].include? status + cls = 'danger' if status == 'ERROR' content_tag :span, '', class: "fa fa-circle text-#{cls}" end diff --git a/app/helpers/exports_helper.rb b/app/helpers/exports_helper.rb index 8ac494cfc..4e92c7e38 100644 --- a/app/helpers/exports_helper.rb +++ b/app/helpers/exports_helper.rb @@ -1,6 +1,25 @@ # -*- coding: utf-8 -*- module ExportsHelper - + def export_status status + import_status status + end + + def export_option_input form, export, attr, option_def, type + opts = { required: option_def[:required], input_html: {value: export.try(attr) || option_def[:default_value]}, as: option_def[:type], selected: export.try(attr) || option_def[:default_value]} + opts[:collection] = option_def[:collection] if option_def.has_key?(:collection) + opts[:collection] = export.instance_exec(&option_def[:collection]) if option_def[:collection].is_a?(Proc) + opts[:label] = t "activerecord.attributes.export.#{type.name.demodulize.underscore}.#{attr}" + form.input attr, opts + end + + def export_message_content message + if message.message_key == "full_text" + message.message_attributes["text"] + else + t([message.class.name.underscore.gsub('/', '_').pluralize, message.message_key].join('.'), message.message_attributes.symbolize_keys) + end + end + def fields_for_export_task_format(form) begin render :partial => export_partial_name(form), :locals => { :form => form } @@ -8,7 +27,7 @@ module ExportsHelper "" end end - + def export_partial_name(form) "fields_#{form.object.format.underscore}_export" end @@ -22,7 +41,7 @@ module ExportsHelper end.join.html_safe end end - + def compliance_icon( export_task) return nil unless export_task.compliance_check_task export_task.compliance_check_task.tap do |cct| @@ -33,5 +52,5 @@ module ExportsHelper end end end - + end diff --git a/app/helpers/stop_areas_helper.rb b/app/helpers/stop_areas_helper.rb index 05ae042f5..fa99f1b4c 100644 --- a/app/helpers/stop_areas_helper.rb +++ b/app/helpers/stop_areas_helper.rb @@ -54,4 +54,20 @@ module StopAreasHelper end end + def stop_area_registration_number_title stop_area + if stop_area&.stop_area_referential&.registration_number_format.present? + return t("formtastic.titles.stop_area.registration_number_format", registration_number_format: stop_area.stop_area_referential.registration_number_format) + end + t "formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.registration_number" + end + + def stop_area_registration_number_is_required stop_area + val = format_restriction_for_locales(@referential) == '.hub' + val ||= stop_area&.stop_area_referential&.registration_number_format.present? + val + end + + def stop_area_registration_number_value stop_area + stop_area&.registration_number || stop_area&.stop_area_referential&.generate_registration_number + end end diff --git a/app/helpers/table_builder_helper/column.rb b/app/helpers/table_builder_helper/column.rb index ff6f2f36f..907707670 100644 --- a/app/helpers/table_builder_helper/column.rb +++ b/app/helpers/table_builder_helper/column.rb @@ -28,7 +28,9 @@ module TableBuilderHelper return @name if @name.present? # Transform `Chouette::Line` into "line" - model_key = model.to_s.demodulize.underscore + model_key = model.to_s.underscore + model_key.gsub! 'chouette/', '' + model_key.gsub! '/', '.' I18n.t("activerecord.attributes.#{model_key}.#{@key}") end diff --git a/app/javascript/helpers/routes_map.coffee b/app/javascript/helpers/routes_map.coffee index 42377cd6e..de6196372 100644 --- a/app/javascript/helpers/routes_map.coffee +++ b/app/javascript/helpers/routes_map.coffee @@ -29,10 +29,10 @@ RoutesLayersControl = (routes, routes_map) -> element.className = 'ol-unselectable ol-routes-layers hidden' Object.keys(routes).forEach (id)=> route = routes[id] - route.active = true + route.active = false label = document.createElement('a') label.title = route.name - label.className = 'active' + label.className = '' label.innerHTML = route.name element.appendChild label label.addEventListener "click", => @@ -140,13 +140,13 @@ class RoutesMap @map.addLayer vectorEdgesLayer @map.addLayer vectorLnsLayer - lineStyle: (active=true)-> + lineStyle: (active=false)-> new ol.style.Style stroke: new ol.style.Stroke color: '#007fbb' width: if active then 3 else 0 - edgeStyles: (active=true)-> + edgeStyles: (active=false)-> new ol.style.Style image: new ol.style.Circle radius: 5 @@ -157,7 +157,7 @@ class RoutesMap color: '#007fbb' width: if active then 3 else 0 - defaultStyles: (active=true)-> + defaultStyles: (active=false)-> new ol.style.Style image: new ol.style.Circle radius: 4 diff --git a/app/javascript/packs/exports/new.js b/app/javascript/packs/exports/new.js new file mode 100644 index 000000000..ffe702cdb --- /dev/null +++ b/app/javascript/packs/exports/new.js @@ -0,0 +1,3 @@ +import MasterSlave from "../../helpers/master_slave" + +new MasterSlave("form") diff --git a/app/javascript/vehicle_journeys/actions/index.js b/app/javascript/vehicle_journeys/actions/index.js index 5fb88f024..e00e9b1b0 100644 --- a/app/javascript/vehicle_journeys/actions/index.js +++ b/app/javascript/vehicle_journeys/actions/index.js @@ -212,14 +212,15 @@ const actions = { toggleArrivals : () => ({ type: 'TOGGLE_ARRIVALS', }), - updateTime : (val, subIndex, index, timeUnit, isDeparture, isArrivalsToggled) => ({ + updateTime : (val, subIndex, index, timeUnit, isDeparture, isArrivalsToggled, enforceConsistency=false) => ({ type: 'UPDATE_TIME', val, subIndex, index, timeUnit, isDeparture, - isArrivalsToggled + isArrivalsToggled, + enforceConsistency }), resetStateFilters: () => ({ type: 'RESET_FILTERS' @@ -486,10 +487,10 @@ const actions = { vjas.delta = delta return vjas }, - adjustSchedule: (action, schedule) => { + adjustSchedule: (action, schedule, enforceConsistency=false) => { // we enforce that the departure time remains after the arrival time actions.getDelta(schedule) - if(schedule.delta < 0){ + if(enforceConsistency && schedule.delta < 0){ if(action.isDeparture){ schedule.arrival_time = schedule.departure_time } diff --git a/app/javascript/vehicle_journeys/components/VehicleJourney.js b/app/javascript/vehicle_journeys/components/VehicleJourney.js index d605614c7..a667bf8aa 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourney.js @@ -67,6 +67,10 @@ export default class VehicleJourney extends Component { return found } + hasPurchaseWindow(purchase_windows, window) { + return this.hasTimeTable(purchase_windows, window) + } + isDisabled(bool1, bool2) { return (bool1 || bool2) } @@ -75,6 +79,8 @@ export default class VehicleJourney extends Component { this.previousCity = undefined let detailed_calendars = this.hasFeature('detailed_calendars') && !this.disabled let detailed_calendars_shown = $('.detailed-timetables-bt').hasClass('active') + let detailed_purchase_windows = this.hasFeature('detailed_purchase_windows') && !this.disabled + let detailed_purchase_windows_shown = $('.detailed-purchase-windows-bt').hasClass('active') let {time_tables, purchase_windows} = this.props.value return ( @@ -97,6 +103,13 @@ export default class VehicleJourney extends Component { {purchase_windows.length > 3 && <span className='vj_tt'> + {purchase_windows.length - 3}</span>} </div> } + { detailed_purchase_windows && + <div className={"detailed-purchase-windows" + (detailed_purchase_windows_shown ? "" : " hidden")}> + {this.props.allPurchaseWindows.map((w, i) => + <div key={i} className={(this.hasPurchaseWindow(purchase_windows, w) ? "active" : "inactive")}></div> + )} + </div> + } <div> {time_tables.slice(0,3).map((tt, i)=> <span key={i} className='vj_tt'>{this.timeTableURL(tt)}</span> @@ -125,6 +138,8 @@ export default class VehicleJourney extends Component { )} </div> } + + </div> {this.props.value.vehicle_journey_at_stops.map((vj, i) => <div key={i} className='td text-center'> @@ -140,6 +155,7 @@ export default class VehicleJourney extends Component { disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} readOnly={!this.props.editMode && !vj.dummy} onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', false, false)}} + onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', false, false, true)}} value={vj.arrival_time['hour']} /> <span>:</span> @@ -151,6 +167,7 @@ export default class VehicleJourney extends Component { disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} readOnly={!this.props.editMode && !vj.dummy} onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'minute', false, false)}} + onBlur={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'minute', false, false, true)}} value={vj.arrival_time['minute']} /> </span> @@ -171,6 +188,7 @@ export default class VehicleJourney extends Component { disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} readOnly={!this.props.editMode && !vj.dummy} onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', true, this.props.filters.toggleArrivals)}} + onBlur={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', true, this.props.filters.toggleArrivals, true)}} value={vj.departure_time['hour']} /> <span>:</span> @@ -182,13 +200,11 @@ export default class VehicleJourney extends Component { disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} readOnly={!this.props.editMode && !vj.dummy} onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, "minute", true, this.props.filters.toggleArrivals)}} + onBlur={(e) => {this.props.onUpdateTime(e, i, this.props.index, "minute", true, this.props.filters.toggleArrivals, true)}} value={vj.departure_time['minute']} /> - </span> - </div> - {vj.errors && <div className="errors"> - {vj.errors} - </div>} + </span> + </div> </div> </div> )} @@ -205,4 +221,5 @@ VehicleJourney.propTypes = { onSelectVehicleJourney: PropTypes.func.isRequired, vehicleJourneys: PropTypes.object.isRequired, allTimeTables: PropTypes.array.isRequired, + allPurchaseWindows: PropTypes.array.isRequired, } diff --git a/app/javascript/vehicle_journeys/components/VehicleJourneys.js b/app/javascript/vehicle_journeys/components/VehicleJourneys.js index 384afba17..ae38a21af 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourneys.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourneys.js @@ -12,6 +12,7 @@ export default class VehicleJourneys extends Component { this.stopPoints(), this.props.filters.features ) + this.togglePurchaseWindows = this.togglePurchaseWindows.bind(this) this.toggleTimetables = this.toggleTimetables.bind(this) } @@ -49,23 +50,42 @@ export default class VehicleJourneys extends Component { return this.headerManager.showHeader(object_id) } - allTimeTables() { - if(this._allTimeTables){ - return this._allTimeTables - } - let keys = [] + getPurchaseWindowsAndTimeTables(){ + let timetables_keys = [] + let windows_keys = [] this._allTimeTables = [] + this._allPurchaseWindows = [] this.vehicleJourneysList().map((vj, index) => { vj.time_tables.map((tt, _) => { - if(keys.indexOf(tt.id) < 0){ - keys.push(tt.id) + if(timetables_keys.indexOf(tt.id) < 0){ + timetables_keys.push(tt.id) this._allTimeTables.push(tt) } }) + vj.purchase_windows.map((tt, _) => { + if(windows_keys.indexOf(tt.id) < 0){ + windows_keys.push(tt.id) + this._allPurchaseWindows.push(tt) + } + }) }) + } + + allTimeTables() { + if(! this._allTimeTables){ + this.getPurchaseWindowsAndTimeTables() + } return this._allTimeTables } + allPurchaseWindows() { + if(!this._allPurchaseWindows){ + this.getPurchaseWindowsAndTimeTables() + } + + return this._allPurchaseWindows + } + toggleTimetables(e) { $('.table-2entries .detailed-timetables').toggleClass('hidden') $('.table-2entries .detailed-timetables-bt').toggleClass('active') @@ -74,6 +94,14 @@ export default class VehicleJourneys extends Component { false } + togglePurchaseWindows(e) { + $('.table-2entries .detailed-purchase-windows').toggleClass('hidden') + $('.table-2entries .detailed-purchase-windows-bt').toggleClass('active') + this.componentDidUpdate() + e.preventDefault() + false + } + componentDidUpdate(prevProps, prevState) { if(this.props.status.isFetching == false){ $('.table-2entries').each(function() { @@ -127,10 +155,20 @@ export default class VehicleJourneys extends Component { ) } + purchaseWindowURL(tt) { + let refURL = window.location.pathname.split('/', 3).join('/') + let ttURL = refURL + '/purchase_windows/' + tt.id + return ( + <a href={ttURL} title='Voir le calendrier commercial'><span className='fa fa-calendar' style={{color: (tt.color ? tt.color : '#4B4B4B')}}></span>{tt.name}</a> + ) + } + render() { this.previousBreakpoint = undefined this._allTimeTables = null + this._allPurchaseWindows = null let detailed_calendars = this.hasFeature('detailed_calendars') && !this.isReturn() && (this.allTimeTables().length > 0) + let detailed_purchase_windows = this.hasFeature('detailed_purchase_windows') && !this.isReturn() && (this.allPurchaseWindows().length > 0) if(this.props.status.isFetching == true) { return ( <div className="isLoading" style={{marginTop: 80, marginBottom: 80}}> @@ -170,17 +208,39 @@ export default class VehicleJourneys extends Component { <div>{I18n.attribute_name("vehicle_journey", "name")}</div> <div>{I18n.attribute_name("vehicle_journey", "journey_pattern_id")}</div> <div>{I18n.model_name("company")}</div> - { this.hasFeature('purchase_windows') && <div>{I18n.model_name("purchase_window", "plural": true)}</div> } + { this.hasFeature('purchase_windows') && + <div> + { detailed_purchase_windows && + <a href='#' onClick={this.togglePurchaseWindows} className='detailed-purchase-windows-bt'> + <span className='fa fa-angle-up'></span> + {I18n.model_name("purchase_window", {"plural": true})} + </a> + } + { !detailed_purchase_windows && I18n.model_name("purchase_window", {"plural": true})} + </div> + } + { detailed_purchase_windows && + <div className="detailed-purchase-windows hidden"> + {this.allPurchaseWindows().map((tt, i)=> + <div key={i}> + <p> + {this.purchaseWindowURL(tt)} + </p> + <p>{tt.bounding_dates.join(' > ')}</p> + </div> + )} + </div> + } <div> { detailed_calendars && <a href='#' onClick={this.toggleTimetables} className='detailed-timetables-bt'> <span className='fa fa-angle-up'></span> - {I18n.model_name("time_table", "plural": true)} + {I18n.model_name("time_table", {"plural": true})} </a> } - { !detailed_calendars && I18n.model_name("time_table", "plural": true)} + { !detailed_calendars && I18n.model_name("time_table", {"plural": true})} </div> - { !this.isReturn() && + { detailed_calendars && <div className="detailed-timetables hidden"> {this.allTimeTables().map((tt, i)=> <div key={i}> @@ -217,6 +277,7 @@ export default class VehicleJourneys extends Component { vehicleJourneys={this} disabled={this.isReturn()} allTimeTables={this.allTimeTables()} + allPurchaseWindows={this.allPurchaseWindows()} /> )} </div> diff --git a/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js b/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js index 76d1c3a78..81d603fd4 100644 --- a/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js +++ b/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js @@ -20,8 +20,8 @@ const mapDispatchToProps = (dispatch) => { dispatch(actions.fetchingApi()) actions.fetchVehicleJourneys(dispatch, undefined, undefined, filters.queryString, routeUrl) }, - onUpdateTime: (e, subIndex, index, timeUnit, isDeparture, isArrivalsToggled) => { - dispatch(actions.updateTime(e.target.value, subIndex, index, timeUnit, isDeparture, isArrivalsToggled)) + onUpdateTime: (e, subIndex, index, timeUnit, isDeparture, isArrivalsToggled, enforceConsistency=false) => { + dispatch(actions.updateTime(e.target.value, subIndex, index, timeUnit, isDeparture, isArrivalsToggled, enforceConsistency)) }, onSelectVehicleJourney: (index) => { dispatch(actions.selectVehicleJourney(index)) diff --git a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js index e7f68761e..6524f8d6f 100644 --- a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js +++ b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js @@ -153,11 +153,11 @@ const vehicleJourney= (state = {}, action, keep) => { newSchedule.departure_time[action.timeUnit] = actions.pad(action.val, action.timeUnit) if(!action.isArrivalsToggled) newSchedule.arrival_time[action.timeUnit] = newSchedule.departure_time[action.timeUnit] - newSchedule = actions.adjustSchedule(action, newSchedule) + newSchedule = actions.adjustSchedule(action, newSchedule, action.enforceConsistency) return _.assign({}, state.vehicle_journey_at_stops[action.subIndex], {arrival_time: newSchedule.arrival_time, departure_time: newSchedule.departure_time, delta: newSchedule.delta}) }else{ newSchedule.arrival_time[action.timeUnit] = actions.pad(action.val, action.timeUnit) - newSchedule = actions.adjustSchedule(action, newSchedule) + newSchedule = actions.adjustSchedule(action, newSchedule, action.enforceConsistency) return _.assign({}, state.vehicle_journey_at_stops[action.subIndex], {arrival_time: newSchedule.arrival_time, departure_time: newSchedule.departure_time, delta: newSchedule.delta}) } }else{ diff --git a/app/models/chouette/purchase_window.rb b/app/models/chouette/purchase_window.rb index 157390a21..4c8014780 100644 --- a/app/models/chouette/purchase_window.rb +++ b/app/models/chouette/purchase_window.rb @@ -39,6 +39,13 @@ module Chouette self.slice(*attrs).values + ranges_attrs end + def bounding_dates + [ + date_ranges.map(&:first).min, + date_ranges.map(&:last).max, + ] + end + # def checksum_attributes # end diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb index f58f97eee..0a27b2f39 100644 --- a/app/models/chouette/stop_area.rb +++ b/app/models/chouette/stop_area.rb @@ -46,6 +46,11 @@ module Chouette validates_numericality_of :waiting_time, greater_than_or_equal_to: 0, only_integer: true, if: :waiting_time validate :parent_area_type_must_be_greater validate :area_type_of_right_kind + validate :registration_number_is_set + + before_validation do + self.registration_number ||= self.stop_area_referential.generate_registration_number + end def self.nullable_attributes [:registration_number, :street_name, :country_code, :fare_code, @@ -73,6 +78,22 @@ module Chouette end end + def registration_number_is_set + return unless self.stop_area_referential.registration_number_format.present? + if self.stop_area_referential.stop_areas.where(registration_number: self.registration_number).\ + where.not(id: self.id).exists? + errors.add(:registration_number, I18n.t('stop_areas.errors.registration_number.already_taken')) + end + + unless self.registration_number.present? + errors.add(:registration_number, I18n.t('stop_areas.errors.registration_number.cannot_be_empty')) + end + + unless self.stop_area_referential.validates_registration_number(self.registration_number) + errors.add(:registration_number, I18n.t('stop_areas.errors.registration_number.invalid')) + end + end + after_update :clean_invalid_access_links before_save :coordinates_to_lat_lng diff --git a/app/models/chouette/vehicle_journey_at_stop.rb b/app/models/chouette/vehicle_journey_at_stop.rb index d875442ee..3f5bd5abf 100644 --- a/app/models/chouette/vehicle_journey_at_stop.rb +++ b/app/models/chouette/vehicle_journey_at_stop.rb @@ -4,8 +4,10 @@ module Chouette include Chouette::ForAlightingEnumerations include ChecksumSupport - DAY_OFFSET_MAX = 1 + DAY_OFFSET_MAX = 2 + @@day_offset_max = DAY_OFFSET_MAX + mattr_accessor :day_offset_max belongs_to :stop_point belongs_to :vehicle_journey @@ -40,7 +42,7 @@ module Chouette I18n.t( 'vehicle_journey_at_stops.errors.day_offset_must_not_exceed_max', short_id: vehicle_journey&.get_objectid&.short_id, - max: DAY_OFFSET_MAX + 1 + max: Chouette::VehicleJourneyAtStop.day_offset_max + 1 ) ) end @@ -51,7 +53,7 @@ module Chouette I18n.t( 'vehicle_journey_at_stops.errors.day_offset_must_not_exceed_max', short_id: vehicle_journey&.get_objectid&.short_id, - max: DAY_OFFSET_MAX + 1 + max: Chouette::VehicleJourneyAtStop.day_offset_max + 1 ) ) end @@ -62,7 +64,7 @@ module Chouette # nil offsets. Handle these gracefully by forcing them to a 0 offset. offset ||= 0 - offset < 0 || offset > DAY_OFFSET_MAX + offset < 0 || offset > Chouette::VehicleJourneyAtStop.day_offset_max end def checksum_attributes @@ -82,12 +84,12 @@ module Chouette format_time arrival_time.utc end - def departure_local_time - local_time departure_time + def departure_local_time offset=nil + local_time departure_time, offset end - def arrival_local_time - local_time arrival_time + def arrival_local_time offset=nil + local_time arrival_time, offset end def departure_local @@ -98,12 +100,15 @@ module Chouette format_time arrival_local_time end + def time_zone_offset + return 0 unless stop_point&.stop_area&.time_zone.present? + ActiveSupport::TimeZone[stop_point.stop_area.time_zone]&.utc_offset || 0 + end + private - def local_time time - return unless time - return time unless stop_point&.stop_area&.time_zone.present? - return time unless ActiveSupport::TimeZone[stop_point.stop_area.time_zone].present? - time + ActiveSupport::TimeZone[stop_point.stop_area.time_zone].utc_offset + def local_time time, offset=nil + return nil unless time + time + (offset || time_zone_offset) end def format_time time diff --git a/app/models/chouette/vehicle_journey_at_stops_day_offset.rb b/app/models/chouette/vehicle_journey_at_stops_day_offset.rb index 7497cd72c..cfa0e8bfc 100644 --- a/app/models/chouette/vehicle_journey_at_stops_day_offset.rb +++ b/app/models/chouette/vehicle_journey_at_stops_day_offset.rb @@ -4,31 +4,32 @@ module Chouette @at_stops = at_stops end - def calculate! - arrival_offset = 0 - departure_offset = 0 + def time_from_fake_date fake_date + fake_date - fake_date.to_date.to_time + end + def calculate! + offset = 0 + tz_offset = @at_stops.first&.time_zone_offset @at_stops.inject(nil) do |prior_stop, stop| next stop if prior_stop.nil? # we only compare time of the day, not actual times - stop_arrival_time = stop.arrival_time - stop.arrival_time.to_date.to_time - stop_departure_time = stop.departure_time - stop.departure_time.to_date.to_time - prior_stop_arrival_time = prior_stop.arrival_time - prior_stop.arrival_time.to_date.to_time - prior_stop_departure_time = prior_stop.departure_time - prior_stop.departure_time.to_date.to_time - - if stop_arrival_time < prior_stop_departure_time || - stop_arrival_time < prior_stop_arrival_time - arrival_offset += 1 + stop_arrival_time = time_from_fake_date stop.arrival_local_time(tz_offset) + stop_departure_time = time_from_fake_date stop.departure_local_time(tz_offset) + prior_stop_departure_time = time_from_fake_date prior_stop.departure_local_time(tz_offset) + + if stop_arrival_time < prior_stop_departure_time + offset += 1 end - if stop_departure_time < stop_arrival_time || - stop_departure_time < prior_stop_departure_time - departure_offset += 1 + stop.arrival_day_offset = offset + + if stop_departure_time < stop_arrival_time + offset += 1 end - stop.arrival_day_offset = arrival_offset - stop.departure_day_offset = departure_offset + stop.departure_day_offset = offset stop end diff --git a/app/models/compliance_check_resource.rb b/app/models/compliance_check_resource.rb index 2989bf3cf..777254aaf 100644 --- a/app/models/compliance_check_resource.rb +++ b/app/models/compliance_check_resource.rb @@ -3,7 +3,7 @@ class ComplianceCheckResource < ActiveRecord::Base belongs_to :compliance_check_set - enumerize :status, in: %i(OK ERROR WARNING IGNORED), scope: true + enumerize :status, in: %i(OK ERROR WARNING IGNORED) validates_presence_of :compliance_check_set end diff --git a/app/models/compliance_check_set.rb b/app/models/compliance_check_set.rb index 289fc134f..49d324c53 100644 --- a/app/models/compliance_check_set.rb +++ b/app/models/compliance_check_set.rb @@ -49,39 +49,25 @@ class ComplianceCheckSet < ActiveRecord::Base end def update_status - statuses = compliance_check_resources.map do |resource| - case resource.status - when 'ERROR' - return update(status: 'failed') - when 'WARNING' - return update(status: 'warning') - else - resource.status + status = + if compliance_check_resources.where(status: 'ERROR').count > 0 + 'failed' + elsif compliance_check_resources.where(status: ["WARNING", "IGNORED"]).count > 0 + 'warning' + elsif compliance_check_resources.where(status: "OK").count == compliance_check_resources.count + 'successful' end - end - if statuses_ok_or_ignored?(statuses) - return update(status: 'successful') + attributes = { + status: status + } + + if self.class.finished_statuses.include?(status) + attributes[:ended_at] = Time.now end - true + update attributes end - private - - def statuses_ok_or_ignored?(statuses) - uniform_statuses = statuses.uniq - - ( - # All statuses OK - uniform_statuses.length == 1 && - uniform_statuses.first == 'OK' - ) || - ( - # Statuses OK or IGNORED - uniform_statuses.length == 2 && - uniform_statuses.include?('OK') && - uniform_statuses.include?('IGNORED') - ) - end + end diff --git a/app/models/concerns/iev_interfaces/message.rb b/app/models/concerns/iev_interfaces/message.rb new file mode 100644 index 000000000..ad41e98b7 --- /dev/null +++ b/app/models/concerns/iev_interfaces/message.rb @@ -0,0 +1,9 @@ +module IevInterfaces::Message + extend ActiveSupport::Concern + + included do + extend Enumerize + enumerize :criticity, in: %i(info warning error) + validates :criticity, presence: true + end +end diff --git a/app/models/concerns/iev_interfaces/resource.rb b/app/models/concerns/iev_interfaces/resource.rb new file mode 100644 index 000000000..7f8c3eefd --- /dev/null +++ b/app/models/concerns/iev_interfaces/resource.rb @@ -0,0 +1,9 @@ +module IevInterfaces::Resource + extend ActiveSupport::Concern + + included do + extend Enumerize + enumerize :status, in: %i(OK ERROR WARNING IGNORED), scope: true + validates_presence_of :name, :resource_type, :reference + end +end diff --git a/app/models/concerns/iev_interfaces/task.rb b/app/models/concerns/iev_interfaces/task.rb new file mode 100644 index 000000000..fdd976f39 --- /dev/null +++ b/app/models/concerns/iev_interfaces/task.rb @@ -0,0 +1,119 @@ +module IevInterfaces::Task + extend ActiveSupport::Concern + + included do + belongs_to :parent, polymorphic: true + belongs_to :workbench, class_name: "::Workbench" + belongs_to :referential + + mount_uploader :file, ImportUploader + + has_many :children, foreign_key: :parent_id, class_name: self.name, dependent: :destroy + + extend Enumerize + enumerize :status, in: %w(new pending successful warning failed running aborted canceled), scope: true, default: :new + + validates :name, presence: true + validates_presence_of :workbench, :creator + + has_many :messages, class_name: messages_class_name, dependent: :destroy, foreign_key: "#{messages_class_name.split('::').first.downcase}_id" + has_many :resources, class_name: resources_class_name, dependent: :destroy, foreign_key: "#{resources_class_name.split('::').first.downcase}_id" + + scope :where_started_at_in, ->(period_range) do + where('started_at BETWEEN :begin AND :end', begin: period_range.begin, end: period_range.end) + end + + scope :blocked, -> { where('created_at < ? AND status = ?', 4.hours.ago, 'running') } + + before_create :initialize_fields + after_save :notify_parent + end + + module ClassMethods + def launched_statuses + %w(new pending) + end + + def failed_statuses + %w(failed aborted canceled) + end + + def finished_statuses + %w(successful failed warning aborted canceled) + end + + def abort_old + where( + 'created_at < ? AND status NOT IN (?)', + 4.hours.ago, + finished_statuses + ).update_all(status: 'aborted') + end + end + + def notify_parent + return unless parent.present? + return unless status_changed? + parent.child_change + t = Time.now + self.notified_parent_at = t + self.class.where(id: self.id).update_all notified_parent_at: t + end + + def children_succeedeed + children.with_status(:successful, :warning).count + end + + def update_status + status = + if children.where(status: self.class.failed_statuses).count > 0 + 'failed' + elsif children.where(status: "warning").count > 0 + 'warning' + elsif children.where(status: "successful").count == children.count + 'successful' + else + 'running' + end + + attributes = { + current_step: children.count, + status: status + } + + if self.class.finished_statuses.include?(status) + attributes[:ended_at] = Time.now + end + + update attributes + end + + def child_change + return if self.class.finished_statuses.include?(status) + update_status + end + + def call_iev_callback + return if self.class.finished_statuses.include?(status) + threaded_call_boiv_iev + end + + private + + def threaded_call_boiv_iev + Thread.new(&method(:call_boiv_iev)) + end + + def call_boiv_iev + Rails.logger.error("Begin IEV call for import") + Net::HTTP.get iev_callback_url + Rails.logger.error("End IEV call for import") + rescue Exception => e + logger.error "IEV server error : #{e.message}" + logger.error e.backtrace.inspect + end + + private + def initialize_fields + end +end diff --git a/app/models/export.rb b/app/models/export.rb deleted file mode 100644 index 8c38d6684..000000000 --- a/app/models/export.rb +++ /dev/null @@ -1,53 +0,0 @@ -class Export - include JobConcern - - def initialize( response ) - @datas = response - end - - def report? - links["action_report"].present? - end - - def report - Rails.cache.fetch("#{cache_key}/action_report", expires_in: cache_expiration) do - report_path = links["action_report"] - if report_path - response = Ievkit.get(report_path) - ExportReport.new(response) - else - nil - end - end - end - - def destroy - delete_path = links["delete"] - cancel_path = links["cancel"] - - if delete_path - Ievkit.delete(delete_path) - elsif cancel_path - Ievkit.delete(cancel_path) - else - nil - end - end - - def file_path? - links["data"].present? - end - - def file_path - links["data"] - end - - def filename - File.basename(file_path) if file_path - end - - def filename_extension - File.extname(filename).gsub(".", "") if filename - end - -end diff --git a/app/models/export/base.rb b/app/models/export/base.rb new file mode 100644 index 000000000..6085e0ffb --- /dev/null +++ b/app/models/export/base.rb @@ -0,0 +1,89 @@ +class Export::Base < ActiveRecord::Base + self.table_name = "exports" + + validates :type, presence: true + + def self.messages_class_name + "Export::Message" + end + + def self.resources_class_name + "Export::Resource" + end + + def self.human_name + I18n.t("export.#{self.name.demodulize.underscore}") + end + + def self.file_extension_whitelist + %w(zip csv json) + end + + if Rails.env.development? + def self.force_load_descendants + path = Rails.root.join 'app/models/export' + Dir.chdir path do + Dir['**/*.rb'].each do |src| + next if src =~ /^base/ + klass_name = "Export::#{src[0..-4].camelize}" + Rails.logger.info "Loading #{klass_name}" + begin + klass_name.constantize + rescue => e + Rails.logger.info "Failed: #{e.message}" + nil + end + end + end + end + end + + def self.option name, opts={} + store_accessor :options, name + if !!opts[:required] + validates name, presence: true + end + @options ||= {} + @options[name] = opts + end + + def self.options + @options ||= {} + end + + include IevInterfaces::Task + + def self.model_name + ActiveModel::Name.new Export::Base, Export::Base, "Export" + end + + def self.user_visible_descendants + descendants.select &:user_visible? + end + + def self.user_visible? + true + end + + def visible_options + options.select{|k, v| ! k.match /^_/} + end + + def display_option_value option_name, context + option = self.class.options[option_name.to_sym] + val = self.options[option_name.to_s] + if option[:display] + context.instance_exec(val, &option[:display]) + else + val + end + end + + private + + def initialize_fields + super + self.token_upload = SecureRandom.urlsafe_base64 + end + +end diff --git a/app/models/export/message.rb b/app/models/export/message.rb new file mode 100644 index 000000000..b64b524ac --- /dev/null +++ b/app/models/export/message.rb @@ -0,0 +1,8 @@ +class Export::Message < ActiveRecord::Base + self.table_name = :export_messages + + include IevInterfaces::Message + + belongs_to :export, class_name: Export::Base + belongs_to :resource, class_name: Export::Resource +end diff --git a/app/models/export/netex.rb b/app/models/export/netex.rb new file mode 100644 index 000000000..069ec2209 --- /dev/null +++ b/app/models/export/netex.rb @@ -0,0 +1,22 @@ +class Export::Netex < Export::Base + after_commit :call_iev_callback, on: :create + option :export_type, collection: %w(line full), required: true + option :duration, type: :integer, default_value: 90, required: true + option :line_code + + private + + def iev_callback_url + URI("#{Rails.configuration.iev_url}/boiv_iev/referentials/exporter/new?id=#{id}") + end + + # def self.user_visible? + # false + # end + + def destroy_non_ready_referential + if referential && !referential.ready + referential.destroy + end + end +end diff --git a/app/models/export/referential_companies.rb b/app/models/export/referential_companies.rb new file mode 100644 index 000000000..0b6187060 --- /dev/null +++ b/app/models/export/referential_companies.rb @@ -0,0 +1,92 @@ +class Export::ReferentialCompanies < Export::Base + option :referential_id, + type: :select, + collection: ->(){workbench.referentials.all}, + required: true, + display: ->(val){r = Referential.find(val); link_to(r.name, [r])} + + after_create :call_exporter_async + + def referential + Referential.find referential_id + end + + def call_exporter_async + SimpleExportWorker.perform_async(id) + end + + def exporter + SimpleExporter.define :referential_companies do |config| + config.separator = ";" + config.encoding = 'ISO-8859-1' + config.add_column :name + config.add_column :registration_number + end + + @exporter ||= begin + if options[:_exporter_id] + exporter = SimpleExporter.find options[:exporter_id] + else + exporter = SimpleExporter.create configuration_name: :referential_companies + options[:_exporter_id] = exporter.id + end + exporter + end + end + + def call_exporter + tmp = Tempfile.new ["referential_companies", ".csv"] + referential.switch + exporter.configure do |config| + config.collection = referential.companies.order(:name) + end + exporter.filepath = tmp.path + exporter.export + set_status_from_exporter + convert_exporter_journal_to_messages + self.file = tmp + self.save! + end + + def set_status_from_exporter + if exporter.status == :error + self.status = :failed + else + if exporter.status == :success + self.status = :successful + else + self.status = :warning + end + end + end + + def convert_exporter_journal_to_messages + self.messages.destroy_all + exporter.journal.each do |journal_item| + journal_item.symbolize_keys! + vals = {} + + if journal_item[:kind].to_s == "warning" + vals[:criticity] = :warning + elsif journal_item[:kind].to_s == "error" + vals[:criticity] = :error + else + vals[:criticity] = :info + if journal_item[:event].to_s == "success" + vals[:message_key] = :success + end + end + vals[:resource_attributes] = journal_item[:row] + + if journal_item[:message].present? + vals[:message_key] = :full_text + vals[:message_attributes] = { + text: journal_item[:message] + } + end + vals[:message_attributes] ||= {} + vals[:message_attributes][:line] = journal_item[:line] + self.messages.build vals + end + end +end diff --git a/app/models/export/resource.rb b/app/models/export/resource.rb new file mode 100644 index 000000000..98f103be4 --- /dev/null +++ b/app/models/export/resource.rb @@ -0,0 +1,8 @@ +class Export::Resource < ActiveRecord::Base + self.table_name = :export_resources + + include IevInterfaces::Resource + + belongs_to :export, class_name: Export::Base + has_many :messages, class_name: "ExportMessage", foreign_key: :resource_id +end diff --git a/app/models/export/workgroup.rb b/app/models/export/workgroup.rb new file mode 100644 index 000000000..3430596c7 --- /dev/null +++ b/app/models/export/workgroup.rb @@ -0,0 +1,9 @@ +class Export::Workgroup < Export::Base + after_commit :launch_worker, :on => :create + + option :duration, required: true, type: :integer, default_value: 90 + + def launch_worker + WorkgroupExportWorker.perform_async(id) + end +end diff --git a/app/models/export_log_message.rb b/app/models/export_log_message.rb deleted file mode 100644 index 4bb9d3cc7..000000000 --- a/app/models/export_log_message.rb +++ /dev/null @@ -1,42 +0,0 @@ -class ExportLogMessage < ActiveRecord::Base - belongs_to :export - - acts_as_list :scope => :export - - validates_presence_of :key - validates_inclusion_of :severity, :in => %w{info warning error ok uncheck fatal} - - def arguments=(arguments) - write_attribute :arguments, (arguments.to_json if arguments.present?) - end - - def arguments - @decoded_arguments ||= - begin - if (stored_arguments = raw_attributes).present? - ActiveSupport::JSON.decode stored_arguments - else - {} - end - end - end - - def raw_attributes - read_attribute(:arguments) - end - - before_validation :define_default_attributes, :on => :create - def define_default_attributes - self.severity ||= "info" - end - - def full_message - last_key=key.rpartition("|").last - begin - I18n.translate last_key, arguments.symbolize_keys.merge(:scope => "export_log_messages.messages").merge(:default => :undefined).merge(:key => last_key) - rescue => e - Rails.logger.error "missing arguments for message "+last_key - I18n.translate "WRONG_DATA",{"0"=>last_key}.symbolize_keys.merge(:scope => "export_log_messages.messages").merge(:default => :undefined).merge(:key => "WRONG_DATA") - end - end -end diff --git a/app/models/export_report.rb b/app/models/export_report.rb deleted file mode 100644 index 3c0788106..000000000 --- a/app/models/export_report.rb +++ /dev/null @@ -1,8 +0,0 @@ -class ExportReport - #include ReportConcern - - def initialize( response ) - @datas = response.action_report - end - -end diff --git a/app/models/export_service.rb b/app/models/export_service.rb deleted file mode 100644 index 2dbe0d7b3..000000000 --- a/app/models/export_service.rb +++ /dev/null @@ -1,23 +0,0 @@ -class ExportService - - attr_reader :referential - - def initialize(referential) - @referential = referential - end - - # Find an export whith his id - def find(id) - Export.new( Ievkit.scheduled_job(referential.slug, id, { :action => "exporter" }) ) - end - - # Find all exports - def all - [].tap do |jobs| - Ievkit.jobs(referential.slug, { :action => "exporter" }).each do |job| - jobs << Export.new( job ) - end - end - end - -end diff --git a/app/models/export_task.rb b/app/models/export_task.rb deleted file mode 100644 index f02cb914e..000000000 --- a/app/models/export_task.rb +++ /dev/null @@ -1,119 +0,0 @@ -class ExportTask - extend Enumerize - extend ActiveModel::Naming - extend ActiveModel::Translation - extend ActiveModel::Callbacks - include ActiveModel::Validations - include ActiveModel::Conversion - - attr_accessor :start_date, :end_date - - define_model_callbacks :initialize, only: :after - - enumerize :data_format, in: %w( neptune netex gtfs hub kml ) - attr_accessor :referential_id, :user_id, :user_name, :references_type, :data_format, :name, :projection_type, :reference_ids - - validates_presence_of :referential_id - validates_presence_of :user_id - validates_presence_of :user_name - validates_presence_of :name - validates_presence_of :data_format - - validate :period_validation - - after_initialize :init_period - - def initialize( params = {} ) - run_callbacks :initialize do - params.each {|k,v| send("#{k}=",v)} - end - end - - def period_validation - st_date = start_date.is_a?(String) ? Date.parse(start_date) : start_date - ed_date = end_date.is_a?(String) ? Date.parse(end_date) : end_date - - unless Chouette::TimeTable.start_validity_period.nil? || st_date.nil? - tt_st_date = Chouette::TimeTable.start_validity_period - errors.add(:start_date, ExportTask.human_attribute_name("start_date_greater_than" , {:tt_st_date => tt_st_date})) unless tt_st_date <= st_date - end - unless st_date.nil? || ed_date.nil? - errors.add(:end_date, ExportTask.human_attribute_name("end_date_greater_than_start_date")) unless st_date <= ed_date - end - unless ed_date.nil? || Chouette::TimeTable.end_validity_period.nil? - tt_ed_date = Chouette::TimeTable.end_validity_period - errors.add(:end_date, ExportTask.human_attribute_name("end_date_less_than", {:tt_ed_date => tt_ed_date})) unless ed_date <= tt_ed_date - end - end - - def init_period - unless Chouette::TimeTable.start_validity_period.nil? - if start_date.nil? - self.start_date = Chouette::TimeTable.start_validity_period - end - if end_date.nil? - self.end_date = Chouette::TimeTable.end_validity_period - end - end - end - - def referential - Referential.find(referential_id) - end - - def organisation - referential.organisation - end - - def save - if self.valid? - # Call Iev Server - begin - Ievkit.create_job( referential.slug, "exporter", data_format, { - :file1 => params_io, - } ) - rescue Exception => exception - raise exception - end - true - else - false - end - end - - def self.data_formats - self.data_format.values - end - - def self.references_types - self.references_type.values - end - - def params - {}.tap do |h| - h["parameters"] = action_params - end - end - - def action_params - {} - end - - def params_io - file = StringIO.new( params.to_json ) - Faraday::UploadIO.new(file, "application/json", "parameters.json") - end - - def self.optional_attributes(references_type) - [] - end - - def optional_attributes - self.class.optional_attributes(references_type.to_s) - end - - def optional_attribute?(attribute) - optional_attributes.include? attribute.to_sym - end - -end diff --git a/app/models/gtfs_export.rb b/app/models/gtfs_export.rb deleted file mode 100644 index d0b9fc4f9..000000000 --- a/app/models/gtfs_export.rb +++ /dev/null @@ -1,47 +0,0 @@ -class GtfsExport < ExportTask - - validates_presence_of :time_zone, unless: Proc.new { |e| e.optional_attribute? :time_zone } - attr_accessor :object_id_prefix, :time_zone - - enumerize :references_type, in: %w( network line company group_of_line stop_area ) - - after_initialize :init_params - - def init_params - if time_zone.nil? - self.time_zone = "Paris" - end - end - - def real_time_zone - ActiveSupport::TimeZone.find_tzinfo(time_zone).name - end - - def action_params - { - "gtfs-export" => { - "name" => name, - "references_type" => references_type, - "reference_ids" => reference_ids, - "user_name" => user_name, - "organisation_name" => organisation.name, - "referential_name" => referential.name, - "time_zone" => real_time_zone, - "object_id_prefix" => object_id_prefix, - "start_date" => start_date, - "end_date" => end_date - } - } - end - - def self.optional_attributes(references_type) - super.tap do |optional_attributes| - optional_attributes.push :time_zone, :start_date, :end_date if references_type == "stop_area" - end - end - - def data_format - "gtfs" - end - -end diff --git a/app/models/hub_export.rb b/app/models/hub_export.rb deleted file mode 100644 index 802600692..000000000 --- a/app/models/hub_export.rb +++ /dev/null @@ -1,24 +0,0 @@ -class HubExport < ExportTask - - enumerize :references_type, in: %w( network line company group_of_line ) - - def action_params - { - "hub-export" => { - "name" => name, - "references_type" => references_type, - "reference_ids" => reference_ids, - "user_name" => user_name, - "organisation_name" => organisation.name, - "referential_name" => referential.name, - "start_date" => start_date, - "end_date" => end_date - } - } - end - - def data_format - "hub" - end - -end diff --git a/app/models/import.rb b/app/models/import.rb deleted file mode 100644 index 29aadcd56..000000000 --- a/app/models/import.rb +++ /dev/null @@ -1,103 +0,0 @@ -class Import < ActiveRecord::Base - mount_uploader :file, ImportUploader - belongs_to :workbench - belongs_to :referential - - belongs_to :parent, polymorphic: true - - has_many :messages, class_name: "ImportMessage", dependent: :destroy - has_many :resources, class_name: "ImportResource", dependent: :destroy - has_many :children, foreign_key: :parent_id, class_name: "Import", dependent: :destroy - - scope :where_started_at_in, ->(period_range) do - where('started_at BETWEEN :begin AND :end', begin: period_range.begin, end: period_range.end) - end - - scope :blocked, -> { where('created_at < ? AND status = ?', 4.hours.ago, 'running') } - - extend Enumerize - enumerize :status, in: %w(new pending successful warning failed running aborted canceled), scope: true, default: :new - - validates :name, presence: true - validates :file, presence: true - validates_presence_of :workbench, :creator - - before_create :initialize_fields - - def self.model_name - ActiveModel::Name.new Import, Import, "Import" - end - - def children_succeedeed - children.with_status(:successful, :warning).count - end - - def self.launched_statuses - %w(new pending) - end - - def self.failed_statuses - %w(failed aborted canceled) - end - - def self.finished_statuses - %w(successful failed warning aborted canceled) - end - - def self.abort_old - where( - 'created_at < ? AND status NOT IN (?)', - 4.hours.ago, - finished_statuses - ).update_all(status: 'aborted') - end - - def notify_parent - parent.child_change - update(notified_parent_at: DateTime.now) - end - - def child_change - return if self.class.finished_statuses.include?(status) - - update_status - update_referentials - end - - def update_status - status = - if children.where(status: self.class.failed_statuses).count > 0 - 'failed' - elsif children.where(status: "warning").count > 0 - 'warning' - elsif children.where(status: "successful").count == children.count - 'successful' - end - - attributes = { - current_step: children.count, - status: status - } - - if self.class.finished_statuses.include?(status) - attributes[:ended_at] = Time.now - end - - update attributes - end - - def update_referentials - return unless self.class.finished_statuses.include?(status) - - children.each do |import| - import.referential.update(ready: true) if import.referential - end - end - - private - - def initialize_fields - self.token_download = SecureRandom.urlsafe_base64 - end - -end diff --git a/app/models/import/base.rb b/app/models/import/base.rb new file mode 100644 index 000000000..1dd9c4195 --- /dev/null +++ b/app/models/import/base.rb @@ -0,0 +1,45 @@ +class Import::Base < ActiveRecord::Base + self.table_name = "imports" + validates :file, presence: true + + def self.messages_class_name + "Import::Message" + end + + def self.resources_class_name + "Import::Resource" + end + + def self.file_extension_whitelist + %w(zip) + end + + include IevInterfaces::Task + + def self.model_name + ActiveModel::Name.new Import::Base, Import::Base, "Import" + end + + def child_change + return if self.class.finished_statuses.include?(status) + + super + update_referentials + end + + def update_referentials + return unless self.class.finished_statuses.include?(status) + + children.each do |import| + import.referential.update(ready: true) if import.referential + end + end + + private + + def initialize_fields + super + self.token_download = SecureRandom.urlsafe_base64 + end + +end diff --git a/app/models/gtfs_import.rb b/app/models/import/gtfs.rb index d09ca4cb3..03cf49e60 100644 --- a/app/models/gtfs_import.rb +++ b/app/models/import/gtfs.rb @@ -1,5 +1,5 @@ require 'net/http' -class GtfsImport < Import +class Import::Gtfs < Import::Base before_destroy :destroy_non_ready_referential after_commit :launch_java_import, on: :create diff --git a/app/models/import/message.rb b/app/models/import/message.rb new file mode 100644 index 000000000..c1900a718 --- /dev/null +++ b/app/models/import/message.rb @@ -0,0 +1,8 @@ +class Import::Message < ActiveRecord::Base + self.table_name = :import_messages + + include IevInterfaces::Message + + belongs_to :import, class_name: Import::Base + belongs_to :resource, class_name: Import::Resource +end diff --git a/app/models/import_message_export.rb b/app/models/import/message_export.rb index 991eb0f61..7a7add08c 100644 --- a/app/models/import_message_export.rb +++ b/app/models/import/message_export.rb @@ -2,7 +2,7 @@ require "csv" require "zip" -class ImportMessageExport +class Import::MessageExport include ActiveModel::Validations include ActiveModel::Conversion extend ActiveModel::Naming @@ -29,7 +29,7 @@ class ImportMessageExport csv_string = CSV.generate(options) do |csv| csv << column_names import_messages.each do |import_message| - csv << [import_message.criticity, import_message.message_key, I18n.t("import_messages.#{import_message.message_key}", import_message.message_attributes.deep_symbolize_keys), *import_message.resource_attributes.values_at("filename", "line_number", "column_number") ] + csv << [import_message.criticity, import_message.message_attributes['test_id'], I18n.t("import_messages.#{import_message.message_key}", import_message.message_attributes.deep_symbolize_keys), *import_message.resource_attributes.values_at("filename", "line_number", "column_number") ] end end # We add a BOM to indicate we use UTF-8 diff --git a/app/models/import/netex.rb b/app/models/import/netex.rb new file mode 100644 index 000000000..2b0982229 --- /dev/null +++ b/app/models/import/netex.rb @@ -0,0 +1,24 @@ +require 'net/http' +class Import::Netex < Import::Base + before_destroy :destroy_non_ready_referential + + after_commit :call_iev_callback, on: :create + + before_save def abort_unless_referential + self.status = 'aborted' unless referential + end + + validates_presence_of :parent + + private + + def iev_callback_url + URI("#{Rails.configuration.iev_url}/boiv_iev/referentials/importer/new?id=#{id}") + end + + def destroy_non_ready_referential + if referential && !referential.ready + referential.destroy + end + end +end diff --git a/app/models/import/resource.rb b/app/models/import/resource.rb new file mode 100644 index 000000000..5bd011039 --- /dev/null +++ b/app/models/import/resource.rb @@ -0,0 +1,8 @@ +class Import::Resource < ActiveRecord::Base + self.table_name = :import_resources + + include IevInterfaces::Resource + + belongs_to :import, class_name: Import::Base + has_many :messages, class_name: "Import::Message", foreign_key: :resource_id +end diff --git a/app/models/workbench_import.rb b/app/models/import/workbench.rb index 27f53a44f..f6e15cb89 100644 --- a/app/models/workbench_import.rb +++ b/app/models/import/workbench.rb @@ -1,4 +1,4 @@ -class WorkbenchImport < Import +class Import::Workbench < Import::Base after_commit :launch_worker, :on => :create def launch_worker diff --git a/app/models/import_message.rb b/app/models/import_message.rb deleted file mode 100644 index de70c35d1..000000000 --- a/app/models/import_message.rb +++ /dev/null @@ -1,8 +0,0 @@ -class ImportMessage < ActiveRecord::Base - extend Enumerize - belongs_to :import - belongs_to :resource, class_name: ImportResource - enumerize :criticity, in: %i(info warning error) - - validates :criticity, presence: true -end diff --git a/app/models/import_report.rb b/app/models/import_report.rb deleted file mode 100644 index ba13f0118..000000000 --- a/app/models/import_report.rb +++ /dev/null @@ -1,8 +0,0 @@ -class ImportReport - #include ReportConcern - - def initialize( response ) - @datas = response.action_report - end - -end diff --git a/app/models/import_resource.rb b/app/models/import_resource.rb deleted file mode 100644 index 55e752e74..000000000 --- a/app/models/import_resource.rb +++ /dev/null @@ -1,11 +0,0 @@ -class ImportResource < ActiveRecord::Base - belongs_to :import - - extend Enumerize - enumerize :status, in: %i(OK ERROR WARNING IGNORED), scope: true - - validates_presence_of :name, :resource_type, :reference - - has_many :messages, class_name: "ImportMessage", foreign_key: :resource_id - -end diff --git a/app/models/import_service.rb b/app/models/import_service.rb deleted file mode 100644 index 2e3c1012b..000000000 --- a/app/models/import_service.rb +++ /dev/null @@ -1,23 +0,0 @@ -class ImportService - - attr_reader :referential - - def initialize( referential ) - @referential = referential - end - - # Find an import whith his id - def find(id) - Import.new( Ievkit.scheduled_job(referential.slug, id, { :action => "importer" }) ) - end - - # Find all imports - def all - [].tap do |jobs| - Ievkit.jobs(referential.slug, { :action => "importer" }).each do |job| - jobs << Import.new( job ) - end - end - end - -end diff --git a/app/models/import_task.rb b/app/models/import_task.rb deleted file mode 100644 index 7dfa2c644..000000000 --- a/app/models/import_task.rb +++ /dev/null @@ -1,141 +0,0 @@ -require "zip" - -class ImportTask - extend Enumerize - extend ActiveModel::Naming - extend ActiveModel::Translation - include ActiveModel::Validations - include ActiveModel::Conversion - - # TODO : Move in configuration - @@root = "#{Rails.root}/tmp/imports" - cattr_accessor :root - - enumerize :data_format, in: %w( neptune netex gtfs ) - attr_accessor :referential_id, :user_id, :user_name, :data_format, :resources, :name, :no_save - - validates_presence_of :referential_id - validates_presence_of :resources - validates_presence_of :user_id - validates_presence_of :user_name - validates_presence_of :name - - validate :validate_file_size, :validate_file_content - - def initialize( params = {} ) - params.each {|k,v| send("#{k}=",v)} - end - - def referential - Referential.find(referential_id) - end - - def organisation - referential.organisation - end - - def save - if valid? - # Save resources - save_resources - - # Call Iev Server - begin - Ievkit.create_job(referential.slug, "importer", data_format, { - :file1 => params_io, - :file2 => transport_data_io - } - - ) - - # Delete resources - delete_resources - rescue Exception => exception - # If iev server has an error must delete resources before - delete_resources - - raise exception - end - true - else - false - end - end - - def params - {}.tap do |h| - h["parameters"] = {} - end - end - - def self.data_formats - self.data_format.values - end - - def params_io - file = StringIO.new( params.to_json ) - Faraday::UploadIO.new(file, "application/json", "parameters.json") - end - - def transport_data_io - file = File.new(saved_resources_path, "r") - if file_extname == ".zip" - Faraday::UploadIO.new(file, "application/zip", original_filename ) - elsif file_extname == ".xml" - Faraday::UploadIO.new(file, "application/xml", original_filename ) - end - end - - def save_resources - FileUtils.mkdir_p root - FileUtils.cp resources.path, saved_resources_path - end - - def delete_resources - FileUtils.rm saved_resources_path if File.exists? saved_resources_path - end - - def original_filename - resources.original_filename - end - - def file_extname - File.extname(original_filename) if original_filename - end - - def saved_resources_path - @saved_resources_path ||= "#{root}/#{Time.now.to_i}#{file_extname}" - end - - @@maximum_file_size = 80.megabytes - cattr_accessor :maximum_file_size - - def validate_file_size - return unless resources.present? and resources.path.present? and File.exists? resources.path - - if File.size(resources.path) > maximum_file_size - message = I18n.t("activemodel.errors.models.import_task.attributes.resources.maximum_file_size", file_size: ActionController::Base.helpers.number_to_human_size(File.size(resources.path)), maximum_file_size: ActionController::Base.helpers.number_to_human_size(maximum_file_size)) - errors.add(:resources, message) - end - end - - @@valid_mime_types = { - neptune: %w{application/zip application/xml}, - netex: %w{application/zip}, - gtfs: %w{application/zip text/plain} - } - cattr_accessor :valid_mime_types - - def validate_file_content - return unless resources.present? and resources.path.present? and File.exists? resources.path - - mime_type = (File.open(resources.path) { |f| MimeMagic.by_magic f }).try :type - expected_mime_types = valid_mime_types[data_format.to_sym] - - unless expected_mime_types.include? mime_type - message = I18n.t("activemodel.errors.models.import_task.attributes.resources.invalid_mime_type", mime_type: mime_type) - errors.add(:resources, message) - end - end - -end diff --git a/app/models/kml_export.rb b/app/models/kml_export.rb deleted file mode 100644 index f6db77172..000000000 --- a/app/models/kml_export.rb +++ /dev/null @@ -1,24 +0,0 @@ -class KmlExport < ExportTask - - enumerize :references_type, in: %w( network line company group_of_line ) - - def action_params - { - "kml-export" => { - "name" => name, - "references_type" => references_type, - "reference_ids" => reference_ids, - "user_name" => user_name, - "organisation_name" => organisation.name, - "referential_name" => referential.name, - "start_date" => start_date, - "end_date" => end_date - } - } - end - - def data_format - "kml" - end - -end diff --git a/app/models/neptune_export.rb b/app/models/neptune_export.rb deleted file mode 100644 index f25db69c0..000000000 --- a/app/models/neptune_export.rb +++ /dev/null @@ -1,27 +0,0 @@ -class NeptuneExport < ExportTask - - attr_accessor :extensions, :export_type - enumerize :references_type, in: %w( network line company group_of_line ) - - def action_params - { - "neptune-export" => { - "name" => name, - "references_type" => references_type, - "reference_ids" => reference_ids, - "user_name" => user_name, - "organisation_name" => organisation.name, - "referential_name" => referential.name, - "projection_type" => projection_type || "", - "add_extension" => extensions, - "start_date" => start_date, - "end_date" => end_date - } - } - end - - def data_format - "neptune" - end - -end diff --git a/app/models/neptune_import.rb b/app/models/neptune_import.rb deleted file mode 100644 index 1f0bdaa13..000000000 --- a/app/models/neptune_import.rb +++ /dev/null @@ -1,19 +0,0 @@ -class NeptuneImport < ImportTask - - def action_params - { - "neptune-import" => { - "no_save" => no_save, - "user_name" => user_name, - "name" => name, - "organisation_name" => organisation.name, - "referential_name" => referential.name, - } - } - end - - def data_format - "neptune" - end - -end diff --git a/app/models/netex_export.rb b/app/models/netex_export.rb deleted file mode 100644 index a4c3e2454..000000000 --- a/app/models/netex_export.rb +++ /dev/null @@ -1,24 +0,0 @@ -class NetexExport < ExportTask - - enumerize :references_type, in: %w( network line company group_of_line ) - - def action_params - { - "netex-export" => { - "name" => name, - "references_type" => references_type, - "reference_ids" => reference_ids, - "user_name" => user_name, - "organisation_name" => organisation.name, - "referential_name" => referential.name, - "start_date" => start_date, - "end_date" => end_date - } - } - end - - def data_format - "netex" - end - -end diff --git a/app/models/netex_import.rb b/app/models/netex_import.rb deleted file mode 100644 index b21af3408..000000000 --- a/app/models/netex_import.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'net/http' -class NetexImport < Import - before_destroy :destroy_non_ready_referential - - after_commit :launch_java_import, on: :create - before_save def abort_unless_referential - self.status = 'aborted' unless referential - end - - validates_presence_of :parent - - def launch_java_import - return if self.class.finished_statuses.include?(status) - threaded_call_boiv_iev - end - - private - - def destroy_non_ready_referential - if referential && !referential.ready - referential.destroy - end - end - - def threaded_call_boiv_iev - Thread.new(&method(:call_boiv_iev)) - end - - def call_boiv_iev - Rails.logger.error("Begin IEV call for import") - Net::HTTP.get(URI("#{Rails.configuration.iev_url}/boiv_iev/referentials/importer/new?id=#{id}")) - Rails.logger.error("End IEV call for import") - rescue Exception => e - logger.error "IEV server error : #{e.message}" - logger.error e.backtrace.inspect - end - -end diff --git a/app/models/simple_exporter.rb b/app/models/simple_exporter.rb index c1ba75d0a..c267b5b8c 100644 --- a/app/models/simple_exporter.rb +++ b/app/models/simple_exporter.rb @@ -16,19 +16,14 @@ class SimpleExporter < SimpleInterface @statuses = "" - if ENV["NO_TRANSACTION"] - process_collection - else - ActiveRecord::Base.transaction do - process_collection - end - end + process_collection + self.status ||= :success rescue SimpleInterface::FailedOperation self.status = :failed ensure @csv&.close - self.save! + task_finished end def collection @@ -46,14 +41,13 @@ class SimpleExporter < SimpleInterface protected def init_env opts @number_of_lines = collection.size - super opts end def process_collection self.configuration.before_actions(:all).each do |action| action.call self end - log "Starting export ...", color: :green - log "Export will be written in #{filepath}", color: :green + log "Starting export ..." + log "Export will be written in #{filepath}" @csv << self.configuration.columns.map(&:name) if collection.is_a?(ActiveRecord::Relation) && collection.model.column_names.include?("id") ids = collection.pluck :id @@ -87,6 +81,7 @@ class SimpleExporter < SimpleInterface if val.nil? && !col.omit_nil? push_in_journal({event: :attribute_not_found, message: "Value missing for: #{[col.scope, col.attribute].flatten.join('.')}", kind: :warning}) self.status ||= :success_with_warnings + @new_status ||= colorize("✓", :orange) end if val.nil? && col.required? @@ -137,6 +132,10 @@ class SimpleExporter < SimpleInterface flatten_hash(v).map do |h_k, h_v| h["#{k}.#{h_k}".to_sym] = h_v end + elsif v.is_a? ActiveRecord::Base + flatten_hash(v.attributes).map do |h_k, h_v| + h["#{k}.#{h_k}".to_sym] = h_v + end else h[k] = v end diff --git a/app/models/simple_importer.rb b/app/models/simple_importer.rb index 6f0b8d7a8..4cfe90cff 100644 --- a/app/models/simple_importer.rb +++ b/app/models/simple_importer.rb @@ -33,10 +33,11 @@ class SimpleImporter < SimpleInterface rescue SimpleInterface::FailedOperation self.status = :failed ensure - self.save! + task_finished end def encode_string s + return if s.nil? s.encode("utf-8").force_encoding("utf-8") end @@ -80,6 +81,7 @@ class SimpleImporter < SimpleInterface if self.configuration.ignore_failures unless @current_record.save @new_status = colorize("x", :red) + self.status = :success_with_errors push_in_journal({message: "errors: #{@current_record.errors.messages}", error: "invalid record", event: :error, kind: :error}) end else diff --git a/app/models/simple_interface.rb b/app/models/simple_interface.rb index 489419482..43c740b57 100644 --- a/app/models/simple_interface.rb +++ b/app/models/simple_interface.rb @@ -1,5 +1,5 @@ class SimpleInterface < ActiveRecord::Base - attr_accessor :configuration + attr_accessor :configuration, :interfaces_group class << self def configuration_class @@ -16,7 +16,7 @@ class SimpleInterface < ActiveRecord::Base def find_configuration name @importers ||= {} configuration = @importers[name.to_sym] - raise "Importer not found: #{name}" unless configuration + raise "#{self.name} not found: #{name}" unless configuration configuration end end @@ -27,14 +27,21 @@ class SimpleInterface < ActiveRecord::Base self.journal ||= [] end + def configuration + @configuration ||= self.class.find_configuration self.configuration_name + end + def init_env opts @verbose = opts.delete :verbose - @errors = [] + @_errors = [] @messages = [] @padding = 1 @current_line = -1 + @number_of_lines ||= 1 @padding = [1, Math.log([@number_of_lines, 1].max, 10).ceil()].max + @output_dir = opts[:output_dir] || Rails.root.join('tmp', self.class.name.tableize) + @start_time = Time.now end def configure @@ -55,6 +62,7 @@ class SimpleInterface < ActiveRecord::Base custom_print "\nFAILED: \n errors: #{msg}\n exception: #{e.message}\n#{e.backtrace.join("\n")}", color: :red unless self.configuration.ignore_failures push_in_journal({message: msg, error: e.message, event: :error, kind: :error}) @new_status = colorize("x", :red) + self.status = :success_with_errors if self.configuration.ignore_failures raise SimpleInterface::FailedRow if opts[:abort_row] else @@ -66,27 +74,66 @@ class SimpleInterface < ActiveRecord::Base def log msg, opts={} msg = msg.to_s msg = colorize msg, opts[:color] if opts[:color] + @start_time ||= Time.now + time = Time.now - @start_time + @messages ||= [] if opts[:append] - @messages[-1] = (@messages[-1] || "") + msg + _time, _msg = @messages.pop || [] + _time ||= time + _msg ||= "" + @messages.push [_time, _msg+msg] + elsif opts[:replace] + @messages.pop + @messages << [time, msg] else - @messages << msg + @messages << [time, msg] + end + print_state true + end + + def output_filepath + @output_filepath ||= File.join @output_dir, "#{self.configuration_name}_#{Time.now.strftime "%y%m%d%H%M"}_out.csv" + end + + def write_output_to_csv + cols = %i(line kind event message error) + if self.journal.size > 0 && self.journal.first[:row].present? + log "Writing output log" + FileUtils.mkdir_p @output_dir + keys = self.journal.first[:row].map(&:first) + CSV.open(output_filepath, "w") do |csv| + csv << cols + keys + self.journal.each do |j| + csv << cols.map{|c| j[c]} + j[:row].map(&:last) + end + end + log "Output written in #{output_filepath}", replace: true end - print_state end protected + def task_finished + log "Saving..." + self.save! + log "Saved", replace: true + write_output_to_csv + log "FINISHED, status: " + log status, color: SimpleInterface.status_color(status), append: true + print_state true + end + def push_in_journal data line = (@current_line || 0) + 1 line += 1 if configuration.headers - @errors ||= [] + @_errors ||= [] self.journal.push data.update(line: line, row: @current_row) if data[:kind] == :error || data[:kind] == :warning - @errors.push data + @_errors.push data end end - def colorize txt, color + def self.colorize txt, color color = { red: "31", green: "32", @@ -95,12 +142,25 @@ class SimpleInterface < ActiveRecord::Base "\e[#{color}m#{txt}\e[0m" end - def print_state + def self.status_color status + color = :green + color = :orange if status.to_s == "success_with_warnings" + color = :red if status.to_s == "success_with_errors" + color = :red if status.to_s == "error" + color + end + + def colorize txt, color + SimpleInterface.colorize txt, color + end + + def print_state force=false return unless @verbose + return if !@last_repaint.nil? && (Time.now - @last_repaint < 0.1) && !force @status_width ||= begin - term_width = %x(tput cols).to_i - term_width - @padding - 10 + @term_width = %x(tput cols).to_i + @term_width - @padding - 10 rescue 100 end @@ -112,12 +172,24 @@ class SimpleInterface < ActiveRecord::Base 50 end + msg = "" + + if @banner.nil? && interfaces_group.present? + @banner = interfaces_group.banner @status_width + @status_height -= @banner.lines.count + 2 + end + + if @banner.present? + msg += @banner + msg += "\n" + "-"*@term_width + "\n" + end + full_status = @statuses || "" full_status = full_status.last(@status_width*10) || "" padding_size = [(@number_of_lines - @current_line - 1), (@status_width - full_status.size/10)].min full_status = "#{full_status}#{"."*[padding_size, 0].max}" - msg = "#{"%#{@padding}d" % (@current_line + 1)}/#{@number_of_lines}: #{full_status}" + msg += "#{"%#{@padding}d" % (@current_line + 1)}/#{@number_of_lines}: #{full_status}" lines_count = [(@status_height / 2) - 3, 1].max @@ -125,15 +197,17 @@ class SimpleInterface < ActiveRecord::Base msg += "\n\n" msg += colorize "=== MESSAGES (#{@messages.count}) ===\n", :green msg += "[...]\n" if @messages.count > lines_count - msg += @messages.last(lines_count).map{|m| m.truncate(@status_width)}.join("\n") + msg += @messages.last(lines_count).map do |m| + "[#{"%.5f" % m[0]}]\t" + m[1].truncate(@status_width - 10) + end.join("\n") msg += "\n"*[lines_count-@messages.count, 0].max end - if @errors.any? + if @_errors.any? msg += "\n\n" - msg += colorize "=== ERRORS (#{@errors.count}) ===\n", :red - msg += "[...]\n" if @errors.count > lines_count - msg += @errors.last(lines_count).map do |j| + msg += colorize "=== ERRORS (#{@_errors.count}) ===\n", :red + msg += "[...]\n" if @_errors.count > lines_count + msg += @_errors.last(lines_count).map do |j| kind = j[:kind] kind = colorize(kind, kind == :error ? :red : :orange) kind = "[#{kind}]" @@ -142,6 +216,7 @@ class SimpleInterface < ActiveRecord::Base end.join("\n") end custom_print msg, clear: true + @last_repaint = Time.now end def custom_print msg, opts={} diff --git a/app/models/simple_interfaces_group.rb b/app/models/simple_interfaces_group.rb new file mode 100644 index 000000000..808be6570 --- /dev/null +++ b/app/models/simple_interfaces_group.rb @@ -0,0 +1,76 @@ +class SimpleInterfacesGroup + attr_accessor :name, :shared_options + + def initialize name + @name = name + @interfaces = [] + @current_step = 0 + end + + def add_interface interface, name, action, opts={} + @interfaces.push({interface: interface, name: name, action: action, opts: opts}) + end + + def run + @interfaces.each do |interface_def| + interface = interface_def[:interface] + interface.interfaces_group = self + interface.send interface_def[:action], interface_def[:opts].reverse_update(shared_options || {}) + return if interface.status == :error + @current_step += 1 + end + + print_summary + end + + def banner width=nil + width ||= @width + width ||= 128 + @width = width + + name = "### #{self.name} ###" + centered_name = " " * ([width - name.size, 0].max / 2) + name + banner = [centered_name, ""] + banner << @interfaces.each_with_index.map do |interface, i| + if interface[:interface].status.present? + SimpleInterface.colorize interface[:name], SimpleInterface.status_color(interface[:interface].status) + elsif i == @current_step + "☕︎ #{interface[:name]}" + else + interface[:name] + end + end.join(' > ') + banner.join("\n") + end + + def print_summary + puts "\e[H\e[2J" + out = [banner] + out << "-" * @width + out << "" + out << SimpleInterface.colorize("=== STATUSES ===", :green) + out << "" + @interfaces.each do |i| + out << "#{i[:name].rjust(@interfaces.map{|i| i[:name].size}.max)}:\t#{SimpleInterface.colorize i[:interface].status, SimpleInterface.status_color(i[:interface].status)}" + end + out << "" + out << SimpleInterface.colorize("=== OUTPUTS ===", :green) + out << "" + @interfaces.each do |i| + if i[:interface].is_a? SimpleExporter + out << "#{i[:name].rjust(@interfaces.map{|i| i[:name].size}.max)}:\t#{i[:interface].filepath}" + end + end + out << "" + out << "" + out << SimpleInterface.colorize("=== DEBUG OUTPUTS ===", :green) + out << "" + @interfaces.each do |i| + out << "#{i[:name].rjust(@interfaces.map{|i| i[:name].size}.max)}:\t#{i[:interface].output_filepath}" + end + out << "" + out << "" + + print out.join("\n") + end +end diff --git a/app/models/simple_json_exporter.rb b/app/models/simple_json_exporter.rb index 8c44c149a..024d75c97 100644 --- a/app/models/simple_json_exporter.rb +++ b/app/models/simple_json_exporter.rb @@ -21,22 +21,18 @@ class SimpleJsonExporter < SimpleExporter @statuses = "" - if ENV["NO_TRANSACTION"] - process_collection - else - ActiveRecord::Base.transaction do - process_collection - end - end + process_collection self.status ||= :success rescue SimpleInterface::FailedOperation self.status = :failed ensure if @file + log "Writing to JSON file..." @file.write @out.to_json + log "JSON file written", replace: true @file.close end - self.save! + task_finished end protected @@ -50,20 +46,22 @@ class SimpleJsonExporter < SimpleExporter def process_collection self.configuration.before_actions(:all).each do |action| action.call self end - log "Starting export ...", color: :green - log "Export will be written in #{filepath}", color: :green + log "Starting export ..." + log "Export will be written in #{filepath}" if collection.is_a?(ActiveRecord::Relation) && collection.model.column_names.include?("id") + log "Using paginated collection", color: :green ids = collection.pluck :id ids.in_groups_of(configuration.batch_size).each do |batch_ids| - collection.where(id: batch_ids).each do |item| + collection.where(id: batch_ids.compact).each do |item| handle_item item end end else + log "Using non-paginated collection", color: :orange collection.each{|item| handle_item item } end - print_state + print_state true end def resolve_node item, node @@ -96,7 +94,7 @@ class SimpleJsonExporter < SimpleExporter map_item_to_rows(item).each_with_index do |item, i| @number_of_lines = number_of_lines + i serialized_item = {} - @current_row = item.attributes + @current_row = item.attributes.symbolize_keys @current_row = @current_row.slice(*configuration.logged_attributes) if configuration.logged_attributes.present? @new_status = nil diff --git a/app/models/stop_area_referential.rb b/app/models/stop_area_referential.rb index 54e895cd0..9e9e02d80 100644 --- a/app/models/stop_area_referential.rb +++ b/app/models/stop_area_referential.rb @@ -1,4 +1,6 @@ class StopAreaReferential < ActiveRecord::Base + validates :registration_number_format, format: { with: /\AX*\z/ } + include ObjectidFormatterSupport has_many :stop_area_referential_memberships has_many :organisations, through: :stop_area_referential_memberships @@ -15,4 +17,30 @@ class StopAreaReferential < ActiveRecord::Base def last_sync stop_area_referential_syncs.last end + + def generate_registration_number + return "" unless registration_number_format.present? + last = self.stop_areas.order("registration_number DESC NULLS LAST").limit(1).first&.registration_number + if self.stop_areas.count == 26**self.registration_number_format.size + raise "NO MORE AVAILABLE VALUES FOR registration_number in referential #{self.name}" + end + + return "A" * self.registration_number_format.size unless last + + if last == "Z" * self.registration_number_format.size + val = "A" * self.registration_number_format.size + while self.stop_areas.where(registration_number: val).exists? + val = val.next + end + val + else + last.next + end + end + + def validates_registration_number value + return false unless value.size == registration_number_format.size + return false unless value =~ /^[A-Z]*$/ + true + end end diff --git a/app/models/workbench.rb b/app/models/workbench.rb index b6f90c7dc..b5f4673bb 100644 --- a/app/models/workbench.rb +++ b/app/models/workbench.rb @@ -13,8 +13,9 @@ class Workbench < ActiveRecord::Base has_many :companies, through: :line_referential has_many :group_of_lines, through: :line_referential has_many :stop_areas, through: :stop_area_referential - has_many :imports - has_many :workbench_imports + has_many :imports, class_name: Import::Base + has_many :exports, class_name: Export::Base + has_many :workbench_imports, class_name: Import::Workbench has_many :compliance_check_sets has_many :compliance_control_sets has_many :merges diff --git a/app/policies/export_policy.rb b/app/policies/export_policy.rb new file mode 100644 index 000000000..e667f3207 --- /dev/null +++ b/app/policies/export_policy.rb @@ -0,0 +1,15 @@ +class ExportPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope + end + end + + def create? + user.has_permission?('exports.create') + end + + def update? + user.has_permission?('exports.update') + end +end diff --git a/app/uploaders/import_uploader.rb b/app/uploaders/import_uploader.rb index 60e17ca0f..3491768f6 100644 --- a/app/uploaders/import_uploader.rb +++ b/app/uploaders/import_uploader.rb @@ -37,7 +37,7 @@ class ImportUploader < CarrierWave::Uploader::Base # Add a white list of extensions which are allowed to be uploaded. # For images you might use something like this: def extension_whitelist - %w(zip) + model.class.try(:file_extension_whitelist) || %w(zip) end # Override the filename of the uploaded files: diff --git a/app/views/compliance_checks/show.html.slim b/app/views/compliance_checks/show.html.slim index 8dd699c65..3b3861e0c 100644 --- a/app/views/compliance_checks/show.html.slim +++ b/app/views/compliance_checks/show.html.slim @@ -10,4 +10,4 @@ - if resource.compliance_check_block = definition_list t('compliance_controls.show.metadatas.compliance_check_block'), I18n.t('activerecord.attributes.compliance_control_blocks.transport_mode') => I18n.t("enumerize.transport_mode.#{resource.compliance_check_block.transport_mode}"), - I18n.t('activerecord.attributes.compliance_control_blocks.transport_submode') => I18n.t("enumerize.transport_submode.#{resource.compliance_check_block.transport_submode}") + I18n.t('activerecord.attributes.compliance_control_blocks.transport_submode') => resource.compliance_check_block.transport_submode.empty? ? I18n.t("enumerize.transport_submode.undefined") : I18n.t("enumerize.transport_submode.#{resource.compliance_check_block.transport_submode}") diff --git a/app/views/compliance_control_sets/_filters.html.slim b/app/views/compliance_control_sets/_filters.html.slim index 4348defac..5cf282559 100644 --- a/app/views/compliance_control_sets/_filters.html.slim +++ b/app/views/compliance_control_sets/_filters.html.slim @@ -11,7 +11,7 @@ = f.input :organisation_name_eq_any, collection: organisations_filters_values, as: :check_boxes, label: false, label_method: lambda {|w| ("<span>#{w.name}</span>").html_safe}, required: false, wrapper_html: {class: 'checkbox_list'} .form-group.togglable class=filter_item_class(params[:q], :updated_at) - = f.label Import.human_attribute_name(:updated_at), required: false, class: 'control-label' + = f.label Import::Base.human_attribute_name(:updated_at), required: false, class: 'control-label' .filter_menu = f.simple_fields_for :updated_at do |p| = p.input :start_date, as: :date, label: false, wrapper_html: {class: 'date smart_date filter_menu-item'}, default: @begin_range, include_blank: @begin_range ? false : true diff --git a/app/views/compliance_controls/show.html.slim b/app/views/compliance_controls/show.html.slim index 6e7a45d12..643237676 100644 --- a/app/views/compliance_controls/show.html.slim +++ b/app/views/compliance_controls/show.html.slim @@ -10,4 +10,4 @@ - if @compliance_control.compliance_control_block = definition_list t('compliance_controls.show.metadatas.compliance_control_block'), I18n.t('activerecord.attributes.compliance_control_blocks.transport_mode') => I18n.t("enumerize.transport_mode.#{@compliance_control.compliance_control_block.transport_mode}"), - I18n.t('activerecord.attributes.compliance_control_blocks.transport_submode') => I18n.t("enumerize.transport_submode.#{@compliance_control.compliance_control_block.transport_submode}") + I18n.t('activerecord.attributes.compliance_control_blocks.transport_submode') => @compliance_control.compliance_control_block.transport_submode.empty? ? I18n.t("enumerize.transport_submode.undefined") : I18n.t("enumerize.transport_submode.#{@compliance_control.compliance_control_block.transport_submode}") diff --git a/app/views/dashboards/_dashboard.html.slim b/app/views/dashboards/_dashboard.html.slim index f93b85cad..4adf335d2 100644 --- a/app/views/dashboards/_dashboard.html.slim +++ b/app/views/dashboards/_dashboard.html.slim @@ -28,7 +28,7 @@ - if workbench.calendars.present? .list-group - workbench.calendars.order("updated_at desc").limit(5).each do |calendar| - = link_to calendar.name, workgroup_calendars_path(workbench.workgroup, calendar), class: 'list-group-item' + = link_to calendar.name, workgroup_calendar_path(workbench.workgroup, calendar), class: 'list-group-item' - else .panel-body em.small.text-muted diff --git a/app/views/exports/_export.html.slim b/app/views/exports/_export.html.slim deleted file mode 100644 index f1f7e9753..000000000 --- a/app/views/exports/_export.html.slim +++ /dev/null @@ -1,20 +0,0 @@ -#index_item.panel.panel-default.export - .panel-heading - .panel-title.clearfix - span.pull-right - = link_to referential_export_path(@referential, export.id), :method => :delete, :data => {:confirm => t('exports.actions.destroy_confirm')}, class: 'btn btn-danger btn-sm' do - span.fa.fa-trash-o - - h5 - = link_to( referential_export_path(@referential, export.id), :class => "preview", :title => "#{Export.model_name.human.capitalize} #{export.name}") do - = job_status_title(export) - - .panel-body - p - = link_to( font_awesome_classic_tag("fa-file-#{export.filename_extension}-o") + t("exports.show.exported_file"), exported_file_referential_export_path(@referential, export.id) ) if export.file_path - - .panel-footer - = export_attributes_tag(export) - .history - = l export.created_at, :format => "%d/%m/%Y %H:%M" - = " | #{export.user_name}"
\ No newline at end of file diff --git a/app/views/exports/_exports.html.slim b/app/views/exports/_exports.html.slim deleted file mode 100644 index 7a0461def..000000000 --- a/app/views/exports/_exports.html.slim +++ /dev/null @@ -1,9 +0,0 @@ -.page_info - span.search = t("will_paginate.page_entries_info.search") - = page_entries_info @exports - -.exports.paginated_content - = paginated_content @exports, "exports/export" - -.pagination - = will_paginate @exports, :container => false, renderer: RemoteBootstrapPaginationLinkRenderer
\ No newline at end of file diff --git a/app/views/exports/_form.html.slim b/app/views/exports/_form.html.slim new file mode 100644 index 000000000..7817fdf1a --- /dev/null +++ b/app/views/exports/_form.html.slim @@ -0,0 +1,16 @@ += simple_form_for export, as: :export, url: workbench_exports_path(workbench), html: {class: 'form-horizontal', id: 'wb_export_form'}, wrapper: :horizontal_form do |form| + + .row + .col-lg-12 + = form.input :name + .col-lg-12 + = form.input :type, as: :select, collection: Export::Base.user_visible_descendants, label_method: :human_name + + - Export::Base.user_visible_descendants.each do |child| + .slave data-master="[name='export[type]']" data-value=child.name + - child.options.each do |attr, option_def| + = export_option_input form, export, attr, option_def, child + + = form.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'wb_export_form' + += javascript_pack_tag "exports/new" diff --git a/app/views/exports/index.html.slim b/app/views/exports/index.html.slim index bbcb2a5a7..f97b07231 100644 --- a/app/views/exports/index.html.slim +++ b/app/views/exports/index.html.slim @@ -1,10 +1,44 @@ -= title_tag t('.title') +- breadcrumb :exports, @workbench -.warning = t('.warning') +.page_content + .container-fluid + - if params[:q].present? or collection.any? + .row + .col-lg-12 + = render 'shared/iev_interfaces/filters' -#exports = render 'exports' + - if collection.any? + .row + .col-lg-12 + = table_builder_2 collection, + [ \ + TableBuilderHelper::Column.new( \ + key: :status, \ + attribute: Proc.new { |n| export_status(n.status) }, \ + ), \ + TableBuilderHelper::Column.new( \ + key: :started_at, \ + attribute: Proc.new { |n| l(n.started_at, format: :long) if n.started_at }, \ + ), \ + TableBuilderHelper::Column.new( \ + key: :name, \ + attribute: 'name', \ + link_to: lambda do |export| \ + workbench_export_path(@workbench, export) \ + end \ + ), \ + TableBuilderHelper::Column.new( \ + key: :creator, \ + attribute: 'creator' \ + ) \ + ], + cls: 'table has-search' -- content_for :sidebar do - ul.actions - li - = link_to t('exports.actions.new'), new_referential_export_task_path(@referential), class: 'add'
\ No newline at end of file + = new_pagination collection, 'pull-right' + + - unless collection.any? + .row.mt-xs + .col-lg-12 + = replacement_msg t('exports.search_no_results') + += javascript_pack_tag 'date_filters' diff --git a/app/views/exports/index.js.slim b/app/views/exports/index.js.slim deleted file mode 100644 index b9a413b1b..000000000 --- a/app/views/exports/index.js.slim +++ /dev/null @@ -1 +0,0 @@ -| $('#exports').html("#{escape_javascript(render('exports'))}");
\ No newline at end of file diff --git a/app/views/exports/new.html.slim b/app/views/exports/new.html.slim new file mode 100644 index 000000000..f62386caf --- /dev/null +++ b/app/views/exports/new.html.slim @@ -0,0 +1,7 @@ +- breadcrumb :exports, @workbench + +.page_content + .container-fluid + .row + .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 + = render 'form', export: @export, workbench: @workbench diff --git a/app/views/exports/show.html.slim b/app/views/exports/show.html.slim index 1631e0e7e..2a7d7583c 100644 --- a/app/views/exports/show.html.slim +++ b/app/views/exports/show.html.slim @@ -1,26 +1,49 @@ -.title.row - .col-md-8 - = title_tag job_status_title(@export) +- breadcrumb :export, @workbench, @export - .col-md-4 - = export_attributes_tag(@export) +- page_header_content_for @export -- if @export.report.failure_code? - .alert.alert-danger - = t("iev.failure.#{@export.report.failure_code}") +.page_content + .container-fluid + .row + .col-lg-6.col-md-6.col-sm-12.col-xs-12 + - metadatas = { I18n.t("activerecord.attributes.export.type") => @export.object.class.human_name } + - metadatas = metadatas.update({I18n.t("activerecord.attributes.export.status") => export_status(@export.status)}) + - metadatas = metadatas.update({I18n.t("activerecord.attributes.export.parent") => link_to(@export.parent.name, [@export.parent.workbench, @export.parent])}) if @export.parent.present? + - metadatas = metadatas.update Hash[*@export.visible_options.map{|k, v| [t("activerecord.attributes.export.#{@export.object.class.name.demodulize.underscore}.#{k}"), @export.display_option_value(k, self)]}.flatten] + - metadatas = metadatas.update({I18n.t("activerecord.attributes.export.file") => (@export.file.present? ? link_to(t("actions.download"), @export.file.url) : "-")}) + = definition_list t('metadatas'), metadatas -.progress_bars - = progress_bar_tag(@export) + .row + .col-lg-12 + .error_messages + = render 'shared/iev_interfaces/messages', messages: @export.messages -.export_show - .links - = link_to( font_awesome_classic_tag("fa-file-#{@export.filename_extension}-o") + t("exports.show.exported_file"), exported_file_referential_export_path(@referential, @export.id) ) if @export.file_path - - = render partial: "shared/ie_report.html", locals: { job: @export, line_items: @line_items } + - if @export.children.any? + .row + .col-lg-12 + - coll = @export.children.paginate(page: params[:page] || 1) + = table_builder_2 coll, + [ \ + TableBuilderHelper::Column.new( \ + key: :status, \ + attribute: Proc.new { |n| export_status(n.status) }, \ + ), \ + TableBuilderHelper::Column.new( \ + key: :started_at, \ + attribute: Proc.new { |n| l(n.started_at, format: :long) if n.started_at }, \ + ), \ + TableBuilderHelper::Column.new( \ + key: :name, \ + attribute: 'name', \ + link_to: lambda do |export| \ + workbench_export_path(@workbench, export) \ + end \ + ), \ + TableBuilderHelper::Column.new( \ + key: :creator, \ + attribute: 'creator' \ + ) \ + ], + cls: 'table has-search' -- content_for :sidebar do - ul.actions - li - = link_to t('exports.actions.destroy'), referential_export_path(@referential, @export.id), :method => :delete, :data => {:confirm => t('exports.actions.destroy_confirm')}, class: 'remove' - - = history_tag(@export)
\ No newline at end of file + = new_pagination coll, 'pull-right' diff --git a/app/views/imports/_import_messages.html.slim b/app/views/imports/_import_messages.html.slim deleted file mode 100644 index af10b23e5..000000000 --- a/app/views/imports/_import_messages.html.slim +++ /dev/null @@ -1,8 +0,0 @@ -- if import_messages.any? - ul.list-unstyled.import_message-list - - import_messages.each do | import_message | - li - span(class="#{bootstrap_class_for_message_criticity import_message.criticity}") - = t( ['import_messages', - 'compliance_check_messages', - import_message.message_key].join('.'), import_message.message_attributes.symbolize_keys) diff --git a/app/views/imports/index.html.slim b/app/views/imports/index.html.slim index 4fc077bd6..3dff4d80e 100644 --- a/app/views/imports/index.html.slim +++ b/app/views/imports/index.html.slim @@ -2,15 +2,15 @@ .page_content .container-fluid - - if params[:q].present? or @imports.any? + - if params[:q].present? or collection.any? .row .col-lg-12 - = render 'filters' + = render 'shared/iev_interfaces/filters' - if @imports.any? .row .col-lg-12 - = table_builder_2 @imports, + = table_builder_2 collection, [ \ TableBuilderHelper::Column.new( \ key: :status, \ @@ -34,9 +34,9 @@ ], cls: 'table has-search' - = new_pagination @imports, 'pull-right' + = new_pagination collection, 'pull-right' - - unless @imports.any? + - unless collection.any? .row.mt-xs .col-lg-12 = replacement_msg t('imports.search_no_results') diff --git a/app/views/imports/show.html.slim b/app/views/imports/show.html.slim index 7a9d02077..48a4f334c 100644 --- a/app/views/imports/show.html.slim +++ b/app/views/imports/show.html.slim @@ -11,7 +11,7 @@ .row .col-lg-12 .error_messages - = render 'import_messages', import_messages: @import.messages + = render 'shared/iev_interfaces/messages', messages: @import.messages - if @import.children.any? .row diff --git a/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim b/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim index cb0698cf8..02614dcab 100644 --- a/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim +++ b/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim @@ -29,6 +29,8 @@ span Jeux de données = link_to workbench_imports_path(current_workbench), class: "list-group-item #{(params[:controller] == 'imports') ? 'active' : ''}" do span Import + = link_to workbench_exports_path(current_workbench), class: "list-group-item #{(params[:controller] == 'exports') ? 'active' : ''}" do + span Export = link_to workgroup_calendars_path(current_workbench.workgroup), class: 'list-group-item' do span Modèles de calendrier = link_to workbench_compliance_check_sets_path(current_workbench), class: 'list-group-item' do diff --git a/app/views/layouts/navigation/_page_header.html.slim b/app/views/layouts/navigation/_page_header.html.slim index e407e53da..49f56544b 100644 --- a/app/views/layouts/navigation/_page_header.html.slim +++ b/app/views/layouts/navigation/_page_header.html.slim @@ -1,6 +1,5 @@ - action_links = resource.action_links(params[:action]) rescue nil - action_links ||= decorated_collection.action_links(params[:action]) rescue nil - .page_header .container-fluid .row @@ -13,7 +12,7 @@ h1 = yield :page_header_title - else - if defined?(resource_class) - h1 = t("#{resource_class.model_name.name.underscore.pluralize}.#{params[:action]}.title") + h1 = t("#{resource_class.model_name.name.underscore.gsub('/', '.').pluralize}.#{params[:action]}.title") .col-lg-3.col-md-4.col-sm-5.col-xs-5.text-right .page-action diff --git a/app/views/imports/_filters.html.slim b/app/views/shared/iev_interfaces/_filters.html.slim index 25c0d10d9..9b114c38d 100644 --- a/app/views/imports/_filters.html.slim +++ b/app/views/shared/iev_interfaces/_filters.html.slim @@ -8,11 +8,11 @@ .ffg-row .form-group.togglable class=filter_item_class(params[:q], :status_eq_any) - = f.label Import.human_attribute_name(:status), required: false, class: 'control-label' + = f.label Import::Base.human_attribute_name(:status), required: false, class: 'control-label' = f.input :status_eq_any, collection: %w(pending successful warning failed), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + import_status(l) + "</span>").html_safe}, required: false, wrapper_html: { class: "checkbox_list"} .form-group.togglable class=filter_item_class(params[:q], :started_at) - = f.label Import.human_attribute_name(:started_at), required: false, class: 'control-label' + = f.label Import::Base.human_attribute_name(:started_at), required: false, class: 'control-label' .filter_menu = f.simple_fields_for :started_at do |p| = p.input :start_date, as: :date, label: false, wrapper_html: { class: 'date smart_date filter_menu-item' }, default: @begin_range, include_blank: @begin_range ? false : true diff --git a/app/views/shared/iev_interfaces/_messages.html.slim b/app/views/shared/iev_interfaces/_messages.html.slim new file mode 100644 index 000000000..82f1add57 --- /dev/null +++ b/app/views/shared/iev_interfaces/_messages.html.slim @@ -0,0 +1,14 @@ +- if messages.any? + ul.list-unstyled.import_message-list + - messages.order(:created_at).each do | message | + li + .row class=bootstrap_class_for_message_criticity(message.criticity) + - if message.message_attributes["line"] + .col-md-1= "L. #{message.message_attributes["line"]}" + .col-md-5= export_message_content message + - else + .col-md-6= export_message_content message + .col-md-6 + - if message.criticity != "info" + pre + = JSON.pretty_generate message.resource_attributes || {} diff --git a/app/views/stop_areas/_form.html.slim b/app/views/stop_areas/_form.html.slim index bb1fbe1e9..c63e95c89 100644 --- a/app/views/stop_areas/_form.html.slim +++ b/app/views/stop_areas/_form.html.slim @@ -48,7 +48,7 @@ - if has_feature?(:stop_area_waiting_time) = f.input :waiting_time, input_html: { min: 0 } - = f.input :registration_number, required: format_restriction_for_locales(@referential) == '.hub', :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.registration_number")} + = f.input :registration_number, required: stop_area_registration_number_is_required(f.object), :input_html => {title: stop_area_registration_number_title(f.object), value: stop_area_registration_number_value(f.object)} = f.input :fare_code = f.input :nearest_topic_name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.nearest_topic_name")} = f.input :comment, as: :text, :input_html => {:rows => 5, :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.comment")} diff --git a/app/views/vehicle_journeys/show.rabl b/app/views/vehicle_journeys/show.rabl index bb26ce797..d218038a6 100644 --- a/app/views/vehicle_journeys/show.rabl +++ b/app/views/vehicle_journeys/show.rabl @@ -36,7 +36,7 @@ end if has_feature? :purchase_windows child(:purchase_windows, :object_root => false) do |purchase_windows| - attributes :id, :objectid, :name, :color + attributes :id, :objectid, :name, :color, :bounding_dates end end diff --git a/app/workers/simple_export_worker.rb b/app/workers/simple_export_worker.rb new file mode 100644 index 000000000..d41736307 --- /dev/null +++ b/app/workers/simple_export_worker.rb @@ -0,0 +1,10 @@ +class SimpleExportWorker + include Sidekiq::Worker + + def perform(export_id) + export = Export::Base.find(export_id) + export.update(status: 'running', started_at: Time.now) + export.call_exporter + export.update(ended_at: Time.now) + end +end diff --git a/app/workers/workbench_import_worker.rb b/app/workers/workbench_import_worker.rb index 53cbb222a..fd2a888f0 100644 --- a/app/workers/workbench_import_worker.rb +++ b/app/workers/workbench_import_worker.rb @@ -12,7 +12,7 @@ class WorkbenchImportWorker def perform(import_id) @entries = 0 - @workbench_import ||= WorkbenchImport.find(import_id) + @workbench_import ||= Import::Workbench.find(import_id) workbench_import.update(status: 'running', started_at: Time.now) zip_service = ZipService.new(downloaded, allowed_lines) @@ -44,7 +44,6 @@ class WorkbenchImportWorker raise end - def upload_entry_group entry, element_count update_object_state entry, element_count.succ return unless entry.ok? @@ -80,7 +79,6 @@ class WorkbenchImportWorker File.unlink(eg_file.path) end - # Queries # ======= diff --git a/app/workers/workgroup_export_worker.rb b/app/workers/workgroup_export_worker.rb new file mode 100644 index 000000000..29493cea6 --- /dev/null +++ b/app/workers/workgroup_export_worker.rb @@ -0,0 +1,38 @@ +class WorkgroupExportWorker + include Sidekiq::Worker + + attr_reader :workbench_export + + # Workers + # ======= + + def perform(export_id) + @entries = 0 + @workbench_export ||= Export::Workgroup.find(export_id) + + workbench_export.update(status: 'running', started_at: Time.now) + create_sub_jobs + rescue Exception => e + logger.error e.message + workbench_export.update( status: 'failed' ) + raise + end + + def create_sub_jobs + # XXX TO DO + workbench_export.workbench.workgroup.referentials.each do |ref| + ref.lines.each do |line| + netex_export = Export::Netex.new + netex_export.name = "Export line #{line.name} of Referential #{ref.name}" + netex_export.workbench = workbench_export.workbench + netex_export.creator = workbench_export.creator + netex_export.export_type = :line + netex_export.duration = workbench_export.duration + netex_export.line_code = line.objectid + netex_export.parent = workbench_export + netex_export.save! + end + end + end + +end diff --git a/config/breadcrumbs.rb b/config/breadcrumbs.rb index adcbb0b6f..e57cbc4f2 100644 --- a/config/breadcrumbs.rb +++ b/config/breadcrumbs.rb @@ -111,13 +111,23 @@ crumb :imports do |workbench| parent :workbench, workbench end +crumb :exports do |workbench| + link I18n.t('exports.index.title'), workbench_exports_path(workbench) + parent :workbench, workbench +end + crumb :import do |workbench, import| link breadcrumb_name(import), workbench_import_path(workbench, import) parent :imports, workbench end +crumb :export do |workbench, export| + link breadcrumb_name(export), workbench_export_path(workbench, export) + parent :exports, workbench +end + crumb :import_resources do |import, import_resources| - link I18n.t('import_resources.index.title'), workbench_import_import_resources_path(import.workbench, import.parent) + link I18n.t('import.resources.index.title'), workbench_import_import_resources_path(import.workbench, import.parent) parent :import, import.workbench, import.parent end diff --git a/config/database/ci.yml b/config/database/ci.yml new file mode 100644 index 000000000..44103454a --- /dev/null +++ b/config/database/ci.yml @@ -0,0 +1,11 @@ +test: + adapter: <%= ENV.fetch 'RAILS_DB_ADAPTER', 'postgis' %> + encoding: unicode + pool: <%= ENV.fetch 'RAILS_DB_POOLSIZE', '5' %> + host: <%= ENV.fetch 'RAILS_DB_HOST', 'localhost' %> + port: <%= ENV.fetch 'RAILS_DB_PORT', '5432' %> + schema_search_path: 'public,shared_extensions' + postgis_schema: 'shared_extensions' + database: <%= ENV.fetch 'RAILS_DB_NAME', 'stif_boiv_test' %> + username: <%= ENV['RAILS_DB_USER'] || ENV['POSTGRESQL_ENV_POSTGRES_USER'] || 'jenkins' %> + password: <%= ENV['RAILS_DB_PASSWORD'] || ENV['POSTGRESQL_ENV_POSTGRES_PASSWORD'] %> diff --git a/config/database/jenkins.yml b/config/database/jenkins.yml deleted file mode 100644 index 8b7a8ef2f..000000000 --- a/config/database/jenkins.yml +++ /dev/null @@ -1,8 +0,0 @@ -test: - adapter: postgis - encoding: unicode - schema_search_path: 'public,shared_extensions' - postgis_schema: 'shared_extensions' - username: <%= ENV['POSTGRESQL_ENV_POSTGRES_USER'] || 'jenkins' %> - password: <%= ENV['POSTGRESQL_ENV_POSTGRES_PASSWORD'] %> - database: stif_boiv_test diff --git a/config/environments/development.rb b/config/environments/development.rb index 446e72190..7bd049979 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,3 +1,5 @@ +require Rails.root + 'config/middlewares/cachesettings' + Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. @@ -96,6 +98,13 @@ Rails.application.configure do config.middleware.insert_after(ActionDispatch::Static, Rack::LiveReload) if ENV['LIVERELOAD'] config.middleware.use I18n::JS::Middleware + config.middleware.use CacheSettings, { + /\/assets\/.*/ => { + cache_control: "max-age=86400, public", + expires: 86400 + } + } + config.development_toolbar = false if ENV['TOOLBAR'] && File.exists?("config/development_toolbar.rb") config.development_toolbar = OpenStruct.new diff --git a/config/environments/production.rb b/config/environments/production.rb index 9a699eb44..eb44e1ab1 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,3 +1,5 @@ +require Rails.root + 'config/middlewares/cachesettings' + Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. @@ -136,6 +138,12 @@ Rails.application.configure do config.iev_url = ENV.fetch('IEV_URL',"http://iev:8080") config.rails_host = ENV.fetch('RAILS_HOST','http://front:3000') + config.middleware.use CacheSettings, { + /\/assets\/.*/ => { + cache_control: "max-age=#{1.year.to_i}, public", + expires: 1.year.to_i + } + } # Set node env for browserify-rails # config.browserify_rails.node_env = "production" end diff --git a/config/initializers/apartment.rb b/config/initializers/apartment.rb index f5fb8cd5e..6b817caed 100644 --- a/config/initializers/apartment.rb +++ b/config/initializers/apartment.rb @@ -18,72 +18,76 @@ Apartment.configure do |config| # config.excluded_models = %w{Tenant} # config.excluded_models = [ - 'Referential', - 'ReferentialMetadata', - 'ReferentialSuite', - 'Organisation', - 'User', 'Api::V1::ApiKey', - 'StopAreaReferential', - 'StopAreaReferentialMembership', - 'StopAreaReferentialSync', - 'StopAreaReferentialSyncMessage', - 'Chouette::StopArea', - 'LineReferential', - 'LineReferentialMembership', - 'LineReferentialSync', - 'LineReferentialSyncMessage', - 'Chouette::Line', - 'Chouette::GroupOfLine', + 'Calendar', 'Chouette::Company', + 'Chouette::GroupOfLine', + 'Chouette::Line', 'Chouette::Network', - 'ReferentialCloning', - 'Workbench', - 'Workgroup', + 'Chouette::StopArea', 'CleanUp', 'CleanUpResult', - 'Calendar', - 'Import', - 'NetexImport', - 'WorkbenchImport', - 'ImportMessage', - 'ImportResource', + 'ComplianceCheck', + 'ComplianceCheckBlock', + 'ComplianceCheckMessage', + 'ComplianceCheckResource', + 'ComplianceCheckSet', 'ComplianceControl', + 'ComplianceControlBlock', + 'ComplianceControlSet', + 'CustomField', + 'Export::Base', + 'Export::Message', + 'Export::Resource', 'GenericAttributeControl::MinMax', 'GenericAttributeControl::Pattern', 'GenericAttributeControl::Uniqueness', + 'Import::Base', + 'Import::Gtfs', + 'Import::Message', + 'Import::Netex', + 'Import::Resource', + 'Import::Workbench', 'JourneyPatternControl::Duplicates', 'JourneyPatternControl::VehicleJourney', 'LineControl::Route', + 'LineReferential', + 'LineReferentialMembership', + 'LineReferentialSync', + 'LineReferentialSyncMessage', + 'Merge', + 'Organisation', + 'Referential', + 'ReferentialCloning', + 'ReferentialMetadata', + 'ReferentialSuite', 'RouteControl::Duplicates', 'RouteControl::JourneyPattern', 'RouteControl::MinimumLength', 'RouteControl::OmnibusJourneyPattern', - 'RouteControl::OppositeRouteTerminus', 'RouteControl::OppositeRoute', + 'RouteControl::OppositeRouteTerminus', 'RouteControl::StopPointsInJourneyPattern', 'RouteControl::UnactivatedStopPoint', 'RouteControl::ZDLStopArea', 'RoutingConstraintZoneControl::MaximumLength', 'RoutingConstraintZoneControl::MinimumLength', 'RoutingConstraintZoneControl::UnactivatedStopPoint', + 'SimpleExporter', + 'SimpleImporter', + 'SimpleInterface', + 'StopAreaReferential', + 'StopAreaReferentialMembership', + 'StopAreaReferentialSync', + 'StopAreaReferentialSyncMessage', + 'User', 'VehicleJourneyControl::Delta', - 'VehicleJourneyControl::WaitingTime', 'VehicleJourneyControl::Speed', 'VehicleJourneyControl::TimeTable', 'VehicleJourneyControl::VehicleJourneyAtStops', - 'ComplianceControlSet', - 'ComplianceControlBlock', - 'ComplianceCheck', - 'ComplianceCheckSet', - 'ComplianceCheckBlock', - 'ComplianceCheckResource', - 'ComplianceCheckMessage', - 'Merge', - 'CustomField', - 'SimpleInterface', - 'SimpleImporter', - 'SimpleExporter', + 'VehicleJourneyControl::WaitingTime', + 'Workbench', + 'Workgroup', ] # use postgres schemas? diff --git a/config/initializers/exporters.rb b/config/initializers/exporters.rb new file mode 100644 index 000000000..dfd82a54c --- /dev/null +++ b/config/initializers/exporters.rb @@ -0,0 +1,6 @@ +SimpleExporter.define :referential_companies do |config| + config.separator = ";" + config.encoding = 'ISO-8859-1' + config.add_column :name + config.add_column :registration_number +end diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 2f65b8800..a177e7091 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -14,8 +14,4 @@ Sidekiq.configure_client do |config| config.redis = { url: ENV.fetch('SIDEKIQ_REDIS_URL', 'redis://localhost:6379/12') } end -Sidekiq.configure_client do |config| - config.redis = { url: ENV.fetch('SIDEKIQ_REDIS_URL', 'redis://localhost:6379/12') } -end - Sidekiq.default_worker_options = { retry: false } diff --git a/config/initializers/stif.rb b/config/initializers/stif.rb index a73e4931b..2ddadbc7e 100644 --- a/config/initializers/stif.rb +++ b/config/initializers/stif.rb @@ -27,8 +27,6 @@ Rails.application.config.to_prepare do Organisation.before_validation(on: :create) do |organisation| organisation.custom_view = "stif" end -end - -Rails.application.config.to_prepare do Dashboard.default_class = Stif::Dashboard + Chouette::VehicleJourneyAtStop.day_offset_max = 1 end diff --git a/config/locales/compliance_controls.fr.yml b/config/locales/compliance_controls.fr.yml index 44d01a973..78b92451f 100644 --- a/config/locales/compliance_controls.fr.yml +++ b/config/locales/compliance_controls.fr.yml @@ -191,7 +191,7 @@ fr: vehicle_journey_control/speed: one: "La vitesse entre deux arrêts doit être dans une fourchette paramétrable" vehicle_journey_control/delta: - one: "Les vitesses entre 2 arrêts doivent être similaires pour toutes les courses d’une même mission" + one: "Les temps de parcours entre 2 arrêts doivent être similaires pour toutes les courses d’une même mission" vehicle_journey_control/time_table: one: "Une course doit avoir au moins un calendrier d’application" vehicle_journey_control/vehicle_journey_at_stops: diff --git a/config/locales/export_messages.en.yml b/config/locales/export_messages.en.yml new file mode 100644 index 000000000..f7951a103 --- /dev/null +++ b/config/locales/export_messages.en.yml @@ -0,0 +1,3 @@ +en: + export_messages: + success: Success diff --git a/config/locales/export_messages.fr.yml b/config/locales/export_messages.fr.yml new file mode 100644 index 000000000..5c2191f35 --- /dev/null +++ b/config/locales/export_messages.fr.yml @@ -0,0 +1,3 @@ +fr: + export_messages: + success: Succès diff --git a/config/locales/exports.en.yml b/config/locales/exports.en.yml index 2a47fba54..88c1b99f8 100644 --- a/config/locales/exports.en.yml +++ b/config/locales/exports.en.yml @@ -1,25 +1,32 @@ en: - exports: + exports: &exports + search_no_results: "No export matching your query" + filters: + referential: "Select data space..." + name_or_creator_cont: "Select an export or creator name..." + error_period_filter: "End date must be greater or equal than begin date" actions: new: "New export" + create: "New export" + show: "Export report" + download: "Download original file" destroy: "Destroy" destroy_confirm: "Are you sure you want destroy this export?" - new: - title: "New export" - all: "All" - flash: "Export task on queue, refresh page to see progression" index: title: "Exports" warning: "" + new: + title: "Generate a new export" + create: + title: "Generate a new export" show: + title: "Export %{name}" report: "Report" - exported_file: "Exported file" - statuses: - started: "Started" - scheduled: "Processing ..." - terminated: "Completed" - canceled: "Canceled" - aborted: "Failed" + exported_file: "Original file" + compliance_check: "Validation report" + compliance_check_of: "Validation of export: " + export_of_validation: "Export of the validation" + compliance_check_task: "Validate Report" severities: info: "Information" uncheck: "Unchecked" @@ -27,7 +34,13 @@ en: warning: "Warning" error: "Error" fatal: "Fatal" - activemodel: + export: + workgroup: Workgroup + netex: Netex + referential_companies: Companies + base: + <<: *exports + activerecord: models: export: zero: "export" @@ -37,6 +50,10 @@ en: zero: "export" one: "Neptune export" other: "exports" + csv_export: + zero: "export" + one: "CSV export" + other: "exports" gtfs_export: zero: "export" one: "GTFS export" @@ -44,4 +61,39 @@ en: netex_export: zero: "export" one: "NeTEx export" - other: "exports"
\ No newline at end of file + other: "exports" + errors: + models: + export: + base: + attributes: + file: + wrong_file_extension: "The exported file must be a zip file" + attributes: + attrs: &attrs + resources: "File to export" + created_at: "Created on" + started_at: "Started at" + name: "Name" + status: "Status" + creator: "Creator" + references_type: "Data to be exported" + no_save: "No save" + object_id_prefix: "Neptune Id prefix" + max_distance_for_commercial: "Max distance for commercial stop" + max_distance_for_connection_link: "Max distance for connection link" + ignore_last_word: "ignore last word" + ignore_end_chars: "ignore last chars" + parent: Parent + export: + <<: *attrs + base: + <<: *attrs + workgroup: + duration: Duration + referential_companies: + referential_id: Referential + flash: + exports: + create: + notice: "The export is in progress. Please wait and refresh the page in a few moments." diff --git a/config/locales/exports.fr.yml b/config/locales/exports.fr.yml index 2d7cc0259..fa3ac8fc7 100644 --- a/config/locales/exports.fr.yml +++ b/config/locales/exports.fr.yml @@ -1,33 +1,46 @@ fr: - exports: + exports: &exports + search_no_results: "Aucun export ne correspond à votre recherche" + filters: + referential: "Sélectionnez un jeu de données..." + name_or_creator_cont: "Indiquez un nom d'export ou d'opérateur..." + error_period_filter: "La date de fin doit être supérieure ou égale à la date de début" actions: new: "Nouvel export" + create: "Nouvel export" + show: "Rapport d'export" + download: "Téléch. fichier source" destroy: "Supprimer cet export" destroy_confirm: "Etes vous sûr de supprimer cet export ?" - new: - title: "Nouvel export" - all: "Toutes" - flash: "La demande d'export est mise en file d'attente, veuillez rafraichir régulièrement la page pour en suivre la progression" index: title: "Exports" warning: "" + new: + title: "Générer un export" + create: + title: "Générer un export" show: + title: "Export %{name}" report: "Rapport" - exported_file: "Fichier exporté" - statuses: - started: "En file d'attente..." - scheduled: "En cours..." - terminated: "Achevé" - canceled: "Annulé" - aborted: "Echoué" + exported_file: "Fichier source" + compliance_check: "Test de conformité" + compliance_check_of: "Validation de l'export : " + export_of_validation: "L'export de la validation" + compliance_check_task: "Validation" severities: info: "Information" - uncheck: "Non disponible" + uncheck: "Non testé" ok: "Ok" warning: "Alerte" error: "Erreur" fatal: "Fatal" - activemodel: + export: + workgroup: Groupe de travail + netex: Netex + referential_companies: Transporteurs + base: + <<: *exports + activerecord: models: export: zero: "export" @@ -35,7 +48,11 @@ fr: other: "exports" neptune_export: zero: "export" - one: "export neptune" + one: "export Neptune" + other: "exports" + csv_export: + zero: "export" + one: "export CSV" other: "exports" gtfs_export: zero: "export" @@ -45,3 +62,40 @@ fr: zero: "export" one: "export NeTEx" other: "exports" + errors: + models: + export: + base: + attributes: + file: + wrong_file_extension: "Le fichier exporté doit être au format zip" + attributes: + attrs: &attrs + resources: "Fichier à exporter" + created_at: "Créé le" + started_at: Démarrage + name: "Nom de l'export" + status: "Etat" + creator: "Opérateur" + no_save: "Pas de sauvegarde" + references_type: "Données à exporter" + object_id_prefix: "Préfixe d'identifiants" + max_distance_for_commercial: "Distance max pour créer les zones" + max_distance_for_connection_link: "Distance max pour créer les correspondances" + ignore_last_word: "ignorer le dernier mot" + ignore_end_chars: "ignorer les n derniers caractères" + type: "Type d'export" + file: "Résultat" + parent: Parent + export: + <<: *attrs + base: + <<: *attrs + workgroup: + duration: Durée + referential_companies: + referential_id: Jeu de données + flash: + exports: + create: + notice: "L'export est en cours, veuillez patienter. Actualiser votre page si vous voulez voir l'avancement de votre traitement." diff --git a/config/locales/import_messages.en.yml b/config/locales/import_messages.en.yml index aad4fb772..bc06c46f0 100644 --- a/config/locales/import_messages.en.yml +++ b/config/locales/import_messages.en.yml @@ -1,5 +1,5 @@ en: - import_messages: + import_message: corrupt_zip_file: "The zip file %{source_filename} is corrupted and cannot be read" inconsistent_zip_file: "The zip file %{source_filename} contains unexpected directories: %{spurious_dirs}, which are ignored" referential_creation: "Le référentiel n'a pas pu être créé car un référentiel existe déjà sur les mêmes périodes et lignes" @@ -50,4 +50,4 @@ en: 2_netexstif_servicejourneypattern_2: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number} : l'objet ServiceJourneyPattern d'identifiant %{source_objectid} doit contenir au moins 2 StopPointInJourneyPattern" 2_netexstif_servicejourneypattern_3_1: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number} : l'objet ServiceJourneyPattern d'identifiant %{source_objectid} n'a pas de valeur pour l'attribut ServiceJourneyPatternType" 2_netexstif_servicejourneypattern_3_2: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number} : l'objet ServiceJourneyPattern d'identifiant %{source_objectid} a une valeur interdite %{error_value} pour l'attribut ServiceJourneyPatternType différente de 'passenger'" - 2_netexstif_servicejourneypattern_4: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number}, objet ServiceJourneyPattern d'identifiant %{source_objectid} : les attributs 'order' des StopPointInJourneyPattern ne sont pas croissants."
\ No newline at end of file + 2_netexstif_servicejourneypattern_4: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number}, objet ServiceJourneyPattern d'identifiant %{source_objectid} : les attributs 'order' des StopPointInJourneyPattern ne sont pas croissants." diff --git a/config/locales/import_resources.en.yml b/config/locales/import_resources.en.yml index 5f0f3213e..386039319 100644 --- a/config/locales/import_resources.en.yml +++ b/config/locales/import_resources.en.yml @@ -1,11 +1,14 @@ en: + import: + resources: &resources + index: + title: "NeTEx conformity" + table_state: "%{lines_imported} line(s) imported on %{lines_in_zipfile} presents in zipfile" + table_title: "Satus of anlyzed files" + table_explanation: "When calendriers.xml and/or commun.xml are not imported, then all lines file are not processed." + metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a" import_resources: - index: - title: "NeTEx conformity" - table_state: "%{lines_imported} line(s) imported on %{lines_in_zipfile} presents in zipfile" - table_title: "Satus of anlyzed files" - table_explanation: "When calendriers.xml and/or commun.xml are not imported, then all lines file are not processed." - metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a" + <<: *resources activerecord: models: import_resource: @@ -14,5 +17,6 @@ en: other: "netex conformities" attributes: import: - name: "Filename" - status: "Status" + resource: + name: "Filename" + status: "Status" diff --git a/config/locales/import_resources.fr.yml b/config/locales/import_resources.fr.yml index a271ae1ca..50fb7f1ca 100644 --- a/config/locales/import_resources.fr.yml +++ b/config/locales/import_resources.fr.yml @@ -1,11 +1,14 @@ fr: + import: + resources: &resources + index: + title: "Rapport de conformité NeTEx" + table_state: "%{lines_imported} ligne(s) importée(s) sur %{lines_in_zipfile} présente(s) dans l'archive" + table_title: "Etat des fichiers analysés" + table_explanation: "Dans le cas ou le(s) fichiers calendriers.xml et/ou commun.xml sont dans un état non importé, alors tous les fichiers lignes sont automatiquement dans un état non traité." + metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a" import_resources: - index: - title: "Rapport de conformité NeTEx" - table_state: "%{lines_imported} ligne(s) importée(s) sur %{lines_in_zipfile} présente(s) dans l'archive" - table_title: "Etat des fichiers analysés" - table_explanation: "Dans le cas ou le(s) fichiers calendriers.xml et/ou commun.xml sont dans un état non importé, alors tous les fichiers lignes sont automatiquement dans un état non traité." - metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a" + <<: *resources activerecord: models: import_resource: @@ -13,6 +16,7 @@ fr: one: "rapport de conformité Netex" other: "rapports de conformité Netex" attributes: - import_resource: - name: "Fichier" - status: "Etat" + import: + resource: + name: "Fichier" + status: "Etat" diff --git a/config/locales/import_tasks.en.yml b/config/locales/import_tasks.en.yml deleted file mode 100644 index 34f7e6998..000000000 --- a/config/locales/import_tasks.en.yml +++ /dev/null @@ -1,52 +0,0 @@ -en: - import_tasks: - actions: - new: "New import" - new: - title: "New import" - all: "All" - flash: "Import task on queue, refresh page to see progression" - activemodel: - models: - import_task: - zero: "import" - one: "import" - other: "imports" - neptune_import: - zero: "import" - one: "Neptune import" - other: "imports" - gtfs_import: - zero: "import" - one: "GTFS import" - other: "imports" - netex_import: - zero: "import" - one: "NeTEx import" - other: "imports" - attributes: - import_task: - name: "Import name" - no_save: "No save" - resources: "File to import" - references_type: "Data to be imported" - object_id_prefix: "Neptune Id prefix" - max_distance_for_commercial: "Max distance for commercial stop" - max_distance_for_connection_link: "Max distance for connection link" - ignore_last_word: "ignore last word" - ignore_end_chars: "ignore last chars" - errors: - models: - import_task: - attributes: - resources: - maximum_file_size: "File should smaller than %{maximum_file_size} (%{file_size} sent)" - invalid_mime_type: "File doesn't use the expected format (%{mime_type} sent)" - formtastic: - titles: - import_task: - max_distance_for_commercial: "Maximal distance to merge homonymous stops in commercial stop in meter" - max_distance_for_connection_link: "Maximal distance to link stops by connection link stop in meter" - ignore_last_word: "ignore last word on stop name in homonymous detection (inappliable when just one word occurs)" - ignore_end_chars: "ignore some chars at the end of stop names in homonymous detection" - references_type: "Filter on stop areas import only GTFS stops and transfers files, these may contain extra attributes" diff --git a/config/locales/import_tasks.fr.yml b/config/locales/import_tasks.fr.yml deleted file mode 100644 index 002ca03cb..000000000 --- a/config/locales/import_tasks.fr.yml +++ /dev/null @@ -1,53 +0,0 @@ -fr: - import_tasks: - actions: - new: "Nouvel import" - new: - title: "Nouvel import" - all: "Tout" - flash: "La demande d'import est mise en file d'attente, veuillez rafraichir régulièrement la page pour en suivre la progression" - activemodel: - models: - import_task: - zero: "import" - one: "import" - other: "imports" - neptune_import: - zero: "import" - one: "import Neptune" - other: "imports" - gtfs_import: - zero: "import" - one: "import GTFS" - other: "imports" - netex_import: - zero: "import" - one: "import NeTEx" - other: "imports" - attributes: - import_task: - name: "Nom de l'import" - no_save: "Pas de sauvegarde" - resources: "Fichier à importer" - references_type: "Données à importer" - object_id_prefix: "Préfixe d'identifiants" - max_distance_for_commercial: "Distance max pour créer les zones" - max_distance_for_connection_link: "Distance max pour créer les correspondances" - ignore_last_word: "ignorer le dernier mot" - ignore_end_chars: "ignorer les n derniers caractères" - errors: - models: - import_task: - attributes: - resources: - maximum_file_size: "Le fichier ne peut dépasser %{maximum_file_size} (%{file_size} soumis)" - invalid_mime_type: "Le fichier n'utilise pas le format attendu (%{mime_type} soumis)" - - formtastic: - titles: - import_task: - max_distance_for_commercial: "Distance maximale entre deux arrêts homonymes pour créer les zones d'arrêt (en mètre)" - max_distance_for_connection_link: "Distance maximale entre deux arrêts pour créer les correspondances (en mètre)" - ignore_last_word: "Ignorer le dernier mot pour détecter l'homonymie des noms d'arrêt (inapplicable quand le nom ne comporte qu'un mot)" - ignore_end_chars: "Ignorer les n derniers caractères du nom de l'arrêt pour détecter l'homonymie" - references_type: "Le filtre sur arrêts importe uniquement les fichiers GTFS stops et transfers gtfs, ceux-ci pouvant contenir des attributs supplémentaires" diff --git a/config/locales/imports.en.yml b/config/locales/imports.en.yml index b0644acd3..d0db87fb1 100644 --- a/config/locales/imports.en.yml +++ b/config/locales/imports.en.yml @@ -59,11 +59,12 @@ en: errors: models: import: - attributes: - file: - wrong_file_extension: "The imported file must be a zip file" + base: + attributes: + file: + wrong_file_extension: "The imported file must be a zip file" attributes: - import: + attrs: &attrs resources: "File to import" created_at: "Created on" started_at: "Started at" @@ -77,6 +78,10 @@ en: max_distance_for_connection_link: "Max distance for connection link" ignore_last_word: "ignore last word" ignore_end_chars: "ignore last chars" + import: + <<: *attrs + base: + <<: *attrs flash: imports: create: diff --git a/config/locales/imports.fr.yml b/config/locales/imports.fr.yml index 2380eac45..40272889a 100644 --- a/config/locales/imports.fr.yml +++ b/config/locales/imports.fr.yml @@ -1,5 +1,5 @@ fr: - imports: + imports: &imports search_no_results: "Aucun import ne correspond à votre recherche" filters: referential: "Sélectionnez un jeu de données..." @@ -34,6 +34,9 @@ fr: warning: "Alerte" error: "Erreur" fatal: "Fatal" + import: + base: + <<: *imports activerecord: models: import: @@ -59,11 +62,12 @@ fr: errors: models: import: - attributes: - file: - wrong_file_extension: "Le fichier importé doit être au format zip" + base: + attributes: + file: + wrong_file_extension: "Le fichier importé doit être au format zip" attributes: - import: + attrs: &attrs resources: "Fichier à importer" created_at: "Créé le" started_at: Démarrage @@ -77,6 +81,12 @@ fr: max_distance_for_connection_link: "Distance max pour créer les correspondances" ignore_last_word: "ignorer le dernier mot" ignore_end_chars: "ignorer les n derniers caractères" + + import: + <<: *attrs + base: + <<: *attrs + flash: imports: create: diff --git a/config/locales/stop_areas.en.yml b/config/locales/stop_areas.en.yml index ac3dce280..33722b60b 100644 --- a/config/locales/stop_areas.en.yml +++ b/config/locales/stop_areas.en.yml @@ -5,6 +5,10 @@ en: errors: empty: Aucun stop_area_id parent_area_type: can not be of type %{area_type} + registration_number: + already_taken: Already taken + cannot_be_empty: This field is mandatory + invalid: Incorrect value default_geometry_success: "%{count} modified stop areas" stop_area: no_position: "No Position" @@ -148,6 +152,7 @@ en: stop_area: name: "" registration_number: "only alphanumerical or underscore characters" + registration_number_format: "authorized format : %{registration_number_format}" objectid: "[prefix]:StopArea:[unique_key] : prefix contains only alphanumerical or underscore characters, unique_key accepts also minus character" nearest_topic_name: "" city_name: "" diff --git a/config/locales/stop_areas.fr.yml b/config/locales/stop_areas.fr.yml index f75c4ebe7..605e6158e 100644 --- a/config/locales/stop_areas.fr.yml +++ b/config/locales/stop_areas.fr.yml @@ -6,6 +6,10 @@ fr: empty: Aucun stop_area_id parent_area_type: ne peut être de type %{area_type} incorrect_kind_area_type: Ce type d'arrêt est invalide pour cette catégorie + registration_number: + already_taken: Déjà utilisé + cannot_be_empty: Ce champ est requis + invalid: Valeur invalide default_geometry_success: "%{count} arrêts édités" stop_area: no_position: "Pas de position" @@ -150,6 +154,7 @@ fr: stop_area: name: "" registration_number: "caractères autorisés : alphanumériques et 'souligné'" + registration_number_format: "format autorisé: %{registration_number_format}" objectid: "[prefixe]:StopArea:[clé_unique] caractères autorisés : alphanumériques et 'souligné' pour le préfixe, la clé unique accepte en plus le 'moins'" nearest_topic_name: "" city_name: "" diff --git a/config/middlewares/cachesettings.rb b/config/middlewares/cachesettings.rb new file mode 100644 index 000000000..8a122891f --- /dev/null +++ b/config/middlewares/cachesettings.rb @@ -0,0 +1,20 @@ +class CacheSettings + def initialize app, pat + @app = app + @pat = pat + end + + def call env + res = @app.call(env) + path = env["REQUEST_PATH"] + @pat.each do |pattern,data| + if path =~ pattern + res[1]["Cache-Control"] = data[:cache_control] if data.has_key?(:cache_control) + res[1]["Expires"] = (Time.now + data[:expires]).utc.rfc2822 if data.has_key?(:expires) + res[1]["X-Custom-Cache-Control"] = "yes" if Rails.env.development? + return res + end + end + res + end +end diff --git a/config/routes.rb b/config/routes.rb index b6934936b..6313b5678 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,6 +11,12 @@ ChouetteIhm::Application.routes.draw do resources :import_messages, only: [:index] end end + resources :exports do + post :upload, on: :member + resources :export_resources, only: [:index] do + resources :export_messages, only: [:index] + end + end resources :compliance_check_sets, only: [:index, :show] do get :executed, on: :member resources :compliance_checks, only: [:show] diff --git a/db/migrate/20171106111448_update_import_message_criticity_type_to_string.rb b/db/migrate/20171106111448_update_import_message_criticity_type_to_string.rb index c14450387..a08a10b9a 100644 --- a/db/migrate/20171106111448_update_import_message_criticity_type_to_string.rb +++ b/db/migrate/20171106111448_update_import_message_criticity_type_to_string.rb @@ -7,10 +7,10 @@ class UpdateImportMessageCriticityTypeToString < ActiveRecord::Migration when 0 then "info" when 1 then "warning" when 2 then "error" - else + else "info" end end - ImportMessage.all.each { |im| im.update_attribute(:criticity, change_criticity_value(im.criticity)) } + Import::Message.all.each { |im| im.update_attribute(:criticity, change_criticity_value(im.criticity)) } end end diff --git a/db/migrate/20180226074739_add_registration_number_format_to_stop_area_referentials.rb b/db/migrate/20180226074739_add_registration_number_format_to_stop_area_referentials.rb new file mode 100644 index 000000000..3f4231acb --- /dev/null +++ b/db/migrate/20180226074739_add_registration_number_format_to_stop_area_referentials.rb @@ -0,0 +1,5 @@ +class AddRegistrationNumberFormatToStopAreaReferentials < ActiveRecord::Migration + def change + add_column :stop_area_referentials, :registration_number_format, :string + end +end diff --git a/db/migrate/20180306135204_clean_former_exports.rb b/db/migrate/20180306135204_clean_former_exports.rb new file mode 100644 index 000000000..46a595c12 --- /dev/null +++ b/db/migrate/20180306135204_clean_former_exports.rb @@ -0,0 +1,5 @@ +class CleanFormerExports < ActiveRecord::Migration + def change + drop_table :exports + end +end diff --git a/db/migrate/20180306152953_update_imports_names.rb b/db/migrate/20180306152953_update_imports_names.rb new file mode 100644 index 000000000..fc57ff849 --- /dev/null +++ b/db/migrate/20180306152953_update_imports_names.rb @@ -0,0 +1,13 @@ +class UpdateImportsNames < ActiveRecord::Migration + def change + Import::Base.all.pluck(:type).uniq.each do |type| + next if type =~ /^Import/ + Import::Base.where(type: type).update_all type: "Import::#{type.gsub 'Import', ''}" + end + + Import::Base.all.pluck(:parent_type).uniq.compact.each do |type| + next if type =~ /^Import/ + Import::Base.where(parent_type: type).update_all parent_type: "Import::#{type.gsub 'Import', ''}" + end + end +end diff --git a/db/migrate/20180307071448_create_new_exports.rb b/db/migrate/20180307071448_create_new_exports.rb new file mode 100644 index 000000000..74921d108 --- /dev/null +++ b/db/migrate/20180307071448_create_new_exports.rb @@ -0,0 +1,56 @@ +class CreateNewExports < ActiveRecord::Migration + def change + create_table :exports do |t| + t.string "status" + t.string "current_step_id" + t.float "current_step_progress" + t.integer "workbench_id", limit: 8 + t.integer "referential_id", limit: 8 + t.string "name" + t.datetime "created_at" + t.datetime "updated_at" + t.string "file" + t.datetime "started_at" + t.datetime "ended_at" + t.string "token_upload" + t.string "type" + t.integer "parent_id", limit: 8 + t.string "parent_type" + t.datetime "notified_parent_at" + t.integer "current_step", default: 0 + t.integer "total_steps", default: 0 + t.string "creator" + end + + add_index "exports", ["referential_id"], name: "index_exports_on_referential_id", using: :btree + add_index "exports", ["workbench_id"], name: "index_exports_on_workbench_id", using: :btree + + create_table "export_messages", id: :bigserial, force: :cascade do |t| + t.string "criticity" + t.string "message_key" + t.hstore "message_attributes" + t.integer "export_id", limit: 8 + t.integer "resource_id", limit: 8 + t.datetime "created_at" + t.datetime "updated_at" + t.hstore "resource_attributes" + end + + add_index "export_messages", ["export_id"], name: "index_export_messages_on_export_id", using: :btree + add_index "export_messages", ["resource_id"], name: "index_export_messages_on_resource_id", using: :btree + + create_table "export_resources", id: :bigserial, force: :cascade do |t| + t.integer "export_id", limit: 8 + t.string "status" + t.datetime "created_at" + t.datetime "updated_at" + t.string "resource_type" + t.string "reference" + t.string "name" + t.hstore "metrics" + end + + add_index "export_resources", ["export_id"], name: "index_export_resources_on_export_id", using: :btree + + end +end diff --git a/db/migrate/20180308095116_add_options_to_exports.rb b/db/migrate/20180308095116_add_options_to_exports.rb new file mode 100644 index 000000000..02744c5cb --- /dev/null +++ b/db/migrate/20180308095116_add_options_to_exports.rb @@ -0,0 +1,5 @@ +class AddOptionsToExports < ActiveRecord::Migration + def change + add_column :exports, :options, :hstore + end +end diff --git a/db/schema.rb b/db/schema.rb index 27e38e1b7..cf0a32a3b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,8 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180301142531) do - +ActiveRecord::Schema.define(version: 20180308095116) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" enable_extension "postgis" @@ -299,18 +298,58 @@ ActiveRecord::Schema.define(version: 20180301142531) do add_index "custom_fields", ["resource_type"], name: "index_custom_fields_on_resource_type", using: :btree + create_table "export_messages", id: :bigserial, force: :cascade do |t| + t.string "criticity" + t.string "message_key" + t.hstore "message_attributes" + t.integer "export_id", limit: 8 + t.integer "resource_id", limit: 8 + t.datetime "created_at" + t.datetime "updated_at" + t.hstore "resource_attributes" + end + + add_index "export_messages", ["export_id"], name: "index_export_messages_on_export_id", using: :btree + add_index "export_messages", ["resource_id"], name: "index_export_messages_on_resource_id", using: :btree + + create_table "export_resources", id: :bigserial, force: :cascade do |t| + t.integer "export_id", limit: 8 + t.string "status" + t.datetime "created_at" + t.datetime "updated_at" + t.string "resource_type" + t.string "reference" + t.string "name" + t.hstore "metrics" + end + + add_index "export_resources", ["export_id"], name: "index_export_resources_on_export_id", using: :btree + create_table "exports", id: :bigserial, force: :cascade do |t| - t.integer "referential_id", limit: 8 t.string "status" - t.string "type" - t.string "options" + t.string "current_step_id" + t.float "current_step_progress" + t.integer "workbench_id", limit: 8 + t.integer "referential_id", limit: 8 + t.string "name" t.datetime "created_at" t.datetime "updated_at" - t.string "references_type" - t.string "reference_ids" + t.string "file" + t.datetime "started_at" + t.datetime "ended_at" + t.string "token_upload" + t.string "type" + t.integer "parent_id", limit: 8 + t.string "parent_type" + t.datetime "notified_parent_at" + t.integer "current_step", default: 0 + t.integer "total_steps", default: 0 + t.string "creator" + t.hstore "options" end add_index "exports", ["referential_id"], name: "index_exports_on_referential_id", using: :btree + add_index "exports", ["workbench_id"], name: "index_exports_on_workbench_id", using: :btree create_table "facilities", id: :bigserial, force: :cascade do |t| t.integer "stop_area_id", limit: 8 @@ -421,9 +460,9 @@ ActiveRecord::Schema.define(version: 20180301142531) do t.string "type" t.integer "parent_id", limit: 8 t.string "parent_type" + t.datetime "notified_parent_at" t.integer "current_step", default: 0 t.integer "total_steps", default: 0 - t.datetime "notified_parent_at" t.string "creator" end @@ -766,6 +805,7 @@ ActiveRecord::Schema.define(version: 20180301142531) do t.datetime "created_at" t.datetime "updated_at" t.string "objectid_format" + t.string "registration_number_format" end create_table "stop_areas", id: :bigserial, force: :cascade do |t| diff --git a/lib/stif/permission_translator.rb b/lib/stif/permission_translator.rb index 9e0feb9b8..09a7c610c 100644 --- a/lib/stif/permission_translator.rb +++ b/lib/stif/permission_translator.rb @@ -21,6 +21,7 @@ module Stif calendars footnotes imports + exports merges journey_patterns referentials diff --git a/lib/tasks/ci.rake b/lib/tasks/ci.rake index 89f9aa9c8..5b2c8ae3c 100644 --- a/lib/tasks/ci.rake +++ b/lib/tasks/ci.rake @@ -1,9 +1,18 @@ namespace :ci do + + def database_name + @database_name ||= + begin + config = YAML.load(ERB.new(File.read('config/database.yml')).result) + config["test"]["database"] + end + end + desc "Prepare CI build" task :setup do - cp "config/database/jenkins.yml", "config/database.yml" + cp "config/database/ci.yml", "config/database.yml" + puts "Use #{database_name} database" sh "RAILS_ENV=test rake db:drop db:create db:migrate" - sh "yarn --no-progress install" end def git_branch @@ -59,7 +68,18 @@ namespace :ci do sh "rm -rf log/test.log" sh "RAILS_ENV=test bundle exec rake assets:clobber" end + + task :build => ["ci:setup", "ci:assets", "ci:i18n_js_export", "spec", "ci:jest", "cucumber", "ci:check_security"] + + namespace :docker do + task :clean do + puts "Drop #{database_name} database" + sh "RAILS_ENV=test rake db:drop" + end + end + + task :docker => ["ci:build"] end desc "Run continuous integration tasks (spec, ...)" -task :ci => ["ci:setup", "ci:assets", "ci:i18n_js_export", "spec", "ci:jest", "cucumber", "ci:check_security", "ci:deploy", "ci:clean"] +task :ci => ["ci:build", "ci:deploy", "ci:clean"] diff --git a/lib/tasks/exports.rake b/lib/tasks/exports.rake index 547388b35..845d581d3 100644 --- a/lib/tasks/exports.rake +++ b/lib/tasks/exports.rake @@ -51,6 +51,9 @@ namespace :export do if journeys.count == 0 puts "No maching journeys were found".red else + exports_group = SimpleInterfacesGroup.new "Export Complet \"#{referential.name}\" du #{Time.now.to_date} au #{args[:timelapse].to_i.days.from_now.to_date}" + exports_group.shared_options = {verbose: true} + exporter = SimpleJsonExporter.create configuration_name: "#{args[:configuration_name]}_companies", filepath: "#{args[:output_dir]}/#{args[:configuration_name]}_companies.json" ids = journeys.pluck :company_id ids += journeys.joins(route: :line).pluck :"lines.company_id" @@ -59,39 +62,37 @@ namespace :export do config.collection = Chouette::Company.where(id: ids.uniq).order('name') end - SimpleInterfacesHelper.run_interface_controlling_interruption exporter, :export, args - break if exporter.status == :error + exports_group.add_interface exporter, "Services Types", :export exporter = SimpleJsonExporter.create configuration_name: "#{args[:configuration_name]}_schedules", filepath: "#{args[:output_dir]}/#{args[:configuration_name]}_schedules.json" exporter.configure do |config| config.collection = journeys end - SimpleInterfacesHelper.run_interface_controlling_interruption exporter, :export, args - break if exporter.status == :error + exports_group.add_interface exporter, "Schedules", :export exporter = SimpleJsonExporter.create configuration_name: "#{args[:configuration_name]}_routes", filepath: "#{args[:output_dir]}/#{args[:configuration_name]}_routes.json" exporter.configure do |config| config.collection = Chouette::JourneyPattern.where(id: journeys.pluck(:journey_pattern_id).uniq) end - SimpleInterfacesHelper.run_interface_controlling_interruption exporter, :export, args - break if exporter.status == :error + exports_group.add_interface exporter, "Routes", :export exporter = SimpleJsonExporter.create configuration_name: "#{args[:configuration_name]}_stops", filepath: "#{args[:output_dir]}/#{args[:configuration_name]}_stops.json" exporter.configure do |config| config.collection = Chouette::StopArea.where(id: journeys.joins(:stop_points).pluck(:"stop_points.stop_area_id").uniq).order('parent_id ASC NULLS FIRST') end - SimpleInterfacesHelper.run_interface_controlling_interruption exporter, :export, args - break if exporter.status == :error + exports_group.add_interface exporter, "Stops", :export exporter = SimpleJsonExporter.create configuration_name: "#{args[:configuration_name]}_journeys", filepath: "#{args[:output_dir]}/#{args[:configuration_name]}_journeys.json" exporter.configure do |config| config.collection = journeys end - SimpleInterfacesHelper.run_interface_controlling_interruption exporter, :export, args + exports_group.add_interface exporter, "Services", :export + + exports_group.run end end end diff --git a/lib/tasks/helpers/simple_interfaces.rb b/lib/tasks/helpers/simple_interfaces.rb index 5b593be43..61dd38399 100644 --- a/lib/tasks/helpers/simple_interfaces.rb +++ b/lib/tasks/helpers/simple_interfaces.rb @@ -1,28 +1,11 @@ module SimpleInterfacesHelper - def self.interface_output_to_csv interface, output_dir - FileUtils.mkdir_p output_dir - filepath = File.join output_dir, + "#{interface.configuration_name}_#{Time.now.strftime "%y%m%d%H%M"}_out.csv" - cols = %w(line kind event message error) - if interface.reload.journal.size > 0 && interface.journal.first["row"].present? - keys = interface.journal.first["row"].map(&:first) - CSV.open(filepath, "w") do |csv| - csv << cols + keys - interface.journal.each do |j| - csv << cols.map{|c| j[c]} + j["row"].map(&:last) - end - end - puts "Task Output written in #{filepath}" - end - end - def self.run_interface_controlling_interruption interface, method, args begin interface.send(method, verbose: true) rescue Interrupt + interface.write_output_to_csv raise ensure - puts "\n\e[33m***\e[0m Done, status: " + (interface.status == "success" ? "\e[32m" : "\e[31m" ) + (interface.status || "") + "\e[0m" - interface_output_to_csv interface, args[:logs_output_dir] end end end diff --git a/script/launch-cron b/script/launch-cron index 183e5a331..1a38ffcbd 100644 --- a/script/launch-cron +++ b/script/launch-cron @@ -6,7 +6,7 @@ function append_var_if_defined OUTPUT=$2 grep -qE "^${VAR_NAME}=" ${OUTPUT}||env|grep -E "^${VAR_NAME}=">>${OUTPUT} } -VAR_LIST="RAILS_DB_HOST RAILS_DB_PORT RAILS_DB_USER RAILS_DB_PASSWORD RAILS_DB_NAME MAIL_HOST MAIL_ASSETS_URL_BASE MAIL_FROM SMTP_HOST SECRET_BASE SIDEKIQ_REDIS_URL CODIFLIGNE_API_URL REDIS_CACHE_STORE_URL RAILS_LOG_TO_STDOUT PATH" +VAR_LIST="SESAME_API_SETTINGS RAILS_DB_HOST RAILS_DB_PORT RAILS_DB_USER RAILS_DB_PASSWORD RAILS_DB_NAME MAIL_HOST MAIL_ASSETS_URL_BASE MAIL_FROM SMTP_HOST SECRET_BASE SIDEKIQ_REDIS_URL CODIFLIGNE_API_URL REFLEX_API_URL REDIS_CACHE_STORE_URL RAILS_LOG_TO_STDOUT PATH" TMPF=$(tempfile) for v in $VAR_LIST; do diff --git a/spec/controllers/api/v1/imports_controller_spec.rb b/spec/controllers/api/v1/imports_controller_spec.rb index 8077dd052..f7022115a 100644 --- a/spec/controllers/api/v1/imports_controller_spec.rb +++ b/spec/controllers/api/v1/imports_controller_spec.rb @@ -30,7 +30,7 @@ RSpec.describe Api::V1::ImportsController, type: :controller do it 'should be successful' do expect { post :create, workbench_id: workbench.id, workbench_import: {name: "test", file: file, creator: 'test'}, format: :json - }.to change{WorkbenchImport.count}.by(1) + }.to change{Import::Workbench.count}.by(1) expect(response).to be_success end end diff --git a/spec/controllers/exports_controller_spec.rb b/spec/controllers/exports_controller_spec.rb index 6cd6e4c54..3a67497ec 100644 --- a/spec/controllers/exports_controller_spec.rb +++ b/spec/controllers/exports_controller_spec.rb @@ -1,22 +1,97 @@ -require 'spec_helper' - -describe ExportsController, :type => :controller do +RSpec.describe ExportsController, :type => :controller do login_user - describe "GET 'new'" do - it "returns http success" do - pending - get 'new' + let(:workbench) { create :workbench } + let(:export) { create(:netex_export, workbench: workbench) } + + describe 'GET #new' do + it 'should be successful if authorized' do + get :new, workbench_id: workbench.id expect(response).to be_success end + + it 'should be unsuccessful unless authorized' do + remove_permissions('exports.create', from_user: @user, save: true) + get :new, workbench_id: workbench.id + expect(response).not_to be_success + end end - describe "GET 'index'" do - it "returns http success" do - pending - get 'index' - expect(response).to be_success + describe "POST #create" do + let(:params){ {name: "foo"} } + let(:request){ post :create, workbench_id: workbench.id, export: params } + it 'should create no objects' do + expect{request}.to_not change{Export::Base.count} + end + + context "with full params" do + let(:params){{ + name: "foo", + type: "Export::Netex", + duration: 12, + export_type: :line + }} + + it 'should be successful' do + expect{request}.to change{Export::Base.count}.by(1) + end + + it "displays a flash message" do + request + expect(controller).to set_flash[:notice].to( + I18n.t('flash.exports.create.notice') + ) + end + end + + context "with missing options" do + let(:params){{ + name: "foo", + type: "Export::Workgroup" + }} + + it 'should be unsuccessful' do + expect{request}.to change{Export::Base.count}.by(0) + end + end + + context "with all options" do + let(:params){{ + name: "foo", + type: "Export::Workgroup", + duration: 90 + }} + + it 'should be successful' do + expect{request}.to change{Export::Base.count}.by(1) + end + end + + context "with wrong type" do + let(:params){{ + name: "foo", + type: "Export::Foo" + }} + + it 'should be unsuccessful' do + expect{request}.to raise_error ActiveRecord::SubclassNotFound + end end end + describe 'POST #upload' do + context "with the token" do + it 'should be successful' do + post :upload, workbench_id: workbench.id, id: export.id, token: export.token_upload + expect(response).to be_redirect + end + end + + context "without the token" do + it 'should be unsuccessful' do + post :upload, workbench_id: workbench.id, id: export.id, token: "foo" + expect(response).to_not be_success + end + end + end end diff --git a/spec/controllers/vehicle_journey_imports_controller_spec.rb b/spec/controllers/vehicle_journey_imports_controller_spec.rb deleted file mode 100644 index 633f90b70..000000000 --- a/spec/controllers/vehicle_journey_imports_controller_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'spec_helper' - -describe ImportTasksController, :type => :controller do - login_user -end diff --git a/spec/factories/exports.rb b/spec/factories/exports.rb deleted file mode 100644 index 34427edb8..000000000 --- a/spec/factories/exports.rb +++ /dev/null @@ -1,5 +0,0 @@ -FactoryGirl.define do - factory :export do - referential { Referential.find_by_slug("first") } - end -end diff --git a/spec/factories/exports/export_messages.rb b/spec/factories/exports/export_messages.rb new file mode 100644 index 000000000..55394ec45 --- /dev/null +++ b/spec/factories/exports/export_messages.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :export_message, class: Export::Message do + association :export + association :resource, factory: :export_resource + criticity :info + end +end diff --git a/spec/factories/exports/export_resources.rb b/spec/factories/exports/export_resources.rb new file mode 100644 index 000000000..8e38235cd --- /dev/null +++ b/spec/factories/exports/export_resources.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do + factory :export_resource, class: Export::Resource do + sequence(:name) { |n| "Export resource #{n}" } + association :export, factory: :netex_export + status :WARNING + resource_type 'type' + reference 'reference' + end +end diff --git a/spec/factories/exports/exports.rb b/spec/factories/exports/exports.rb new file mode 100644 index 000000000..c8aaf30a9 --- /dev/null +++ b/spec/factories/exports/exports.rb @@ -0,0 +1,34 @@ +FactoryGirl.define do + factory :export, class: Export::Base do + sequence(:name) { |n| "Export #{n}" } + current_step_id "MyString" + current_step_progress 1.5 + association :workbench + association :referential + status :new + started_at nil + ended_at nil + creator 'rspec' + + after(:build) do |export| + export.class.skip_callback(:create, :before, :initialize_fields) + end + end + + factory :bad_export, class: Export::Base do + sequence(:name) { |n| "Export #{n}" } + current_step_id "MyString" + current_step_progress 1.5 + association :workbench + association :referential + file {File.open(File.join(Rails.root, 'spec', 'fixtures', 'terminated_job.json'))} + status :new + started_at nil + ended_at nil + creator 'rspec' + + after(:build) do |export| + export.class.skip_callback(:create, :before, :initialize_fields) + end + end +end diff --git a/spec/factories/exports/netex_exports.rb b/spec/factories/exports/netex_exports.rb new file mode 100644 index 000000000..0648bbc56 --- /dev/null +++ b/spec/factories/exports/netex_exports.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :netex_export, class: Export::Netex, parent: :export do + association :parent, factory: :workgroup_export + export_type :line + duration 90 + end +end diff --git a/spec/factories/exports/workgroup_exports.rb b/spec/factories/exports/workgroup_exports.rb new file mode 100644 index 000000000..f5dfb6b94 --- /dev/null +++ b/spec/factories/exports/workgroup_exports.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :workgroup_export, class: Export::Workgroup, parent: :export do + duration 90 + end +end diff --git a/spec/factories/import_messages.rb b/spec/factories/imports/import_messages.rb index 5d936679a..f5edf1685 100644 --- a/spec/factories/import_messages.rb +++ b/spec/factories/imports/import_messages.rb @@ -1,5 +1,5 @@ FactoryGirl.define do - factory :import_message do + factory :import_message, class: Import::Message do association :import association :resource, factory: :import_resource criticity :info diff --git a/spec/factories/import_resources.rb b/spec/factories/imports/import_resources.rb index 76afcc486..aaf7e3111 100644 --- a/spec/factories/import_resources.rb +++ b/spec/factories/imports/import_resources.rb @@ -1,5 +1,5 @@ FactoryGirl.define do - factory :import_resource do + factory :import_resource, class: Import::Resource do association :import status :WARNING sequence(:name) { |n| "Import resource #{n}" } diff --git a/spec/factories/imports.rb b/spec/factories/imports/imports.rb index e07447b60..cb7764cc6 100644 --- a/spec/factories/imports.rb +++ b/spec/factories/imports/imports.rb @@ -1,5 +1,5 @@ FactoryGirl.define do - factory :import do + factory :import, class: Import::Base do sequence(:name) { |n| "Import #{n}" } current_step_id "MyString" current_step_progress 1.5 @@ -16,7 +16,7 @@ FactoryGirl.define do end end - factory :bad_import do + factory :bad_import, class: Import::Base do sequence(:name) { |n| "Import #{n}" } current_step_id "MyString" current_step_progress 1.5 diff --git a/spec/factories/netex_imports.rb b/spec/factories/imports/netex_imports.rb index b59267a0a..7ee6839e8 100644 --- a/spec/factories/netex_imports.rb +++ b/spec/factories/imports/netex_imports.rb @@ -1,7 +1,7 @@ FactoryGirl.define do - factory :netex_import, class: NetexImport, parent: :import do + factory :netex_import, class: Import::Netex, parent: :import do file { File.open(Rails.root.join('spec', 'fixtures', 'OFFRE_TRANSDEV_2017030112251.zip')) } association :parent, factory: :workbench_import - + end end diff --git a/spec/factories/workbench_imports.rb b/spec/factories/imports/workbench_imports.rb index 466bfe688..5ed1ee4e5 100644 --- a/spec/factories/workbench_imports.rb +++ b/spec/factories/imports/workbench_imports.rb @@ -1,5 +1,5 @@ FactoryGirl.define do - factory :workbench_import, class: WorkbenchImport, parent: :import do + factory :workbench_import, class: Import::Workbench, parent: :import do file { File.open(Rails.root.join('spec', 'fixtures', 'OFFRE_TRANSDEV_2017030112251.zip')) } end end diff --git a/spec/javascript/vehicle_journeys/actions_spec.js b/spec/javascript/vehicle_journeys/actions_spec.js index 9710d833c..bfbb4fb36 100644 --- a/spec/javascript/vehicle_journeys/actions_spec.js +++ b/spec/javascript/vehicle_journeys/actions_spec.js @@ -194,7 +194,7 @@ describe('when toggling arrivals', () => { }) describe('when updating vjas time', () => { it('should create an action to update time', () => { - const val = 33, subIndex = 0, index = 0, timeUnit = 'minute', isDeparture = true, isArrivalsToggled = true + const val = 33, subIndex = 0, index = 0, timeUnit = 'minute', isDeparture = true, isArrivalsToggled = true, enforceConsistency = false const expectedAction = { type: 'UPDATE_TIME', val, @@ -202,7 +202,8 @@ describe('when updating vjas time', () => { index, timeUnit, isDeparture, - isArrivalsToggled + isArrivalsToggled, + enforceConsistency } expect(actions.updateTime(val, subIndex, index, timeUnit, isDeparture, isArrivalsToggled)).toEqual(expectedAction) }) @@ -640,7 +641,7 @@ describe('actions.adjustSchedule', () => { } }) it('should do nothing', () => { - expect(actions.adjustSchedule(action, schedule)).toEqual(schedule) + expect(actions.adjustSchedule(action, schedule, true)).toEqual(schedule) }) }), context('with a delta < 0', () => { @@ -662,7 +663,7 @@ describe('actions.adjustSchedule', () => { arrival_time: departure_time, delta: 0 } - expect(actions.adjustSchedule(action, schedule)).toEqual(expected) + expect(actions.adjustSchedule(action, schedule, true)).toEqual(expected) }) }) }), @@ -676,7 +677,7 @@ describe('actions.adjustSchedule', () => { } }) it('should do nothing', () => { - expect(actions.adjustSchedule(action, schedule)).toEqual(schedule) + expect(actions.adjustSchedule(action, schedule, true)).toEqual(schedule) }) }), context('with a delta < 0', () => { @@ -698,7 +699,7 @@ describe('actions.adjustSchedule', () => { arrival_time: arrival_time, delta: 0 } - expect(actions.adjustSchedule(action, schedule)).toEqual(expected) + expect(actions.adjustSchedule(action, schedule, true)).toEqual(expected) }) }) }) diff --git a/spec/models/chouette/stop_area_spec.rb b/spec/models/chouette/stop_area_spec.rb index 32ee5a3a6..e35300caf 100644 --- a/spec/models/chouette/stop_area_spec.rb +++ b/spec/models/chouette/stop_area_spec.rb @@ -24,6 +24,82 @@ describe Chouette::StopArea, :type => :model do end end + describe "#registration_number" do + let(:registration_number){ nil } + let(:registration_number_format){ nil } + let(:stop_area_referential){ create :stop_area_referential, registration_number_format: registration_number_format} + let(:stop_area){ build :stop_area, stop_area_referential: stop_area_referential, registration_number: registration_number} + context "without registration_number_format on the StopAreaReferential" do + it "should not generate a registration_number" do + stop_area.save! + expect(stop_area.registration_number).to_not be_present + end + + it "should not validate the registration_number format" do + stop_area.registration_number = "1234455" + expect(stop_area).to be_valid + end + + it "should not validate the registration_number uniqueness" do + stop_area.registration_number = "1234455" + create :stop_area, stop_area_referential: stop_area_referential, registration_number: stop_area.registration_number + expect(stop_area).to be_valid + end + end + + context "with a registration_number_format on the StopAreaReferential" do + let(:registration_number_format){ "XXX" } + + it "should generate a registration_number" do + stop_area.save! + expect(stop_area.registration_number).to be_present + expect(stop_area.registration_number).to match /[A-Z]{3}/ + end + + context "with a previous stop_area" do + it "should generate a registration_number" do + create :stop_area, stop_area_referential: stop_area_referential, registration_number: "AAA" + stop_area.save! + expect(stop_area.registration_number).to be_present + expect(stop_area.registration_number).to eq "AAB" + end + + it "should generate a registration_number" do + create :stop_area, stop_area_referential: stop_area_referential, registration_number: "ZZZ" + stop_area.save! + expect(stop_area.registration_number).to be_present + expect(stop_area.registration_number).to eq "AAA" + end + + it "should generate a registration_number" do + create :stop_area, stop_area_referential: stop_area_referential, registration_number: "AAA" + create :stop_area, stop_area_referential: stop_area_referential, registration_number: "ZZZ" + stop_area.save! + expect(stop_area.registration_number).to be_present + expect(stop_area.registration_number).to eq "AAB" + end + end + + it "should validate the registration_number format" do + stop_area.registration_number = "1234455" + expect(stop_area).to_not be_valid + stop_area.registration_number = "ABC" + expect(stop_area).to be_valid + expect{ stop_area.save! }.to_not raise_error + end + + it "should validate the registration_number uniqueness" do + stop_area.registration_number = "ABC" + create :stop_area, stop_area_referential: stop_area_referential, registration_number: stop_area.registration_number + expect(stop_area).to_not be_valid + + stop_area.registration_number = "ABD" + create :stop_area, registration_number: stop_area.registration_number + expect(stop_area).to be_valid + end + end + end + # describe ".latitude" do # it "should accept -90 value" do # subject = create :stop_area, :area_type => "BoardingPosition" diff --git a/spec/models/chouette/vehicle_journey_at_stop_spec.rb b/spec/models/chouette/vehicle_journey_at_stop_spec.rb index f79d19c88..ae9823243 100644 --- a/spec/models/chouette/vehicle_journey_at_stop_spec.rb +++ b/spec/models/chouette/vehicle_journey_at_stop_spec.rb @@ -27,13 +27,13 @@ RSpec.describe Chouette::VehicleJourneyAtStop, type: :model do it "disallows offsets greater than DAY_OFFSET_MAX" do expect(at_stop.day_offset_outside_range?( - Chouette::VehicleJourneyAtStop::DAY_OFFSET_MAX + 1 + Chouette::VehicleJourneyAtStop.day_offset_max + 1 )).to be true end it "allows offsets between 0 and DAY_OFFSET_MAX inclusive" do expect(at_stop.day_offset_outside_range?( - Chouette::VehicleJourneyAtStop::DAY_OFFSET_MAX + Chouette::VehicleJourneyAtStop.day_offset_max )).to be false end @@ -79,7 +79,7 @@ RSpec.describe Chouette::VehicleJourneyAtStop, type: :model do describe "#validate" do it "displays the proper error message when day offset exceeds the max" do - bad_offset = Chouette::VehicleJourneyAtStop::DAY_OFFSET_MAX + 1 + bad_offset = Chouette::VehicleJourneyAtStop.day_offset_max + 1 at_stop = build_stubbed( :vehicle_journey_at_stop, diff --git a/spec/models/chouette/vehicle_journey_at_stops_day_offset_spec.rb b/spec/models/chouette/vehicle_journey_at_stops_day_offset_spec.rb index 69a2d5cb9..91cbf9097 100644 --- a/spec/models/chouette/vehicle_journey_at_stops_day_offset_spec.rb +++ b/spec/models/chouette/vehicle_journey_at_stops_day_offset_spec.rb @@ -86,5 +86,76 @@ describe Chouette::VehicleJourneyAtStop do expect(at_stops[3].arrival_day_offset).to eq(2) expect(at_stops[3].departure_day_offset).to eq(2) end + + context "with stops in a different timezone" do + before do + allow_any_instance_of(Chouette::VehicleJourneyAtStop).to receive(:local_time).and_wrap_original {|m, t| m.call(t - 12.hours)} + end + + it "should apply the TZ" do + at_stops = [] + [ + ['22:30', '22:35'], + ['01:02', '01:14'], + ['12:02', '12:14'], + ].each do |arrival_time, departure_time| + at_stops << build_stubbed( + :vehicle_journey_at_stop, + arrival_time: arrival_time, + departure_time: departure_time + ) + end + offsetter = Chouette::VehicleJourneyAtStopsDayOffset.new(at_stops) + + offsetter.calculate! + + expect(at_stops[0].arrival_day_offset).to eq(0) + expect(at_stops[0].departure_day_offset).to eq(0) + + expect(at_stops[1].arrival_day_offset).to eq(0) + expect(at_stops[1].departure_day_offset).to eq(0) + + expect(at_stops[2].arrival_day_offset).to eq(1) + expect(at_stops[2].departure_day_offset).to eq(1) + end + end + + context "with stops in different timezones" do + + it "should apply the TZ" do + at_stops = [] + + stop_area = create(:stop_area, time_zone: "Atlantic Time (Canada)") + stop_point = create(:stop_point, stop_area: stop_area) + vehicle_journey_at_stop = build_stubbed( + :vehicle_journey_at_stop, + stop_point: stop_point, + arrival_time: '09:00', + departure_time: '09:05' + ) + + at_stops << vehicle_journey_at_stop + + stop_area = create(:stop_area, time_zone: "Paris") + stop_point = create(:stop_point, stop_area: stop_area) + vehicle_journey_at_stop = build_stubbed( + :vehicle_journey_at_stop, + stop_point: stop_point, + arrival_time: '05:00', + departure_time: '05:05' + ) + at_stops << vehicle_journey_at_stop + + offsetter = Chouette::VehicleJourneyAtStopsDayOffset.new(at_stops) + + offsetter.calculate! + + expect(at_stops[0].arrival_day_offset).to eq(0) + expect(at_stops[0].departure_day_offset).to eq(0) + + expect(at_stops[1].arrival_day_offset).to eq(1) + expect(at_stops[1].departure_day_offset).to eq(1) + end + end end end diff --git a/spec/models/compliance_check_set_spec.rb b/spec/models/compliance_check_set_spec.rb index b981a68bb..61421287a 100644 --- a/spec/models/compliance_check_set_spec.rb +++ b/spec/models/compliance_check_set_spec.rb @@ -81,14 +81,9 @@ RSpec.describe ComplianceCheckSet, type: :model do check_set.update_status - expect(check_set.status).to eq('successful') + expect(check_set.status).to eq('warning') end - it "returns true when the status did not get updated" do - check_set = create(:compliance_check_set) - - expect(check_set.update_status).to be true - end end describe 'possibility to delete the associated compliance_control_set' do diff --git a/spec/models/export/export_message_spec.rb b/spec/models/export/export_message_spec.rb new file mode 100644 index 000000000..61a3b6319 --- /dev/null +++ b/spec/models/export/export_message_spec.rb @@ -0,0 +1,7 @@ +require 'rails_helper' + +RSpec.describe Export::Message, :type => :model do + it { should validate_presence_of(:criticity) } + it { should belong_to(:export) } + it { should belong_to(:resource) } +end diff --git a/spec/models/export/export_resource_spec.rb b/spec/models/export/export_resource_spec.rb new file mode 100644 index 000000000..7537cd2a8 --- /dev/null +++ b/spec/models/export/export_resource_spec.rb @@ -0,0 +1,19 @@ +require 'rails_helper' + +RSpec.describe Export::Resource, :type => :model do + it { should belong_to(:export) } + + it { should enumerize(:status).in("OK", "ERROR", "WARNING", "IGNORED") } + + it { should validate_presence_of(:name) } + it { should validate_presence_of(:resource_type) } + it { should validate_presence_of(:reference) } + + describe 'states' do + let(:export_resource) { create(:export_resource) } + + it 'should initialize with new state' do + expect(export_resource.status).to eq("WARNING") + end + end +end diff --git a/spec/models/export/export_spec.rb b/spec/models/export/export_spec.rb new file mode 100644 index 000000000..edc0788f2 --- /dev/null +++ b/spec/models/export/export_spec.rb @@ -0,0 +1,239 @@ +RSpec.describe Export::Base, type: :model do + + it { should belong_to(:referential) } + it { should belong_to(:workbench) } + it { should belong_to(:parent) } + + it { should enumerize(:status).in("aborted", "canceled", "failed", "new", "pending", "running", "successful", "warning") } + + it { should validate_presence_of(:workbench) } + it { should validate_presence_of(:creator) } + + include ActionDispatch::TestProcess + it { should allow_value(fixture_file_upload('OFFRE_TRANSDEV_2017030112251.zip')).for(:file) } + it { should_not allow_value(fixture_file_upload('reflex_updated.xml')).for(:file).with_message(I18n.t('errors.messages.extension_whitelist_error', extension: '"xml"', allowed_types: "zip, csv, json")) } + + let(:workgroup_export) {netex_export.parent} + let(:workgroup_export_with_completed_steps) do + build_stubbed( + :workgroup_export, + total_steps: 2, + current_step: 2 + ) + end + + let(:netex_export) do + create( + :netex_export + ) + end + + describe ".abort_old" do + it "changes exports older than 4 hours to aborted" do + Timecop.freeze(Time.now) do + old_export = create( + :workgroup_export, + status: 'pending', + created_at: 4.hours.ago - 1.minute + ) + current_export = create(:workgroup_export, status: 'pending') + + Export::Base.abort_old + + expect(current_export.reload.status).to eq('pending') + expect(old_export.reload.status).to eq('aborted') + end + end + + it "doesn't work on exports with a `finished_status`" do + Timecop.freeze(Time.now) do + export = create( + :workgroup_export, + status: 'successful', + created_at: 4.hours.ago - 1.minute + ) + + Export::Base.abort_old + + expect(export.reload.status).to eq('successful') + end + end + + it "only works on the caller type" do + Timecop.freeze(Time.now) do + workgroup_export = create( + :workgroup_export, + status: 'pending', + created_at: 4.hours.ago - 1.minute + ) + netex_export = create( + :netex_export, + status: 'pending', + created_at: 4.hours.ago - 1.minute + ) + + Export::Netex.abort_old + + expect(workgroup_export.reload.status).to eq('pending') + expect(netex_export.reload.status).to eq('aborted') + end + end + end + + describe "#destroy" do + it "must destroy all child exports" do + netex_export = create(:netex_export) + + netex_export.parent.destroy + + expect(netex_export.parent).to be_destroyed + expect(Export::Netex.count).to eq(0) + end + + it "must destroy all associated Export::Messages" do + export = create(:netex_export) + create(:export_resource, export: export) + + export.destroy + + expect(Export::Resource.count).to eq(0) + end + + it "must destroy all associated Export::Resources" do + export = create(:netex_export) + create(:export_message, export: export) + + export.destroy + + expect(Export::Message.count).to eq(0) + end + end + + describe "#notify_parent" do + it "must call #child_change on its parent" do + allow(netex_export).to receive(:update) + + expect(workgroup_export).to receive(:child_change) + netex_export.status = :foo + netex_export.notify_parent + end + + it "must update the :notified_parent_at field of the child export" do + allow(workgroup_export).to receive(:child_change) + + Timecop.freeze(Time.now) do + netex_export.status = :bar + + netex_export.notify_parent + expect(netex_export.notified_parent_at.strftime('%Y-%m-%d %H:%M:%S.%3N')).to eq Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N') + expect(netex_export.reload.notified_parent_at.strftime('%Y-%m-%d %H:%M:%S.%3N')).to eq Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N') + end + end + end + + describe "#child_change" do + it "calls #update_status" do + allow(workgroup_export).to receive(:update) + + expect(workgroup_export).to receive(:update_status) + workgroup_export.child_change + end + end + + describe "#update_status" do + shared_examples( + "updates :status to failed when >=1 child has failing status" + ) do |failure_status| + it "updates :status to failed when >=1 child has failing status" do + workgroup_export = create(:workgroup_export) + create( + :netex_export, + parent: workgroup_export, + status: failure_status + ) + + workgroup_export.update_status + + expect(workgroup_export.status).to eq('failed') + end + end + + include_examples( + "updates :status to failed when >=1 child has failing status", + "failed" + ) + include_examples( + "updates :status to failed when >=1 child has failing status", + "aborted" + ) + include_examples( + "updates :status to failed when >=1 child has failing status", + "canceled" + ) + + it "updates :status to successful when all children are successful" do + workgroup_export = create(:workgroup_export) + exports = create_list( + :netex_export, + 2, + parent: workgroup_export, + status: 'successful' + ) + + workgroup_export.update_status + + expect(workgroup_export.status).to eq('successful') + end + + it "updates :status to failed when any child has failed" do + workgroup_export = create(:workgroup_export) + [ + 'failed', + 'successful' + ].each do |status| + create( + :netex_export, + parent: workgroup_export, + status: status + ) + end + + workgroup_export.update_status + + expect(workgroup_export.status).to eq('failed') + end + + it "updates :status to warning when any child has warning or successful" do + workgroup_export = create(:workgroup_export) + [ + 'warning', + 'successful' + ].each do |status| + create( + :netex_export, + parent: workgroup_export, + status: status + ) + end + + workgroup_export.update_status + + expect(workgroup_export.status).to eq('warning') + end + + it "updates :ended_at to now when status is finished" do + workgroup_export = create(:workgroup_export) + create( + :netex_export, + parent: workgroup_export, + status: 'failed' + ) + + Timecop.freeze(Time.now) do + workgroup_export.update_status + + expect(workgroup_export.ended_at).to eq(Time.now) + end + end + end +end diff --git a/spec/models/export/netex_export_spec.rb b/spec/models/export/netex_export_spec.rb new file mode 100644 index 000000000..d9cccd6ad --- /dev/null +++ b/spec/models/export/netex_export_spec.rb @@ -0,0 +1,19 @@ +RSpec.describe Export::Netex, type: [:model, :with_commit] do + + let( :boiv_iev_uri ){ URI("#{Rails.configuration.iev_url}/boiv_iev/referentials/exporter/new?id=#{subject.id}")} + + before do + allow(Thread).to receive(:new).and_yield + end + + context 'with referential' do + subject{ build( :netex_export, id: random_int ) } + + it 'will trigger the Java API' do + with_stubbed_request(:get, boiv_iev_uri) do |request| + with_commit{ subject.save! } + expect(request).to have_been_requested + end + end + end +end diff --git a/spec/models/export/workgroup_export_spec.rb b/spec/models/export/workgroup_export_spec.rb new file mode 100644 index 000000000..c812b2b21 --- /dev/null +++ b/spec/models/export/workgroup_export_spec.rb @@ -0,0 +1,10 @@ +RSpec.describe Export::Workgroup, type: [:model, :with_commit] do + it { should validate_presence_of(:duration) } + + it "should set options" do + expect(Export::Workgroup.options).to have_key :duration + expect(Export::Workgroup.options[:duration][:required]).to be_truthy + expect(Export::Workgroup.options[:duration][:default_value]).to eq 90 + expect(Export::Workgroup.options[:duration][:type]).to eq :integer + end +end diff --git a/spec/models/export_log_message_spec.rb b/spec/models/export_log_message_spec.rb deleted file mode 100644 index 5ab32dec0..000000000 --- a/spec/models/export_log_message_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'spec_helper' - -describe ExportLogMessage, :type => :model do - - # describe "#attributes" do - - # subject { create :export_log_message } - - # it "should read json stored in database" do - # subject.update_attribute :arguments, { "key" => "value"} - # expect(subject.raw_attributes).to eq({ "key" => "value"}.to_json) - # end - - # end - -end diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb deleted file mode 100644 index 13953078a..000000000 --- a/spec/models/export_spec.rb +++ /dev/null @@ -1,66 +0,0 @@ -# require 'spec_helper' - -# describe Export, :type => :model do - -# subject { create :export } - -# RSpec::Matchers.define :be_log_message do |expected| -# match do |actual| -# actual and expected.all? { |k,v| actual[k.to_s] == v } -# end -# end - -# describe "#export" do - -# before(:each) do -# allow(subject).to receive_messages :exporter => double(:export => true) -# end - -# it "should create a ExportLogmessage :started when started" do -# subject.export -# expect(subject.log_messages.first).to be_log_message(:key => "started") -# end - -# it "should create a ExportLogmessage :completed when completed" do -# subject.export -# expect(subject.log_messages.last).to be_log_message(:key => "completed") -# end - -# it "should create a ExportLogmessage :failed when failed" do -# pending -# # subject.loader.stub(:export).and_raise("export failed") -# subject.export -# expect(subject.log_messages.last).to be_log_message(:key => "failed") -# end - -# end - -# describe "#options" do - -# it "should be empty by default" do -# expect(subject.options).to be_empty -# end - -# end - -# describe ".types" do - -# it "should return available Export implementations" do -# expect(Export.types).to match_array(%w{NeptuneExport CsvExport GtfsExport NetexExport KmlExport HubExport}) -# end - -# end - -# describe ".new" do - -# it "should use type attribute to create a subclass" do -# expect(Export.new(:type => "NeptuneExport")).to be_an_instance_of(NeptuneExport) -# end - -# end - -# it_behaves_like TypeIdsModelable do -# let(:type_ids_model) { subject} -# end - -# end diff --git a/spec/models/export_task_spec.rb b/spec/models/export_task_spec.rb deleted file mode 100644 index 1a52a6175..000000000 --- a/spec/models/export_task_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'spec_helper' - -describe ExportTask, :type => :model do - - it { should_not validate_presence_of(:start_date) } - it { should_not validate_presence_of(:end_date) } - -end diff --git a/spec/models/gtfs_export_spec.rb b/spec/models/gtfs_export_spec.rb index ccc98e872..0ef3660f5 100644 --- a/spec/models/gtfs_export_spec.rb +++ b/spec/models/gtfs_export_spec.rb @@ -1,33 +1,33 @@ require 'spec_helper' -describe GtfsExport, :type => :model do - - describe "#time_zone" do - - context "when exported data are not StopAreas" do - - before do - subject.references_type = "network" - end - - it "should be mandatory" do - should validate_presence_of(:time_zone) - end - - end - - context "when export data are StopArea" do - - before do - subject.references_type = "stop_area" - end - - it "should be mandatory" do - should_not validate_presence_of(:time_zone) - end - - end - - end - -end +# describe GtfsExport, :type => :model do +# +# describe "#time_zone" do +# +# context "when exported data are not StopAreas" do +# +# before do +# subject.references_type = "network" +# end +# +# it "should be mandatory" do +# should validate_presence_of(:time_zone) +# end +# +# end +# +# context "when export data are StopArea" do +# +# before do +# subject.references_type = "stop_area" +# end +# +# it "should be mandatory" do +# should_not validate_presence_of(:time_zone) +# end +# +# end +# +# end +# +# end diff --git a/spec/models/gtfs_import_spec.rb b/spec/models/gtfs_import_spec.rb index 07cc1905d..5cb69332c 100644 --- a/spec/models/gtfs_import_spec.rb +++ b/spec/models/gtfs_import_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe GtfsImport, :type => :model do +describe Import::Gtfs, :type => :model do # describe "#object_id_prefix" do diff --git a/spec/models/import_message_spec.rb b/spec/models/import/import_message_spec.rb index 2d8aac2b7..48e03a2cc 100644 --- a/spec/models/import_message_spec.rb +++ b/spec/models/import/import_message_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe ImportMessage, :type => :model do +RSpec.describe Import::Message, :type => :model do it { should validate_presence_of(:criticity) } it { should belong_to(:import) } it { should belong_to(:resource) } diff --git a/spec/models/import_resource_spec.rb b/spec/models/import/import_resource_spec.rb index c88bb5dd2..7d2eab8f1 100644 --- a/spec/models/import_resource_spec.rb +++ b/spec/models/import/import_resource_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe ImportResource, :type => :model do +RSpec.describe Import::Resource, :type => :model do it { should belong_to(:import) } it { should enumerize(:status).in("OK", "ERROR", "WARNING", "IGNORED") } diff --git a/spec/models/import_spec.rb b/spec/models/import/import_spec.rb index 8b85f151b..102c0e1d6 100644 --- a/spec/models/import_spec.rb +++ b/spec/models/import/import_spec.rb @@ -1,4 +1,4 @@ -RSpec.describe Import, type: :model do +RSpec.describe Import::Base, type: :model do it { should belong_to(:referential) } it { should belong_to(:workbench) } @@ -24,7 +24,7 @@ RSpec.describe Import, type: :model do end let(:netex_import) do - build_stubbed( + create( :netex_import ) end @@ -39,7 +39,7 @@ RSpec.describe Import, type: :model do ) current_import = create(:workbench_import, status: 'pending') - Import.abort_old + Import::Base.abort_old expect(current_import.reload.status).to eq('pending') expect(old_import.reload.status).to eq('aborted') @@ -54,7 +54,7 @@ RSpec.describe Import, type: :model do created_at: 4.hours.ago - 1.minute ) - Import.abort_old + Import::Base.abort_old expect(import.reload.status).to eq('successful') end @@ -73,7 +73,7 @@ RSpec.describe Import, type: :model do created_at: 4.hours.ago - 1.minute ) - NetexImport.abort_old + Import::Netex.abort_old expect(workbench_import.reload.status).to eq('pending') expect(netex_import.reload.status).to eq('aborted') @@ -88,25 +88,25 @@ RSpec.describe Import, type: :model do netex_import.parent.destroy expect(netex_import.parent).to be_destroyed - expect(NetexImport.count).to eq(0) + expect(Import::Netex.count).to eq(0) end - it "must destroy all associated ImportMessages" do + it "must destroy all associated Import::Messages" do import = create(:import) create(:import_resource, import: import) import.destroy - expect(ImportResource.count).to eq(0) + expect(Import::Resource.count).to eq(0) end - it "must destroy all associated ImportResources" do + it "must destroy all associated Import::Resources" do import = create(:import) create(:import_message, import: import) import.destroy - expect(ImportMessage.count).to eq(0) + expect(Import::Message.count).to eq(0) end end @@ -115,19 +115,18 @@ RSpec.describe Import, type: :model do allow(netex_import).to receive(:update) expect(workbench_import).to receive(:child_change) - + netex_import.status = :foo netex_import.notify_parent end it "must update the :notified_parent_at field of the child import" do allow(workbench_import).to receive(:child_change) - - Timecop.freeze(DateTime.now) do - expect(netex_import).to receive(:update).with( - notified_parent_at: DateTime.now - ) + Timecop.freeze(Time.now) do + netex_import.status = :bar netex_import.notify_parent + expect(netex_import.notified_parent_at.strftime('%Y-%m-%d %H:%M:%S.%3N')).to eq Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N') + expect(netex_import.reload.notified_parent_at.strftime('%Y-%m-%d %H:%M:%S.%3N')).to eq Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N') end end end diff --git a/spec/models/import/netex_import_spec.rb b/spec/models/import/netex_import_spec.rb index 8ffeed1f4..6424fbfe1 100644 --- a/spec/models/import/netex_import_spec.rb +++ b/spec/models/import/netex_import_spec.rb @@ -1,8 +1,7 @@ -RSpec.describe NetexImport, type: [:model, :with_commit] do +RSpec.describe Import::Netex, type: [:model, :with_commit] do let( :boiv_iev_uri ){ URI("#{Rails.configuration.iev_url}/boiv_iev/referentials/importer/new?id=#{subject.id}")} - before do allow(Thread).to receive(:new).and_yield end @@ -30,4 +29,42 @@ RSpec.describe NetexImport, type: [:model, :with_commit] do end end + describe "#destroy" do + it "must destroy its associated Referential if ready: false" do + workbench_import = create(:workbench_import) + referential_ready_false = create(:referential, ready: false) + referential_ready_true = create(:referential, ready: true) + create( + :netex_import, + parent: workbench_import, + referential: referential_ready_false + ) + create( + :netex_import, + parent: workbench_import, + referential: referential_ready_true + ) + + workbench_import.destroy + + expect( + Referential.where(id: referential_ready_false.id).exists? + ).to be false + expect( + Referential.where(id: referential_ready_true.id).exists? + ).to be true + end + + it "doesn't try to destroy nil referentials" do + workbench_import = create(:workbench_import) + create( + :netex_import, + parent: workbench_import, + referential: nil + ) + + expect { workbench_import.destroy }.not_to raise_error + end + end + end diff --git a/spec/models/import_service_spec.rb b/spec/models/import_service_spec.rb deleted file mode 100644 index e7ee062d6..000000000 --- a/spec/models/import_service_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'spec_helper' - -describe ImportService, :type => :model do - - let(:referential) { create(:referential, :slug => "test") } - - subject { ImportService.new(referential) } - - describe '.find' do - - it "should build an import with a scheduled job" do - end - - it "should build an import with a terminated job" do - end - - end - -end diff --git a/spec/models/import_task_spec.rb b/spec/models/import_task_spec.rb deleted file mode 100644 index 3aa006a69..000000000 --- a/spec/models/import_task_spec.rb +++ /dev/null @@ -1,196 +0,0 @@ -# require 'spec_helper' - -# describe ImportTask, :type => :model do - -# subject { build :import_task } - -# describe ".new" do - -# it "should use type attribute to create a subclass" do -# expect(ImportTask.new(:format => "Neptune")).to be_an_instance_of(NeptuneImport) -# expect(ImportTask.new(:format => "Gtfs")).to be_an_instance_of(GtfsImport) -# expect(ImportTask.new(:format => "Netex")).to be_an_instance_of(NetexImport) -# expect(ImportTask.new(:format => "Csv")).to be_an_instance_of(CsvImport) - -# expect(NeptuneImport.new).to be_an_instance_of(NeptuneImport) -# expect(GtfsImport.new).to be_an_instance_of(GtfsImport) -# expect(NetexImport.new).to be_an_instance_of(NetexImport) -# expect(CsvImport.new).to be_an_instance_of(CsvImport) -# end - -# end - -# describe "#delayed_import" do -# before(:each) do -# allow(subject).to receive_messages( :delay => double( :import => true)) -# end -# it "should call delay#import" do -# expect(subject.delay).to receive( :import) -# subject.send :delayed_import -# end -# end - -# describe ".create" do -# before(:each) do -# allow(subject).to receive_messages( :save_resources => true ) -# end -# it "should call save_resource" do -# expect(subject).to receive( :save_resources) -# subject.send :save -# end -# it "should update file_path with #saved_resources" do -# subject.send :save -# expect(ImportTask.find( subject.id).file_path).to eq(subject.send( :saved_resources)) -# end -# it "should have a compliance_check_task" do -# subject.send :save -# expect(ImportTask.find( subject.id).compliance_check_task).not_to be_nil -# end -# end - -# describe "#compliance_check_task" do -# let(:rule_parameter_set){ Factory( :rule_parameter_set) } -# let(:import_task){ Factory(:import_task, :rule_parameter_set_id => rule_parameter_set.id) } -# let(:compliance_check_task){ import_task.compliance_check_task } - -# it "should have same #referential as import_task" do -# expect(compliance_check_task.referential).to eq(import_task.referential) -# end - -# it "should have same #rule_parameter_set_id as import_task" do -# expect(compliance_check_task.rule_parameter_set_id).to eq(import_task.rule_parameter_set_id) -# end - -# it "should have same #user_id as import_task" do -# expect(compliance_check_task.user_id).to eq(import_task.user_id) -# end - -# it "should have same #user_name as import_task" do -# expect(compliance_check_task.user_name).to eq(import_task.user_name) -# end -# end - -# describe "#file_path_extension" do -# let(:import_task){ Factory(:import_task) } -# context "zip file to import" do -# before(:each) do -# import_task.file_path = "aaa/bbb.zip" -# end -# it "should return zip" do -# expect(import_task.file_path_extension).to eq("zip") -# end -# end -# context "xml file to import" do -# before(:each) do -# import_task.file_path = "aaa/bbb.xml" -# end -# it "should return xml" do -# expect(import_task.file_path_extension).to eq("xml") -# end -# end -# context "csv file to import" do -# before(:each) do -# import_task.file_path = "aaa/bbb.csv" -# end -# it "should return csv" do -# expect(import_task.file_path_extension).to eq("basic") -# end -# end - -# end - -# context "options attributes" do -# let(:import_task){ Factory(:import_task) } -# describe "#no_save" do -# it "should read parameter_set['no_save']" do -# import_task.parameter_set[ "no_save"] = "dummy" -# expect(import_task.no_save).to eq("dummy") -# end -# end -# describe "#format" do -# it "should read parameter_set['format']" do -# import_task.parameter_set[ "format"] = "dummy" -# expect(import_task.format).to eq("dummy") -# end -# end -# describe "#file_path" do -# it "should read parameter_set['file_path']" do -# import_task.parameter_set[ "file_path"] = "dummy" -# expect(import_task.file_path).to eq("dummy") -# end -# end -# describe "#no_save=" do -# it "should read parameter_set['no_save']" do -# import_task.no_save = "dummy" -# expect(import_task.parameter_set[ "no_save"]).to eq(false) -# end -# end -# describe "#format=" do -# it "should read parameter_set['format']" do -# import_task.format = "dummy" -# expect(import_task.parameter_set[ "format"]).to eq("dummy") -# end -# end -# describe "#file_path=" do -# it "should read parameter_set['file_path']" do -# import_task.file_path = "dummy" -# expect(import_task.parameter_set[ "file_path"]).to eq("dummy") -# end -# end -# end - -# describe "#chouette_command" do -# it "should be a Chouette::Command instance" do -# expect(subject.send( :chouette_command).class).to eq(Chouette::Command) -# end -# it "should have schema same as referential.slug" do -# expect(subject.send( :chouette_command).schema).to eq(subject.referential.slug) -# end -# end - -# describe "#import" do -# let(:import_task){ Factory(:import_task) } -# let(:chouette_command) { "dummy" } -# context "for failing import" do -# before(:each) do -# allow(chouette_command).to receive( :run!).and_raise( "dummy") -# allow(import_task).to receive_messages( :chouette_command => chouette_command) -# end -# it "should have status 'failed'" do -# import_task.import -# expect(import_task.status).to eq("failed") -# end -# it "should have status 'failed' for compliance_check_task" do -# import_task.import -# expect(import_task.compliance_check_task.status).to eq("failed") -# end -# end -# context "for successful import" do -# before(:each) do -# allow(import_task).to receive_messages( :chouette_command => double( :run! => true )) -# end -# it "should have status 'completed'" do -# import_task.import -# expect(import_task.status).to eq("completed") -# end -# it "should have status 'completed' for compliance_check_task" do -# import_task.import -# expect(import_task.status).to eq("completed") -# end -# end -# end - -# describe "#import" do -# let(:import_task){ Factory(:import_task) } -# let(:command_args){ "dummy" } -# before(:each) do -# allow(import_task).to receive_messages( :chouette_command => double( :run! => true )) -# allow(import_task).to receive_messages( :chouette_command_args => command_args) -# end -# it "should call chouette_command.run! with :c => 'import', :id => id" do -# expect(import_task.send( :chouette_command)).to receive( :run! ).with( command_args) -# import_task.import -# end -# end - -# end diff --git a/spec/models/netex_export_spec.rb b/spec/models/netex_export_spec.rb index 1d09fa07f..345bf4d5a 100644 --- a/spec/models/netex_export_spec.rb +++ b/spec/models/netex_export_spec.rb @@ -1,10 +1,10 @@ require 'spec_helper' -describe NetexExport, :type => :model do - - # describe '#export_options' do - # subject { super().export_options } - # it { is_expected.to include(:format => :netex) } - # end - -end +# describe NetexExport, :type => :model do +# +# # describe '#export_options' do +# # subject { super().export_options } +# # it { is_expected.to include(:format => :netex) } +# # end +# +# end diff --git a/spec/models/netex_import_spec.rb b/spec/models/netex_import_spec.rb deleted file mode 100644 index c6051a869..000000000 --- a/spec/models/netex_import_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -RSpec.describe NetexImport, type: :model do - describe "#destroy" do - it "must destroy its associated Referential if ready: false" do - workbench_import = create(:workbench_import) - referential_ready_false = create(:referential, ready: false) - referential_ready_true = create(:referential, ready: true) - create( - :netex_import, - parent: workbench_import, - referential: referential_ready_false - ) - create( - :netex_import, - parent: workbench_import, - referential: referential_ready_true - ) - - workbench_import.destroy - - expect( - Referential.where(id: referential_ready_false.id).exists? - ).to be false - expect( - Referential.where(id: referential_ready_true.id).exists? - ).to be true - end - - it "doesn't try to destroy nil referentials" do - workbench_import = create(:workbench_import) - create( - :netex_import, - parent: workbench_import, - referential: nil - ) - - expect { workbench_import.destroy }.not_to raise_error - end - end -end diff --git a/spec/models/simple_exporter_spec.rb b/spec/models/simple_exporter_spec.rb index 75051aeb9..a42daafe1 100644 --- a/spec/models/simple_exporter_spec.rb +++ b/spec/models/simple_exporter_spec.rb @@ -5,7 +5,7 @@ RSpec.describe SimpleExporter do SimpleExporter.define :foo expect do SimpleExporter.new(configuration_name: :test).export - end.to raise_error + end.to raise_error(RuntimeError) end end context "with a complete configuration" do @@ -18,9 +18,9 @@ RSpec.describe SimpleExporter do it "should define an exporter" do expect{SimpleExporter.find_configuration(:foo)}.to_not raise_error expect{SimpleExporter.new(configuration_name: :foo, filepath: "").export}.to_not raise_error - expect{SimpleExporter.find_configuration(:bar)}.to raise_error - expect{SimpleExporter.new(configuration_name: :bar, filepath: "")}.to raise_error - expect{SimpleExporter.new(configuration_name: :bar, filepath: "").export}.to raise_error + expect{SimpleExporter.find_configuration(:bar)}.to raise_error(RuntimeError) + expect{SimpleExporter.new(configuration_name: :bar, filepath: "")}.to raise_error(RuntimeError) + expect{SimpleExporter.new(configuration_name: :bar, filepath: "").export}.to raise_error(RuntimeError) expect{SimpleExporter.create(configuration_name: :foo, filepath: "")}.to change{SimpleExporter.count}.by 1 end end @@ -33,7 +33,7 @@ RSpec.describe SimpleExporter do config.add_column :name config.add_column :name end - end.to raise_error + end.to raise_error(RuntimeError) end end end diff --git a/spec/models/simple_importer_spec.rb b/spec/models/simple_importer_spec.rb index 5f9eb0651..8f4d7cfdd 100644 --- a/spec/models/simple_importer_spec.rb +++ b/spec/models/simple_importer_spec.rb @@ -6,7 +6,7 @@ RSpec.describe SimpleImporter do SimpleImporter.define :foo expect do SimpleImporter.new(configuration_name: :foo, filepath: "").import - end.to raise_error + end.to raise_error(RuntimeError) end end context "with a complete configuration" do @@ -20,8 +20,8 @@ RSpec.describe SimpleImporter do expect{SimpleImporter.find_configuration(:foo)}.to_not raise_error expect{SimpleImporter.new(configuration_name: :foo, filepath: "")}.to_not raise_error expect{SimpleImporter.new(configuration_name: :foo, filepath: "").import}.to_not raise_error - expect{SimpleImporter.find_configuration(:bar)}.to raise_error - expect{SimpleImporter.new(configuration_name: :bar, filepath: "")}.to raise_error + expect{SimpleImporter.find_configuration(:bar)}.to raise_error(RuntimeError) + expect{SimpleImporter.new(configuration_name: :bar, filepath: "")}.to raise_error(RuntimeError) expect{SimpleImporter.create(configuration_name: :foo, filepath: "")}.to change{SimpleImporter.count}.by 1 end end @@ -49,7 +49,7 @@ RSpec.describe SimpleImporter do end it "should import the given file" do - expect{importer.import verbose: true}.to change{Chouette::StopArea.count}.by 1 + expect{importer.import verbose: false}.to change{Chouette::StopArea.count}.by 1 expect(importer.status).to eq "success" stop = Chouette::StopArea.last expect(stop.name).to eq "Nom du Stop" diff --git a/spec/models/simple_interfaces_group_spec.rb b/spec/models/simple_interfaces_group_spec.rb new file mode 100644 index 000000000..0b6d360de --- /dev/null +++ b/spec/models/simple_interfaces_group_spec.rb @@ -0,0 +1,31 @@ +RSpec.describe SimpleInterfacesGroup do + context "with successful interfaces" do + before do + create :stop_area + SimpleExporter.define :test_1 do |config| + config.collection = Chouette::StopArea.all + config.key = "name" + config.add_column :name + end + + SimpleExporter.define :test_2 do |config| + config.collection = Chouette::StopArea.all + config.key = "name" + config.add_column :lat, attribute: :latitude + end + end + + it "should run all interfaces" do + test_1 = SimpleExporter.new(configuration_name: :test_1, filepath: "tmp/test1.csv") + test_2 = SimpleExporter.new(configuration_name: :test_2, filepath: "tmp/test1.csv") + + expect(test_1).to receive(:export).and_call_original + expect(test_2).to receive(:export).and_call_original + + group = SimpleInterfacesGroup.new "group" + group.add_interface test_1, "Test 1", :export + group.add_interface test_2, "Test 2", :export + group.run + end + end +end diff --git a/spec/models/stop_area_referential_spec.rb b/spec/models/stop_area_referential_spec.rb index dd2bdce20..d68b5b809 100644 --- a/spec/models/stop_area_referential_spec.rb +++ b/spec/models/stop_area_referential_spec.rb @@ -8,4 +8,9 @@ RSpec.describe StopAreaReferential, :type => :model do it { is_expected.to have_many(:stop_area_referential_syncs) } it { is_expected.to have_many(:workbenches) } it { should validate_presence_of(:objectid_format) } + it { should allow_value('').for(:registration_number_format) } + it { should allow_value('X').for(:registration_number_format) } + it { should allow_value('XXXXX').for(:registration_number_format) } + it { should_not allow_value('123').for(:registration_number_format) } + it { should_not allow_value('ABC').for(:registration_number_format) } end diff --git a/spec/requests/api/v1/netex_import_spec.rb b/spec/requests/api/v1/netex_import_spec.rb index 8597c1d32..14dac9a25 100644 --- a/spec/requests/api/v1/netex_import_spec.rb +++ b/spec/requests/api/v1/netex_import_spec.rb @@ -1,4 +1,4 @@ -RSpec.describe "NetexImport", type: :request do +RSpec.describe "Import::Netex", type: :request do describe 'POST netex_imports' do @@ -39,7 +39,7 @@ RSpec.describe "NetexImport", type: :request do post_request.(netex_import: legal_attributes) expect( response ).to be_success expect( json_response_body ).to eq( - 'id' => NetexImport.last.id, + 'id' => Import::Netex.last.id, 'referential_id' => Referential.last.id, 'workbench_id' => workbench.id ) @@ -51,7 +51,7 @@ RSpec.describe "NetexImport", type: :request do create(:line, objectid: 'STIF:CODIFLIGNE:Line:C00108', line_referential: workbench.line_referential) create(:line, objectid: 'STIF:CODIFLIGNE:Line:C00109', line_referential: workbench.line_referential) - expect{ post_request.(netex_import: legal_attributes) }.to change{NetexImport.count}.by(1) + expect{ post_request.(netex_import: legal_attributes) }.to change{Import::Netex.count}.by(1) end it 'creates a correct Referential', pending: 'see #5073' do @@ -96,7 +96,7 @@ RSpec.describe "NetexImport", type: :request do end it 'does not create an Import object' do - expect{ post_request.(netex_import: illegal_attributes) }.not_to change{Import.count} + expect{ post_request.(netex_import: illegal_attributes) }.not_to change{Import::Base.count} end it 'might not create a referential' do diff --git a/spec/services/parent_notifier_spec.rb b/spec/services/parent_notifier_spec.rb index ecf508fcd..d2dc6b184 100644 --- a/spec/services/parent_notifier_spec.rb +++ b/spec/services/parent_notifier_spec.rb @@ -20,7 +20,7 @@ RSpec.describe ParentNotifier do expect(netex_import).to receive(:notify_parent) end - ParentNotifier.new(Import).notify_when_finished(netex_imports) + ParentNotifier.new(Import::Base).notify_when_finished(netex_imports) end it "doesn't call #notify_parent if its `notified_parent_at` is set" do @@ -33,7 +33,7 @@ RSpec.describe ParentNotifier do expect(netex_import).not_to receive(:notify_parent) - ParentNotifier.new(Import).notify_when_finished + ParentNotifier.new(Import::Base).notify_when_finished end end @@ -46,8 +46,10 @@ RSpec.describe ParentNotifier do notified_parent_at: nil ) + Import::Base.where(id: netex_import).update_all notified_parent_at: nil + expect( - ParentNotifier.new(Import).objects_pending_notification + ParentNotifier.new(Import::Base).objects_pending_notification ).to eq([netex_import]) end @@ -55,7 +57,7 @@ RSpec.describe ParentNotifier do create(:import, parent: nil) expect( - ParentNotifier.new(Import).objects_pending_notification + ParentNotifier.new(Import::Base).objects_pending_notification ).to be_empty end @@ -70,7 +72,7 @@ RSpec.describe ParentNotifier do end expect( - ParentNotifier.new(Import).objects_pending_notification + ParentNotifier.new(Import::Base).objects_pending_notification ).to be_empty end @@ -83,7 +85,7 @@ RSpec.describe ParentNotifier do ) expect( - ParentNotifier.new(Import).objects_pending_notification + ParentNotifier.new(Import::Base).objects_pending_notification ).to be_empty end end diff --git a/spec/support/permissions.rb b/spec/support/permissions.rb index 95afd6c1c..825e44725 100644 --- a/spec/support/permissions.rb +++ b/spec/support/permissions.rb @@ -17,6 +17,7 @@ module Support connection_links calendars footnotes + exports imports merges journey_patterns |
