Skip to content

Commit 4dd216e

Browse files
author
John Pinto
committed
Fix issues with Conditional question serialization (offered by @briri
from DMPTool). Based on DMPtool commit CDLUC3#667. Changes proposed in this PR (text from cited PR): - There is a migration file with code for MySQL and Postgres to update the Conditions table to convert JSON Arrays in string format records in the conditions table so that they are JSON Arrays. - Updated the form partials in app/views/org_admin/conditions/_form.erb.rb to properly send condition data to the controller. - Removed all JSON.parse calls in the app/helpers/conditions.rb helper Made the following changes to simplify this patch and to make it a little more user friendly: - The "Add Conditions" button will now say "Edit Conditions" if there are any. - Updated the column heading over the "thing that happens when the condition is met" from "Remove" to "Target" since the content of the column can either be questions being removed or an email notification being sent. - Conditions can be added or removed (not updated anymore) - Hovering over the email of an existing condition displays a tooltip that shows the email message, subject, etc. - We allow only one question option to be selected when adding a Condition unlike inthe DMPTool version because experience with multiple options chosen has been problematic and buggy when used by users in DMPOnline.
1 parent 994bb60 commit 4dd216e

File tree

11 files changed

+345
-154
lines changed

11 files changed

+345
-154
lines changed

CHANGELOG.md

Lines changed: 111 additions & 95 deletions
Large diffs are not rendered by default.

app/controllers/org_admin/questions_controller.rb

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -227,14 +227,12 @@ def sanitize_hash(param_conditions)
227227
return {} if param_conditions.empty?
228228

229229
res = {}
230-
hash_of_hashes = param_conditions[0]
231-
hash_of_hashes.each do |cond_name, cond_hash|
230+
param_conditions.each do |cond_id, cond_hash|
232231
sanitized_hash = {}
233232
cond_hash.each do |k, v|
234-
v = ActionController::Base.helpers.sanitize(v) if k.start_with?('webhook')
235-
sanitized_hash[k] = v
233+
sanitized_hash[k] = k.start_with?('webhook') ? ActionController::Base.helpers.sanitize(v) : v
236234
end
237-
res[cond_name] = sanitized_hash
235+
res[cond_id] = sanitized_hash
238236
end
239237
res
240238
end

app/helpers/conditions_helper.rb

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def remove_list(object)
1010
id_list = []
1111
plan_answers = object.answers if object.is_a?(Plan)
1212
plan_answers = object[:answers] if object.is_a?(Hash)
13-
return [] unless plan_answers.present?
13+
return [] if plan_answers.blank?
1414

1515
plan_answers.each { |answer| id_list += answer_remove_list(answer) }
1616
id_list
@@ -32,7 +32,7 @@ def answer_remove_list(answer, user = nil)
3232
rems = cond.remove_data.map(&:to_i)
3333
id_list += rems
3434
elsif !user.nil?
35-
UserMailer.question_answered(JSON.parse(cond.webhook_data), user, answer,
35+
UserMailer.question_answered(cond.webhook_data, user, answer,
3636
chosen.join(' and ')).deliver_now
3737
end
3838
end
@@ -57,7 +57,7 @@ def email_trigger_list(answer)
5757
chosen = answer.question_option_ids.sort
5858
next unless chosen == opts
5959

60-
email_list << JSON.parse(cond.webhook_data)['email'] if action == 'add_webhook'
60+
email_list << cond.webhook_data['email'] if action == 'add_webhook'
6161
end
6262
# uniq because could get same remove id from diff conds
6363
email_list.uniq.join(',')
@@ -70,7 +70,7 @@ def num_section_answers(plan, section)
7070
plan_remove_list = remove_list(plan)
7171
plan.answers.each do |answer|
7272
next unless answer.question.section_id == section.id &&
73-
!plan_remove_list.include?(answer.question_id) &&
73+
plan_remove_list.exclude?(answer.question_id) &&
7474
section.question_ids.include?(answer.question_id) &&
7575
answer.answered?
7676

@@ -107,10 +107,11 @@ def num_section_questions(plan, section, phase = nil)
107107
def sections_info(plan)
108108
return [] if plan.nil?
109109

110-
info = plan.sections.map do |section|
111-
section_info(plan, section)
110+
info = []
111+
plan.sections.each do |section|
112+
info.push(section_info(plan, section))
112113
end
113-
info || []
114+
info
114115
end
115116

116117
def section_info(plan, section)
@@ -190,7 +191,7 @@ def condition_to_text(conditions)
190191
return_string += "<dd>#{_('Answering')} "
191192
return_string += opts.join(' and ')
192193
if cond.action_type == 'add_webhook'
193-
subject_string = text_formatted(JSON.parse(cond.webhook_data)['subject'])
194+
subject_string = text_formatted(cond.webhook_data['subject'])
194195
return_string += format(_(' will <b>send an email</b> with subject %{subject_name}'),
195196
subject_name: subject_string)
196197
else
@@ -209,7 +210,7 @@ def condition_to_text(conditions)
209210
def text_formatted(object)
210211
text = Question.find(object).text if object.is_a?(Integer)
211212
text = object if object.is_a?(String)
212-
return 'type error' unless text.present?
213+
return 'type error' if text.blank?
213214

214215
cleaned_text = text
215216
text = ActionController::Base.helpers.truncate(cleaned_text, length: DISPLAY_LENGTH,
@@ -231,7 +232,7 @@ def conditions_to_param_form(conditions)
231232
webhook_data: condition.webhook_data } }
232233
if param_conditions.key?(title)
233234
param_conditions[title].merge!(condition_hash[title]) do |_key, val1, val2|
234-
if val1.is_a?(Array) && !val1.include?(val2[0])
235+
if val1.is_a?(Array) && val1.exclude?(val2[0])
235236
val1 + val2
236237
else
237238
val1
@@ -249,7 +250,7 @@ def conditions_to_param_form(conditions)
249250
def webhook_hash(conditions)
250251
web_hash = {}
251252
param_conditions = conditions_to_param_form(conditions)
252-
param_conditions.each_value do |params|
253+
param_conditions.each do |_title, params|
253254
web_hash.merge!(params[:number] => params[:webhook_data])
254255
end
255256
web_hash

