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/helpers/dynamic_form_helper.rb b/app/helpers/dynamic_form_helper.rb index 9d4aee9..3b77d5d 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" - 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" + 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/models/madmp_fragment.rb b/app/models/madmp_fragment.rb index 0dab9e2..891d4a4 100644 --- a/app/models/madmp_fragment.rb +++ b/app/models/madmp_fragment.rb @@ -155,10 +155,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/shared/dynamic_form/_form.html.erb b/app/views/branded/shared/dynamic_form/_form.html.erb index 52f9a18..b54c444 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"] : {} %> + <% 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[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' %> - <%= 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/_validation_indicator.html.erb b/app/views/branded/shared/dynamic_form/_validation_indicator.html.erb new file mode 100644 index 0000000..a411fb7 --- /dev/null +++ b/app/views/branded/shared/dynamic_form/_validation_indicator.html.erb @@ -0,0 +1,6 @@ +<% if validation.nil? %> + +<% else %> + + +<% end %> \ No newline at end of file diff --git a/app/views/branded/shared/dynamic_form/fields/_checkbox_field.html.erb b/app/views/branded/shared/dynamic_form/fields/_checkbox_field.html.erb index f5b8150..c598f83 100644 --- a/app/views/branded/shared/dynamic_form/fields/_checkbox_field.html.erb +++ b/app/views/branded/shared/dynamic_form/fields/_checkbox_field.html.erb @@ -1,12 +1,17 @@ -<%# locals: { f, field_label, field_name, multiple, classname, field_name } %> +<%# locals: { f, field_label, field_name, multiple, classname, field_name, readonly, validation } %> <% field_class = nil if local_assigns[:field_class].nil? %>