Skip to content

Commit cd1ebec

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): Run SQL script to convert existing records in the conditions table so that they are JSON Arrays (see query below) 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. Needed to update underlying data within the table so that they are JSON arrays using MySQL or Postgres scripts in db/migrate-sql-scripts folder: 20241219_UpdateConditions-MySql.sql.txt 20241219_UpdateConditions-Postgres.sql.txt (Note. The scripts have .txt endings to be able to get round .sql in .gitignore.)
1 parent 994bb60 commit cd1ebec

File tree

9 files changed

+185
-60
lines changed

9 files changed

+185
-60
lines changed

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: 17 additions & 14 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,9 +206,9 @@ 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

211-
param_conditions.each_value do |value|
211+
param_conditions.each do |_key, value|
212212
save_condition(value, old_to_new_opts, question_id_map)
213213
end
214214
end
@@ -221,28 +221,31 @@ 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.each 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.each 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
238241
end
239242
else
240243
c.webhook_data = {
241244
name: value['webhook-name'],
242245
email: value['webhook-email'],
243246
subject: value['webhook-subject'],
244247
message: value['webhook-message']
245-
}.to_json
248+
}
246249
end
247250
c.save
248251
end

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@
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-6 mb-2">
88
<%= label(:text, _('Action'), class: "control-label") %>
99
</div>
1010
<div class="col-md-3 mb-2 bold">
1111
<%= _('Remove')%>
1212
</div>
13-
<div class="col-md-3">
14-
</div>
1513
</div>
1614
<% conditions_params = conditions_to_param_form(conditions).sort_by { |key| key }.to_h %>
1715
<% conditions_params.each_with_index do |(title, params), i| %> <!-- iterate through conditions sorted by order of creation where i is condition number-->
Lines changed: 76 additions & 22 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,77 @@
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'}) %>
19-
</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'}) %>
23-
</div>
24-
<div class="webhook-replacement display-off my-auto text-center">
25-
<%= link_to _('Edit email'), '#' %>
26-
</div>
27-
</div>
28-
<%= hidden_field_tag(name_start + "[number]", condition_no) %>
2916

