Skip to content

Commit 44666cb

Browse files
author
John Pinto
committed
Fix for bugs in the Conditional Questions functionality:
In the case of a conditional question with answers that removed questions from different sections of a phase, any answers of removed questions were not removed, just hidden. Nor were the removed answers deleted in the database. Changes: - Fixed the broken functionality. Rspec tests updated in next commit.
1 parent 994bb60 commit 44666cb

File tree

6 files changed

+70
-14
lines changed

6 files changed

+70
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Changelog
22

3+
- Fix for bug in Conditional questions functionality. In the case of a conditional question with answers that removed questions, any answers of removed questions was not removed. Nor were the removed answers deleted in the database. The Rspec tests were also updated to reflect this change.
34
- Fixed a bug in the deep copy of plans where the old identifier was being copied into the new plan. We now copy the generated id of the new plan to the identifier field.
45
- Fixed bar chart click function in the Usage dashboard (GitHub issue #3443)
56

app/controllers/answers_controller.rb

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,32 @@ def create_or_update
9898
@section = @plan.sections.find_by(id: @question.section_id)
9999
template = @section.phase.template
100100

101-
remove_list_after = remove_list(@plan)
102-
101+
# Get list of questions to be removed from the plan based on any conditional questions.
102+
questions_remove_list_before_destroying_answers = remove_list(@plan)
103103
all_question_ids = @plan.questions.pluck(:id)
104-
# rubocop pointed out that these variable is not used
105-
# all_answers = @plan.answers
104+
105+
# Destroy all answers for removed questions
106+
questions_remove_list_before_destroying_answers.each do |id|
107+
Answer.where(question_id: id, plan: @plan).each do |a|
108+
Answer.destroy(a.id)
109+
end
110+
end
111+
# Now update @plan after removing answers of questions removed from the plan.
112+
@plan = Plan.includes(
113+
sections: {
114+
questions: %i[
115+
answers
116+
question_format
117+
]
118+
}
119+
).find(p_params[:plan_id])
120+
121+
# Now get list of question ids to remove based on remaining answers.
122+
remove_list_question_ids = remove_list(@plan)
123+
106124
qn_data = {
107-
to_show: all_question_ids - remove_list_after,
108-
to_hide: remove_list_after
125+
to_show: all_question_ids - remove_list_question_ids,
126+
to_hide: remove_list_question_ids
109127
}
110128

111129
section_data = []

app/helpers/conditions_helper.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ def answer_remove_list(answer, user = nil)
2727
opts = cond.option_list.map(&:to_i).sort
2828
action = cond.action_type
2929
chosen = answer.question_option_ids.sort
30-
if chosen == opts
30+
31+
# If the chosen (options) include the opts (options list) in the condition,
32+
# then we apply the action.
33+
# Currently, the Template edit through the UI only allows an action to be added to a single
34+
# option at a time, so the opts array is always length 0 or 1.
35+
if !opts.empty? && !chosen.empty? && (chosen & opts) == opts
3136
if action == 'remove'
3237
rems = cond.remove_data.map(&:to_i)
3338
id_list += rems

app/javascript/src/answers/edit.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
} from '../utils/isType';
66
import { Tinymce } from '../utils/tinymce.js';
77
import debounce from '../utils/debounce';
8-
import { updateSectionProgress, getQuestionDiv } from '../utils/sectionUpdate';
8+
import { updateSectionProgress, getQuestionDiv , deleteAllAnswersForQuestion } from '../utils/sectionUpdate';
99
import datePicker from '../utils/datePicker';
1010
import TimeagoFactory from '../utils/timeagoFactory.js.erb';
1111

@@ -23,7 +23,9 @@ $(() => {
2323
updateSectionProgress(section.sec_id, section.no_ans, section.no_qns);
2424
});
2525
data.qn_data.to_hide.forEach((questionid) => {
26+
deleteAllAnswersForQuestion(questionid);
2627
getQuestionDiv(questionid).slideUp();
28+
2729
});
2830
data.qn_data.to_show.forEach((questionid) => {
2931
getQuestionDiv(questionid).slideDown();
@@ -100,11 +102,6 @@ $(() => {
100102
const form = target.closest('form');
101103
const id = questionId(target);
102104

103-
console.log('SUBMITTING');
104-
console.log(target);
105-
console.log(form);
106-
console.log(id);
107-
108105
if (debounceMap[id]) {
109106
// Cancels the delated execution of autoSaving
110107
// (e.g. user clicks the button before the delay is met)

app/javascript/src/utils/sectionUpdate.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Tinymce } from '../utils/tinymce.js';
2+
13
// update details in section progress panel
24
export const updateSectionProgress = (id, numSecAnswers, numSecQuestions) => {
35
const progressDiv = $(`#section-panel-${id}`).find('.section-status');
@@ -25,3 +27,30 @@ export const updateSectionProgress = (id, numSecAnswers, numSecQuestions) => {
2527
// given a question id find the containing div
2628
// used inconditional questions
2729
export const getQuestionDiv = (id) => $(`#answer-form-${id}`).closest('.question-body');
30+
31+
// Clear an answers for a given question id.
32+
export const deleteAllAnswersForQuestion = (questionid) => {
33+
const answerFormDiv = $(`#answer-form-${questionid}`);
34+
const editAnswerForm = $(`#answer-form-${questionid}`).find('.form-answer');
35+
36+
editAnswerForm.find('input:checkbox').prop('checked', false);
37+
editAnswerForm.find('input:radio').prop('checked', false);
38+
editAnswerForm.find('option').prop('selected', false);
39+
editAnswerForm.find('input:text').text('');
40+
41+
// Get the TinyMce editor textarea and rest content to ''
42+
const editorAnswerTextAreaId = `answer-text-${questionid}`;
43+
const tinyMceAnswerEditor = Tinymce.findEditorById(editorAnswerTextAreaId);
44+
if (tinyMceAnswerEditor) {
45+
tinyMceAnswerEditor.setContent('');
46+
}
47+
// Date fields in form are input of type="date"
48+
// The editAnswerForm.find('input:date') throws error, so
49+
// we need an alternate way to reset date.
50+
editAnswerForm.find('#answer_text').each ( (el) => {
51+
if($(el).attr('type') === 'date') {
52+
$(el).val('');
53+
}
54+
55+
});
56+
};

app/models/condition.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,18 @@ class Condition < ApplicationRecord
3434

3535
# Sort order: Number ASC
3636
default_scope { order(number: :asc) }
37-
37+
# rubocop:disable Metrics/AbcSize
3838
def deep_copy(**options)
3939
copy = dup
4040
copy.question_id = options.fetch(:question_id, nil)
41+
# Added to allow options to be passed in for all fields
42+
copy.option_list = options.fetch(:option_list, option_list) if options.key?(:option_list)
43+
copy.remove_data = options.fetch(:remove_data, remove_data) if options.key?(:remove_data)
44+
copy.action_type = options.fetch(:action_type, action_type) if options.key?(:action_type)
45+
copy.webhook_data = options.fetch(:webhook_data, webhook_data) if options.key?(:webhook_data)
4146
# TODO: why call validate false here
4247
copy.save!(validate: false) if options.fetch(:save, false)
4348
copy
4449
end
50+
# rubocop:enable Metrics/AbcSize
4551
end

0 commit comments

Comments
 (0)