diff --git a/Gemfile b/Gemfile index 3ee9804..dfc239e 100644 --- a/Gemfile +++ b/Gemfile @@ -192,7 +192,7 @@ gem 'activerecord-session_store' # JSON Schema validation -gem 'json_schemer' +gem 'json_schemer', '0.2.11' # JsonPath is a way of addressing elements within a JSON object. Similar to xpath of yore, JsonPath lets you traverse a json object and manipulate or access it. gem "jsonpath" diff --git a/Gemfile.lock b/Gemfile.lock index 74c4de1..7074a85 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -118,7 +118,7 @@ dragonfly-s3_data_store (1.3.0) dragonfly (~> 1.0) fog-aws - ecma-re-validator (0.2.0) + ecma-re-validator (0.2.1) regexp_parser (~> 1.2) erubi (1.9.0) erubis (2.7.0) @@ -188,7 +188,7 @@ guard (~> 2.1) guard-compat (~> 1.1) rspec (>= 2.99.0, < 4.0) - hana (1.3.5) + hana (1.3.6) hashdiff (1.0.0) hashie (3.6.0) highline (2.0.3) @@ -207,7 +207,7 @@ activesupport (>= 3.0.0) multi_json (>= 1.2) json (2.3.0) - json_schemer (0.2.10) + json_schemer (0.2.11) ecma-re-validator (~> 0.2) hana (~> 1.3) regexp_parser (~> 1.5) @@ -526,7 +526,7 @@ htmltoword (= 1.1.0) httparty jbuilder (~> 2.6.0) - json_schemer + json_schemer (= 0.2.11) jsonpath kaminari ledermann-rails-settings diff --git a/app/assets/stylesheets/dmpopidor.scss b/app/assets/stylesheets/dmpopidor.scss index 6e2282e..d50bd1c 100644 --- a/app/assets/stylesheets/dmpopidor.scss +++ b/app/assets/stylesheets/dmpopidor.scss @@ -12,6 +12,7 @@ $light-rust: #faefed; $yellow: #FFCC00; $light-yellow: #fff9e5; +$green: #2CAD5D; body { display: flex; @@ -1013,10 +1014,24 @@ display: flex; align-items: center; padding-top: 5px; - + justify-content: space-between; input { flex: 9; } + span { + text-align: center; + margin-left: 5px; + &.invalid { + color: $rust; + } + &.valid { + color: $green; + } + &.explanation { + color: $blue; + cursor: pointer; + } + } } fieldset.answer-fieldset { width: 100%; diff --git a/app/controllers/answers_controller.rb b/app/controllers/answers_controller.rb index d03fad6..07d5c90 100644 --- a/app/controllers/answers_controller.rb +++ b/app/controllers/answers_controller.rb @@ -4,7 +4,6 @@ class AnswersController < ApplicationController prepend Dmpopidor::Controllers::Answers - include DynamicFormHelper respond_to :html # POST /answers/create_or_update diff --git a/app/controllers/madmp_fragments_controller.rb b/app/controllers/madmp_fragments_controller.rb index 493ea78..9f75614 100644 --- a/app/controllers/madmp_fragments_controller.rb +++ b/app/controllers/madmp_fragments_controller.rb @@ -3,44 +3,83 @@ class MadmpFragmentsController < ApplicationController after_action :verify_authorized + include DynamicFormHelper def create_or_update p_params = permitted_params() schema = MadmpSchema.find(p_params[:schema_id]) classname = schema.classname - data = schema_params(schema) + data = data_reformater(schema.schema, schema_params(schema)) + @fragment = nil - # rubocop:disable BlockLength - MadmpFragment.transaction do - if p_params[:id].empty? - @fragment = MadmpFragment.new( - dmp_id: p_params[:dmp_id], - parent_id: p_params[:parent_id], - madmp_schema: schema, - data: data - ) - @fragment.classname = classname - authorize @fragment - @fragment.save! - else - @fragment = MadmpFragment.find_by!({ - id: p_params[:id], - dmp_id: p_params[:dmp_id] - }) - new_data = @fragment.data.merge(data) - authorize @fragment - @fragment.update( - data: new_data - ) + if params[:id].present? + # rubocop:disable BlockLength + Answer.transaction do + begin + @fragment = MadmpFragment.find_by!( + id: params[:id], + dmp_id: p_params[:dmp_id] + ) + data = @fragment.data.merge(data) + data = data.merge({ + "validations" => MadmpFragment.validate_data(data, schema.schema) + }) + @fragment.assign_attributes(data: data) + + authorize @fragment + unless p_params[:source] == "modal" + @fragment.answer.update!({ + lock_version: p_params[:answer][:lock_version], + is_common: p_params[:answer][:is_common], + user_id: current_user.id + }) + end + @fragment.save! + rescue ActiveRecord::StaleObjectError + @stale_fragment = @fragment + @fragment = MadmpFragment.find_by({ + id: params[:id], + dmp_id: p_params[:dmp_id] + }) + end end + else + @fragment = MadmpFragment.new( + dmp_id: p_params[:dmp_id], + parent_id: p_params[:parent_id], + madmp_schema: schema + ) + @fragment.classname = classname + data = data.merge({ + "validations" => MadmpFragment.validate_data(data, schema.schema) + }) + @fragment.assign_attributes(data: data) + + unless p_params[:source] == "modal" + @fragment.answer = Answer.create!({ + research_output_id: p_params[:answer][:research_output_id], + plan_id: p_params[:answer][:plan_id], + question_id: p_params[:answer][:question_id], + lock_version: p_params[:answer][:lock_version], + is_common: p_params[:answer][:is_common], + user_id: current_user.id + }) + end + authorize @fragment + @fragment.save! + end - + if @fragment.present? - render json: { - "fragment_id" => @fragment.parent_id, - "classname" => classname, - "html" => render_fragment_list(@fragment.dmp_id, @fragment.parent_id, schema.id) - } + if @fragment.answer.present? + render json: render_fragment_form(@fragment, @stale_fragment) + else + render json: { + "fragment_id" => @fragment.parent_id, + "classname" => classname, + "html" => render_fragment_list(@fragment.dmp_id, @fragment.parent_id, schema.id) + }.to_json + end end end @@ -141,6 +180,62 @@ end end + def render_fragment_form(fragment, stale_fragment = nil) + answer = fragment.answer + question = answer.question + research_output = answer.research_output + section = question.section + plan = answer.plan + + return { + "answer" => { + "id" => answer.id + }, + "question" => { + "id" => question.id, + "answer_lock_version" => answer.lock_version, + "locking" => stale_fragment ? + render_to_string(partial: "madmp_fragments/locking", locals: { + question: question, + answer: answer, + fragment: stale_fragment, + research_output: research_output, + user: answer.user + }, formats: [:html]) : + nil, + "form" => render_to_string(partial: "madmp_fragments/new_edit", locals: { + question: question, + answer: answer, + fragment: fragment , + research_output: research_output, + dmp_id: fragment.dmp_id, + parent_id: fragment.parent_id, + readonly: false + }, formats: [:html]), + "answer_status" => render_to_string(partial: "answers/status", locals: { + answer: answer + }, formats: [:html]) + }, + "section" => { + "id" => section.id, + "progress" => render_to_string(partial: "/org_admin/sections/progress", locals: { + section: section, + plan: plan + }, formats: [:html]) + }, + "plan" => { + "id" => plan.id, + "progress" => render_to_string(partial: "plans/progress", locals: { + plan: plan, + current_phase: section.phase + }, formats: [:html]) + }, + "research_output" => { + "id" => research_output.id + } + }.to_json + end + # Get the parameters conresponding to the schema def schema_params(schema, flat = false) s_params = schema.generate_strong_params(flat) @@ -148,7 +243,10 @@ end def permitted_params - permit_arr = [:id, :dmp_id, :parent_id, :schema_id] + permit_arr = [:id, :dmp_id, :parent_id, :schema_id, :source, + answer: [:id, :plan_id, :research_output_id, + :question_id, :lock_version, :is_common] + ] params.require(:madmp_fragment).permit(permit_arr) end end \ No newline at end of file diff --git a/app/helpers/dynamic_form_helper.rb b/app/helpers/dynamic_form_helper.rb index 940f391..14bfba8 100644 --- a/app/helpers/dynamic_form_helper.rb +++ b/app/helpers/dynamic_form_helper.rb @@ -1,6 +1,6 @@ module DynamicFormHelper - def create_text_field(form, value, name, label, html_class: nil, is_multiple: false, readonly: false, index: 0) + def create_text_field(form, value, name, label, validation: nil, html_class: nil, is_multiple: false, readonly: false, index: 0) render partial: 'shared/dynamic_form/fields/text_field', locals: { f: form, @@ -11,13 +11,14 @@ field_label: label, field_class: html_class, input_type: nil, - readonly: readonly + readonly: readonly, + validation: validation } end - def create_url_field(form, value, name, label, html_class: nil, is_multiple: false, readonly: false, index: 0) + def create_url_field(form, value, name, label, validation: nil, html_class: nil, is_multiple: false, readonly: false, index: 0) render partial: 'shared/dynamic_form/fields/text_field', locals: { f: form, @@ -28,13 +29,14 @@ field_label: label, field_class: html_class, input_type: 'url', - readonly: readonly + readonly: readonly, + validation: validation } end - def create_email_field(form, value, name, label, html_class: nil, is_multiple: false, readonly: false, index: 0) + def create_email_field(form, value, name, label, validation: nil, html_class: nil, is_multiple: false, readonly: false, index: 0) render partial: 'shared/dynamic_form/fields/text_field', locals: { f: form, @@ -45,13 +47,14 @@ field_label: label, field_class: html_class, input_type: 'email', - readonly: readonly + readonly: readonly, + validation: validation } end - def create_date_field(form, value, name, label, html_class: nil, is_multiple: false, readonly: false, index: 0) + def create_date_field(form, value, name, label, validation: nil, html_class: nil, is_multiple: false, readonly: false, index: 0) render partial: 'shared/dynamic_form/fields/text_field', locals: { f: form, @@ -62,13 +65,14 @@ field_label: label, field_class: html_class, input_type: 'date', - readonly: readonly + readonly: readonly, + validation: validation } end - def create_number_field(form, value, name, label, html_class: nil, is_multiple: false, readonly: false, index: 0) + def create_number_field(form, value, name, label, validation: nil, html_class: nil, is_multiple: false, readonly: false, index: 0) render partial: 'shared/dynamic_form/fields/number_field', locals: { f: form, @@ -78,24 +82,26 @@ field_name: name, field_label: label, field_class: html_class, - readonly: readonly + readonly: readonly, + validation: validation } end - def create_checkbox_field(form, value, name, label, html_class: nil, readonly: false) + def create_checkbox_field(form, value, name, label, validation: nil, html_class: nil, readonly: false) render partial: 'shared/dynamic_form/fields/checkbox_field', locals: { f: form, field_value: value, field_name: name, field_label: label, - readonly: readonly + readonly: readonly, + validation: validation } end - def create_select_field(form, value, name, label, select_values, html_class: nil, readonly: false) + def create_select_field(form, value, name, label, select_values, validation: nil, html_class: nil, readonly: false) render partial: 'shared/dynamic_form/fields/select_field', locals: { f: form, @@ -104,31 +110,52 @@ field_label: label, select_values: select_values, field_class: html_class, - readonly: readonly + readonly: readonly, + validation: validation } - end + end + def display_validation_message(validations) + message = "" + validations.each do |validation| + case validation + when "required" + message += d_('dmpopidor', 'This property is required.') + when "pattern" + message += d_('dmpopidor', 'This property has an invalid format.') + else + message += d_('dmpopidor', 'This property has an unknown problem : %{validation}') % { + validation: validation + } + end + end + message + end # Formats the data extract from the structured answer form to valid JSON data # This is useful because Rails converts all form data to strings and JSON needs the actual types def data_reformater(schema, data) schema["properties"].each do |key, prop| - case prop["type"] - when "integer", "number" - data[key] = data[key].to_i - when "boolean" - data[key] = data[key] == "1" - when "array" - data[key] = data[key].kind_of?(Array) ? data[key] : [data[key]] - when "object" - if prop['schema_id'].present? - sub_schema = MadmpSchema.find(prop['schema_id']) - data[key] = data_reformater(sub_schema.schema, data[key]) - end - # if value["dictionnary"] - # data[key] = JSON.parse(DictionnaryValue.where(id: data[key]).select(:id, :uri, :label).take.to_json) - # end - end + if data[key] == "" + data.delete(key) + else + case prop["type"] + when "integer", "number" + data[key] = data[key].to_i + when "boolean" + data[key] = data[key] == "1" + when "array" + data[key] = data[key].kind_of?(Array) ? data[key] : [data[key]] + when "object" + if prop['schema_id'].present? + sub_schema = MadmpSchema.find(prop['schema_id']) + data[key] = data_reformater(sub_schema.schema, data[key]) + end + # if value["dictionnary"] + # data[key] = JSON.parse(DictionnaryValue.where(id: data[key]).select(:id, :uri, :label).take.to_json) + # end + end + end end data end diff --git a/app/javascript/views/answers/edit.js b/app/javascript/views/answers/edit.js index ead84b5..287ea98 100644 --- a/app/javascript/views/answers/edit.js +++ b/app/javascript/views/answers/edit.js @@ -167,8 +167,10 @@ const detachEventHandlers = (jQueryForm) => { formHandlers({ jQuery: jQueryForm, attachment: 'off' }); const tinymceId = jQueryForm.find(`.${editorClass}`).attr('id'); - detachEditorHandlers(Tinymce.findEditorById(tinymceId)); - Tinymce.destroyEditorById(tinymceId); + if (tinymceId) { + detachEditorHandlers(Tinymce.findEditorById(tinymceId)); + Tinymce.destroyEditorById(tinymceId); + } }; /* Attaches events for a specific form including its tinymce editor @@ -177,8 +179,10 @@ const attachEventHandlers = (jQueryForm) => { formHandlers({ jQuery: jQueryForm, attachment: 'on' }); const tinymceId = jQueryForm.find(`.${editorClass}`).attr('id'); - Tinymce.init({ selector: `#${tinymceId}` }); - editorHandlers(Tinymce.findEditorById(tinymceId)); + if (tinymceId) { + Tinymce.init({ selector: `#${tinymceId}` }); + editorHandlers(Tinymce.findEditorById(tinymceId)); + } }; datePicker(); diff --git a/app/models/fragment/budget.rb b/app/models/fragment/budget.rb new file mode 100644 index 0000000..99fb4d3 --- /dev/null +++ b/app/models/fragment/budget.rb @@ -0,0 +1,32 @@ +# == Schema Information +# +# Table name: madmp_fragments +# +# id :integer not null, primary key +# data :json +# answer_id :integer +# madmp_schema_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# classname :string +# dmp_id :integer +# parent_id :integer +# +# Indexes +# +# index_madmp_fragments_on_answer_id (answer_id) +# index_madmp_fragments_on_madmp_schema_id (madmp_schema_id) +# + +class Fragment::Budget < MadmpFragment + + def research_output + self.parent + end + + + def self.sti_name + "cost" + end + +end diff --git a/app/models/fragment/data_collection.rb b/app/models/fragment/data_collection.rb index 29fe9d5..9763b8e 100644 --- a/app/models/fragment/data_collection.rb +++ b/app/models/fragment/data_collection.rb @@ -23,6 +23,10 @@ def research_output self.parent end + + def technical_resource_usage + Fragment::TechnicalResourceUsage.where(parent_id: id).first + end def self.sti_name "data_collection" diff --git a/app/models/fragment/data_processing.rb b/app/models/fragment/data_processing.rb new file mode 100644 index 0000000..1245ed4 --- /dev/null +++ b/app/models/fragment/data_processing.rb @@ -0,0 +1,35 @@ +# == Schema Information +# +# Table name: madmp_fragments +# +# id :integer not null, primary key +# data :json +# answer_id :integer +# madmp_schema_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# classname :string +# dmp_id :integer +# parent_id :integer +# +# Indexes +# +# index_madmp_fragments_on_answer_id (answer_id) +# index_madmp_fragments_on_madmp_schema_id (madmp_schema_id) +# + +class Fragment::DataProcessing < MadmpFragment + + def research_output + self.parent + end + + def technical_resource_usage + Fragment::TechnicalResourceUsage.where(parent_id: id).first + end + + def self.sti_name + "data_processing" + end + +end diff --git a/app/models/fragment/data_quality.rb b/app/models/fragment/data_quality.rb deleted file mode 100644 index 3a6d562..0000000 --- a/app/models/fragment/data_quality.rb +++ /dev/null @@ -1,31 +0,0 @@ -# == Schema Information -# -# Table name: madmp_fragments -# -# id :integer not null, primary key -# data :json -# answer_id :integer -# madmp_schema_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# classname :string -# dmp_id :integer -# parent_id :integer -# -# Indexes -# -# index_madmp_fragments_on_answer_id (answer_id) -# index_madmp_fragments_on_madmp_schema_id (madmp_schema_id) -# - -class Fragment::DataQuality < MadmpFragment - - def research_output - self.parent - end - - def self.sti_name - "data_quality" - end - -end diff --git a/app/models/fragment/data_storage.rb b/app/models/fragment/data_storage.rb new file mode 100644 index 0000000..2373f71 --- /dev/null +++ b/app/models/fragment/data_storage.rb @@ -0,0 +1,35 @@ +# == Schema Information +# +# Table name: madmp_fragments +# +# id :integer not null, primary key +# data :json +# answer_id :integer +# madmp_schema_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# classname :string +# dmp_id :integer +# parent_id :integer +# +# Indexes +# +# index_madmp_fragments_on_answer_id (answer_id) +# index_madmp_fragments_on_madmp_schema_id (madmp_schema_id) +# + +class Fragment::DataStorage < MadmpFragment + + def research_output + self.parent + end + + def technical_resource_usage + Fragment::TechnicalResourceUsage.where(parent_id: id).first + end + + def self.sti_name + "data_storage" + end + +end diff --git a/app/models/fragment/documentation.rb b/app/models/fragment/documentation.rb deleted file mode 100644 index 9738ea3..0000000 --- a/app/models/fragment/documentation.rb +++ /dev/null @@ -1,40 +0,0 @@ -# == Schema Information -# -# Table name: madmp_fragments -# -# id :integer not null, primary key -# data :json -# answer_id :integer -# madmp_schema_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# classname :string -# dmp_id :integer -# parent_id :integer -# -# Indexes -# -# index_madmp_fragments_on_answer_id (answer_id) -# index_madmp_fragments_on_madmp_schema_id (madmp_schema_id) -# - -class Fragment::Documentation < MadmpFragment - - def research_output - self.parent - end - - def documentation_administrator - Fragment::Person.where(id: data['documentation_administrator']['dbId']) - end - - def metadata_format - Fragment::MetadataFormat.where(id: data['metadata_format']['dbId']) - end - - - def self.sti_name - "documentation" - end - -end diff --git a/app/models/fragment/documentation_quality.rb b/app/models/fragment/documentation_quality.rb new file mode 100644 index 0000000..6c328a9 --- /dev/null +++ b/app/models/fragment/documentation_quality.rb @@ -0,0 +1,36 @@ +# == Schema Information +# +# Table name: madmp_fragments +# +# id :integer not null, primary key +# data :json +# answer_id :integer +# madmp_schema_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# classname :string +# dmp_id :integer +# parent_id :integer +# +# Indexes +# +# index_madmp_fragments_on_answer_id (answer_id) +# index_madmp_fragments_on_madmp_schema_id (madmp_schema_id) +# + +class Fragment::DocumentationQuality < MadmpFragment + + def research_output + self.parent + end + + def metadata_format + Fragment::MetadataFormat.where(id: data['metadata_format']['dbId']).first + end + + + def self.sti_name + "documentation" + end + +end diff --git a/app/models/fragment/funding.rb b/app/models/fragment/funding.rb index df5d41e..5dcef3f 100644 --- a/app/models/fragment/funding.rb +++ b/app/models/fragment/funding.rb @@ -21,7 +21,7 @@ class Fragment::Funding < MadmpFragment def project - Fragment::Project.where(id: data['project']['dbId']).first + self.parent end def funder diff --git a/app/models/fragment/metadata_format.rb b/app/models/fragment/metadata_format.rb deleted file mode 100644 index 9994278..0000000 --- a/app/models/fragment/metadata_format.rb +++ /dev/null @@ -1,32 +0,0 @@ -# == Schema Information -# -# Table name: madmp_fragments -# -# id :integer not null, primary key -# data :json -# answer_id :integer -# madmp_schema_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# classname :string -# dmp_id :integer -# parent_id :integer -# -# Indexes -# -# index_madmp_fragments_on_answer_id (answer_id) -# index_madmp_fragments_on_madmp_schema_id (madmp_schema_id) -# - -class Fragment::MetadataFormat < MadmpFragment - - def documentation - Fragment::Documentation.where("(data->>'metadata_format'->>'dbId')::int = ?", id) - end - - - def self.sti_name - "metadata_format" - end - -end diff --git a/app/models/fragment/metadata_standard.rb b/app/models/fragment/metadata_standard.rb new file mode 100644 index 0000000..7ee2966 --- /dev/null +++ b/app/models/fragment/metadata_standard.rb @@ -0,0 +1,32 @@ +# == Schema Information +# +# Table name: madmp_fragments +# +# id :integer not null, primary key +# data :json +# answer_id :integer +# madmp_schema_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# classname :string +# dmp_id :integer +# parent_id :integer +# +# Indexes +# +# index_madmp_fragments_on_answer_id (answer_id) +# index_madmp_fragments_on_madmp_schema_id (madmp_schema_id) +# + +class Fragment::MetadataStandard < MadmpFragment + + def documentation + Fragment::Documentation.where("(data->>'metadata_format'->>'dbId')::int = ?", id) + end + + + def self.sti_name + "metadata_format" + end + +end diff --git a/app/models/fragment/partner.rb b/app/models/fragment/partner.rb index ef14870..f4a3eec 100644 --- a/app/models/fragment/partner.rb +++ b/app/models/fragment/partner.rb @@ -21,7 +21,7 @@ class Fragment::Partner < MadmpFragment def project - Fragment::Project.where(id: data['project']['dbId']).first + self.parent end diff --git a/app/models/fragment/preservation_issue.rb b/app/models/fragment/preservation_issue.rb index 3218e88..14a5a91 100644 --- a/app/models/fragment/preservation_issue.rb +++ b/app/models/fragment/preservation_issue.rb @@ -24,6 +24,10 @@ self.parent end + def technical_resource_usage + Fragment::TechnicalResourceUsage.where(parent_id: id).first + end + def self.sti_name "preservation_issue" diff --git a/app/models/fragment/research_output.rb b/app/models/fragment/research_output.rb index d4e20f6..9ce25c3 100644 --- a/app/models/fragment/research_output.rb +++ b/app/models/fragment/research_output.rb @@ -24,60 +24,48 @@ Fragment::Person.where(id: data['contact']['dbId']).first end + + def reuse_data + Fragment::ReuseData.where(parent_id: id).first + end + def data_collection Fragment::DataCollection.where(parent_id: id).first end + + def ethical_issues + Fragment::EthicalIssue.where(parent_id: id).first + end + + def personal_data_issues + Fragment::PersonalDataIssue.where(parent_id: id).first + end + + def legal_issues + Fragment::LegalIssue.where(parent_id: id).first + end - def data_quality - Fragment::DataQuality.where(parent_id: id).first + def data_processing + Fragment::DataProcessing.where(parent_id: id).first + end + + def data_storage + Fragment::DataStorage.where(parent_id: id).first end - def documentation - Fragment::Documentation.where(parent_id: id).first - end - - def preservation_issue - Fragment::PreservationIssue.where(parent_id: id).first + def documentation_quality + Fragment::DocumentationQuality.where(parent_id: id).first end def sharing Fragment::Sharing.where(parent_id: id).first end - - - def costs - Fragment::Cost.where(parent_id: id) - end - - def distributions - Fragment::Distribution.where(parent_id: id) - end - - def ethical_issues - Fragment::EthicalIssue.where(parent_id: id) - end - - def legal_issues - Fragment::LegalIssue.where(parent_id: id) + def preservation_issue + Fragment::PreservationIssue.where(parent_id: id).first end - def personal_data_issues - Fragment::PersonalDataIssue.where(parent_id: id) - end - - def reuse_datas - Fragment::ReuseData.where(parent_id: id) - end - - def staff_members - Fragment::StaffMember.where(parent_id: id) - end - - def technical_resource_usages - Fragment::TechnicalResourceUsage.where(parent_id: id) - end def self.sti_name diff --git a/app/models/fragment/sharing.rb b/app/models/fragment/sharing.rb index 0f22d4a..838cbb7 100644 --- a/app/models/fragment/sharing.rb +++ b/app/models/fragment/sharing.rb @@ -24,6 +24,16 @@ self.parent end + def technical_resource_usage + Fragment::TechnicalResourceUsage.where(parent_id: id).first + end + + + def distribution + Fragment::Distribution.where(parent_id: id).first + end + + def self.sti_name "sharing" diff --git a/app/models/fragment/staff_member.rb b/app/models/fragment/staff_member.rb deleted file mode 100644 index 149a0fd..0000000 --- a/app/models/fragment/staff_member.rb +++ /dev/null @@ -1,36 +0,0 @@ -# == Schema Information -# -# Table name: madmp_fragments -# -# id :integer not null, primary key -# data :json -# answer_id :integer -# madmp_schema_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# classname :string -# dmp_id :integer -# parent_id :integer -# -# Indexes -# -# index_madmp_fragments_on_answer_id (answer_id) -# index_madmp_fragments_on_madmp_schema_id (madmp_schema_id) -# - -class Fragment::StaffMember < MadmpFragment - - def agent - Fragment::Person.where(id: data['agent']['dbId']) - end - - def research_output - self.parent - end - - - def self.sti_name - "staff_member" - end - -end diff --git a/app/models/madmp_fragment.rb b/app/models/madmp_fragment.rb index 0dab9e2..ccaaa18 100644 --- a/app/models/madmp_fragment.rb +++ b/app/models/madmp_fragment.rb @@ -44,17 +44,19 @@ # = Single Table Inheritence = # ================ self.inheritance_column = :classname + scope :budgets, -> { where(classname: 'budgets') } scope :costs, -> { where(classname: 'cost') } scope :data_collections, -> { where(classname: 'data_collection') } - scope :data_qualities, -> { where(classname: 'data_quality') } + scope :data_processings, -> { where(classname: 'data_processing') } + scope :data_storages, -> { where(classname: 'data_storage') } scope :distributions, -> { where(classname: 'distribution') } scope :dmps, -> { where(classname: 'dmp') } - scope :documentations, -> { where(classname: 'documentation') } + scope :documentation_qualities, -> { where(classname: 'documentation_quality') } scope :ethical_issues, -> { where(classname: 'ethical_issue') } scope :funders, -> { where(classname: 'funder') } scope :fundings, -> { where(classname: 'funding') } scope :metas, -> { where(classname: 'meta') } - scope :metadata_formats, -> { where(classname: 'metadata_format') } + scope :metadata_standards, -> { where(classname: 'metadata_standard') } scope :partners, -> { where(classname: 'partner') } scope :persons, -> { where(classname: 'person') } scope :personal_data_issues, -> { where(classname: 'personal_data_issue') } @@ -63,7 +65,6 @@ scope :research_outputs, -> { where(classname: 'research_output') } scope :reuse_datas, -> { where(classname: 'reuse_data') } scope :sharings, -> { where(classname: 'sharing') } - scope :staff_members, -> { where(classname: 'staff_member') } scope :technical_resource_usages, -> { where(classname: 'technical_resource_usage') } scope :technical_resources, -> { where(classname: 'technical_resource') } @@ -75,6 +76,11 @@ after_create :update_parent_references after_destroy :update_parent_references + # ===================== + # = Nested Attributes = + # ===================== + accepts_nested_attributes_for :answer, allow_destroy: true + # ================= # = Class methods = # ================= @@ -96,9 +102,8 @@ def get_sub_fragments sub_fragments = self.dmp.persons.group_by(&:madmp_schema_id) unless self.children.empty? - sub_fragments.merge(self.children.group_by(&:madmp_schema_id)) + sub_fragments = sub_fragments.merge(self.children.group_by(&:madmp_schema_id)) end - sub_fragments end @@ -155,10 +160,36 @@ sa.dmp_id = answer.plan.json_fragment().id sa.parent_id = parent_id end + data = data.merge({ + "validations" => self.validate_data(data, schema.schema) + }) s_answer.assign_attributes(data: data) s_answer.save end + + # Validate the fragment data with the linked schema + # and saves the result with the fragment data + def self.validate_data(data, schema) + schemer = JSONSchemer.schema(schema) + unformated = schemer.validate(data).to_a + validations = {} + unformated.each do |valid| + unless valid['type'] == "object" + key = valid['data_pointer'][1..-1] + if valid['type'] == "required" + required = JsonPath.on(valid, '$..missing_keys').flatten + required.each do |req| + validations[req] ? validations[req].push("required") : validations[req] = ["required"] + end + else + validations[key] ? validations[key].push(valid['type']) : validations[key] = [valid['type']] + end + end + end + validations + end + def self.find_sti_class(type_name) self end diff --git a/app/views/branded/answers/_new_edit.html.erb b/app/views/branded/answers/_new_edit.html.erb index dcd5013..56e3b38 100644 --- a/app/views/branded/answers/_new_edit.html.erb +++ b/app/views/branded/answers/_new_edit.html.erb @@ -58,27 +58,20 @@ <%= hidden_field_tag :standards, answer_hash['standards'].to_json %> <% end %> <% end %> - <% if question.question_format.structured %> -
- <%= render(partial: 'questions/new_edit_question_structured', locals: { f: f, question: question, answer: answer, research_output: research_output, readonly: readonly }) %> - <%= f.button(_('Save'), class: "btn btn-default", type: "submit") %> -
- <% else %> -
> - <% if question.option_based? || question.question_format.rda_metadata? %> - <%= render(partial: 'questions/new_edit_question_option_based', locals: { f: f, question: question, answer: answer, research_output: research_output, readonly: readonly }) %> - <% elsif question.question_format.textfield?%> - <%= render(partial: 'questions/new_edit_question_textfield', locals: { f: f, question: question, answer: answer, research_output: research_output }) %> - <% elsif question.question_format.textarea? %> - <%= render(partial: 'questions/new_edit_question_textarea', locals: { f: f, question: question, answer: answer, research_output: research_output, locking: locking, readonly: readonly}) %> - <% elsif question.question_format.date? %> - <%= render(partial: 'questions/new_edit_question_datefield', locals: { f: f, question: question, answer: answer, research_output: research_output, readonly: readonly }) %> - <% elsif question.question_format.number? %> - <%= render(partial: 'questions/new_edit_question_numberfield', locals: { f: f, question: question, answer: answer, research_output: research_output, readonly: readonly }) %> - <% end %> - <%= f.button(_('Save'), class: "btn btn-default", type: "submit") %> -
- <% end %> +
> + <% if question.option_based? || question.question_format.rda_metadata? %> + <%= render(partial: 'questions/new_edit_question_option_based', locals: { f: f, question: question, answer: answer, research_output: research_output, readonly: readonly }) %> + <% elsif question.question_format.textfield?%> + <%= render(partial: 'questions/new_edit_question_textfield', locals: { f: f, question: question, answer: answer, research_output: research_output }) %> + <% elsif question.question_format.textarea? %> + <%= render(partial: 'questions/new_edit_question_textarea', locals: { f: f, question: question, answer: answer, research_output: research_output, locking: locking, readonly: readonly}) %> + <% elsif question.question_format.date? %> + <%= render(partial: 'questions/new_edit_question_datefield', locals: { f: f, question: question, answer: answer, research_output: research_output, readonly: readonly }) %> + <% elsif question.question_format.number? %> + <%= render(partial: 'questions/new_edit_question_numberfield', locals: { f: f, question: question, answer: answer, research_output: research_output, readonly: readonly }) %> + <% end %> + <%= f.button(_('Save'), class: "btn btn-default", type: "submit") %> +
<% if template.present? && template.org.present? %> <% question.example_answers([base_template_org.id, template.org.id]).each do |annotation| %> diff --git a/app/views/branded/madmp_fragments/_locking.html.erb b/app/views/branded/madmp_fragments/_locking.html.erb new file mode 100644 index 0000000..640291d --- /dev/null +++ b/app/views/branded/madmp_fragments/_locking.html.erb @@ -0,0 +1,18 @@ + +
+