app/models/condition.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727
# Object that represents a condition of a conditional question
2828
class Condition < ApplicationRecord
2929
belongs_to :question
30-
enum action_type: %i[remove add_webhook]
31-
serialize :option_list, type: Array
32-
serialize :remove_data, type: Array
33-
serialize :webhook_data, coder: JSON
30+
enum :action_type, { remove: 0, add_webhook: 1 }
31+
serialize :option_list, type: Array, coder: JSON
32+
serialize :remove_data, type: Array, coder: JSON
33+
serialize :webhook_data, type: Hash, coder: JSON
3434

3535
# Sort order: Number ASC
3636
default_scope { order(number: :asc) }

app/models/question.rb

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ def guidance_for_org(org)
145145
guidances = {}
146146
if theme_ids.any?
147147
GuidanceGroup.includes(guidances: :themes)
148-
.where(org_id: org.id).each do |group|
148+
.where(org_id: org.id).find_each do |group|
149149
group.guidances.each do |g|
150150
g.themes.each do |theme|
151151
guidances["#{group.name} " + _('guidance on') + " #{theme.title}"] = g if theme_ids.include? theme.id
@@ -196,8 +196,8 @@ def annotations_per_org(org_id)
196196
type: Annotation.types[:example_answer])
197197
guidance = annotations.find_by(org_id: org_id,
198198
type: Annotation.types[:guidance])
199-
example_answer = annotations.build(type: :example_answer, text: '', org_id: org_id) unless example_answer.present?
200-
guidance = annotations.build(type: :guidance, text: '', org_id: org_id) unless guidance.present?
199+
example_answer = annotations.build(type: :example_answer, text: '', org_id: org_id) if example_answer.blank?
200+
guidance = annotations.build(type: :guidance, text: '', org_id: org_id) if guidance.blank?
201201
[example_answer, guidance]
202202
end
203203

@@ -206,7 +206,7 @@ def annotations_per_org(org_id)
206206
# after versioning
207207
def update_conditions(param_conditions, old_to_new_opts, question_id_map)
208208
conditions.destroy_all
209-
return unless param_conditions.present?
209+
return if param_conditions.blank?
210210

211211
param_conditions.each_value do |value|
212212
save_condition(value, old_to_new_opts, question_id_map)
@@ -221,28 +221,47 @@ def save_condition(value, opt_map, question_id_map)
221221
c.number = value['number']
222222
# question options may have changed so rewrite them
223223
c.option_list = value['question_option']
224-
unless opt_map.blank?
225-
new_question_options = c.option_list.map do |qopt|
226-
opt_map[qopt]
224+
225+
if opt_map.present?
226+
new_question_options = []
227+
c.option_list.map do |qopt|
228+
new_question_options << opt_map[qopt]
227229
end
228-
c.option_list = new_question_options || []
230+
c.option_list = new_question_options
229231
end
230232

