quby 4.0.2 → 5.0.0.pre4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.markdown +0 -1
- data/lib/quby.rb +2 -10
- data/lib/quby/answers/entities/answer.rb +4 -2
- data/lib/quby/answers/services/outcome_calculation.rb +1 -2
- data/lib/quby/answers/services/score_calculator.rb +1 -3
- data/lib/quby/engine.rb +0 -1
- data/lib/quby/questionnaires.rb +1 -0
- data/lib/quby/questionnaires/api.rb +5 -1
- data/lib/quby/questionnaires/deserializer.rb +439 -0
- data/lib/quby/questionnaires/dsl.rb +12 -15
- data/lib/quby/questionnaires/entities.rb +1 -0
- data/lib/quby/questionnaires/entities/charting/line_chart.rb +23 -0
- data/lib/quby/questionnaires/entities/charting/overview_chart.rb +3 -1
- data/lib/quby/questionnaires/entities/definition.rb +3 -5
- data/lib/quby/questionnaires/entities/fields.rb +0 -15
- data/lib/quby/questionnaires/entities/question.rb +9 -32
- data/lib/quby/questionnaires/entities/questionnaire.rb +4 -15
- data/lib/quby/questionnaires/entities/questions/checkbox_question.rb +0 -24
- data/lib/quby/questionnaires/entities/questions/date_question.rb +0 -8
- data/lib/quby/questionnaires/entities/score_calculation.rb +36 -3
- data/lib/quby/questionnaires/repos.rb +1 -0
- data/lib/quby/questionnaires/repos/bundle_disk_repo.rb +51 -0
- data/lib/quby/table_backend/range_tree.rb +0 -51
- data/lib/quby/version.rb +1 -1
- data/spec/features/tables_spec.rb +40 -0
- data/spec/internal/log/test-events.log +531 -0
- data/spec/internal/log/test.log +21098 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-22-54.041.html +207 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-22-54.041.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-23.175.html +323 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-23.175.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-32.352.html +323 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-32.352.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-35.247.html +323 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-35.247.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-38.152.html +323 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-38.152.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-41.012.html +323 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-41.012.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-43.918.html +323 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-43.918.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-46.782.html +323 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-46.782.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-49.678.html +323 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-49.678.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-52.495.html +323 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-22-10-23-52.495.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-14-25-21.063.html +207 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-14-25-21.063.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-21-57.510.html +1 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-21-57.510.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-23-56.006.html +1 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-23-56.006.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-24-43.842.html +12 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-24-43.842.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-25-04.631.html +12 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-25-04.631.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-25-11.690.html +12 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-25-11.690.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-26-25.111.html +12 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-26-25.111.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-26-57.026.html +12 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-26-57.026.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-27-13.545.html +12 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-27-13.545.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-27-45.475.html +12 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-27-45.475.png +0 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-32-13.907.html +1 -0
- data/spec/internal/tmp/capybara/screenshot_2020-10-27-18-32-13.907.png +0 -0
- data/spec/quby/answers/services/answer_validations_spec.rb +8 -8
- data/spec/quby/answers/services/score_calculator_spec.rb +4 -14
- data/spec/quby/questionnaires/deserializer/questionnaire_spec.rb +237 -0
- data/spec/quby/questionnaires/dsl_spec.rb +0 -9
- data/spec/quby/questionnaires/entities/fields_spec.rb +3 -3
- data/spec/quby/questionnaires/entities/question_spec.rb +0 -8
- data/spec/quby/questionnaires/entities/questionnaire_spec.rb +2 -26
- data/spec/quby/table_backend/range_tree_spec.rb +46 -13
- data/spec/spec_helper.rb +1 -1
- metadata +108 -55
- data/lib/quby/lookup_table.rb +0 -29
- data/lib/quby/lookup_table_repo.rb +0 -24
- data/lib/quby/questionnaires/dsl/base.rb +0 -20
- data/lib/quby/questionnaires/dsl/calls_custom_methods.rb +0 -29
- data/lib/quby/questionnaires/dsl/charting/bar_chart_builder.rb +0 -18
- data/lib/quby/questionnaires/dsl/charting/chart_builder.rb +0 -91
- data/lib/quby/questionnaires/dsl/charting/line_chart_builder.rb +0 -47
- data/lib/quby/questionnaires/dsl/charting/overview_chart_builder.rb +0 -31
- data/lib/quby/questionnaires/dsl/charting/radar_chart_builder.rb +0 -18
- data/lib/quby/questionnaires/dsl/helpers.rb +0 -51
- data/lib/quby/questionnaires/dsl/panel_builder.rb +0 -80
- data/lib/quby/questionnaires/dsl/question_builder.rb +0 -40
- data/lib/quby/questionnaires/dsl/questionnaire_builder.rb +0 -252
- data/lib/quby/questionnaires/dsl/questions/base.rb +0 -179
- data/lib/quby/questionnaires/dsl/questions/checkbox_question_builder.rb +0 -20
- data/lib/quby/questionnaires/dsl/questions/date_question_builder.rb +0 -18
- data/lib/quby/questionnaires/dsl/questions/deprecated_question_builder.rb +0 -18
- data/lib/quby/questionnaires/dsl/questions/float_question_builder.rb +0 -21
- data/lib/quby/questionnaires/dsl/questions/integer_question_builder.rb +0 -21
- data/lib/quby/questionnaires/dsl/questions/radio_question_builder.rb +0 -20
- data/lib/quby/questionnaires/dsl/questions/select_question_builder.rb +0 -18
- data/lib/quby/questionnaires/dsl/questions/string_question_builder.rb +0 -20
- data/lib/quby/questionnaires/dsl/questions/text_question_builder.rb +0 -22
- data/lib/quby/questionnaires/dsl/score_builder.rb +0 -22
- data/lib/quby/questionnaires/dsl/standardized_panel_generators.rb +0 -33
- data/lib/quby/questionnaires/dsl/table_builder.rb +0 -48
- data/lib/quby/questionnaires/services/definition_validator.rb +0 -298
- data/spec/benchmarks/load_normscore_csv.rb +0 -18
- data/spec/quby/lookup_table_repo_spec.rb +0 -20
- data/spec/quby/lookup_table_spec.rb +0 -38
- data/spec/quby/questionnaires/dsl/calls_custom_methods_spec.rb +0 -38
- data/spec/quby/questionnaires/dsl/charting/bar_chart_builder_spec.rb +0 -41
- data/spec/quby/questionnaires/dsl/charting/chart_builder_spec.rb +0 -127
- data/spec/quby/questionnaires/dsl/charting/line_chart_builder_spec.rb +0 -58
- data/spec/quby/questionnaires/dsl/charting/radar_chart_builder_spec.rb +0 -41
- data/spec/quby/questionnaires/dsl/helpers_spec.rb +0 -80
- data/spec/quby/questionnaires/dsl/questionnaire_builder_spec.rb +0 -480
- data/spec/quby/questionnaires/services/definition_validator_spec.rb +0 -793
- data/spec/support/examples_for_chart_builders.rb +0 -59
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ddc10a44111cef2b40f5bdacb0e71087d785834eb1870318eb7c98f29cbbfb3e
|
4
|
+
data.tar.gz: 974ad9eca496310b69b7b6c1b99ddd4c37207b83f4bf4e34ab5b21b474792f61
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e5cde14431489cb85d05cba333d6f4f3daca33d5859ebf7342a127f9a7807822d0376fb24f396799b0049c90d6a965da2827fb9b576af46854836032fbf1c3b
|
7
|
+
data.tar.gz: ed1786bc33ffb3c17ccc49cde2ae7afc6610ee52a57f709e669ebabec761af91c3633ffe75dd46951bb3b0fc447efe4211f5447033208afb32181dbaab33ef28
|
data/README.markdown
CHANGED
@@ -27,7 +27,6 @@ and where it can store its answers.
|
|
27
27
|
```ruby
|
28
28
|
Quby.questionnaire_repo = Quby::Questionnaires::Repos::DiskRepo.new(Rails.root.join("db/questionnaires/definitions"))
|
29
29
|
Quby.answer_repo = Quby::Answers::Repos::MongoidRepo.new
|
30
|
-
Quby.lookup_table_repo = Quby::LookupTableRepo::Disk.new(Rails.root.join("db/questionnaires/lookup_tables"))
|
31
30
|
Quby::Settings.shared_secret = ENV["QUBY_SHARED_SECRET"]
|
32
31
|
```
|
33
32
|
|
data/lib/quby.rb
CHANGED
@@ -38,14 +38,6 @@ module Quby
|
|
38
38
|
@questionnaires_api = nil
|
39
39
|
end
|
40
40
|
|
41
|
-
def lookup_table_repo=(repo)
|
42
|
-
@lookup_table_repo = repo
|
43
|
-
end
|
44
|
-
|
45
|
-
def lookup_table_repo
|
46
|
-
@lookup_table_repo || fail("Quby does not have its lookup table repo (Quby.lookup_table_repo) configured.")
|
47
|
-
end
|
48
|
-
|
49
41
|
def fixtures_path
|
50
42
|
File.expand_path File.join('..', '..', 'spec', 'fixtures'), __FILE__
|
51
43
|
end
|
@@ -79,7 +71,7 @@ module Quby
|
|
79
71
|
end
|
80
72
|
|
81
73
|
require 'quby/settings'
|
74
|
+
require 'quby/table_backend/range_tree'
|
82
75
|
require 'quby/questionnaires'
|
83
76
|
require 'quby/answers'
|
84
|
-
require 'quby/engine'
|
85
|
-
require 'quby/lookup_table_repo'
|
77
|
+
require 'quby/engine'
|
@@ -82,7 +82,7 @@ module Quby
|
|
82
82
|
attr_accessor :extra_question_values
|
83
83
|
attr_accessor :extra_failed_validations
|
84
84
|
|
85
|
-
def initialize(_id: nil, questionnaire_id: nil, questionnaire_key: nil,
|
85
|
+
def initialize(_id: nil, questionnaire_id: nil, questionnaire_key: nil, questionnaire: nil,
|
86
86
|
raw_params: nil, value: nil, patient_id: nil, patient: nil,
|
87
87
|
token: nil, active: true, test: false, created_at: nil, updated_at: nil,
|
88
88
|
started_at: nil, completed_at: nil, outcome: nil, outcome_generated_at: nil,
|
@@ -111,6 +111,8 @@ module Quby
|
|
111
111
|
self.import_notes = import_notes || {}
|
112
112
|
self.flags = flags
|
113
113
|
self.textvars = textvars
|
114
|
+
|
115
|
+
@questionnaire = questionnaire
|
114
116
|
end
|
115
117
|
|
116
118
|
def id
|
@@ -162,7 +164,7 @@ module Quby
|
|
162
164
|
|
163
165
|
# Faux belongs_to :questionnaire
|
164
166
|
def questionnaire
|
165
|
-
Quby.questionnaires.find(questionnaire_key)
|
167
|
+
@questionnaire ||= Quby.questionnaires.find(questionnaire_key)
|
166
168
|
end
|
167
169
|
|
168
170
|
def mark_completed(start_time)
|
@@ -74,8 +74,7 @@ module Quby
|
|
74
74
|
end
|
75
75
|
|
76
76
|
def value_by_regular_values
|
77
|
-
|
78
|
-
@value_by_regular_values ||= regular_values.sort_by do |key, value|
|
77
|
+
@value_by_regular_values ||= answer.value_by_regular_values.sort_by do |key, value|
|
79
78
|
questionnaire.fields.question_hash.keys.index(key) || Float::INFINITY
|
80
79
|
end.to_h
|
81
80
|
end
|
@@ -231,9 +231,7 @@ module Quby
|
|
231
231
|
end
|
232
232
|
|
233
233
|
def table_lookup(table_key, parameters)
|
234
|
-
@questionnaire.lookup_tables
|
235
|
-
@questionnaire.lookup_tables[table_key] \
|
236
|
-
.lookup(parameters)
|
234
|
+
@questionnaire.lookup_tables.fetch(table_key).lookup(parameters)
|
237
235
|
end
|
238
236
|
|
239
237
|
# Public: Ensure given question_keys have answers. Strings with nothing but whitespace are
|
data/lib/quby/engine.rb
CHANGED
data/lib/quby/questionnaires.rb
CHANGED
@@ -39,7 +39,11 @@ module Quby
|
|
39
39
|
|
40
40
|
def build_from_definition(definition)
|
41
41
|
ActiveSupport::Notifications.instrument('quby.questionaire.build') do
|
42
|
-
|
42
|
+
if definition.json
|
43
|
+
DSL.from_json(definition.json)
|
44
|
+
else
|
45
|
+
DSL.build_from_definition(definition)
|
46
|
+
end
|
43
47
|
end
|
44
48
|
end
|
45
49
|
|
@@ -0,0 +1,439 @@
|
|
1
|
+
module Quby
|
2
|
+
module Questionnaires
|
3
|
+
module Deserializer
|
4
|
+
# This symbolizes various things. Do not run on arbitrary JSON.
|
5
|
+
def self.from_json(json)
|
6
|
+
# TODO: last_update
|
7
|
+
Entities::Questionnaire.new(json.fetch("key"), json).tap do |questionnaire|
|
8
|
+
questionnaire.title = json.fetch("title")
|
9
|
+
questionnaire.description = json.fetch("description")
|
10
|
+
questionnaire.outcome_description = json.fetch("outcome_description")
|
11
|
+
questionnaire.short_description = json.fetch("short_description")
|
12
|
+
questionnaire.abortable = json.fetch("abortable")
|
13
|
+
questionnaire.enable_previous_questionnaire_button = json.fetch("enable_previous_questionnaire_button")
|
14
|
+
questionnaire.default_answer_value = json.fetch("default_answer_value")
|
15
|
+
questionnaire.leave_page_alert = json.fetch("leave_page_alert")
|
16
|
+
questionnaire.allow_hotkeys = json.fetch("allow_hotkeys")
|
17
|
+
questionnaire.license = json.fetch("license").try(:to_sym)
|
18
|
+
questionnaire.licensor = json.fetch("licensor")
|
19
|
+
questionnaire.language = json.fetch("language").try(:to_sym)
|
20
|
+
questionnaire.renderer_version = json.fetch("renderer_version")
|
21
|
+
questionnaire.last_update = Time.zone.parse(json.fetch("last_update"))
|
22
|
+
questionnaire.last_author = json.fetch("last_author")
|
23
|
+
questionnaire.extra_css = json.fetch("extra_css")
|
24
|
+
questionnaire.allow_switch_to_bulk = json.fetch("allow_switch_to_bulk")
|
25
|
+
|
26
|
+
questionnaire.flags = json.fetch("flags").with_indifferent_access.transform_values do |attrs|
|
27
|
+
build_flag(attrs)
|
28
|
+
end
|
29
|
+
|
30
|
+
questionnaire.textvars = json.fetch("textvars").with_indifferent_access.transform_values do |attrs|
|
31
|
+
build_textvar(attrs)
|
32
|
+
end
|
33
|
+
|
34
|
+
questionnaire.lookup_tables = YAML.load(json.fetch("lookup_tables")).transform_values do |attrs|
|
35
|
+
Quby::TableBackend::RangeTree.new(levels: attrs[:levels], tree: attrs[:tree])
|
36
|
+
end
|
37
|
+
|
38
|
+
questionnaire.score_calculations = json.fetch("score_calculations").with_indifferent_access.transform_values do |attrs|
|
39
|
+
build_score_calculation(attrs)
|
40
|
+
end
|
41
|
+
|
42
|
+
questionnaire.score_schemas = json.fetch("score_schemas").with_indifferent_access.transform_values do |schema|
|
43
|
+
build_score_schema(schema)
|
44
|
+
end
|
45
|
+
|
46
|
+
json.fetch("panels").each do |panel_json|
|
47
|
+
load_panel(questionnaire, panel_json)
|
48
|
+
end
|
49
|
+
|
50
|
+
# roqua domain
|
51
|
+
questionnaire.roqua_keys = json.fetch("roqua_keys")
|
52
|
+
questionnaire.sbg_key = json.fetch("sbg_key")
|
53
|
+
questionnaire.sbg_domains = json.fetch("sbg_domains").map(&:symbolize_keys)
|
54
|
+
questionnaire.outcome_regeneration_requested_at = json.fetch("outcome_regeneration_requested_at").try { |str| Time.zone.parse(str) }
|
55
|
+
questionnaire.deactivate_answers_requested_at = json.fetch("deactivate_answers_requested_at").try { |str| Time.zone.parse(str) }
|
56
|
+
questionnaire.respondent_types = json.fetch("respondent_types").map(&:to_sym)
|
57
|
+
questionnaire.tags = json.fetch("tags")
|
58
|
+
|
59
|
+
if overview_json = json.fetch("charts").fetch("overview")
|
60
|
+
questionnaire.charts.overview = Quby::Questionnaires::Entities::Charting::OverviewChart.new(
|
61
|
+
subscore: overview_json.fetch("subscore").to_sym,
|
62
|
+
y_max: overview_json.fetch("y_max"),
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
json.fetch("charts").fetch("others").each do |chart_json|
|
67
|
+
questionnaire.add_chart(build_chart(questionnaire, chart_json))
|
68
|
+
end
|
69
|
+
|
70
|
+
questionnaire.outcome_tables = json.fetch("outcome_tables").map do |attributes|
|
71
|
+
build_outcome_table(questionnaire, attributes)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.load_panel(questionnaire, panel_json)
|
77
|
+
panel = Entities::Panel.new(
|
78
|
+
questionnaire: questionnaire,
|
79
|
+
key: panel_json.fetch("key"),
|
80
|
+
title: panel_json.fetch("title"),
|
81
|
+
items: []
|
82
|
+
)
|
83
|
+
|
84
|
+
panel_json.fetch("items").each do |item_json|
|
85
|
+
load_item(questionnaire, item_json, panel: panel)
|
86
|
+
end
|
87
|
+
|
88
|
+
questionnaire.add_panel(panel)
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.load_item(questionnaire, item_json, panel: nil)
|
92
|
+
case item_json.fetch("type")
|
93
|
+
when "text"
|
94
|
+
panel.items << build_text(item_json)
|
95
|
+
when "question"
|
96
|
+
question = build_question(questionnaire, item_json)
|
97
|
+
questionnaire.register_question(question)
|
98
|
+
panel.items << question
|
99
|
+
when "table"
|
100
|
+
table = Entities::Table.new(
|
101
|
+
title: item_json.fetch("title"),
|
102
|
+
description: item_json.fetch("description"),
|
103
|
+
columns: item_json.fetch("columns"),
|
104
|
+
show_option_desc: item_json.fetch("show_option_desc"),
|
105
|
+
)
|
106
|
+
panel.items << table
|
107
|
+
|
108
|
+
item_json.fetch("items").each do |table_item_json|
|
109
|
+
case table_item_json.fetch("type")
|
110
|
+
when "text"
|
111
|
+
table.items << build_text(table_item_json)
|
112
|
+
when "question"
|
113
|
+
question = build_question(questionnaire, table_item_json, table: table)
|
114
|
+
questionnaire.register_question(question)
|
115
|
+
table.items << question
|
116
|
+
panel.items << question
|
117
|
+
else
|
118
|
+
raise "Unknown table item: #{table_item_json}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
else
|
122
|
+
raise "Unknown item: #{item_json}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.build_text(item_json)
|
127
|
+
Entities::Text.new(item_json.fetch("str"), {
|
128
|
+
html_content: item_json.fetch("html_content"),
|
129
|
+
display_in: item_json.fetch("display_in").map(&:to_sym),
|
130
|
+
col_span: item_json.fetch("col_span"),
|
131
|
+
row_span: item_json.fetch("row_span"),
|
132
|
+
raw_content: item_json.fetch("raw_content"),
|
133
|
+
switch_cycle: item_json.fetch("switch_cycle")
|
134
|
+
})
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.build_question(questionnaire, item_json, parent: nil, table: nil)
|
138
|
+
key = item_json.fetch("key").to_sym
|
139
|
+
attributes = {
|
140
|
+
questionnaire: questionnaire,
|
141
|
+
parent: parent,
|
142
|
+
type: item_json.fetch("question_type").to_sym,
|
143
|
+
title: item_json.fetch("title"),
|
144
|
+
context_free_title: item_json.fetch("context_free_title"),
|
145
|
+
description: item_json.fetch("description"),
|
146
|
+
presentation: item_json.fetch("presentation").to_sym,
|
147
|
+
hidden: item_json.fetch("hidden"),
|
148
|
+
depends_on: item_json.fetch("depends_on")&.map(&:to_sym),
|
149
|
+
default_position: item_json.fetch("default_position"),
|
150
|
+
validations: item_json.fetch("validations").map {|attrs| build_question_validation(attrs)},
|
151
|
+
table: table,
|
152
|
+
col_span: item_json.fetch("col_span"),
|
153
|
+
row_span: item_json.fetch("row_span"),
|
154
|
+
|
155
|
+
# only selectable via options passed in DSL, not via DSL methods
|
156
|
+
# many apply only to certain types of questions
|
157
|
+
sbg_key: item_json.fetch("sbg_key"),
|
158
|
+
allow_duplicate_option_values: item_json.fetch("allow_duplicate_option_values"),
|
159
|
+
allow_blank_titles: item_json.fetch("allow_blank_titles"),
|
160
|
+
as: item_json.fetch("as")&.to_sym,
|
161
|
+
display_modes: item_json.fetch("display_modes")&.map(&:to_sym),
|
162
|
+
autocomplete: item_json.fetch("autocomplete"),
|
163
|
+
show_values: item_json.fetch("show_values").to_sym,
|
164
|
+
deselectable: item_json.fetch("deselectable"),
|
165
|
+
disallow_bulk: item_json.fetch("disallow_bulk"),
|
166
|
+
score_header: item_json.fetch("score_header").to_sym,
|
167
|
+
sets_textvar: item_json.fetch("sets_textvar"),
|
168
|
+
default_invisible: item_json.fetch("default_invisible"),
|
169
|
+
question_group: item_json.fetch("question_group"), # sometimes string, sometimes a symbol in the DSL. Just have to hope this works
|
170
|
+
group_minimum_answered: item_json.fetch("group_minimum_answered"),
|
171
|
+
group_maximum_answered: item_json.fetch("group_maximum_answered"),
|
172
|
+
value_tooltip: item_json.fetch("value_tooltip"),
|
173
|
+
|
174
|
+
# might be able to deduce from tree structure
|
175
|
+
parent_option_key: item_json.fetch("parent_option_key")&.to_sym
|
176
|
+
}
|
177
|
+
|
178
|
+
case item_json.fetch("question_type")
|
179
|
+
when "check_box"
|
180
|
+
Entities::Questions::CheckboxQuestion.new(key, attributes.merge(
|
181
|
+
check_all_option: item_json.fetch("check_all_option")&.to_sym,
|
182
|
+
uncheck_all_option: item_json.fetch("uncheck_all_option")&.to_sym,
|
183
|
+
maximum_checked_allowed: item_json.fetch("maximum_checked_allowed"),
|
184
|
+
minimum_checked_required: item_json.fetch("minimum_checked_required"),
|
185
|
+
)).tap do |question|
|
186
|
+
item_json.fetch("options").each do |option_json|
|
187
|
+
question.options << build_option(questionnaire, question, option_json)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
when "date"
|
191
|
+
Entities::Questions::DateQuestion.new(key, attributes.merge(
|
192
|
+
components: item_json.fetch("components").map(&:to_sym),
|
193
|
+
required_components: item_json.fetch("required_components").map(&:to_sym),
|
194
|
+
year_key: item_json.fetch("year_key")&.to_sym,
|
195
|
+
month_key: item_json.fetch("month_key")&.to_sym,
|
196
|
+
day_key: item_json.fetch("day_key")&.to_sym,
|
197
|
+
hour_key: item_json.fetch("hour_key")&.to_sym,
|
198
|
+
minute_key: item_json.fetch("minute_key")&.to_sym,
|
199
|
+
))
|
200
|
+
when "deprecated", "hidden"
|
201
|
+
Entities::Questions::DeprecatedQuestion.new(key, attributes).tap do |question|
|
202
|
+
item_json.fetch("options").each do |option_json|
|
203
|
+
question.options << build_option(questionnaire, question, option_json)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
when "float"
|
207
|
+
Entities::Questions::FloatQuestion.new(key, attributes.merge(
|
208
|
+
labels: item_json.fetch("labels"),
|
209
|
+
unit: item_json.fetch("unit"),
|
210
|
+
size: item_json.fetch("size"),
|
211
|
+
))
|
212
|
+
when "integer"
|
213
|
+
Entities::Questions::IntegerQuestion.new(key, attributes.merge(
|
214
|
+
labels: item_json.fetch("labels"),
|
215
|
+
unit: item_json.fetch("unit"),
|
216
|
+
size: item_json.fetch("size"),
|
217
|
+
))
|
218
|
+
when "radio", "scale"
|
219
|
+
Entities::Questions::RadioQuestion.new(key, attributes).tap do |question|
|
220
|
+
item_json.fetch("options").each do |option_json|
|
221
|
+
question.options << build_option(questionnaire, question, option_json)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
when "select"
|
225
|
+
Entities::Questions::SelectQuestion.new(key, attributes).tap do |question|
|
226
|
+
item_json.fetch("options").each do |option_json|
|
227
|
+
question.options << build_option(questionnaire, question, option_json)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
when "string"
|
231
|
+
Entities::Questions::StringQuestion.new(key, attributes.merge(
|
232
|
+
unit: item_json.fetch("unit"),
|
233
|
+
size: item_json.fetch("size"),
|
234
|
+
))
|
235
|
+
when "textarea"
|
236
|
+
Entities::Questions::TextQuestion.new(key, attributes.merge(
|
237
|
+
lines: item_json.fetch("lines"),
|
238
|
+
))
|
239
|
+
else
|
240
|
+
raise "Unknown question type: #{item_json}"
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def self.build_option(questionnaire, question, option_json)
|
245
|
+
option = Entities::QuestionOption.new(option_json.fetch("key")&.to_sym, question,
|
246
|
+
value: option_json.fetch("value"),
|
247
|
+
description: option_json.fetch("description"),
|
248
|
+
context_free_description: option_json.fetch("context_free_description"),
|
249
|
+
inner_title: option_json.fetch("inner_title"),
|
250
|
+
hides_questions: option_json.fetch("hides_questions").map(&:to_sym),
|
251
|
+
shows_questions: option_json.fetch("shows_questions").map(&:to_sym),
|
252
|
+
hidden: option_json.fetch("hidden"),
|
253
|
+
placeholder: option_json.fetch("placeholder"),
|
254
|
+
)
|
255
|
+
|
256
|
+
option_json.fetch("questions").each do |question_json|
|
257
|
+
subquestion = build_question(questionnaire, question_json, parent: question)
|
258
|
+
questionnaire.register_question(subquestion)
|
259
|
+
option.questions << subquestion
|
260
|
+
end
|
261
|
+
|
262
|
+
option
|
263
|
+
end
|
264
|
+
|
265
|
+
def self.build_question_validation(attrs)
|
266
|
+
base_validation = {
|
267
|
+
type: attrs.fetch("type").to_sym,
|
268
|
+
explanation: attrs["explanation"] # not always specified for min/max validation
|
269
|
+
}
|
270
|
+
|
271
|
+
case attrs.fetch("type")
|
272
|
+
when "requires_answer"
|
273
|
+
base_validation
|
274
|
+
when "answer_group_minimum", "answer_group_maximum"
|
275
|
+
base_validation.merge(
|
276
|
+
group: attrs.fetch("group"), # TODO: sometimes a symbol, sometimes a string in the original, but I hope it doesn't matter
|
277
|
+
value: attrs.fetch("value")
|
278
|
+
)
|
279
|
+
when "valid_integer", "valid_float"
|
280
|
+
base_validation
|
281
|
+
when "valid_date"
|
282
|
+
base_validation.merge(
|
283
|
+
subtype: attrs.fetch("subtype").to_sym
|
284
|
+
)
|
285
|
+
when "minimum", "maximum"
|
286
|
+
value = case attrs.fetch("value_type")
|
287
|
+
when "Date"
|
288
|
+
Date.parse(attrs.fetch("value"))
|
289
|
+
when "DateTime"
|
290
|
+
DateTime.parse(attrs.fetch("value"))
|
291
|
+
when "Time", "ActiveSuport::TimeWithZone"
|
292
|
+
Time.zone.parse(attrs.fetch("value"))
|
293
|
+
else
|
294
|
+
attrs.fetch("value")
|
295
|
+
end
|
296
|
+
|
297
|
+
base_validation.merge(
|
298
|
+
value: value,
|
299
|
+
subtype: attrs.fetch("subtype").to_sym,
|
300
|
+
)
|
301
|
+
when "too_many_checked"
|
302
|
+
base_validation.merge(
|
303
|
+
uncheck_all_key: attrs.fetch("uncheck_all_key").to_sym
|
304
|
+
)
|
305
|
+
when "minimum_checked_required"
|
306
|
+
base_validation.merge(
|
307
|
+
minimum_checked_value: attrs.fetch("minimum_checked_value")
|
308
|
+
)
|
309
|
+
when "maximum_checked_allowed"
|
310
|
+
base_validation.merge(
|
311
|
+
maximum_checked_value: attrs.fetch("maximum_checked_value")
|
312
|
+
)
|
313
|
+
when "regexp"
|
314
|
+
base_validation.merge(
|
315
|
+
matcher: Regexp.new(attrs.fetch("matcher"))
|
316
|
+
)
|
317
|
+
when "not_all_checked"
|
318
|
+
base_validation.merge(
|
319
|
+
check_all_key: attrs.fetch("check_all_key").to_sym
|
320
|
+
)
|
321
|
+
else
|
322
|
+
raise "Unknown validation type: #{attrs.inspect}"
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
def self.build_score_calculation(attrs)
|
327
|
+
Entities::ScoreCalculation.new(attrs.fetch("key").to_sym,
|
328
|
+
label: attrs.fetch("label"),
|
329
|
+
sbg_key: attrs.fetch("sbg_key"),
|
330
|
+
options: attrs.fetch("options").symbolize_keys,
|
331
|
+
sourcecode: attrs.fetch("sourcecode"),
|
332
|
+
)
|
333
|
+
end
|
334
|
+
|
335
|
+
def self.build_flag(attrs)
|
336
|
+
Entities::Flag.new(
|
337
|
+
key: attrs.fetch("key").to_sym,
|
338
|
+
description_true: attrs.fetch("description_true"),
|
339
|
+
description_false: attrs.fetch("description_false"),
|
340
|
+
description: attrs.fetch("description"),
|
341
|
+
internal: attrs.fetch("internal"),
|
342
|
+
trigger_on: attrs.fetch("trigger_on"),
|
343
|
+
shows_questions: attrs.fetch("shows_questions").map(&:to_sym),
|
344
|
+
hides_questions: attrs.fetch("hides_questions").map(&:to_sym),
|
345
|
+
depends_on: attrs.fetch("depends_on"), # TODO: emperically determined to be a string in DSL, is that right?
|
346
|
+
default_in_interface: attrs.fetch("default_in_interface"),
|
347
|
+
)
|
348
|
+
end
|
349
|
+
|
350
|
+
def self.build_textvar(attrs)
|
351
|
+
Entities::Textvar.new(
|
352
|
+
key: attrs.fetch("key").to_sym,
|
353
|
+
description: attrs.fetch("description"),
|
354
|
+
default: attrs.fetch("default"),
|
355
|
+
depends_on_flag: attrs.fetch("depends_on_flag")&.to_sym
|
356
|
+
)
|
357
|
+
end
|
358
|
+
|
359
|
+
def self.build_chart(questionnaire, chart_json)
|
360
|
+
base_args = {
|
361
|
+
title: chart_json.fetch("title"),
|
362
|
+
plottables: chart_json.fetch("plottables").map do |plottable_json|
|
363
|
+
Quby::Questionnaires::Entities::Charting::Plottable.new(
|
364
|
+
plottable_json.fetch("key").to_sym,
|
365
|
+
label: plottable_json.fetch("label"),
|
366
|
+
plotted_key: plottable_json.fetch("plotted_key").to_sym,
|
367
|
+
global: plottable_json.fetch("global"),
|
368
|
+
questionnaire_key: plottable_json.fetch("questionnaire_key")
|
369
|
+
)
|
370
|
+
end,
|
371
|
+
y_categories: chart_json.fetch("y_categories"),
|
372
|
+
y_range_categories: chart_json.fetch("y_range_categories"),
|
373
|
+
chart_type: chart_json.fetch("chart_type"),
|
374
|
+
y_range: deserialize_range(chart_json.fetch("y_range")),
|
375
|
+
tick_interval: chart_json.fetch("tick_interval"),
|
376
|
+
plotbands: chart_json.fetch("plotbands").map do |plotband_json|
|
377
|
+
{
|
378
|
+
color: plotband_json.fetch("color").to_sym,
|
379
|
+
from: plotband_json.fetch("from"),
|
380
|
+
to: plotband_json.fetch("to")
|
381
|
+
}
|
382
|
+
end,
|
383
|
+
}
|
384
|
+
|
385
|
+
case chart_json.fetch("type")
|
386
|
+
when "bar_chart"
|
387
|
+
Quby::Questionnaires::Entities::Charting::BarChart.new(chart_json.fetch("key").to_sym,
|
388
|
+
plotlines: chart_json.fetch("plotlines"),
|
389
|
+
**base_args
|
390
|
+
)
|
391
|
+
when "line_chart"
|
392
|
+
Quby::Questionnaires::Entities::Charting::LineChart.new(chart_json.fetch("key").to_sym,
|
393
|
+
y_label: chart_json.fetch("y_label"),
|
394
|
+
tonality: chart_json.fetch("tonality").to_sym,
|
395
|
+
baseline: YAML.load(chart_json.fetch("baseline")),
|
396
|
+
clinically_relevant_change: chart_json.fetch("clinically_relevant_change"),
|
397
|
+
**base_args
|
398
|
+
)
|
399
|
+
when "radar_chart"
|
400
|
+
Quby::Questionnaires::Entities::Charting::BarChart.new(chart_json.fetch("key").to_sym,
|
401
|
+
plotlines: chart_json.fetch("plotlines"),
|
402
|
+
**base_args
|
403
|
+
)
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
def self.build_score_schema(attributes)
|
408
|
+
Entities::ScoreSchema.new(
|
409
|
+
key: attributes.fetch("key").to_sym,
|
410
|
+
label: attributes.fetch("label"),
|
411
|
+
subscore_schemas: attributes.fetch("subscore_schemas").map do |subschema|
|
412
|
+
{
|
413
|
+
key: subschema.fetch("key").to_sym,
|
414
|
+
label: subschema.fetch("label"),
|
415
|
+
export_key: subschema.fetch("export_key").to_sym,
|
416
|
+
only_for_export: subschema.fetch("only_for_export")
|
417
|
+
}
|
418
|
+
end
|
419
|
+
)
|
420
|
+
end
|
421
|
+
|
422
|
+
def self.build_outcome_table(questionnaire, attributes)
|
423
|
+
Entities::OutcomeTable.new(
|
424
|
+
questionnaire: questionnaire,
|
425
|
+
key: attributes.fetch("key").to_sym,
|
426
|
+
score_keys: attributes.fetch("score_keys").map(&:to_sym),
|
427
|
+
subscore_keys: attributes.fetch("subscore_keys").map(&:to_sym),
|
428
|
+
name: attributes.fetch("name"),
|
429
|
+
default_collapsed: attributes.fetch("default_collapsed"),
|
430
|
+
)
|
431
|
+
end
|
432
|
+
|
433
|
+
def self.deserialize_range(range_attributes)
|
434
|
+
return unless range_attributes
|
435
|
+
Range.new(range_attributes.fetch("begin"), range_attributes.fetch("end"), range_attributes.fetch("exclude_end"))
|
436
|
+
end
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|