<%= _('The following answer cannot be saved') %>

+ <%# We do not need to re-show example answers in this lock conflict section so leave template nil %> + <%= render partial: '/madmp_fragments/new_edit', locals: { + template: nil, + question: question, + answer: answer, + fragment: fragment, + research_output: research_output, + dmp_id: fragment.dmp_id, + parent_id: fragment.parent_id, + readonly: true, locking: true + } %> +

<%= _('since %{name} saved the answer below while you were editing. Please, combine your changes and then save the answer again.') % { name: user.name} %>

+
diff --git a/app/views/branded/madmp_fragments/_new_edit.html.erb b/app/views/branded/madmp_fragments/_new_edit.html.erb new file mode 100644 index 0000000..86eee12 --- /dev/null +++ b/app/views/branded/madmp_fragments/_new_edit.html.erb @@ -0,0 +1,41 @@ +<%# locals: { template, question, fragment, research_output, readonly, locking } %> + + +<% q_format = question.question_format %> +<% fragment_id = fragment.id unless fragment.nil? %> +<% url = fragment.present? ? create_or_update_madmp_fragments_path(id: fragment.id) : create_or_update_madmp_fragments_path %> +<%= form_for :madmp_fragment, url: url, html: {method: :post, 'data-autosave': question.id, class: 'form-answer', id: nil } do |f| %> + <% if !readonly %> + <%= f.fields_for(:answer, answer) do |af| %> + <%= af.hidden_field :plan_id, id: nil %> + <%= af.hidden_field :question_id, id: nil %> + <%= af.hidden_field :lock_version, id: nil %> + <%= af.hidden_field :research_output_id, :value => research_output.id, id: nil%> + <%= af.hidden_field :is_common, class: 'ans_is_common' if research_output.main? %> + <%= af.hidden_field :id, id: nil, class: 'answer_id' %> + <% end %> + <% end %> + <%= f.hidden_field :schema_id, :value => question.madmp_schema.id, id: nil %> + <%= f.hidden_field :dmp_id, :value => dmp_id, id: nil %> + <%= f.hidden_field :parent_id, :value => parent_id, id: nil %> + <%= f.hidden_field "source", :value => "form" %> + +
+ <% classname = fragment.classname unless fragment.nil? %> + <%= f.label(:text, sanitize(question.text), class: 'control-label') unless question.nil? %> +
+ <%= render(partial: 'shared/dynamic_form/form', locals: { + f: f, + fragment: fragment, + schema: question.madmp_schema, + readonly: readonly, + classname: classname, + fragment_id: fragment_id + }) %> +
+ <%= f.button(_('Save'), class: "btn btn-default", type: "submit") %> +
+ +<% end %> diff --git a/app/views/branded/phases/_edit_plan_answers.html.erb b/app/views/branded/phases/_edit_plan_answers.html.erb index 083510e..5fcd673 100644 --- a/app/views/branded/phases/_edit_plan_answers.html.erb +++ b/app/views/branded/phases/_edit_plan_answers.html.erb @@ -93,7 +93,21 @@
" class="answer-locking">
" class="answer-form"> - <%= render(partial: '/answers/new_edit', + <% if question.question_format.structured? %> + <% fragment = answer.madmp_fragment unless answer.nil? %> + <% dmp_id = plan.json_fragment.id %> + <% parent_id = research_output.json_fragment.id %> + <%= render(partial: '/madmp_fragments/new_edit', + locals: { question: question, + answer: answer, + fragment: fragment , + research_output: research_output, + dmp_id: dmp_id, + parent_id: parent_id, + readonly: readonly, + locking: false }) %> + <% else %> + <%= render(partial: '/answers/new_edit', locals: { template: phase.template, question: question, answer: answer, @@ -101,6 +115,7 @@ readonly: readonly, locking: false, base_template_org: base_template_org }) %> + <% end %>
" class="mt-10"> <%= render(partial: '/answers/status', diff --git a/app/views/branded/questions/_new_edit_question_structured.erb b/app/views/branded/questions/_new_edit_question_structured.erb deleted file mode 100644 index c87bbaa..0000000 --- a/app/views/branded/questions/_new_edit_question_structured.erb +++ /dev/null @@ -1,13 +0,0 @@ -<%= f.label(:text, sanitize(question.text), class: 'control-label') unless question.nil? %> -<% fragment_id = answer.madmp_fragment.nil? ? nil : answer.madmp_fragment.id %> -<% classname = answer.madmp_fragment.classname unless answer.madmp_fragment.nil? %> -
- <%= render(partial: 'shared/dynamic_form/form', locals: { - f: f, - fragment: answer.madmp_fragment, - schema: question.madmp_schema, - readonly: readonly, - classname: classname, - fragment_id: fragment_id - }) %> -
\ No newline at end of file diff --git a/app/views/branded/shared/dynamic_form/_form.html.erb b/app/views/branded/shared/dynamic_form/_form.html.erb index cb32141..9f14fd6 100644 --- a/app/views/branded/shared/dynamic_form/_form.html.erb +++ b/app/views/branded/shared/dynamic_form/_form.html.erb @@ -1,29 +1,32 @@ -<%# locals: { f, fragment, schema, readonly, classname, Fragment_id } %> +<%# locals: { f, fragment, schema, readonly, classname, fragment_id } %> <% sub_schemas = schema.get_sub_schemas %> <% data = fragment.data unless fragment.nil? %> +<% validations = data.present? && data['validations'].present? ? data["validations"] : nil %> + <% sub_fragments = fragment.present? ? fragment.get_sub_fragments() : [] %> <% schema_properties = schema.schema["properties"]%> <% schema_properties.each do |key, prop| %> <% value = data[key] unless data.nil? %> <% field_name = defined?(form_prefix) ? "#{form_prefix}[#{key}]" : key %> + <% validation = validations.nil? ? "none" : validations[key] %> <% case prop['type'] %> <% when 'string' %> <% if prop['format'].nil?%> - <%= create_text_field(f, value, field_name, prop['label'], readonly: readonly) %> + <%= create_text_field(f, value, field_name, prop['label'], readonly: readonly, validation: validation) %> <% elsif prop['format'] == 'date' %> - <%= create_date_field(f, value, field_name, prop['label'], readonly: readonly) %> + <%= create_date_field(f, value, field_name, prop['label'], readonly: readonly, validation: validation) %> <% elsif prop['format'] == 'uri' %> - <%= create_url_field(f, value, field_name, prop['label'], readonly: readonly) %> + <%= create_url_field(f, value, field_name, prop['label'], readonly: readonly, validation: validation) %> <% elsif prop['format'] == 'email' %> - <%= create_email_field(f, value, field_name, prop['label'], readonly: readonly) %> + <%= create_email_field(f, value, field_name, prop['label'], readonly: readonly, validation: validation) %> <% elsif prop['format'] == 'select' && prop['values'] %> - <%= create_select_field(f, value, "#{f.object_name}[#{field_name}]", prop['label'], prop['values'], readonly: readonly) %> + <%= create_select_field(f, value, "#{f.object_name}[#{field_name}]", prop['label'], prop['values'], readonly: readonly, validation: validation) %> <%end%> <% when 'integer', 'number' %> - <%= create_number_field(f, value, field_name, prop['label'], readonly: readonly) %> + <%= create_number_field(f, value, field_name, prop['label'], readonly: readonly, validation: validation) %> <% when 'boolean' %> - <%= create_checkbox_field(f, value, field_name, prop['label'], readonly: readonly) %> + <%= create_checkbox_field(f, value, field_name, prop['label'], readonly: readonly, validation: validation) %> <% when 'array' %> <% if prop['items']['type'] == 'object' && prop['items']['schema_id'].present? %> <% unless classname == "research_output" %> diff --git a/app/views/branded/shared/dynamic_form/_modal.html.erb b/app/views/branded/shared/dynamic_form/_modal.html.erb index 059317f..5155ef5 100644 --- a/app/views/branded/shared/dynamic_form/_modal.html.erb +++ b/app/views/branded/shared/dynamic_form/_modal.html.erb @@ -6,12 +6,13 @@
- <%= form_for @fragment, url: create_or_update_madmp_fragments_path(@fragment.id), + <%= form_for @fragment, url: create_or_update_madmp_fragments_path(id: @fragment.id), html: {method: :post, remote: true, class: 'form-horizontal', id: "madmp_fragment_form" } do |f| %> <%= f.hidden_field :dmp_id, id: nil %> <%= f.hidden_field :parent_id, id: nil %> <%= f.hidden_field :id, id: nil %> <%= f.hidden_field :schema_id, :value => @schema.id, id: nil%> + <%= f.hidden_field "source", :value => "modal" %> diff --git a/app/views/branded/shared/dynamic_form/fields/_select_field.html.erb b/app/views/branded/shared/dynamic_form/fields/_select_field.html.erb index b5e453b..1b3f217 100644 --- a/app/views/branded/shared/dynamic_form/fields/_select_field.html.erb +++ b/app/views/branded/shared/dynamic_form/fields/_select_field.html.erb @@ -1,11 +1,15 @@ -<%# locals: { f, field_label, field_name, select_values, selected_value, field_class } %> +<%# locals: { f, field_label, field_name, select_values, selected_value, field_class, readonly, validation } %>
<%= f.label field_name, field_label, class: 'control-label' %>
-
+
<%= select_tag field_name, options_for_select(select_values, selected: selected_value), disabled: readonly, class: "form-control #{field_class}" %> + <%= render partial: "shared/dynamic_form/validation_indicator", locals: { + validation: validation + } unless validation == "none" + %>
\ No newline at end of file diff --git a/app/views/branded/shared/dynamic_form/fields/_text_field.html.erb b/app/views/branded/shared/dynamic_form/fields/_text_field.html.erb index 2395a07..e93d1e3 100644 --- a/app/views/branded/shared/dynamic_form/fields/_text_field.html.erb +++ b/app/views/branded/shared/dynamic_form/fields/_text_field.html.erb @@ -1,4 +1,4 @@ -<%# locals: { f, field_label, field_name, multiple, classname, field_name, readonly } %> +<%# locals: { f, field_label, field_name, multiple, classname, field_name, readonly, validation } %> <% unless multiple %>
<%= f.label field_name, field_label, class: 'control-label' %> @@ -21,4 +21,9 @@ <% end %>
<% end %> + + <%= render partial: "shared/dynamic_form/validation_indicator", locals: { + validation: validation + } unless validation == "none" + %>
diff --git a/config/locale/dmpopidor.pot b/config/locale/dmpopidor.pot index 9eb2047..f4062a0 100644 --- a/config/locale/dmpopidor.pot +++ b/config/locale/dmpopidor.pot @@ -461,4 +461,15 @@ msgstr "" msgid "Request Date" -msgstr "" \ No newline at end of file +msgstr "" + +# maDMP Fragments + +msgid "This property is required." +msgstr "" + +msgid "This property has an invalid format." +msgstr "" + +msgid "This property has an unknown problem : %{validation}" +msgstr "" diff --git a/config/locale/en_GB/dmpopidor.po b/config/locale/en_GB/dmpopidor.po index d470ee1..ea41751 100644 --- a/config/locale/en_GB/dmpopidor.po +++ b/config/locale/en_GB/dmpopidor.po @@ -457,4 +457,15 @@ msgstr "You are about to archive %{user_name}. This will remove their personal information. This user will be unable to use their DMP OPIDoR account and the plans they created or shared by their colleagues. Are you sure?" msgid "Request Date" -msgstr "Request Date" \ No newline at end of file +msgstr "Request Date" + +# maDMP Fragments + +msgid "This property is required." +msgstr "This property is required." + +msgid "This property has an invalid format." +msgstr "This property has an invalid format." + +msgid "This property has an unknown problem : %{validation}" +msgstr "This property has an unknown problem : %{validation}" \ No newline at end of file diff --git a/config/locale/fr_FR/dmpopidor.po b/config/locale/fr_FR/dmpopidor.po index cbe3e01..855c899 100644 --- a/config/locale/fr_FR/dmpopidor.po +++ b/config/locale/fr_FR/dmpopidor.po @@ -459,4 +459,15 @@ msgstr "Vous êtes sur le point d’archiver %{user_name}. Les informations personnelles de ce compte seront anonymisées. Par conséquent cet utilisateur ne pourra plus accéder à son compte DMP OPIDoR et aux plans qu’il aura créés, ni à ceux partagés avec lui par ses collègues. Etes-vous sûr ?" msgid "Request Date" -msgstr "Date de la demande" \ No newline at end of file +msgstr "Date de la demande" + +# maDMP Fragments + +msgid "This property is required." +msgstr "Cette propriété est obligatoire." + +msgid "This property has an invalid format." +msgstr "Le format de cette propriété est invalide." + +msgid "This property has an unknown problem : %{validation}" +msgstr "Cette propriété a un problème inconnu : %{validation}" \ No newline at end of file diff --git a/lib/dmpopidor/controllers/answers.rb b/lib/dmpopidor/controllers/answers.rb index 7688eb6..fc5334a 100644 --- a/lib/dmpopidor/controllers/answers.rb +++ b/lib/dmpopidor/controllers/answers.rb @@ -48,13 +48,6 @@ # Needed if only answer.question_options is updated @answer.touch() end - if q.question_format.structured - @answer.touch() - s_params = schema_params(params, json_schema) - data = data_reformater(json_schema.schema, s_params) - parent_id = ResearchOutput.find(p_params[:research_output_id]).json_fragment().id - MadmpFragment.save_madmp_fragment(@answer, data, json_schema, parent_id) - end if q.question_format.rda_metadata? @answer.update_answer_hash( JSON.parse(params[:standards]), p_params[:text] @@ -73,12 +66,6 @@ end @answer.save! - if q.question_format.structured - s_params = schema_params(params, json_schema) - data = data_reformater(json_schema.schema, s_params) - parent_id = ResearchOutput.find(p_params[:research_output_id]).json_fragment().id - MadmpFragment.save_madmp_fragment(@answer, data, json_schema, parent_id) - end rescue ActiveRecord::StaleObjectError @stale_answer = @answer @answer = Answer.find_by(