30-
<div class="col-md-3">
31-
<a href="#anotherurl" class="delete-condition"><%= _('Remove') %></a>
32-
</div>
17+
<%# If this is a new condition then display the interactive controls. otherwise just display the logic %>
18+
<% if condition.nil? %>
19+
<div class="col-md-3">
20+
<%= select_tag(:question_option, options_from_collection_for_select(question.question_options.sort_by(&:number), "id", "text",
21+
condition_exists ? condition[:question_option_id] : question.question_options.sort_by(&:number)[0]), {class: 'form-selectpicker regular', 'data-bs-style': 'dropdown-toggle bg-white px-4 py-3', multiple: multiple, name: name_start + "[question_option][]"}) %>
22+
</div>
23+
<div class="col-md-3 pe-2">
24+
<%= select_tag(:action_type, options_for_select(action_type_arr, type_default), {name: name_start + "[action_type]", class: 'action-type form-selectpicker narrow', 'data-bs-style': 'dropdown-toggle bg-white px-4 py-3'}) %>
25+
</div>
26+
<div class="col-md-3">
27+
<div class="remove-dropdown">
28+
<%= select_tag(:remove_question_id, remove_question_group, {name: name_start + "[remove_question_id][]", class: 'form-selectpicker regular', multiple: true, 'data-bs-style': 'dropdown-toggle bg-white px-4 py-3'}) %>
29+
</div>
30+
<div class="webhook-replacement display-off my-auto text-center">
31+
<%= link_to _('Edit email'), '#' %>
32+
</div>
33+
</div>
34+
<%= hidden_field_tag(name_start + "[number]", condition_no) %>
35+
36+
<div class="col-md-3">
37+
<a href="#anotherurl" class="delete-condition"><%= _('Remove') %></a>
38+
</div>
39+
40+
<%= render partial: 'org_admin/conditions/webhook_form', locals: {name_start: name_start, condition_no: condition_no} %>
41+
42+
<% else %>
43+
<%
44+
# qopt = condition[:question_option_id].any? ? QuestionOption.find_by(id: condition[:question_option_id].first): nil
45+
qopts = condition[:question_option_id].any? ? QuestionOption.find_by(id: condition[:question_option_id]): []
46+
rques = condition[:remove_question_id].any? ? Question.find_by(id: condition[:remove_question_id].first) : nil
47+
%>
48+
<%= qopts.inspect %>
49+
<div class="col-md-3 pe-2">
50+
<% if qopts.is_a?(Array) %>
51+
<% for qopt in qopts %>
52+
<%= qopt[:text]&.slice(0, 25) %>
53+
<%= hidden_field_tag(name_start + "[question_option][]", condition[:question_option_id]) %>
54+
<% end %>
55+
<% else %>
56+
<% if qopts.is_a?(QuestionOption) %>
57+
<%= qopts[:text]&.slice(0, 25) %>
58+
<%= hidden_field_tag(name_start + "[question_option][]", condition[:question_option_id]) %>
59+
<% end %>
60+
<% end %>
61+
</div>
62+
<div class="col-md-3 pe-2">
63+
<%= condition[:action_type] == 'remove' ? 'Remove' : 'Email' %>
64+
<%= hidden_field_tag(name_start + "[action_type]", condition[:action_type]) %>
65+
</div>
66+
<div class="col-md-3 pe-2">
67+
<% if condition[:remove_question_id].is_a?(Array) && condition[:remove_question_id].any? %>
68+
Question <%= rques[:number] %>: <%= rques.text.gsub(%r{</?p>}, '').slice(0, 50) %>
69+
<%= hidden_field_tag(name_start + "[remove_question_id][]", condition[:remove_question_id]) %>
70+
<% else %>
71+
<%
72+
hook_tip = "Name: #{condition[:webhook_data]['name']}\nEmail: #{condition[:webhook_data]['email']}\n"
73+
hook_tip += "Subject: #{condition[:webhook_data]['subject']}\nMessage: #{condition[:webhook_data]['message']}"
74+
%>
75+
<span title="<%= hook_tip %>"><%= condition[:webhook_data]['email'] %></span>
76+
<%= hidden_field_tag(name_start + "[webhook-email]", condition[:webhook_data]['email']) %>
77+
<%= hidden_field_tag(name_start + "[webhook-name]", condition[:webhook_data]['name']) %>
78+
<%= hidden_field_tag(name_start + "[webhook-subject]", condition[:webhook_data]['subject']) %>
79+
<%= hidden_field_tag(name_start + "[webhook-message]", condition[:webhook_data]['message']) %>
80+
<% end %>
81+
82+
</div>
83+
<%= hidden_field_tag(name_start + "[number]", condition_no) %>
3384

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

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,11 @@
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+
<%= cond_lbl %>
52+
<%= link_to cond_lbl, org_admin_question_open_conditions_path(question_id: question.id, conditions: conditions), class: "add-logic btn btn-default", 'data-loaded': (conditions.size > 0).to_s, remote: true %>
5053
<div id="content" class="col-md-offset-2">
5154
<p>
5255
<%= render partial: 'org_admin/conditions/container', locals: { f: f, question: question, conditions: conditions } %>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
UPDATE conditions
2+
SET
3+
option_list = CONCAT (
4+
CONCAT (
5+
'[',
6+
REPLACE (
7+
REPLACE (
8+
REPLACE (option_list, '---\n- \'', '"'),
9+
'\'\n- \'',
10+
'","'
11+
),
12+
'\'\n',
13+
'"'
14+
)
15+
),
16+
']'
17+
),
18+
remove_data = CONCAT (
19+
CONCAT (
20+
'[',
21+
REPLACE (
22+
REPLACE (
23+
REPLACE (remove_data, '---\n- \'', '"'),
24+
'\'\n- \'',
25+
'","'
26+
),
27+
'\'\n',
28+
'"'
29+
)
30+
),
31+
']'
32+
)
33+
where
34+
option_list like '---%';

0 commit comments

Comments
 (0)