231233
if value['action_type'] == 'remove'
232234
c.remove_data = value['remove_question_id']
233-
unless question_id_map.blank?
234-
new_question_ids = c.remove_data.each do |qid|
235-
question_id_map[qid]
235+
if question_id_map.present?
236+
new_question_ids = []
237+
c.remove_data.map do |qid|
238+
new_question_ids << question_id_map[qid]
236239
end
237-
c.remove_data = new_question_ids || []
240+
c.remove_data = new_question_ids
241+
end
242+
243+
# Do not save the condition if the option_list or remove_data is empty
244+
if c.option_list.empty? || c.remove_data.empty?
245+
c.destroy
246+
return
238247
end
239248
else
240249
c.webhook_data = {
241250
name: value['webhook-name'],
242251
email: value['webhook-email'],
243252
subject: value['webhook-subject'],
244253
message: value['webhook-message']
245-
}.to_json
254+
}
255+
256+
# Do not save the condition if the option_list or if any webhook_data fields is empty
257+
if c.option_list.empty? ||
258+
c.webhook_data['name'].blank? ||
259+
c.webhook_data['email'].blank? ||
260+
c.webhook_data['subject'].blank? ||
261+
c.webhook_data['message'].blank?
262+
c.destroy
263+
return
264+
end
246265
end
247266
c.save
248267
end
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
<div class="row">
22
<div class="col-md-12 mt-2">
33
<%= link_to _('Add condition'), new_org_admin_question_condition_path(question_id: question.id, condition_no: condition_no), remote: true, class: "add-condition" %>
4+
<p>To add a condition you must have selected an Option and Action together with
5+
<ul>
6+
<li>if Action is 'removes', you need to select one or more choices in Target.</li>
7+
<li>if Action is 'add notification', you need to fill in all the fields in the 'Send email' popup.</li>
8+
</ul>
9+
Otherwise, the condition will not be saved.
10+
</p>
411
</div>
512
</div>

