add logical 'or' to field conditions
This commit is contained in:
committed by
Pete Matsyburka
parent
36c64d1de4
commit
75683631f0
@@ -105,7 +105,7 @@ module Api
|
||||
:required, :readonly, :default_value,
|
||||
:title, :description,
|
||||
{ preferences: {},
|
||||
conditions: [%i[field_uuid value action]],
|
||||
conditions: [%i[field_uuid value action operation]],
|
||||
options: [%i[value uuid]],
|
||||
validation: %i[message pattern],
|
||||
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]]
|
||||
|
||||
@@ -115,7 +115,7 @@ class TemplatesController < ApplicationController
|
||||
:required, :readonly, :default_value,
|
||||
:title, :description,
|
||||
{ preferences: {},
|
||||
conditions: [%i[field_uuid value action]],
|
||||
conditions: [%i[field_uuid value action operation]],
|
||||
options: [%i[value uuid]],
|
||||
validation: %i[message pattern],
|
||||
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]] }
|
||||
|
||||
@@ -1027,35 +1027,46 @@ export default {
|
||||
},
|
||||
checkFieldConditions (field) {
|
||||
if (field.conditions?.length) {
|
||||
return field.conditions.reduce((acc, c) => {
|
||||
const field = this.fieldsUuidIndex[c.field_uuid]
|
||||
|
||||
if (['not_empty', 'checked', 'equal', 'contains'].includes(c.action) && field && !this.checkFieldConditions(field)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (['empty', 'unchecked'].includes(c.action)) {
|
||||
return acc && isEmpty(this.values[c.field_uuid])
|
||||
} else if (['not_empty', 'checked'].includes(c.action)) {
|
||||
return acc && !isEmpty(this.values[c.field_uuid])
|
||||
} else if (['equal', 'contains'].includes(c.action) && field) {
|
||||
if (field.options) {
|
||||
const option = field.options.find((o) => o.uuid === c.value)
|
||||
const values = [this.values[c.field_uuid]].flat()
|
||||
|
||||
return acc && values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||
} else {
|
||||
return acc && [this.values[c.field_uuid]].flat().includes(c.value)
|
||||
}
|
||||
} else if (['not_equal', 'does_not_contain'].includes(c.action) && field) {
|
||||
const option = field.options.find((o) => o.uuid === c.value)
|
||||
const values = [this.values[c.field_uuid]].flat()
|
||||
|
||||
return acc && !values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||
const result = field.conditions.reduce((acc, cond) => {
|
||||
if (cond.operation === 'or') {
|
||||
acc.push(acc.pop() || this.checkFieldCondition(cond))
|
||||
} else {
|
||||
return acc
|
||||
acc.push(this.checkFieldCondition(cond))
|
||||
}
|
||||
}, true)
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
return !result.includes(false)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
},
|
||||
checkFieldCondition (condition) {
|
||||
const field = this.fieldsUuidIndex[condition.field_uuid]
|
||||
|
||||
if (['not_empty', 'checked', 'equal', 'contains'].includes(condition.action) && field && !this.checkFieldConditions(field)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (['empty', 'unchecked'].includes(condition.action)) {
|
||||
return isEmpty(this.values[condition.field_uuid])
|
||||
} else if (['not_empty', 'checked'].includes(condition.action)) {
|
||||
return !isEmpty(this.values[condition.field_uuid])
|
||||
} else if (['equal', 'contains'].includes(condition.action) && field) {
|
||||
if (field.options) {
|
||||
const option = field.options.find((o) => o.uuid === condition.value)
|
||||
const values = [this.values[condition.field_uuid]].flat()
|
||||
|
||||
return values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||
} else {
|
||||
return [this.values[condition.field_uuid]].flat().includes(condition.value)
|
||||
}
|
||||
} else if (['not_equal', 'does_not_contain'].includes(condition.action) && field) {
|
||||
const option = field.options.find((o) => o.uuid === condition.value)
|
||||
const values = [this.values[condition.field_uuid]].flat()
|
||||
|
||||
return !values.includes(this.optionValue(option, field.options.indexOf(option)))
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -29,15 +29,26 @@
|
||||
>{{ t('available_in_pro') }}</a>
|
||||
</div>
|
||||
<form @submit.prevent="validateSaveAndClose">
|
||||
<div class="my-4 space-y-5">
|
||||
<div class="my-4">
|
||||
<div
|
||||
v-for="(condition, cindex) in conditions"
|
||||
:key="cindex"
|
||||
class="space-y-4 relative"
|
||||
>
|
||||
<div
|
||||
v-if="cindex > 0"
|
||||
class="divider -mb-2 mx-1"
|
||||
>
|
||||
<button
|
||||
class="btn btn-xs btn-primary w-24"
|
||||
@click.prevent="condition.operation === 'or' ? delete condition.operation : condition.operation = 'or'"
|
||||
>
|
||||
{{ condition.operation === 'or' ? t('or') : t('and') }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="conditions.length > 1"
|
||||
class="flex justify-between border-b mx-1 -mb-1 pb-1"
|
||||
class="flex justify-between mx-1"
|
||||
>
|
||||
<span class="text-sm">
|
||||
{{ t('condition') }} {{ cindex + 1 }}
|
||||
|
||||
@@ -148,7 +148,9 @@ const en = {
|
||||
preferences: 'Preferences',
|
||||
available_in_pro: 'Available in Pro',
|
||||
some_fields_are_missing_in_the_formula: 'Some fields are missing in the formula.',
|
||||
learn_more: 'Learn more'
|
||||
learn_more: 'Learn more',
|
||||
and: 'and',
|
||||
or: 'or'
|
||||
}
|
||||
|
||||
const es = {
|
||||
@@ -301,7 +303,9 @@ const es = {
|
||||
preferences: 'Preferencias',
|
||||
available_in_pro: 'Disponible en Pro',
|
||||
some_fields_are_missing_in_the_formula: 'Faltan algunos campos en la fórmula.',
|
||||
learn_more: 'Aprende más'
|
||||
learn_more: 'Aprende más',
|
||||
and: 'y',
|
||||
or: 'o'
|
||||
}
|
||||
|
||||
const it = {
|
||||
@@ -454,7 +458,9 @@ const it = {
|
||||
preferences: 'Preferenze',
|
||||
available_in_pro: 'Disponibile in Pro',
|
||||
some_fields_are_missing_in_the_formula: 'Alcuni campi mancano nella formula.',
|
||||
learn_more: 'Scopri di più'
|
||||
learn_more: 'Scopri di più',
|
||||
and: 'e',
|
||||
or: 'o'
|
||||
}
|
||||
|
||||
const pt = {
|
||||
@@ -607,7 +613,9 @@ const pt = {
|
||||
preferences: 'Preferências',
|
||||
available_in_pro: 'Disponível no Pro',
|
||||
some_fields_are_missing_in_the_formula: 'Faltam alguns campos na fórmula.',
|
||||
learn_more: 'Saiba mais'
|
||||
learn_more: 'Saiba mais',
|
||||
and: 'e',
|
||||
or: 'ou'
|
||||
}
|
||||
|
||||
const fr = {
|
||||
@@ -760,7 +768,9 @@ const fr = {
|
||||
preferences: 'Préférences',
|
||||
available_in_pro: 'Disponible en version Pro',
|
||||
some_fields_are_missing_in_the_formula: 'Certains champs manquent dans la formule.',
|
||||
learn_more: 'En savoir plus'
|
||||
learn_more: 'En savoir plus',
|
||||
and: 'et',
|
||||
or: 'ou'
|
||||
}
|
||||
|
||||
const de = {
|
||||
@@ -913,7 +923,9 @@ const de = {
|
||||
preferences: 'Einstellungen',
|
||||
available_in_pro: 'In Pro verfügbar',
|
||||
some_fields_are_missing_in_the_formula: 'Einige Felder fehlen in der Formel.',
|
||||
learn_more: 'Erfahren Sie mehr'
|
||||
learn_more: 'Erfahren Sie mehr',
|
||||
and: 'und',
|
||||
or: 'oder'
|
||||
}
|
||||
|
||||
export { en, es, it, pt, fr, de }
|
||||
|
||||
@@ -175,38 +175,46 @@ module Submitters
|
||||
submitter.submission.template_fields.each do |field|
|
||||
next if field['submitter_uuid'] != submitter.uuid
|
||||
|
||||
submitter.values.delete(field['uuid']) unless check_field_condition(submitter, field, fields_uuid_index)
|
||||
submitter.values.delete(field['uuid']) unless check_field_conditions(submitter, field, fields_uuid_index)
|
||||
end
|
||||
|
||||
submitter.values
|
||||
end
|
||||
|
||||
def check_field_condition(submitter, field, fields_uuid_index)
|
||||
def check_field_conditions(submitter, field, fields_uuid_index)
|
||||
return true if field['conditions'].blank?
|
||||
|
||||
submitter_values = submitter.values
|
||||
|
||||
field['conditions'].reduce(true) do |acc, c|
|
||||
case c['action']
|
||||
when 'empty', 'unchecked'
|
||||
acc && submitter_values[c['field_uuid']].blank?
|
||||
when 'not_empty', 'checked'
|
||||
acc && submitter_values[c['field_uuid']].present?
|
||||
when 'equal', 'contains'
|
||||
field = fields_uuid_index[c['field_uuid']]
|
||||
option = field['options'].find { |o| o['uuid'] == c['value'] }
|
||||
values = Array.wrap(submitter_values[c['field_uuid']])
|
||||
|
||||
acc && values.include?(option['value'].presence || "Option #{field['options'].index(option)}")
|
||||
when 'not_equal', 'does_not_contain'
|
||||
field = fields_uuid_index[c['field_uuid']]
|
||||
option = field['options'].find { |o| o['uuid'] == c['value'] }
|
||||
values = Array.wrap(submitter_values[c['field_uuid']])
|
||||
|
||||
acc && values.exclude?(option['value'].presence || "Option #{field['options'].index(option)}")
|
||||
field['conditions'].each_with_object([]) do |c, acc|
|
||||
if c['operation'] == 'or'
|
||||
acc.push(acc.pop || check_field_condition(c, submitter_values, fields_uuid_index))
|
||||
else
|
||||
acc
|
||||
acc.push(check_field_condition(c, submitter_values, fields_uuid_index))
|
||||
end
|
||||
end.exclude?(false)
|
||||
end
|
||||
|
||||
def check_field_condition(condition, submitter_values, fields_uuid_index)
|
||||
case condition['action']
|
||||
when 'empty', 'unchecked'
|
||||
submitter_values[condition['field_uuid']].blank?
|
||||
when 'not_empty', 'checked'
|
||||
submitter_values[condition['field_uuid']].present?
|
||||
when 'equal', 'contains'
|
||||
field = fields_uuid_index[condition['field_uuid']]
|
||||
option = field['options'].find { |o| o['uuid'] == condition['value'] }
|
||||
values = Array.wrap(submitter_values[condition['field_uuid']])
|
||||
|
||||
values.include?(option['value'].presence || "#{I18n.t('option')} #{field['options'].index(option)}")
|
||||
when 'not_equal', 'does_not_contain'
|
||||
field = fields_uuid_index[condition['field_uuid']]
|
||||
option = field['options'].find { |o| o['uuid'] == condition['value'] }
|
||||
values = Array.wrap(submitter_values[condition['field_uuid']])
|
||||
|
||||
values.exclude?(option['value'].presence || "#{I18n.t('option')} #{field['options'].index(option)}")
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -579,6 +579,199 @@ RSpec.describe 'Signing Form', type: :system do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the field with conditions' do
|
||||
let(:template) { create(:template, account:, author:, only_field_types: ['text']) }
|
||||
let(:submission) { create(:submission, :with_submitters, template:) }
|
||||
let(:template_attachment) { template.schema.first }
|
||||
let(:template_submitter) { submission.template_submitters.first }
|
||||
let(:submitter) { submission.submitters.first }
|
||||
let(:fields) do
|
||||
[
|
||||
{
|
||||
'uuid' => 'da7e0d56-fdb0-441a-bbed-d0f6f2e10fd6',
|
||||
'submitter_uuid' => submitter.uuid,
|
||||
'name' => 'Full Name',
|
||||
'type' => 'text',
|
||||
'required' => false,
|
||||
'preferences' => {},
|
||||
'conditions' => [],
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.1117351575121163,
|
||||
'y' => 0.08950650415231329,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => template_attachment['attachment_uuid'],
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => 'd32ad52a-8f6b-4e32-b0d6-6258fb47440b',
|
||||
'submitter_uuid' => submitter.uuid,
|
||||
'name' => 'Email',
|
||||
'type' => 'text',
|
||||
'required' => false,
|
||||
'preferences' => {},
|
||||
'conditions' => [],
|
||||
'validation' => { 'pattern' => '^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$' },
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.1097914983844911,
|
||||
'y' => 0.1417641720258019,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => template_attachment['attachment_uuid'],
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => 'c6e013ae-f9f6-4b3a-ad33-b7e772a0a49f',
|
||||
'submitter_uuid' => submitter.uuid,
|
||||
'name' => 'Phone',
|
||||
'type' => 'text',
|
||||
'required' => false,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.1100060581583199,
|
||||
'y' => 0.2553160344676159,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => template_attachment['attachment_uuid'],
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => '64523936-22fd-41f8-b997-ede8fbe467cc',
|
||||
'submitter_uuid' => submitter.uuid,
|
||||
'name' => 'Comment',
|
||||
'type' => 'text',
|
||||
'required' => false,
|
||||
'preferences' => {},
|
||||
'conditions' => [
|
||||
{ 'field_uuid' => 'da7e0d56-fdb0-441a-bbed-d0f6f2e10fd6', 'action' => 'not_empty' },
|
||||
{ 'field_uuid' => 'd32ad52a-8f6b-4e32-b0d6-6258fb47440b', 'action' => 'not_empty' },
|
||||
{ 'field_uuid' => 'c6e013ae-f9f6-4b3a-ad33-b7e772a0a49f', 'action' => 'not_empty', 'operation' => 'or' }
|
||||
],
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.1145875403877221,
|
||||
'y' => 0.1982961365432846,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => template_attachment['attachment_uuid'],
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
before do
|
||||
template.update(fields:)
|
||||
submission.update(template_fields: fields)
|
||||
end
|
||||
|
||||
it 'completes the form and saves the conditional field when all required fields are filled' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
fill_in 'Full Name (optional)', with: 'John Doe'
|
||||
click_button 'next'
|
||||
|
||||
fill_in 'Email (optional)', with: 'john.due@example.com'
|
||||
click_button 'next'
|
||||
|
||||
fill_in 'Phone (optional)', with: '+1 (773) 229-8825'
|
||||
click_button 'next'
|
||||
|
||||
fill_in 'Comment', with: 'This is a comment'
|
||||
click_button 'Complete'
|
||||
|
||||
expect(page).to have_content('Form has been completed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'Full Name')).to eq 'John Doe'
|
||||
expect(field_value(submitter, 'Email')).to eq 'john.due@example.com'
|
||||
expect(field_value(submitter, 'Phone')).to eq '+1 (773) 229-8825'
|
||||
expect(field_value(submitter, 'Comment')).to eq 'This is a comment'
|
||||
end
|
||||
|
||||
it 'completes the form and saves the conditional field when minimum required fields are filled' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
fill_in 'Full Name (optional)', with: 'John Doe'
|
||||
click_button 'next'
|
||||
|
||||
fill_in 'Email (optional)', with: 'john.due@example.com'
|
||||
click_button 'next'
|
||||
|
||||
fill_in 'Phone (optional)', with: ''
|
||||
click_button 'next'
|
||||
|
||||
fill_in 'Comment', with: 'This is a comment'
|
||||
click_button 'Complete'
|
||||
|
||||
expect(page).to have_content('Form has been completed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'Full Name')).to eq 'John Doe'
|
||||
expect(field_value(submitter, 'Email')).to eq 'john.due@example.com'
|
||||
expect(field_value(submitter, 'Phone')).to be_empty
|
||||
expect(field_value(submitter, 'Comment')).to eq 'This is a comment'
|
||||
end
|
||||
|
||||
it 'completes the form without saving the conditional field when not enough fields are filled' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
fill_in 'Full Name (optional)', with: 'Jane Doe'
|
||||
click_button 'next'
|
||||
|
||||
fill_in 'Email (optional)', with: ''
|
||||
click_button 'next'
|
||||
|
||||
fill_in 'Phone (optional)', with: ''
|
||||
click_button 'Complete'
|
||||
|
||||
expect(page).to have_content('Form has been completed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'Full Name')).to eq 'Jane Doe'
|
||||
expect(field_value(submitter, 'Email')).to be_empty
|
||||
expect(field_value(submitter, 'Phone')).to be_empty
|
||||
expect(field_value(submitter, 'Comment')).to be_nil
|
||||
end
|
||||
|
||||
it 'completes the form without saving the conditional field when only partial fields are filled' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
fill_in 'Full Name (optional)', with: ''
|
||||
click_button 'next'
|
||||
|
||||
fill_in 'Email (optional)', with: 'john.due@example.com'
|
||||
click_button 'next'
|
||||
|
||||
fill_in 'Phone (optional)', with: '+1 (773) 229-8825'
|
||||
click_button 'Complete'
|
||||
|
||||
expect(page).to have_content('Form has been completed!')
|
||||
|
||||
submitter.reload
|
||||
|
||||
expect(submitter.completed_at).to be_present
|
||||
expect(field_value(submitter, 'Full Name')).to be_empty
|
||||
expect(field_value(submitter, 'Email')).to eq 'john.due@example.com'
|
||||
expect(field_value(submitter, 'Phone')).to eq '+1 (773) 229-8825'
|
||||
expect(field_value(submitter, 'Comment')).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'sends completed email' do
|
||||
template = create(:template, account:, author:, only_field_types: %w[text signature])
|
||||
submission = create(:submission, template:)
|
||||
|
||||
Reference in New Issue
Block a user