From 48e1d35a282fd6b53dc47b18d4749a5cfc4181b6 Mon Sep 17 00:00:00 2001 From: Dennis Hernandez Date: Thu, 11 Nov 2021 10:48:24 -0600 Subject: [PATCH 01/38] R2-1883 - Skip logic --- app/javascript/components/form/records.js | 4 ++- .../components/render-form-sections.js | 6 +++++ .../subform-dialog-fields/component.jsx | 25 ++++++++++++++++--- .../subforms/subform-dialog/component.jsx | 8 +++--- .../subforms/subform-field-array/utils.js | 2 +- .../components/record-form/records.js | 2 ++ ...1110000001_add_field_display_conditions.rb | 8 ++++++ db/schema.rb | 4 ++- 8 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 db/migrate/20211110000001_add_field_display_conditions.rb diff --git a/app/javascript/components/form/records.js b/app/javascript/components/form/records.js index d3ab914dbc..7c7f315ed4 100644 --- a/app/javascript/components/form/records.js +++ b/app/javascript/components/form/records.js @@ -76,7 +76,9 @@ export const FieldRecord = Record({ i18nName: false, i18nDescription: false, renderChildren: true, - max_length: null + max_length: null, + display_conditions: [], + parent_display_conditions: [] }); export const FormSectionRecord = Record({ diff --git a/app/javascript/components/record-form/components/render-form-sections.js b/app/javascript/components/record-form/components/render-form-sections.js index 96915919d1..38456562c1 100644 --- a/app/javascript/components/record-form/components/render-form-sections.js +++ b/app/javascript/components/record-form/components/render-form-sections.js @@ -1,4 +1,5 @@ import { Fragment } from "react"; +import { isEmpty } from "lodash"; import { SUBFORM_SECTION } from "../constants"; import RecordFormAlerts from "../../record-form-alerts"; @@ -7,6 +8,7 @@ import RecordFormTitle from "../form/record-form-title"; import { RECORD_FORM_PERMISSION } from "../form/constants"; import FormSectionField from "../form/form-section-field"; import SubformField from "../form/subforms"; +import { dataMeetConditions } from "../form/subforms/subform-field-array/utils"; const renderFormSections = ( externalForms, @@ -57,6 +59,10 @@ const renderFormSections = ( return null; } + if (!isEmpty(field.display_conditions) && !dataMeetConditions(values, field.display_conditions)) { + return null; + } + return (
{SUBFORM_SECTION === field.type ? ( diff --git a/app/javascript/components/record-form/form/subforms/subform-dialog-fields/component.jsx b/app/javascript/components/record-form/form/subforms/subform-dialog-fields/component.jsx index 62373778b4..1a9cd1c5f5 100644 --- a/app/javascript/components/record-form/form/subforms/subform-dialog-fields/component.jsx +++ b/app/javascript/components/record-form/form/subforms/subform-dialog-fields/component.jsx @@ -1,8 +1,9 @@ import PropTypes from "prop-types"; import { connect } from "formik"; +import { isEmpty } from "lodash"; import FormSectionField from "../../form-section-field"; -import { fieldsToRender } from "../subform-field-array/utils"; +import { dataMeetConditions, fieldsToRender } from "../subform-field-array/utils"; import { NAME } from "./constants"; @@ -15,8 +16,10 @@ const Component = ({ field, formSection, isReadWriteForm, + parentValues, recordModuleID, - recordType + recordType, + values }) => { const { subform_section_configuration: subformSectionConfiguration } = field; @@ -53,6 +56,20 @@ const Component = ({ recordType }; + if ( + !isEmpty(subformSectionField.parent_display_conditions) && + !dataMeetConditions(parentValues, subformSectionField.parent_display_conditions) + ) { + return null; + } + + if ( + !isEmpty(subformSectionField.display_conditions) && + !dataMeetConditions(values, subformSectionField.display_conditions) + ) { + return null; + } + return (
@@ -75,9 +92,11 @@ Component.propTypes = { index: PropTypes.number, isReadWriteForm: PropTypes.bool, mode: PropTypes.object.isRequired, + parentValues: PropTypes.object, recordModuleID: PropTypes.string, recordType: PropTypes.string, - setFilterState: PropTypes.func + setFilterState: PropTypes.func, + values: PropTypes.object }; export default connect(Component); diff --git a/app/javascript/components/record-form/form/subforms/subform-dialog/component.jsx b/app/javascript/components/record-form/form/subforms/subform-dialog/component.jsx index 5b1d74298d..da863ba0cc 100644 --- a/app/javascript/components/record-form/form/subforms/subform-dialog/component.jsx +++ b/app/javascript/components/record-form/form/subforms/subform-dialog/component.jsx @@ -97,7 +97,7 @@ const Component = ({ ) : null; - const renderSubform = (subformField, subformIndex) => { + const renderSubform = (subformField, subformIndex, values) => { if (subformField.subform_section_id.unique_id === "services_section") { return ( ); }; @@ -166,7 +168,7 @@ const Component = ({ onSubmit={values => onSubmit(values)} ref={childFormikRef} > - {({ handleSubmit, submitForm, setErrors, setTouched, errors }) => { + {({ handleSubmit, submitForm, setErrors, setTouched, errors, values }) => { bindSubmitForm(submitForm); return ( @@ -177,7 +179,7 @@ const Component = ({ setErrors={setErrors} setTouched={setTouched} /> - {renderSubform(field, index)} + {renderSubform(field, index, values)} ); }} diff --git a/app/javascript/components/record-form/form/subforms/subform-field-array/utils.js b/app/javascript/components/record-form/form/subforms/subform-field-array/utils.js index b02915a52f..6a061975c4 100644 --- a/app/javascript/components/record-form/form/subforms/subform-field-array/utils.js +++ b/app/javascript/components/record-form/form/subforms/subform-field-array/utils.js @@ -3,7 +3,7 @@ import { isEmpty } from "lodash"; import { RECORD_TYPES, TRACES_SUBFORM_UNIQUE_ID } from "../../../../../config"; -const dataMeetConditions = (objectToEval, displayConditions) => { +export const dataMeetConditions = (objectToEval, displayConditions) => { const objToEval = isImmutable(objectToEval) ? objectToEval.toJS() : objectToEval; // displayConditions = // [ {"relation": ["father","mother"],"relation_is_alive": "alive"},{"relation_is_caregiver": true}] diff --git a/app/javascript/components/record-form/records.js b/app/javascript/components/record-form/records.js index 314c91112b..cc66d2aee4 100644 --- a/app/javascript/components/record-form/records.js +++ b/app/javascript/components/record-form/records.js @@ -22,6 +22,8 @@ export const FieldRecord = Record({ show_on_minify_form: false, order: null, subform_section_configuration: null, + display_conditions: [], + parent_display_conditions: [], tick_box_label: {}, link_to_form: "", href: null diff --git a/db/migrate/20211110000001_add_field_display_conditions.rb b/db/migrate/20211110000001_add_field_display_conditions.rb new file mode 100644 index 0000000000..2bc8dd7c87 --- /dev/null +++ b/db/migrate/20211110000001_add_field_display_conditions.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class AddFieldDisplayConditions < ActiveRecord::Migration[5.2] + def change + add_column :fields, :display_conditions, :jsonb + add_column :fields, :parent_display_conditions, :jsonb + end +end diff --git a/db/schema.rb b/db/schema.rb index b251c50efe..cd8e695a1d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_10_01_000005) do +ActiveRecord::Schema.define(version: 2021_11_10_000001) do # These are extensions that must be enabled in order to support this database enable_extension "ltree" @@ -219,6 +219,8 @@ t.boolean "mandatory_for_completion", default: false, null: false t.datetime "created_at", default: -> { "CURRENT_TIMESTAMP" }, null: false t.datetime "updated_at", default: -> { "CURRENT_TIMESTAMP" }, null: false + t.jsonb "display_conditions" + t.jsonb "parent_display_conditions" t.index ["form_section_id"], name: "index_fields_on_form_section_id" t.index ["name"], name: "index_fields_on_name" t.index ["type"], name: "index_fields_on_type" From 68f56236176ff34ef92241a19e3edd87ed0900ce Mon Sep 17 00:00:00 2001 From: Dennis Hernandez Date: Tue, 16 Nov 2021 15:54:21 -0600 Subject: [PATCH 02/38] R2-1891 - Export all linked subforms Since fields can have different display conditions for specific subforms, all the linked subforms will be exported. --- app/models/exporters/excel_exporter.rb | 93 ++++++++++++-------- spec/models/exporters/excel_exporter_spec.rb | 16 ++-- 2 files changed, 68 insertions(+), 41 deletions(-) diff --git a/app/models/exporters/excel_exporter.rb b/app/models/exporters/excel_exporter.rb index 25b32150b5..249a2a5637 100644 --- a/app/models/exporters/excel_exporter.rb +++ b/app/models/exporters/excel_exporter.rb @@ -31,54 +31,76 @@ def export(records, user, options = {}) records.each do |record| write_record(record) - worksheets_reset_written end end + def worksheet_id(form, subform_field = nil) + subform_field.present? ? subform_path(subform_field) : form.unique_id + end + def build_worksheets_with_headers return if worksheets.present? forms.each { |form| build_worksheet_with_headers(form) } end - def build_worksheet_with_headers(form) - # Do not build the worksheet if the form is already there because - # the same form can be referenced by different fields - return if worksheets[form.unique_id].present? - - worksheet = build_worksheet(form) + def build_worksheet_with_headers(form, subform_field = nil) + worksheet = build_worksheet(form, subform_field) worksheet&.write(0, 0, 'ID') form.fields.each_with_index do |field, i| if field.type == Field::SUBFORM - build_worksheet_with_headers(constrained_subforms[field.subform.unique_id]) + build_worksheet_with_headers(constrained_subforms[subform_path(field)], field) else worksheet&.write(0, i + 1, field.display_name(locale)) end end - worksheets[form.unique_id] = { worksheet: worksheet, row: 1 } + worksheets[worksheet_id(form, subform_field)] = { worksheet: worksheet, row: 1 } end - def build_worksheet(form) + def build_worksheet(form, subform_field = nil) # Don't build a separate worksheet for a form that only contains subform fields return if only_subform_fields?(form) - name = worksheet_name(form) + name = worksheet_name(form, subform_field) begin workbook.add_worksheet(name) rescue RuntimeError - workbook.add_worksheet("#{name[0..-3]}-1") + increase_name_counter(name) + workbook.add_worksheet("#{name[name_counter_range(name)]}-#{worksheets[:names][name]}") end end + def increase_name_counter(name) + worksheets[:names] = {} unless worksheets[:names].present? + worksheets[:names][name] = (worksheets[:names][name] || 0) + 1 + end + + def name_counter_range(name) + 0..(-2 - worksheets[:names][name].digits.count) + end + + def form_worksheet(form, subform_field = nil) + worksheets[worksheet_id(form, subform_field)][:worksheet] + end + def only_subform_fields?(form) !form.fields.find { |field| field.type != Field::SUBFORM } end - def worksheet_name(form) - name = form.name(locale.to_s) - name.sub(%r{[\[\]:*?\/\\]}, ' ') - .encode('iso-8859-1', undef: :replace, replace: '') - .strip.truncate(31) + def worksheet_name(form, subform_field = nil) + name = form.name(locale.to_s).sub(%r{[\[\]:*?\/\\]}, ' ') + + return name.encode('iso-8859-1', undef: :replace, replace: '').strip.truncate(31) if subform_field.blank? + + subform_field.form + .name(locale.to_s) + .sub(%r{[\[\]:*?\/\\]}, ' ') + .strip + .slice(0, 15) + .concat("-#{name}") + .encode('iso-8859-1', undef: :replace, replace: '') + .strip + .truncate(31) end def write_record(record) @@ -87,23 +109,20 @@ def write_record(record) end end - def write_record_form(id, data, form, form_name = '') - # Do not write data if already written for this form - return if worksheets[form.unique_id][:written] == true - - rows_written = write_record_row(id, data, form, form_name) - worksheets[form.unique_id][:written] = true - worksheets[form.unique_id][:row] += rows_written + def write_record_form(id, data, form, form_name = '', subform_field = nil) + rows_written = write_record_row(id, data, form, form_name, subform_field) + worksheets[worksheet_id(form, subform_field)][:row] += rows_written end - def write_record_row(id, data, form, form_name) - worksheet = worksheets[form.unique_id][:worksheet] + def write_record_row(id, data, form, form_name, subform_field = nil) values, rows_to_write = field_values(data, form, form_name) - ([id] + values).each_with_index { |value, column| write_value(worksheet, form, value, column, rows_to_write) } + ([id] + values).each_with_index do |value, column| + write_value(form_worksheet(form, subform_field), value, column, rows_to_write, worksheet_id(form, subform_field)) + end form.subform_fields.each do |field| - subform = constrained_subforms[field.subform.unique_id] + subform = constrained_subforms[subform_path(field)] data = apply_subform_display_conditions(data, field) - write_record_form(id, data, subform, field.name) + write_record_form(id, data, subform, field.name, field) end rows_to_write end @@ -130,13 +149,11 @@ def export_field_value(data, field, form_field_name) values end - def write_value(worksheet, form, value, column, rows_to_write) + def write_value(worksheet, value, column, rows_to_write, current_worksheet_id) value_array = value.is_a?(Array) ? value : Array.new(rows_to_write, value) - value_array.each_with_index { |val, i| worksheet&.write((worksheets[form.unique_id][:row] + i), column, val) } - end - - def worksheets_reset_written - worksheets.each { |_, value| value[:written] = false } + value_array.each_with_index do |val, i| + worksheet&.write((worksheets[current_worksheet_id][:row] + i), column, val) + end end def export_value(value, field) @@ -166,10 +183,14 @@ def constraint_subforms subform_fields = forms.map(&:fields).flatten.select { |field| field.type == Field::SUBFORM } self.constrained_subforms = subform_fields.reduce({}) do |acc, subform_field| - acc.merge(subform_field.subform.unique_id => constraint_subform_fields(subform_field)) + acc.merge(subform_path(subform_field) => constraint_subform_fields(subform_field)) end end + def subform_path(field) + "#{field.form.unique_id}.#{field.name}" + end + def constraint_subform_fields(field) field_names = subform_configured_field_names(field) diff --git a/spec/models/exporters/excel_exporter_spec.rb b/spec/models/exporters/excel_exporter_spec.rb index ad5dae6e48..0e9bf97d04 100644 --- a/spec/models/exporters/excel_exporter_spec.rb +++ b/spec/models/exporters/excel_exporter_spec.rb @@ -83,7 +83,7 @@ module Exporters subform4.fields << Field.new(name: 'field_2', type: Field::TEXT_FIELD, display_name: 'field_2') subform4.save! - form_e = FormSection.new(name: 'case_test_form_4', parent_form: 'case', visible: true, + form_e = FormSection.new(name: 'cases_test_form_4', parent_form: 'case', visible: true, order_form_group: 0, order: 0, order_subform: 0, form_group_id: 'Case Form 1', unique_id: 'cases_test_form_4') @@ -127,10 +127,16 @@ module Exporters end it 'contains a worksheet for each form and subform' do - expect(workbook.sheets.size).to eq(7) - expect(workbook.sheets).to match_array(['cases_test_subform_2', 'cases_test_form_2', 'cases_test_form_1', - 'cases_test_subform_1', 'cases_test_subform_3', 'cases_test_subform_4', - 'Test Arabic .']) + + expect(workbook.sheets.size).to eq(11) + expect(workbook.sheets).to match_array( + [ + 'cases_test_subform_2', 'cases_test_form_2', 'cases_test_form_1', + 'cases_test_subform_1', 'cases_test_subform_3', 'cases_test_subform_4', + 'Test Arabic .', 'cases_test_form-cases_test_s...', 'cases_test_form-cases_test_s.-1', + 'cases_test_form-cases_test_s.-2', 'cases_test_form-cases_test_s.-3' + ] + ) end it 'prints a header for each form and subform' do From 2c2a0523f496b8fdb44789908f24f70ab0bd53e4 Mon Sep 17 00:00:00 2001 From: Dennis Hernandez Date: Wed, 17 Nov 2021 22:11:39 -0600 Subject: [PATCH 03/38] R2-1891 - Fix query to pull permitted form The permitted_forms query was not returning the subforms when the include_subforms parameter is defined. The base exporter was trying to pull the permitted_forms and the permitted_subforms for a user, but the permitted_forms query was inadvertently retrieving subforms if they were associated to the role resulting in duplicated subforms, which in turn produced duplicated tabs in the exported excel files. --- app/models/exporters/base_exporter.rb | 8 +- app/models/role.rb | 20 +-- spec/models/exporters/excel_exporter_spec.rb | 20 +-- .../selected_fields_excel_exporter_spec.rb | 11 +- spec/models/role_spec.rb | 136 ++++++++++++++++++ 5 files changed, 165 insertions(+), 30 deletions(-) diff --git a/app/models/exporters/base_exporter.rb b/app/models/exporters/base_exporter.rb index 2ab0b9b87c..8b7f210b18 100644 --- a/app/models/exporters/base_exporter.rb +++ b/app/models/exporters/base_exporter.rb @@ -86,11 +86,9 @@ def buffer def forms_to_export(records, user, options = {}) record_type = model_class(records)&.parent_form - user_forms_subforms_permitted = user.role.permitted_forms(record_type, true, true) + - user.role.permitted_subforms(record_type, true) - forms = user_forms_subforms_permitted.map do |form| - forms_without_hidden_fields(form) - end + user_forms_subforms_permitted = user.role.permitted_forms(record_type, true, true) + forms = user_forms_subforms_permitted.map { |form| forms_without_hidden_fields(form) } + return forms unless options[:form_unique_ids].present? forms.select { |form| options[:form_unique_ids].include?(form.unique_id) } diff --git a/app/models/role.rb b/app/models/role.rb index 648ce30ace..581b69e676 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -101,20 +101,14 @@ def list_external end def permitted_forms(record_type = nil, visible_only = false, include_subforms = false) - forms = form_sections.where({ parent_form: record_type, visible: (visible_only || nil) }.compact) - forms = forms.or(form_sections.where(parent_form: record_type, is_nested: true)) if include_subforms - forms - end + forms = form_sections.where( + { parent_form: record_type, visible: (visible_only || nil) }.compact.merge(is_nested: false) + ) + + return forms unless include_subforms - def permitted_subforms(record_type = nil, visible_only = false) - FormSection.where( - is_nested: true, - subform_field: form_sections.joins(fields: :subform) - .where({ - parent_form: record_type, - visible: (visible_only || nil), - fields: { type: Field::SUBFORM } - }.compact) + FormSection.where(subform_field: forms.joins(:fields).where(fields: { type: Field::SUBFORM })).or( + FormSection.where(id: forms) ) end diff --git a/spec/models/exporters/excel_exporter_spec.rb b/spec/models/exporters/excel_exporter_spec.rb index 0e9bf97d04..f20c124300 100644 --- a/spec/models/exporters/excel_exporter_spec.rb +++ b/spec/models/exporters/excel_exporter_spec.rb @@ -141,17 +141,21 @@ module Exporters it 'prints a header for each form and subform' do expect(workbook.sheet(0).row(1)).to eq(%w[ID field_3 field_4]) - expect(workbook.sheet(1).row(1)).to eq(%w[ID relationship array_field]) - expect(workbook.sheet(2).row(1)).to eq(%w[ID first_name last_name]) + expect(workbook.sheet(1).row(1)).to eq(%w[ID field_3 field_4]) + expect(workbook.sheet(2).row(1)).to eq(%w[ID relationship array_field]) expect(workbook.sheet(3).row(1)).to eq(%w[ID field_1 field_2]) expect(workbook.sheet(4).row(1)).to eq(%w[ID field_5 field_6]) - expect(workbook.sheet(5).row(1)).to eq(['ID', 'arabic text', 'arabic array']) - expect(workbook.sheet(6).row(1)).to eq(%w[ID field_2]) + expect(workbook.sheet(5).row(1)).to eq(%w[ID first_name last_name]) + expect(workbook.sheet(6).row(1)).to eq(%w[ID field_1 field_2]) + expect(workbook.sheet(7).row(1)).to eq(%w[ID field_5 field_6]) + expect(workbook.sheet(8).row(1)).to eq(['ID', 'arabic text', 'arabic array']) + expect(workbook.sheet(9).row(1)).to eq(%w[ID field_1 field_2]) + expect(workbook.sheet(10).row(1)).to eq(%w[ID field_2]) end it 'exports record values for regular forms' do - expect(workbook.sheet(1).row(2)).to eq([@record_id, 'Mother', 'Option 1 ||| Option 2']) - expect(workbook.sheet(2).row(2)).to eq([@record_id, 'John', 'Doe']) + expect(workbook.sheet(2).row(2)).to eq([@record_id, 'Mother', 'Option 1 ||| Option 2']) + expect(workbook.sheet(5).row(2)).to eq([@record_id, 'John', 'Doe']) end it 'exports record values for each instance of subforms' do @@ -169,8 +173,8 @@ module Exporters end it 'exports only the record values for each instance of subforms that meets the condition' do - expect(workbook.sheet(6).last_row).to eq(2) - expect(workbook.sheet(6).row(2)).to eq([@record_id, 'field_2 value']) + expect(workbook.sheet(10).last_row).to eq(2) + expect(workbook.sheet(10).row(2)).to eq([@record_id, 'field_2 value']) end end end diff --git a/spec/models/exporters/selected_fields_excel_exporter_spec.rb b/spec/models/exporters/selected_fields_excel_exporter_spec.rb index b724cb99e6..ef20c05c3c 100644 --- a/spec/models/exporters/selected_fields_excel_exporter_spec.rb +++ b/spec/models/exporters/selected_fields_excel_exporter_spec.rb @@ -235,12 +235,15 @@ end it 'contains a worksheet for every form and nested subform unless they are visible: false or hide_on_view_page' do - expect(workbook.sheets.size).to eq(5 + 1) + expect(workbook.sheets.size).to eq(8 + 1) expect(workbook.sheet(0).row(1)).to eq(%w[ID field_3 field_4]) - expect(workbook.sheet(1).row(1)).to eq(%w[ID relationship array_field]) - expect(workbook.sheet(2).row(1)).to eq(%w[ID name]) + expect(workbook.sheet(1).row(1)).to eq(%w[ID field_3 field_4]) + expect(workbook.sheet(2).row(1)).to eq(%w[ID relationship array_field]) expect(workbook.sheet(3).row(1)).to eq(%w[ID field_1 field_2]) expect(workbook.sheet(4).row(1)).to eq(['ID', 'field X', 'field Y']) + expect(workbook.sheet(5).row(1)).to eq(%w[ID name]) + expect(workbook.sheet(6).row(1)).to eq(%w[ID field_1 field_2]) + expect(workbook.sheet(7).row(1)).to eq(['ID', 'field X', 'field Y']) end it 'correctly exports record values for a form' do @@ -248,7 +251,7 @@ end it 'correctly exports record values for a subform' do - expect(workbook.sheet(1).row(2)).to eq([@records[0].short_id, 'Mother', 'Option1 ||| Option2']) + expect(workbook.sheet(2).row(2)).to eq([@records[0].short_id, 'Mother', 'Option1 ||| Option2']) end end diff --git a/spec/models/role_spec.rb b/spec/models/role_spec.rb index e9dc0ff34c..960d9dfe2b 100644 --- a/spec/models/role_spec.rb +++ b/spec/models/role_spec.rb @@ -634,4 +634,140 @@ expect(new_version).not_to eq(@old_version) end end + + describe 'permitted_forms' do + before :each do + clean_data(Field, FormSection, FormPermission, Role, PrimeroModule) + @subform1 = FormSection.create!( + unique_id: 'subform1', parent_form: 'case', visible: false, is_nested: true, name: 'Subform 1', + fields: [ + Field.new(name: 'nested_field_1', type: Field::TEXT_FIELD, display_name: 'Nested Field 1'), + Field.new(name: 'nested_field_2', type: Field::TEXT_FIELD, display_name: 'Nested Field 2') + ] + ) + @subform2 = FormSection.create!( + unique_id: 'subform2', parent_form: 'case', visible: false, is_nested: true, name: 'Subform 2', + fields: [ + Field.new(name: 'nested_field_3', type: Field::TEXT_FIELD, display_name: 'Nested Field 3'), + Field.new(name: 'nested_field_4', type: Field::TEXT_FIELD, display_name: 'Nested Field 4') + ] + ) + @form_section1 = FormSection.create!( + unique_id: 'form_section1', parent_form: 'case', name_en: 'Form Section 1', + fields: [ + Field.new(name: 'field_1', type: Field::TEXT_FIELD, display_name_en: 'Field 1'), + Field.new(name: 'field_2', type: Field::NUMERIC_FIELD, display_name_en: 'Field 2'), + Field.new(name: 'subform_field_1', type: Field::SUBFORM, + display_name: 'Subform field 1', subform_section_id: @subform1.id) + ] + ) + @form_section2 = FormSection.create!( + unique_id: 'form_section2', parent_form: 'case', name_en: 'Form Section 2', visible: false, + fields: [ + Field.new(name: 'field_3', type: Field::TEXT_FIELD, display_name_en: 'Field 3'), + Field.new(name: 'field_4', type: Field::NUMERIC_FIELD, display_name_en: 'Field 4'), + Field.new(name: 'subform_field_2', type: Field::SUBFORM, + display_name: 'Subform field 2', subform_section_id: @subform2.id) + ] + ) + end + + it 'returns the permitted forms for a role' do + role = Role.create!( + unique_id: 'role_test_01', + name: 'name_test_01', + description: 'description_test_01', + group_permission: 'all', + permissions: [ + Permission.new(resource: Permission::USER, actions: [Permission::READ, Permission::WRITE, Permission::CREATE]) + ], + form_sections: [@form_section1, @form_section2], + modules: [ + PrimeroModule.new( + unique_id: 'primeromodule-cp-a', name: 'CPA', description: 'Child Protection A', + associated_record_types: %w[case tracing_request incident], + primero_program: PrimeroProgram.new(name: 'program'), + form_sections: [@form_section1, @form_section2] + ) + ] + ) + + expect(role.permitted_forms.pluck(:unique_id)).to match_array(%w[form_section1 form_section2]) + end + + it 'returns the permitted forms and visible subforms for a role if visible_only=true and include_subforms=true' do + role = Role.create!( + unique_id: 'role_test_01', + name: 'name_test_01', + description: 'description_test_01', + group_permission: 'all', + permissions: [ + Permission.new(resource: Permission::USER, actions: [Permission::READ, Permission::WRITE, Permission::CREATE]) + ], + form_sections: [@form_section1, @form_section2], + modules: [ + PrimeroModule.new( + unique_id: 'primeromodule-cp-a', name: 'CPA', description: 'Child Protection A', + associated_record_types: %w[case tracing_request incident], + primero_program: PrimeroProgram.new(name: 'program'), + form_sections: [@form_section1, @form_section2] + ) + ] + ) + + expect(role.permitted_forms('case', true, true).pluck(:unique_id)).to match_array( + %w[subform1 form_section1] + ) + end + + it 'does not duplicate permitted forms and subforms for a role if they are associated' do + role = Role.create!( + unique_id: 'role_test_01', + name: 'name_test_01', + description: 'description_test_01', + group_permission: 'all', + permissions: [ + Permission.new(resource: Permission::USER, actions: [Permission::READ, Permission::WRITE, Permission::CREATE]) + ], + form_sections: [@form_section1, @form_section2, @subform1], + modules: [ + PrimeroModule.new( + unique_id: 'primeromodule-cp-a', name: 'CPA', description: 'Child Protection A', + associated_record_types: %w[case tracing_request incident], + primero_program: PrimeroProgram.new(name: 'program'), + form_sections: [@form_section1, @form_section2] + ) + ] + ) + + expect(role.permitted_forms('case', true, true).pluck(:unique_id)).to match_array( + %w[subform1 form_section1] + ) + end + + it 'returns all permitted forms and subforms for a role if include_subforms=true and visible_only=false' do + role = Role.create!( + unique_id: 'role_test_01', + name: 'name_test_01', + description: 'description_test_01', + group_permission: 'all', + permissions: [ + Permission.new(resource: Permission::USER, actions: [Permission::READ, Permission::WRITE, Permission::CREATE]) + ], + form_sections: [@form_section1, @form_section2, @subform1], + modules: [ + PrimeroModule.new( + unique_id: 'primeromodule-cp-a', name: 'CPA', description: 'Child Protection A', + associated_record_types: %w[case tracing_request incident], + primero_program: PrimeroProgram.new(name: 'program'), + form_sections: [@form_section1, @form_section2] + ) + ] + ) + + expect(role.permitted_forms('case', false, true).pluck(:unique_id)).to match_array( + %w[subform1 subform2 form_section1 form_section2] + ) + end + end end From 9fc24e3172c48e9ec3d3fa79fd9059994a7efd11 Mon Sep 17 00:00:00 2001 From: Jasser Gutierrez Date: Fri, 19 Nov 2021 10:42:42 -0600 Subject: [PATCH 04/38] R2-1850 - Can no longer re-arrange lookup options --- .../components/form/component.jsx | 2 +- .../pages/admin/lookups-form/utils.js | 4 +-- .../admin/lookups-form/utils.unit.test.js | 36 +++++++++++++++++-- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/app/javascript/components/pages/admin/lookups-form/components/form/component.jsx b/app/javascript/components/pages/admin/lookups-form/components/form/component.jsx index ffe3fdac6f..34d500255e 100644 --- a/app/javascript/components/pages/admin/lookups-form/components/form/component.jsx +++ b/app/javascript/components/pages/admin/lookups-form/components/form/component.jsx @@ -89,7 +89,7 @@ const Component = ({ mode, lookup }) => { const body = { data: { name, - values: buildValues(lookupValues, i18n.locale, disabled) + values: buildValues(lookupValues, i18n.locale, disabled, items) } }; diff --git a/app/javascript/components/pages/admin/lookups-form/utils.js b/app/javascript/components/pages/admin/lookups-form/utils.js index 71d7cd33de..bea4aa9425 100644 --- a/app/javascript/components/pages/admin/lookups-form/utils.js +++ b/app/javascript/components/pages/admin/lookups-form/utils.js @@ -54,9 +54,9 @@ export const reorderValues = (items, startIndex, endIndex) => { return result; }; -export const buildValues = (values, defaultLocale, disabledValues) => { +export const buildValues = (values, defaultLocale, disabledValues, orderItems) => { const locales = Object.keys(values); - const displayTextKeys = Object.keys(values[defaultLocale]); + const displayTextKeys = orderItems || Object.keys(values[defaultLocale]); return displayTextKeys.map(key => { return { diff --git a/app/javascript/components/pages/admin/lookups-form/utils.unit.test.js b/app/javascript/components/pages/admin/lookups-form/utils.unit.test.js index 078b4671ee..2363395233 100644 --- a/app/javascript/components/pages/admin/lookups-form/utils.unit.test.js +++ b/app/javascript/components/pages/admin/lookups-form/utils.unit.test.js @@ -162,7 +162,7 @@ describe(" - utils", () => { } ]; - expect(utils.buildValues(values, "en", disabled)).to.deep.equal(expected); + expect(utils.buildValues(values, "en", disabled, null)).to.deep.equal(expected); }); it("DEPRECATED should return values with _delete key if there are removed values", () => { const values = { @@ -187,7 +187,39 @@ describe(" - utils", () => { } ]; - expect(utils.buildValues(values, "en", removed)).to.not.equal(expected); + expect(utils.buildValues(values, "en", removed, [])).to.not.equal(expected); + }); + it("should return values for a lookup with ordered item", () => { + const values = { + en: { test: "Test", new_option_1: "Test 1" }, + es: { test: "Prueba", new_option_1: "Prueba 1" } + }; + + const disabled = { + test: true, + test_1: false + }; + + const expected = [ + { + id: "test_1_1234abc", + disabled: true, + display_text: { + en: "Test 1", + es: "Prueba 1" + } + }, + { + id: "test", + disabled: false, + display_text: { + en: "Test", + es: "Prueba" + } + } + ]; + + expect(utils.buildValues(values, "en", disabled, ["new_option_1", "test"])).to.deep.equal(expected); }); }); }); From e4a1b0be3751017818478313daa5b6e728215429 Mon Sep 17 00:00:00 2001 From: Dennis Hernandez Date: Mon, 22 Nov 2021 16:11:41 -0600 Subject: [PATCH 05/38] R2-1895 - Prevent administrators from reporting on date and date-time fields --- .../reports-form/container.unit.test.js | 69 +++++++++++++++++-- .../components/reports-form/form.js | 5 +- .../reports-form/utils/build-fields.js | 1 - 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/app/javascript/components/reports-form/container.unit.test.js b/app/javascript/components/reports-form/container.unit.test.js index 85f2ffb919..3aadbe75e3 100644 --- a/app/javascript/components/reports-form/container.unit.test.js +++ b/app/javascript/components/reports-form/container.unit.test.js @@ -1,22 +1,32 @@ import { fromJS, OrderedMap } from "immutable"; +import { expect } from "chai"; import { PageContent, PageHeading } from "../page"; import { setupMountedComponent } from "../../test"; import { ACTIONS } from "../../libs/permissions"; -import { FormSectionRecord, FieldRecord } from "../record-form/records"; +import { PrimeroModuleRecord } from "../application/records"; +import { FormSectionRecord, FieldRecord } from "../form/records"; import Form from "../form"; +import SelectInput from "../form/fields/select-input"; import ReportsForm from "./container"; describe(" - Container", () => { let component; + const primeroModule = PrimeroModuleRecord({ + unique_id: "module_1", + associated_record_types: ["case"], + name: "Module 1" + }); + const forms = { formSections: OrderedMap({ 1: FormSectionRecord({ id: 1, unique_id: "incident_details_subform_section", name: { en: "Nested Incident Details Subform" }, + is_nested: true, visible: false, fields: [2] }), @@ -26,29 +36,64 @@ describe(" - Container", () => { name: { en: "Incident Details" }, visible: true, parent_form: "case", - fields: [1] + is_nested: false, + fields: [1], + module_ids: ["module_1"] + }), + 3: FormSectionRecord({ + id: 3, + unique_id: "form_1", + name: { en: "Form 1" }, + visible: true, + parent_form: "case", + is_nested: false, + fields: [3, 5], + module_ids: ["module_1"] }) }), fields: OrderedMap({ 1: FieldRecord({ name: "incident_details", - type: "subform" + type: "subform", + visible: true }), 2: FieldRecord({ name: "cp_incident_location_type_other", - type: "text_field" + type: "text_field", + visible: true + }), + 3: FieldRecord({ + name: "cp_create_date", + type: "date_field", + visible: true + }), + 4: FieldRecord({ + name: "status", + type: "text_field", + visible: true + }), + 5: FieldRecord({ + name: "cp_some_field", + type: "select_box", + visible: true }) }) }; const initialState = fromJS({ user: { + modules: ["module_1"], permissions: { reports: [ACTIONS.MANAGE] + }, + permittedForms: { + incident_details_container: "rw", + form_1: "rw" } }, forms, application: { + modules: [primeroModule], ageRanges: { primero: ["0..5", "6..11", "12..17", "18..999"] } @@ -67,6 +112,22 @@ describe(" - Container", () => { expect(component.find(PageContent)).to.have.lengthOf(1); }); + it("does not render date fields in the aggregate/disaggreate select fields", () => { + component.find(SelectInput).at(0).find("button").at(1).simulate("click"); + component.find(SelectInput).at(0).find("ul.MuiAutocomplete-groupUl").at(0).find("li").at(0).simulate("click"); + + component.find(SelectInput).at(1).find("button").at(1).simulate("click"); + component.find(SelectInput).at(1).find("ul.MuiAutocomplete-groupUl").at(0).find("li").at(0).simulate("click"); + + expect( + component + .find(SelectInput) + .at(2) + .props() + .options.map(option => option.id) + ).to.deep.equals(["cp_some_field"]); + }); + it("should contain valid props for
component", () => { const props = Object.keys(component.find(Form).props()); const expected = [ diff --git a/app/javascript/components/reports-form/form.js b/app/javascript/components/reports-form/form.js index 36bba3632e..50b12c1053 100644 --- a/app/javascript/components/reports-form/form.js +++ b/app/javascript/components/reports-form/form.js @@ -5,6 +5,7 @@ import { array, boolean, object, string } from "yup"; import isEmpty from "lodash/isEmpty"; import { FieldRecord, FormSectionRecord, TICK_FIELD, TEXT_FIELD, TEXT_AREA, SELECT_FIELD, OPTION_TYPES } from "../form"; +import { DATE_FIELD } from "../record-form/constants"; import { AGGREGATE_BY_FIELD, @@ -61,7 +62,9 @@ export const form = ( option_strings_source: OPTION_TYPES.RECORD_FORMS, handleWatchedInputs: checkModuleField, filterOptionSource: (watchedInputValues, options) => { - return checkModuleAndRecordType(watchedInputValues, options, reportableFields); + return checkModuleAndRecordType(watchedInputValues, options, reportableFields).filter( + field => field.type !== DATE_FIELD + ); } }; diff --git a/app/javascript/components/reports-form/utils/build-fields.js b/app/javascript/components/reports-form/utils/build-fields.js index 509e318286..2be78f8613 100644 --- a/app/javascript/components/reports-form/utils/build-fields.js +++ b/app/javascript/components/reports-form/utils/build-fields.js @@ -28,7 +28,6 @@ export default (data, i18n, isReportable, reportingLocationConfig, minimumReport } return data.reduce((acc, form) => { - // eslint-disable-next-line camelcase const fields = form.get("fields"); const name = form.get("name"); From ee579d07ee6aae195efa27db7a0078968c66b693 Mon Sep 17 00:00:00 2001 From: Alberto Espinoza Date: Mon, 22 Nov 2021 17:44:09 -0600 Subject: [PATCH 06/38] R2-1889 - MRM: Violations list --- .../components/render-form-sections.js | 81 +++++++++----- .../record-form/form/record-form-title.jsx | 33 ++++-- .../form/record-form-title.unit.test.js | 29 +++++ .../components/record-form/form/styles.css | 6 ++ .../subform-field-array/component.jsx | 9 +- .../component.unit.test.js | 35 ++++++ .../subforms/subform-fields/component.jsx | 3 + .../subform-fields/components/index.js | 1 + .../components/index.unit.test.js | 2 +- .../components/violation-item/component.jsx | 45 ++++++++ .../violation-item/component.unit.test.js | 61 +++++++++++ .../components/violation-item/constants.js | 3 + .../violation-item/constants.unit.test.js | 14 +++ .../components/violation-item/index.js | 1 + .../violation-item/index.unit.test.js | 14 +++ .../components/violation-item/styles.css | 18 ++++ .../components/violation-item/utils.js | 29 +++++ .../violation-item/utils.unit.test.js | 97 +++++++++++++++++ .../subform-header-lookup/component.jsx | 34 +++++- .../component.unit.test.js | 16 +++ .../subforms/subform-header/component.jsx | 25 ++++- .../subform-header/component.unit.test.js | 102 ++++++++++++++++++ .../utils/get-violation-field-for-guidance.js | 9 ++ ...-violation-field-for-guidance.unit.test.js | 14 +++ .../record-form/form/utils/index.js | 2 + .../form/utils/is-violation-subform.js | 7 ++ .../utils/is-violation-subform.unit.test.js | 24 +++++ .../components/record-form/nav/component.jsx | 3 +- .../components/record-form/nav/utils.js | 18 ++++ .../record-form/nav/utils.unit.test.js | 44 ++++++++ app/javascript/config/constants.js | 23 ++++ app/javascript/config/constants.unit.test.js | 5 +- 32 files changed, 762 insertions(+), 45 deletions(-) create mode 100644 app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/component.jsx create mode 100644 app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/component.unit.test.js create mode 100644 app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/constants.js create mode 100644 app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/constants.unit.test.js create mode 100644 app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/index.js create mode 100644 app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/index.unit.test.js create mode 100644 app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/styles.css create mode 100644 app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/utils.js create mode 100644 app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/utils.unit.test.js create mode 100644 app/javascript/components/record-form/form/subforms/subform-header/component.unit.test.js create mode 100644 app/javascript/components/record-form/form/utils/get-violation-field-for-guidance.js create mode 100644 app/javascript/components/record-form/form/utils/get-violation-field-for-guidance.unit.test.js create mode 100644 app/javascript/components/record-form/form/utils/is-violation-subform.js create mode 100644 app/javascript/components/record-form/form/utils/is-violation-subform.unit.test.js create mode 100644 app/javascript/components/record-form/nav/utils.js create mode 100644 app/javascript/components/record-form/nav/utils.unit.test.js diff --git a/app/javascript/components/record-form/components/render-form-sections.js b/app/javascript/components/record-form/components/render-form-sections.js index 96915919d1..76af29830a 100644 --- a/app/javascript/components/record-form/components/render-form-sections.js +++ b/app/javascript/components/record-form/components/render-form-sections.js @@ -7,6 +7,46 @@ import RecordFormTitle from "../form/record-form-title"; import { RECORD_FORM_PERMISSION } from "../form/constants"; import FormSectionField from "../form/form-section-field"; import SubformField from "../form/subforms"; +import { getViolationFieldForGuidance, isViolationSubform } from "../form/utils"; + +const renderFormFields = ( + form, + mode, + recordType, + record, + primeroModule, + isReadWriteForm, + guidanceFieldForViolation +) => { + return form.fields.map(field => { + const fieldProps = { + field, + form, + mode, + recordType, + recordID: record?.get("id"), + recordModuleID: primeroModule + }; + + if (!field?.visible) { + return null; + } + + if (guidanceFieldForViolation === field.name) { + return null; + } + + return ( +
+ {SUBFORM_SECTION === field.type ? ( + + ) : ( + + )} +
+ ); + }); +}; const renderFormSections = ( externalForms, @@ -33,40 +73,29 @@ const renderFormSections = ( if (selectedForm === form.unique_id) { const isReadWriteForm = userPermittedFormsIds?.get(selectedForm) === RECORD_FORM_PERMISSION.readWrite; + const isViolation = isViolationSubform(recordType, selectedForm); + const fieldForGuidance = isViolation ? getViolationFieldForGuidance(form.fields) : {}; + + const titleProps = isViolation + ? { + displayText: i18n.t("forms.record_types.violation"), + subTitle: displayNameHelper(form.name, i18n.locale), + subTitleGuidance: fieldForGuidance.guiding_questions + } + : { displayText: displayNameHelper(form.name, i18n.locale) }; + return ( - - {form.fields.map(field => { - const fieldProps = { - field, - form, - mode, - recordType, - recordID: record?.get("id"), - recordModuleID: primeroModule - }; - - if (!field?.visible) { - return null; - } - - return ( -
- {SUBFORM_SECTION === field.type ? ( - - ) : ( - - )} -
- ); - })} + {renderFormFields(form, mode, recordType, record, primeroModule, isReadWriteForm, fieldForGuidance?.name)}
); } diff --git a/app/javascript/components/record-form/form/record-form-title.jsx b/app/javascript/components/record-form/form/record-form-title.jsx index 5e8016a328..0d0f2fa25e 100644 --- a/app/javascript/components/record-form/form/record-form-title.jsx +++ b/app/javascript/components/record-form/form/record-form-title.jsx @@ -2,20 +2,38 @@ import { IconButton } from "@material-ui/core"; import MenuOpen from "@material-ui/icons/MenuOpen"; import PropTypes from "prop-types"; +import { useI18n } from "../../i18n"; + +import { GuidingQuestions } from "./components"; import css from "./styles.css"; -const RecordFormTitle = ({ displayText, handleToggleNav, mobileDisplay }) => { +const RecordFormTitle = ({ displayText, handleToggleNav, mobileDisplay, subTitle, subTitleGuidance, mode }) => { + const i18n = useI18n(); const showMobileIcon = mobileDisplay ? ( ) : null; - return ( -
- {showMobileIcon} - {displayText} + const renderSubTitle = subTitle ? ( +
+

{subTitle}

+ ) : null; + + const renderGuidingQuestions = subTitleGuidance && (mode.isEdit || mode.isNew) && subTitleGuidance[i18n.locale] && ( + + ); + + return ( + <> +
+ {showMobileIcon} + {displayText} +
+ {renderSubTitle} + {renderGuidingQuestions} + ); }; @@ -24,7 +42,10 @@ RecordFormTitle.displayName = "RecordFormTitle"; RecordFormTitle.propTypes = { displayText: PropTypes.string.isRequired, handleToggleNav: PropTypes.func.isRequired, - mobileDisplay: PropTypes.bool.isRequired + mobileDisplay: PropTypes.bool.isRequired, + mode: PropTypes.object.isRequired, + subTitle: PropTypes.string, + subTitleGuidance: PropTypes.string }; export default RecordFormTitle; diff --git a/app/javascript/components/record-form/form/record-form-title.unit.test.js b/app/javascript/components/record-form/form/record-form-title.unit.test.js index 13c94ffe6e..ed8bc3de5f 100644 --- a/app/javascript/components/record-form/form/record-form-title.unit.test.js +++ b/app/javascript/components/record-form/form/record-form-title.unit.test.js @@ -4,6 +4,7 @@ import MenuOpen from "@material-ui/icons/MenuOpen"; import { setupMountedComponent } from "../../../test"; import RecordFormTitle from "./record-form-title"; +import { GuidingQuestions } from "./components"; describe("", () => { const props = { @@ -29,4 +30,32 @@ describe("", () => { it("renders a valid text passed as a prop", () => { expect(component.text()).to.be.equal("Test title"); }); + + context("when subtitle is present", () => { + it("renders a ", () => { + const { component: currentComponent } = setupMountedComponent( + RecordFormTitle, + { ...props, subTitle: "Test subtitle" }, + {} + ); + const h3Tag = currentComponent.find("h3"); + + expect(h3Tag).to.have.lengthOf(1); + expect(h3Tag.at(0).text()).to.be.equal("Test subtitle"); + }); + }); + + context("when subTitleGuidance is present", () => { + it("renders a ", () => { + const { component: currentComponent } = setupMountedComponent( + RecordFormTitle, + { ...props, subTitleGuidance: { en: "This is a Guidance" }, mode: { isEdit: true } }, + {} + ); + const guidance = currentComponent.find(GuidingQuestions); + + expect(guidance).to.have.lengthOf(1); + expect(guidance.at(0).text()).to.be.equal("buttons.guidance"); + }); + }); }); diff --git a/app/javascript/components/record-form/form/styles.css b/app/javascript/components/record-form/form/styles.css index a6ef0813c0..dcdd0db6a7 100644 --- a/app/javascript/components/record-form/form/styles.css +++ b/app/javascript/components/record-form/form/styles.css @@ -64,6 +64,12 @@ padding-top: var(--sp-3); } +.formSubtitle { + font-size: var(--fs-15); + font-weight: bold; + color: var(--c-solid-black); +} + .attachmentHeading { display: flex; diff --git a/app/javascript/components/record-form/form/subforms/subform-field-array/component.jsx b/app/javascript/components/record-form/form/subforms/subform-field-array/component.jsx index 82bdad1668..3e9a88d77d 100644 --- a/app/javascript/components/record-form/form/subforms/subform-field-array/component.jsx +++ b/app/javascript/components/record-form/form/subforms/subform-field-array/component.jsx @@ -13,6 +13,7 @@ import { useThemeHelper } from "../../../../../libs"; import css from "../styles.css"; import ActionButton from "../../../../action-button"; import { ACTION_BUTTON_TYPES } from "../../../../action-button/constants"; +import { isViolationSubform } from "../../utils"; import { isTracesSubform } from "./utils"; @@ -54,6 +55,9 @@ const Component = ({ const renderAddText = !mobileDisplay ? i18n.t("fields.add") : null; const isTraces = isTracesSubform(recordType, formSection); + const isViolation = isViolationSubform(recordType, formSection.unique_id, true); + const renderAddFieldTitle = !isViolation && !mode.isShow && !displayConditions && i18n.t("fields.add"); + const renderFieldTitle = !isViolation && title; const handleCloseSubformTraces = () => setOpenDialog(false); useEffect(() => { @@ -77,6 +81,7 @@ const Component = ({ form={formSection} recordType={recordType} isTracesSubform={isTraces} + isViolationSubform={isViolation} formik={formik} parentForm={form} /> @@ -87,11 +92,11 @@ const Component = ({

- {!mode.isShow && !displayConditions && i18n.t("fields.add")} {title} + {renderAddFieldTitle} {renderFieldTitle}

- {!mode.isShow && !isDisabled && isReadWriteForm && ( + {!mode.isShow && !isDisabled && isReadWriteForm && !isViolation && ( } diff --git a/app/javascript/components/record-form/form/subforms/subform-field-array/component.unit.test.js b/app/javascript/components/record-form/form/subforms/subform-field-array/component.unit.test.js index fcc5c32a33..56acbf39a2 100644 --- a/app/javascript/components/record-form/form/subforms/subform-field-array/component.unit.test.js +++ b/app/javascript/components/record-form/form/subforms/subform-field-array/component.unit.test.js @@ -149,4 +149,39 @@ describe("", () => { expect(tracingRequestComponent.find(SubformDialog)).lengthOf(0); }); }); + + describe("when is a violation", () => { + let incidentComponent; + + beforeEach(() => { + ({ component: incidentComponent } = setupMountedComponent( + SubformFieldArray, + { + ...props, + recordType: "incidents", + formSection: { + ...props.formSection, + unique_id: "killing" + }, + mode: { + isShow: true + } + }, + Map({ + forms: Map({ + fields: [{ name: "killing" }] + }) + }), + [], + {} + )); + }); + + it("should not renders not render title", () => { + const h3Tag = incidentComponent.find("h3"); + + expect(h3Tag).lengthOf(1); + expect(h3Tag.at(0).text()).to.be.equal(" "); + }); + }); }); diff --git a/app/javascript/components/record-form/form/subforms/subform-fields/component.jsx b/app/javascript/components/record-form/form/subforms/subform-fields/component.jsx index 97e20453bf..350a63b4d6 100644 --- a/app/javascript/components/record-form/form/subforms/subform-fields/component.jsx +++ b/app/javascript/components/record-form/form/subforms/subform-fields/component.jsx @@ -27,6 +27,7 @@ const Component = ({ arrayHelpers, field, isTracesSubform, + isViolationSubform, locale, mode, setDialogIsNew, @@ -142,6 +143,7 @@ const Component = ({ locale={locale} values={values} onClick={handleEdit(index)} + isViolationSubform={isViolationSubform} />
@@ -195,6 +197,7 @@ Component.propTypes = { field: PropTypes.object.isRequired, formik: PropTypes.object.isRequired, isTracesSubform: PropTypes.bool, + isViolationSubform: PropTypes.bool, locale: PropTypes.string.isRequired, mode: PropTypes.object.isRequired, parentForm: PropTypes.object.isRequired, diff --git a/app/javascript/components/record-form/form/subforms/subform-fields/components/index.js b/app/javascript/components/record-form/form/subforms/subform-fields/components/index.js index db9b7f7ee1..4e3c792893 100644 --- a/app/javascript/components/record-form/form/subforms/subform-fields/components/index.js +++ b/app/javascript/components/record-form/form/subforms/subform-fields/components/index.js @@ -1,2 +1,3 @@ /* eslint-disable import/prefer-default-export */ export { default as TracingRequestStatus } from "./tracing-request-status"; +export { default as ViolationItem } from "./violation-item"; diff --git a/app/javascript/components/record-form/form/subforms/subform-fields/components/index.unit.test.js b/app/javascript/components/record-form/form/subforms/subform-fields/components/index.unit.test.js index 938e4d84c3..b4a1a36bec 100644 --- a/app/javascript/components/record-form/form/subforms/subform-fields/components/index.unit.test.js +++ b/app/javascript/components/record-form/form/subforms/subform-fields/components/index.unit.test.js @@ -5,7 +5,7 @@ describe("/form/subforms//components- index", () => { it("should have known properties", () => { expect(clone).to.be.an("object"); - ["TracingRequestStatus"].forEach(property => { + ["TracingRequestStatus", "ViolationItem"].forEach(property => { expect(clone).to.have.property(property); delete clone[property]; }); diff --git a/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/component.jsx b/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/component.jsx new file mode 100644 index 0000000000..71a97eb41e --- /dev/null +++ b/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/component.jsx @@ -0,0 +1,45 @@ +import PropTypes from "prop-types"; +import Chip from "@material-ui/core/Chip"; + +import useOptions from "../../../../../../form/use-options"; + +import { NAME, VIOLATION_STATUS } from "./constants"; +import { getViolationTallyLabel, getShortUniqueId, getVerifiedValue } from "./utils"; +import css from "./styles.css"; + +const Component = ({ fields, values, locale, displayName, index, collapsedFieldValues }) => { + const currentValues = values[index]; + const violationVerifiedField = fields.find(f => f.name === VIOLATION_STATUS); + const optionsStrings = useOptions({ source: violationVerifiedField.option_strings_source }); + + const shortUniqueId = getShortUniqueId(currentValues); + const violationTally = getViolationTallyLabel(fields, currentValues, locale); + const violationStatusLabel = getVerifiedValue(optionsStrings, currentValues); + + return ( +
+

+ {displayName?.[locale]} - {shortUniqueId}{" "} + +

+
+ {violationTally} +
+ {collapsedFieldValues} +
+
+ ); +}; + +Component.propTypes = { + collapsedFieldValues: PropTypes.node, + displayName: PropTypes.object, + fields: PropTypes.array.isRequired, + index: PropTypes.number.isRequired, + locale: PropTypes.string.isRequired, + values: PropTypes.array.isRequired +}; + +Component.displayName = NAME; + +export default Component; diff --git a/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/component.unit.test.js b/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/component.unit.test.js new file mode 100644 index 0000000000..f1187b4c64 --- /dev/null +++ b/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/component.unit.test.js @@ -0,0 +1,61 @@ +import { fromJS } from "immutable"; + +import { setupMountedComponent } from "../../../../../../../test"; +import { FieldRecord } from "../../../../../records"; + +import ViolationItem from "./component"; + +describe("/form/subforms//components/", () => { + const initialState = fromJS({ + forms: { + options: { + lookups: [ + { + unique_id: "lookup-status", + values: [ + { id: "status_1", display_text: { en: "status 1" } }, + { id: "status_2", display_text: { en: "status 2" } } + ] + } + ] + } + } + }); + + it("should render component", () => { + const props = { + fields: [ + FieldRecord({ + name: "relation_name", + visible: true, + type: "text_field" + }), + FieldRecord({ + name: "relation_child_is_in_contact", + visible: true, + type: "text_field" + }), + FieldRecord({ + name: "verified", + visible: true, + type: "text_field", + option_strings_source: "lookup lookup-status" + }), + FieldRecord({ + name: "violation_tally", + visible: true, + type: "tally_field", + display_name: { en: "violation count" } + }) + ], + values: [{ unique_id: "ab123cde", relation_name: "this is a relation" }], + locale: "en", + displayName: { en: "Testing" }, + index: 0 + }; + + const { component } = setupMountedComponent(ViolationItem, props, initialState); + + expect(component.find(ViolationItem)).lengthOf(1); + }); +}); diff --git a/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/constants.js b/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/constants.js new file mode 100644 index 0000000000..fd106f3341 --- /dev/null +++ b/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/constants.js @@ -0,0 +1,3 @@ +export const NAME = "SubformViolations"; +export const VIOLATION_STATUS = "verified"; +export const VIOLATION_TALLY_FIELD = "violation_tally"; diff --git a/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/constants.unit.test.js b/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/constants.unit.test.js new file mode 100644 index 0000000000..13fdccc667 --- /dev/null +++ b/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/constants.unit.test.js @@ -0,0 +1,14 @@ +import * as violationItemConstants from "./constants"; + +describe("Verifying violationItem constant", () => { + it("should have known constant", () => { + const constants = { ...violationItemConstants }; + + ["NAME", "VIOLATION_STATUS", "VIOLATION_TALLY_FIELD"].forEach(property => { + expect(constants).to.have.property(property); + delete constants[property]; + }); + + expect(constants).to.be.empty; + }); +}); diff --git a/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/index.js b/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/index.js new file mode 100644 index 0000000000..b6e0586481 --- /dev/null +++ b/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/index.js @@ -0,0 +1 @@ +export { default } from "./component"; diff --git a/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/index.unit.test.js b/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/index.unit.test.js new file mode 100644 index 0000000000..c3afafc752 --- /dev/null +++ b/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/index.unit.test.js @@ -0,0 +1,14 @@ +import * as indexValues from "./index"; + +describe("/form/subforms//components/ - index", () => { + const clone = { ...indexValues }; + + it("should have known properties", () => { + expect(clone).to.be.an("object"); + ["default"].forEach(property => { + expect(clone).to.have.property(property); + delete clone[property]; + }); + expect(clone).to.be.empty; + }); +}); diff --git a/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/styles.css b/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/styles.css new file mode 100644 index 0000000000..7ba94882a3 --- /dev/null +++ b/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/styles.css @@ -0,0 +1,18 @@ +.subformViolationHeader { + display: flex; + flex-direction: column; + margin-bottom: 20px; + font-family: var(--ff); +} + +.subformViolationHeaderFields { + font-family: var(--ff); + font-size: var(--fs-8); + color: var(--c-grey); +} + +.chipStatus { + color: var(--c-white); + border: solid 1px var(--c-blue); + background-color: var(--c-blue); +} \ No newline at end of file diff --git a/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/utils.js b/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/utils.js new file mode 100644 index 0000000000..8b8cecc0b9 --- /dev/null +++ b/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/utils.js @@ -0,0 +1,29 @@ +import { VIOLATION_TALLY_FIELD } from "./constants"; + +export const getViolationTallyLabel = (fields, currentValues, locale) => { + const displayText = fields.find(f => f.name === VIOLATION_TALLY_FIELD).display_name?.[locale]; + + if (!currentValues.violation_tally) { + return null; + } + + return Object.entries(currentValues.violation_tally).reduce((acc, curr) => { + if (curr[1] === 0 || curr[0] === "total") { + return acc; + } + + return `${acc} ${curr[0]}: (${curr[1]})`; + }, `${displayText}:`); +}; + +export const getShortUniqueId = currentValues => { + if (!currentValues.unique_id) return null; + + return currentValues.unique_id.substring(0, 5); +}; + +export const getVerifiedValue = (optionsStrings, currentValues) => { + const { display_text: displayText } = optionsStrings.find(option => option.id === currentValues.verified) || {}; + + return displayText; +}; diff --git a/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/utils.unit.test.js b/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/utils.unit.test.js new file mode 100644 index 0000000000..7cbc6a90ec --- /dev/null +++ b/app/javascript/components/record-form/form/subforms/subform-fields/components/violation-item/utils.unit.test.js @@ -0,0 +1,97 @@ +import { FieldRecord } from "../../../../../records"; + +import * as helpers from "./utils"; + +describe("Verifying utils", () => { + it("should have known utils", () => { + const clonedHelpers = { ...helpers }; + + ["getViolationTallyLabel", "getShortUniqueId", "getVerifiedValue"].forEach(property => { + expect(clonedHelpers).to.have.property(property); + delete clonedHelpers[property]; + }); + + expect(clonedHelpers).to.deep.equal({}); + }); +}); + +describe("getViolationTallyLabel", () => { + const fields = [ + FieldRecord({ + name: "relation_name", + visible: true, + type: "text_field" + }), + FieldRecord({ + name: "relation_child_is_in_contact", + visible: true, + type: "text_field" + }), + FieldRecord({ + name: "verified", + visible: true, + type: "text_field", + option_strings_source: "lookup lookup-status" + }), + FieldRecord({ + name: "violation_tally", + visible: true, + type: "tally_field", + display_name: { en: "violation count" } + }) + ]; + const currentValues = { + unique_id: "ab123cde", + relation_name: "this is a relation", + violation_tally: { boys: 1, girls: 2, unknow: 0, total: 3 } + }; + const locale = "en"; + + it("should return a short uniqueId if unique_id is present", () => { + expect(helpers.getViolationTallyLabel(fields, currentValues, locale)).to.deep.equal( + "violation count: boys: (1) girls: (2)" + ); + }); +}); + +describe("getShortUniqueId", () => { + it("should return a short uniqueId if unique_id is present", () => { + expect(helpers.getShortUniqueId({ unique_id: "ab123cde" })).to.equal("ab123"); + }); + + it("should return a null if unique_id is NOT present", () => { + expect(helpers.getShortUniqueId({})).to.equal(null); + }); +}); + +describe("getVerifiedValue", () => { + it("should return false if it is not the traces subform", () => { + const optionString = [ + { + id: "verified", + display_text: "Verified" + }, + { + id: "report_pending_verification", + display_text: "Report pending verification" + }, + { + id: "not_mrm", + display_text: "Not MRM" + }, + { + id: "verification_found_that_incident_did_not_occur", + display_text: "Verification found that incident did not occur" + } + ]; + const currentValues = { + unique_id: "ab123cde", + relation_name: "this is a relation", + verified: "verification_found_that_incident_did_not_occur" + }; + + expect(helpers.getVerifiedValue(optionString, currentValues)).to.equal( + "Verification found that incident did not occur" + ); + }); +}); diff --git a/app/javascript/components/record-form/form/subforms/subform-header-lookup/component.jsx b/app/javascript/components/record-form/form/subforms/subform-header-lookup/component.jsx index 2c9a3ee15b..40d5f15702 100644 --- a/app/javascript/components/record-form/form/subforms/subform-header-lookup/component.jsx +++ b/app/javascript/components/record-form/form/subforms/subform-header-lookup/component.jsx @@ -7,9 +7,10 @@ import useOptions from "../../../../form/use-options"; import { getMultiSelectValues } from "./utils"; -const Component = ({ value, optionsStringSource, optionsStringText }) => { +const Component = ({ value, optionsStringSource, optionsStringText, isViolationSubform, displayName, locale }) => { const i18n = useI18n(); const optionsStrings = useOptions({ source: optionsStringSource }); + const renderDisplayName = isViolationSubform && `${displayName?.[locale]}: `; if (isEmpty(value)) return <>{value}; @@ -17,23 +18,43 @@ const Component = ({ value, optionsStringSource, optionsStringText }) => { const { display_text: displayText } = optionsStrings.find(o => o.id === value) || {}; if (!Array.isArray(value)) { - return {displayText}; + return ( + + {renderDisplayName} + {displayText} + + ); } const texts = getMultiSelectValues(value, optionsStrings); - return {texts}; + return ( + + {renderDisplayName} + {texts} + + ); } if (Array.isArray(value)) { const texts = getMultiSelectValues(value, optionsStringText, i18n.locale); - return {texts}; + return ( + + {renderDisplayName} + {texts} + + ); } const { display_text: displayText } = optionsStringText.find(optionStringText => optionStringText.id === value); - return {displayText[i18n.locale]}; + return ( + + {renderDisplayName} + {displayText[i18n.locale]} + + ); }; Component.displayName = SUBFORM_LOOKUP_HEADER_NAME; @@ -43,6 +64,9 @@ Component.defaultProps = { }; Component.propTypes = { + displayName: PropTypes.object, + isViolationSubform: PropTypes.bool, + locale: PropTypes.string, optionsStringSource: PropTypes.string, optionsStringText: PropTypes.array, value: PropTypes.string diff --git a/app/javascript/components/record-form/form/subforms/subform-header-lookup/component.unit.test.js b/app/javascript/components/record-form/form/subforms/subform-header-lookup/component.unit.test.js index 0c2d7e36d2..6df204746c 100644 --- a/app/javascript/components/record-form/form/subforms/subform-header-lookup/component.unit.test.js +++ b/app/javascript/components/record-form/form/subforms/subform-header-lookup/component.unit.test.js @@ -94,4 +94,20 @@ describe(" - Form - Subforms", () => { expect(component.text()).to.equal("Service 2"); }); + + it("should render the displayName if it is a volation", () => { + const props = { + value: "region", + optionsStringSource: "lookup lookup-location-type", + isViolationSubform: true, + displayName: { + en: "Testing Display Name" + }, + locale: "en" + }; + + const { component } = setupMountedComponent(SubformLookupHeader, props, initialState); + + expect(component.text()).to.be.equal("Testing Display Name: Region"); + }); }); diff --git a/app/javascript/components/record-form/form/subforms/subform-header/component.jsx b/app/javascript/components/record-form/form/subforms/subform-header/component.jsx index 0377509fdb..28177a2292 100644 --- a/app/javascript/components/record-form/form/subforms/subform-header/component.jsx +++ b/app/javascript/components/record-form/form/subforms/subform-header/component.jsx @@ -5,10 +5,11 @@ import { NAME_FIELD, DATE_FIELD, SELECT_FIELD, TICK_FIELD, RADIO_FIELD } from ". import SubformLookupHeader from "../subform-header-lookup"; import SubformDateHeader from "../subform-header-date"; import SubformTickBoxHeader from "../subform-header-tickbox"; +import ViolationItem from "../subform-fields/components/violation-item"; import css from "../styles.css"; import { SUBFORM_HEADER } from "../constants"; -const Component = ({ field, values, locale, displayName, index, onClick }) => { +const Component = ({ field, values, locale, displayName, index, onClick, isViolationSubform }) => { const { collapsed_field_names: collapsedFieldNames, fields } = field.subform_section_id; const subformValues = collapsedFieldNames @@ -19,7 +20,8 @@ const Component = ({ field, values, locale, displayName, index, onClick }) => { date_include_time: includeTime, option_strings_source: optionsStringSource, option_strings_text: optionsStringText, - tick_box_label: tickBoxLabel + tick_box_label: tickBoxLabel, + display_name: displayNameCollapsedField } = fields.find(f => f.get(NAME_FIELD) === collapsedFieldName); const value = val[collapsedFieldName]; @@ -47,7 +49,10 @@ const Component = ({ field, values, locale, displayName, index, onClick }) => { value: typeof value === "boolean" ? value.toString() : value, key: collapsedFieldName, optionsStringSource, - optionsStringText + optionsStringText, + isViolationSubform, + displayName: displayNameCollapsedField, + locale }; return ; @@ -61,6 +66,19 @@ const Component = ({ field, values, locale, displayName, index, onClick }) => { const handleClick = () => onClick(index); if (collapsedFieldNames.length && values.length) { + if (isViolationSubform) { + return ( + + ); + } + return (
@@ -81,6 +99,7 @@ Component.propTypes = { displayName: PropTypes.object, field: PropTypes.object.isRequired, index: PropTypes.number.isRequired, + isViolationSubform: PropTypes.bool, locale: PropTypes.string.isRequired, onClick: PropTypes.func.isRequired, values: PropTypes.array.isRequired diff --git a/app/javascript/components/record-form/form/subforms/subform-header/component.unit.test.js b/app/javascript/components/record-form/form/subforms/subform-header/component.unit.test.js new file mode 100644 index 0000000000..b44b8b6843 --- /dev/null +++ b/app/javascript/components/record-form/form/subforms/subform-header/component.unit.test.js @@ -0,0 +1,102 @@ +import { fromJS } from "immutable"; + +import { setupMountedComponent } from "../../../../../test"; +import { FieldRecord, FormSectionRecord } from "../../../records"; +import ViolationItem from "../subform-fields/components/violation-item"; + +import SubformHeader from "./component"; + +describe("/form/subforms/", () => { + const initialState = fromJS({ + forms: { + options: { + lookups: [ + { + unique_id: "lookup-status", + values: [ + { id: "status_1", display_text: { en: "status 1" } }, + { id: "status_2", display_text: { en: "status 2" } } + ] + } + ] + } + } + }); + + it("should render the displayName", () => { + const props = { + field: FieldRecord({ + name: "services_section", + subform_section_id: FormSectionRecord({ + unique_id: "services_subform_section", + fields: [ + FieldRecord({ + name: "relation_name", + visible: true, + type: "text_field" + }), + FieldRecord({ + name: "relation_child_is_in_contact", + visible: true, + type: "text_field" + }) + ] + }) + }), + values: [{ relation_name: "this is a relation" }], + locale: "en", + displayName: { en: "Testing" }, + index: 0, + onClick: () => {}, + isViolationSubform: false + }; + const { component } = setupMountedComponent(SubformHeader, props, fromJS({})); + + expect(component.text()).to.be.equal("Testing"); + }); + + it("should render ViolationItem when is a ViolationSubform", () => { + const props = { + field: FieldRecord({ + name: "services_section", + subform_section_id: FormSectionRecord({ + unique_id: "services_subform_section", + fields: [ + FieldRecord({ + name: "relation_name", + visible: true, + type: "text_field" + }), + FieldRecord({ + name: "relation_child_is_in_contact", + visible: true, + type: "text_field" + }), + FieldRecord({ + name: "verified", + visible: true, + type: "text_field", + option_strings_source: "lookup lookup-status" + }), + FieldRecord({ + name: "violation_tally", + visible: true, + type: "tally_field", + display_name: { en: "violation count" } + }) + ], + collapsed_field_names: ["relation_name"] + }) + }), + values: [{ unique_id: "ab123cde", relation_name: "this is a relation" }], + locale: "en", + displayName: { en: "Testing" }, + index: 0, + onClick: () => {}, + isViolationSubform: true + }; + const { component } = setupMountedComponent(SubformHeader, props, initialState); + + expect(component.find(ViolationItem)).lengthOf(1); + }); +}); diff --git a/app/javascript/components/record-form/form/utils/get-violation-field-for-guidance.js b/app/javascript/components/record-form/form/utils/get-violation-field-for-guidance.js new file mode 100644 index 0000000000..b348b7f79b --- /dev/null +++ b/app/javascript/components/record-form/form/utils/get-violation-field-for-guidance.js @@ -0,0 +1,9 @@ +import { SEPERATOR } from "../../constants"; + +export default fields => { + if (fields[0].type === SEPERATOR) { + return fields[0]; + } + + return {}; +}; diff --git a/app/javascript/components/record-form/form/utils/get-violation-field-for-guidance.unit.test.js b/app/javascript/components/record-form/form/utils/get-violation-field-for-guidance.unit.test.js new file mode 100644 index 0000000000..5ed4861bbb --- /dev/null +++ b/app/javascript/components/record-form/form/utils/get-violation-field-for-guidance.unit.test.js @@ -0,0 +1,14 @@ +import { SEPERATOR, TEXT_FIELD } from "../../constants"; + +import getViolationFieldForGuidance from "./get-violation-field-for-guidance"; + +describe("getViolationFieldForGuidance", () => { + it("should return a field if separator and it's the first one", () => { + expect(getViolationFieldForGuidance([{ type: SEPERATOR }, { type: TEXT_FIELD }])).to.be.deep.equal({ + type: SEPERATOR + }); + }); + it("should return an empty object if it is no a separator", () => { + expect(getViolationFieldForGuidance([{ type: TEXT_FIELD }])).to.be.deep.equal({}); + }); +}); diff --git a/app/javascript/components/record-form/form/utils/index.js b/app/javascript/components/record-form/form/utils/index.js index 0d74091521..3f273ff853 100644 --- a/app/javascript/components/record-form/form/utils/index.js +++ b/app/javascript/components/record-form/form/utils/index.js @@ -5,8 +5,10 @@ export { default as getDefaultForms } from "./get-default-forms"; export { default as getSelectFieldDefaultValue } from "./get-select-field-default-value"; export { default as getSubformValues } from "./get-subform-values"; export { default as getTranslatedText } from "./get-translated-text"; +export { default as getViolationFieldForGuidance } from "./get-violation-field-for-guidance"; export { default as handleChangeOnServiceUser } from "./handle-change-service-user"; export { default as isFormDirty } from "./is-form-dirty"; +export { default as isViolationSubform } from "./is-violation-subform"; export { default as serviceHasReferFields } from "./service-has-refer-fields"; export { default as serviceIsReferrable } from "./service-is-referrable"; export { default as shouldFieldUpdate } from "./should-field-update"; diff --git a/app/javascript/components/record-form/form/utils/is-violation-subform.js b/app/javascript/components/record-form/form/utils/is-violation-subform.js new file mode 100644 index 0000000000..f16582c047 --- /dev/null +++ b/app/javascript/components/record-form/form/utils/is-violation-subform.js @@ -0,0 +1,7 @@ +import { RECORD_TYPES, VIOLATIONS_FORM, VIOLATIONS_SUBFORM_UNIQUE_IDS } from "../../../../config"; + +export default (recordType, uniqueId, useSubformUniqueId = false) => { + const uniqueIds = useSubformUniqueId ? VIOLATIONS_SUBFORM_UNIQUE_IDS : VIOLATIONS_FORM; + + return RECORD_TYPES[recordType] === RECORD_TYPES.incidents && uniqueIds.includes(uniqueId); +}; diff --git a/app/javascript/components/record-form/form/utils/is-violation-subform.unit.test.js b/app/javascript/components/record-form/form/utils/is-violation-subform.unit.test.js new file mode 100644 index 0000000000..2abd5c736c --- /dev/null +++ b/app/javascript/components/record-form/form/utils/is-violation-subform.unit.test.js @@ -0,0 +1,24 @@ +import sample from "lodash/sample"; + +import { CASES, INCIDENTS, VIOLATIONS_FORM, VIOLATIONS_SUBFORM_UNIQUE_IDS } from "../../../../config"; + +import isViolationSubform from "./is-violation-subform"; + +describe("isViolationSubform", () => { + context("when useSubformUniqueId is true", () => { + it("shourl return false if unique_id is not violation form", () => { + expect(isViolationSubform(CASES, "test", true)).to.be.false; + }); + it("shourl return true if unique_id is not violation form", () => { + expect(isViolationSubform(INCIDENTS, sample(VIOLATIONS_SUBFORM_UNIQUE_IDS), true)).to.be.true; + }); + }); + context("when useSubformUniqueId is false", () => { + it("shourl return false if unique_id is not violation form", () => { + expect(isViolationSubform(CASES, "test", false)).to.be.false; + }); + it("shourl return true if unique_id is not violation form", () => { + expect(isViolationSubform(INCIDENTS, sample(VIOLATIONS_FORM), false)).to.be.true; + }); + }); +}); diff --git a/app/javascript/components/record-form/nav/component.jsx b/app/javascript/components/record-form/nav/component.jsx index c5651226d6..ab78dbadcd 100644 --- a/app/javascript/components/record-form/nav/component.jsx +++ b/app/javascript/components/record-form/nav/component.jsx @@ -25,6 +25,7 @@ import useOptions from "../../form/use-options"; import { NAME } from "./constants"; import { NavGroup, RecordInformation } from "./components"; import css from "./styles.css"; +import buildFormGroupData from "./utils"; const Component = ({ firstTab, @@ -178,7 +179,7 @@ const Component = ({ const renderFormGroups = formGroups.map(formGroup => { return ( { + if (formGroupOrdered.valueSeq().first().group !== VIOLATION_GROUP) { + return formGroupOrdered; + } + + return OrderedMap( + VIOLATIONS_FORM.reduce((acc, current, index) => { + const currentForm = formGroupOrdered.valueSeq().find(form => form.formId === current) || NavRecord({}); + + return { ...acc, [index]: currentForm }; + }, {}) + ); +}; diff --git a/app/javascript/components/record-form/nav/utils.unit.test.js b/app/javascript/components/record-form/nav/utils.unit.test.js new file mode 100644 index 0000000000..548a7a9346 --- /dev/null +++ b/app/javascript/components/record-form/nav/utils.unit.test.js @@ -0,0 +1,44 @@ +import { OrderedMap } from "immutable"; + +import { NavRecord } from "../records"; + +import buildFormGroupData from "./utils"; + +describe("