app/views/org_admin/conditions/_container.html.erb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
<div class="col-md-3 mb-2 bold">
55
<%= label(:text, _('Option'), class: "control-label")%>
66
</div>
7-
<div class="col-md-3 mb-2">
7+
<div class="col-md-3 mb-2 bold">
88
<%= label(:text, _('Action'), class: "control-label") %>
99
</div>
1010
<div class="col-md-3 mb-2 bold">
11-
<%= _('Remove')%>
11+
<%= label(:text, _('Target'), class: "control-label") %>
1212
</div>
13-
<div class="col-md-3">
13+
<div class="col-md-3 mb-2 bold">
14+
<%= label(:text, _('Remove'), class: "control-label") %>
1415
</div>
1516
</div>
1617
<% conditions_params = conditions_to_param_form(conditions).sort_by { |key| key }.to_h %>
Lines changed: 74 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
<% condition = condition ||= nil %>
2+
13
<div class="row condition-partial mb-3">
24
<%
3-
action_type_arr = [["removes", :remove], ["adds notification", :add_webhook]]
4-
name_start = "conditions[]condition_" + condition_no.to_s
5+
action_type_arr = [["removes", :remove], ["adds notification", :add_webhook]]
6+
# name_start = "conditions[]condition_" + condition_no.to_s
7+
name_start = "conditions[#{condition_no.to_s}]"
58
remove_question_collection = later_question_list(question)
69
condition_exists = local_assigns.has_key? :condition
710
type_default = condition_exists ? (condition[:action_type] == "remove" ? :remove : :add_webhook) : :remove
@@ -10,26 +13,79 @@
1013
grouped_options_for_select(remove_question_collection)
1114
multiple = (question.question_format.multiselectbox? || question.question_format.checkbox?)
1215
%>
13-
<div class="col-md-3 pe-2">
14-
<%= select_tag(:question_option, options_from_collection_for_select(question.question_options.sort_by(&:number), "id", "text",
15-
condition_exists ? condition[:question_option_id] : question.question_options.sort_by(&:number)[0]), {class: 'form-select regular', 'data-bs-style': 'dropdown-toggle bg-white px-4 py-3', name: name_start + "[question_option][]"}) %>
16-
</div>
17-
<div class="col-md-3 pe-2">
18-
<%= select_tag(:action_type, options_for_select(action_type_arr, type_default), {name: name_start + "[action_type]", class: 'action-type form-select narrow', 'data-bs-style': 'dropdown-toggle bg-white px-4 py-3'}) %>
16+
17+
<%# If this is a new condition then display the interactive controls. otherwise just display the logic %>
18+
<% if condition.nil? %>
19+
<div class="form-label bold">Add condition</div>
20+
<div class="row mb-3">
21+
<div class="col-md-9 pe-2">
22+
<div class="form-label bold"><%= _('Option') %></div>
23+
<%= select_tag(:question_option, options_from_collection_for_select(question.question_options.sort_by(&:number), "id", "text",
24+
condition_exists ? condition[:question_option_id] : question.question_options.sort_by(&:number)[0]), {class: 'form-select regular', 'data-bs-style': 'dropdown-toggle bg-white px-4 py-3', name: name_start + "[question_option][]"}) %>
25+
</div>
26+
<div class="col-md-3 pe-2">
27+
<div class="form-label bold"><%= _('Action') %></div>
28+
<%= select_tag(:action_type, options_for_select(action_type_arr, type_default), {name: name_start + "[action_type]", class: 'action-type form-select narrow', 'data-bs-style': 'dropdown-toggle bg-white px-4 py-3'}) %>
29+
</div>
1930
</div>
20-
<div class="col-md-3 pe-2">
21-
<div class="remove-dropdown">
22-
<%= select_tag(:remove_question_id, remove_question_group, {name: name_start + "[remove_question_id][]", class: 'form-select regular', multiple: true, 'data-bs-style': 'dropdown-toggle bg-white px-4 py-3'}) %>
31+
<div class="row d-flex mb-3">
32+
<div class="col-md-10 pe-2">
33+
<div class="form-label bold"><%= _('Target') %></div>
34+
<div class="remove-dropdown">
35+
<%= select_tag(:remove_question_id, remove_question_group, {name: name_start + "[remove_question_id][]", class: 'form-select regular', multiple: true, 'data-bs-style': 'dropdown-toggle bg-white px-4 py-3'}) %>
36+
</div>
37+
<div class="webhook-replacement display-off my-auto text-center">
38+
<%= link_to _('Edit email'), '#' %>
39+
</div>
40+
<%= hidden_field_tag(name_start + "[number]", condition_no) %>
2341
</div>
24-
<div class="webhook-replacement display-off my-auto text-center">
25-
<%= link_to _('Edit email'), '#' %>
42+
<div class="col-md-2 align-self-center">
43+
<a href="#anotherurl" class="delete-condition btn btn-primary"><%= _('Remove') %></a>
2644
</div>
45+
<%= render partial: 'org_admin/conditions/webhook_form', locals: {name_start: name_start, condition_no: condition_no} %>
2746
</div>
28-
<%= hidden_field_tag(name_start + "[number]", condition_no) %>
47+
<% else %>
48+
<%
49+
qopt = condition[:question_option_id].any? ? QuestionOption.find_by(id: condition[:question_option_id].first): nil
50+
# qopts = condition[:question_option_id].any? ? QuestionOption.find_by(id: condition[:question_option_id]): []
51+
rquesArray = condition[:remove_question_id].any? ? Question.where(id: condition[:remove_question_id]) : nil
52+
# rques = condition[:remove_question_id].any? ? Question.find_by(id: condition[:remove_question_id].first) : nil
53+
%>
54+
<div class="row condition-partial mb-3">
55+
<div class="col-md-3 pe-2">
56+
<%= qopt[:text]&.slice(0, 25) %>
57+
<%= hidden_field_tag(name_start + "[question_option][]", condition[:question_option_id]) %>
58+
</div>
59+
<div class="col-md-3 pe-2">
60+
<%= condition[:action_type] == 'remove' ? 'Remove' : 'Email' %>
61+
<%= hidden_field_tag(name_start + "[action_type]", condition[:action_type]) %>
62+
</div>
63+
<div class="col-md-3 pe-2">
64+
<% if !rquesArray.nil? %>
65+
<% rquesArray.each do |rques| %>
66+
Question <%= rques[:number] %>: <%= rques.text.gsub(%r{</?p>}, '').slice(0, 50) %>
67+
<%= '...' if rques.text.gsub(%r{</?p>}, '').length > 50 %>
68+
<br>
69+
<% end %>
70+
<%= hidden_field_tag(name_start + "[remove_question_id][]", condition[:remove_question_id]) %>
71+
<% else %>
72+
<%
73+
hook_tip = "Name: #{condition[:webhook_data]['name']}\nEmail: #{condition[:webhook_data]['email']}\n"
74+
hook_tip += "Subject: #{condition[:webhook_data]['subject']}\nMessage: #{condition[:webhook_data]['message']}"
75+
%>
76+
<span title="<%= hook_tip %>"><%= condition[:webhook_data]['email'] %></span>
77+
<%= hidden_field_tag(name_start + "[webhook-email]", condition[:webhook_data]['email']) %>
78+
<%= hidden_field_tag(name_start + "[webhook-name]", condition[:webhook_data]['name']) %>
79+
<%= hidden_field_tag(name_start + "[webhook-subject]", condition[:webhook_data]['subject']) %>
80+
<%= hidden_field_tag(name_start + "[webhook-message]", condition[:webhook_data]['message']) %>
81+
<% end %>
82+
83+
</div>
84+
<%= hidden_field_tag(name_start + "[number]", condition_no) %>
2985

