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? %>
<%= f.label check_box, field_label, class: 'control-label' %>
-
+
<%= f.number_field field_name, value: field_value, multiple: multiple, disabled: readonly, class: "form-control #{field_class}" %> + + <%= render partial: "shared/dynamic_form/validation_indicator", locals: { + validation: validation + } + %>
diff --git a/app/views/branded/shared/dynamic_form/fields/_date_field.html.erb b/app/views/branded/shared/dynamic_form/fields/_date_field.html.erb index 71b0048..8f523e4 100644 --- a/app/views/branded/shared/dynamic_form/fields/_date_field.html.erb +++ b/app/views/branded/shared/dynamic_form/fields/_date_field.html.erb @@ -1,4 +1,4 @@ -<%# 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? %> <% unless multiple %>
@@ -21,4 +21,9 @@ <% end %>
<% end %> + + <%= render partial: "shared/dynamic_form/validation_indicator", locals: { + validation: validation + } + %>
diff --git a/app/views/branded/shared/dynamic_form/fields/_number_field.html.erb b/app/views/branded/shared/dynamic_form/fields/_number_field.html.erb index 7ea54fd..8b2e87b 100644 --- a/app/views/branded/shared/dynamic_form/fields/_number_field.html.erb +++ b/app/views/branded/shared/dynamic_form/fields/_number_field.html.erb @@ -1,4 +1,4 @@ -<%# 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? %> <% unless multiple %>
@@ -21,4 +21,8 @@ <% end %>
<% end %> + <%= render partial: "shared/dynamic_form/validation_indicator", locals: { + validation: validation + } + %> 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..dae8685 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 + } + %>
\ 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..b5e98c3 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,8 @@ <% end %>
<% end %> + <%= render partial: "shared/dynamic_form/validation_indicator", locals: { + validation: validation + } + %>
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