30-
<div class="col-md-3">
31-
<a href="#anotherurl" class="delete-condition"><%= _('Remove') %></a>
86+
<div class="col-md-3">
87+
<a href="#anotherurl" class="delete-condition"><%= _('Remove') %></a>
88+
</div>
3289
</div>
33-
34-
<%= render partial: 'org_admin/conditions/webhook_form', locals: {name_start: name_start, condition_no: condition_no} %>
90+
<% end %>
3591
</div>

app/views/org_admin/conditions/_webhook_form.html.erb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,25 @@
99
<div class="modal-body mx-3">
1010
<div class="md-form mb-5 prefix grey-text">
1111
<i class="fa-solid fa-user"></i>
12-
<%= label_tag name_start + "[webhook-name]", _('Name'), {'data-error': 'wrong', 'data-success': 'right', class: 'form-label'} %>
12+
<%= label_tag name_start + "[webhook-name]", _('Name'), {'data-error': 'wrong', 'data-success': 'right', class: 'form-label', "aria-required": true, required: true} %>
1313
<%= text_field_tag name_start + "[webhook-name]", nil, class: 'form-control validate' %>
1414
</div>
1515

1616
<div class="md-form mb-5 prefix grey-text">
1717
<i class="fa-solid fa-envelope"></i> <!-- mdbootstrap should have treatment for bad input for this -->
18-
<%= label_tag name_start + "[webhook-email]", _('Email'), {'data-error': 'wrong', 'data-success': 'right', class: 'form-label'} %>
18+
<%= label_tag name_start + "[webhook-email]", _('Email'), {'data-error': 'wrong', 'data-success': 'right', class: 'form-label', "aria-required": true, required: true} %>
1919
<%= email_field_tag name_start + "[webhook-email]", nil, { placeholder: _('recipient_email@example.com'), class: 'form-control validate'} %>
2020
</div>
2121

2222
<div class="md-form mb-5 prefix grey-text">
2323
<i class="fa-solid fa-tag"></i>
24-
<%= label_tag name_start + "[webhook-subject]", _('Subject'), {'data-error': 'wrong', 'data-success': 'right', class: 'form-label'} %>
24+
<%= label_tag name_start + "[webhook-subject]", _('Subject'), {'data-error': 'wrong', 'data-success': 'right', class: 'form-label', "aria-required": true, required: true} %>
2525
<%= text_field_tag name_start + "[webhook-subject]", nil, class: 'form-control validate' %>
2626
</div>
2727

2828
<div class="md-form prefix grey-text">
2929
<i class="fa-solid fa-pencil"></i>
30-
<%= label_tag name_start + "[webhook-message]", _('Message'), {'data-error': 'wrong', 'data-success': 'right', class: 'form-label'} %>
30+
<%= label_tag name_start + "[webhook-message]", _('Message'), {'data-error': 'wrong', 'data-success': 'right', class: 'form-label', "aria-required": true, required: true}%>
3131
<%= text_area_tag name_start + "[webhook-message]", nil, { placeholder: _('Email content'), class: 'form-control md-textarea', rows: 4 } %>
3232
</div>
3333
</div>

app/views/org_admin/questions/_form.html.erb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@
4545
<%= render "/org_admin/question_options/option_fields", f: f, q: question %>
4646
<!--display for selecting comment box when multiple choice is being used-->
4747
</div>
48+
4849
<% if question.id != nil && question.question_options[0].text != nil %>
49-
<%= link_to _('Add Conditions'), org_admin_question_open_conditions_path(question_id: question.id, conditions: conditions), class: "add-logic btn btn-secondary", 'data-loaded': (conditions.size > 0).to_s, remote: true %>
50+
<% cond_lbl = (!conditions.nil? && conditions.any?) ? 'Edit Conditions' : 'Add Conditions' %>
51+
<%= link_to cond_lbl, org_admin_question_open_conditions_path(question_id: question.id, conditions: conditions), class: "add-logic btn btn-secondary", 'data-loaded': (conditions.size > 0).to_s, remote: true %>
5052
<div id="content" class="col-md-offset-2">
5153
<p>
5254
<%= render partial: 'org_admin/conditions/container', locals: { f: f, question: question, conditions: conditions } %>

0 commit comments

Comments
 (0)