quby-compiler 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.gitlab-ci.yml +5 -0
  4. data/.rspec +3 -0
  5. data/.ruby-version +1 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Dockerfile +11 -0
  8. data/Gemfile +8 -0
  9. data/Gemfile.lock +133 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +44 -0
  12. data/Rakefile +6 -0
  13. data/bin/console +14 -0
  14. data/bin/rspec +29 -0
  15. data/bin/setup +8 -0
  16. data/config/locales/de.yml +58 -0
  17. data/config/locales/en.yml +57 -0
  18. data/config/locales/nl.yml +57 -0
  19. data/config/locales/rails-i18n/README.md +4 -0
  20. data/config/locales/rails-i18n/de.yml +223 -0
  21. data/config/locales/rails-i18n/en.yml +216 -0
  22. data/config/locales/rails-i18n/nl.yml +214 -0
  23. data/exe/quby-compile +56 -0
  24. data/lib/quby/array_attribute_valid_validator.rb +15 -0
  25. data/lib/quby/attribute_valid_validator.rb +14 -0
  26. data/lib/quby/compiler.rb +50 -0
  27. data/lib/quby/compiler/dsl.rb +29 -0
  28. data/lib/quby/compiler/dsl/base.rb +20 -0
  29. data/lib/quby/compiler/dsl/calls_custom_methods.rb +29 -0
  30. data/lib/quby/compiler/dsl/charting/bar_chart_builder.rb +14 -0
  31. data/lib/quby/compiler/dsl/charting/chart_builder.rb +95 -0
  32. data/lib/quby/compiler/dsl/charting/line_chart_builder.rb +34 -0
  33. data/lib/quby/compiler/dsl/charting/overview_chart_builder.rb +31 -0
  34. data/lib/quby/compiler/dsl/charting/radar_chart_builder.rb +14 -0
  35. data/lib/quby/compiler/dsl/helpers.rb +53 -0
  36. data/lib/quby/compiler/dsl/panel_builder.rb +80 -0
  37. data/lib/quby/compiler/dsl/question_builder.rb +40 -0
  38. data/lib/quby/compiler/dsl/questionnaire_builder.rb +279 -0
  39. data/lib/quby/compiler/dsl/questions/base.rb +180 -0
  40. data/lib/quby/compiler/dsl/questions/checkbox_question_builder.rb +20 -0
  41. data/lib/quby/compiler/dsl/questions/date_question_builder.rb +18 -0
  42. data/lib/quby/compiler/dsl/questions/deprecated_question_builder.rb +18 -0
  43. data/lib/quby/compiler/dsl/questions/float_question_builder.rb +21 -0
  44. data/lib/quby/compiler/dsl/questions/integer_question_builder.rb +21 -0
  45. data/lib/quby/compiler/dsl/questions/radio_question_builder.rb +20 -0
  46. data/lib/quby/compiler/dsl/questions/select_question_builder.rb +18 -0
  47. data/lib/quby/compiler/dsl/questions/string_question_builder.rb +20 -0
  48. data/lib/quby/compiler/dsl/questions/text_question_builder.rb +22 -0
  49. data/lib/quby/compiler/dsl/score_builder.rb +22 -0
  50. data/lib/quby/compiler/dsl/score_schema_builder.rb +53 -0
  51. data/lib/quby/compiler/dsl/standardized_panel_generators.rb +33 -0
  52. data/lib/quby/compiler/dsl/table_builder.rb +48 -0
  53. data/lib/quby/compiler/entities.rb +38 -0
  54. data/lib/quby/compiler/entities/charting/bar_chart.rb +17 -0
  55. data/lib/quby/compiler/entities/charting/chart.rb +101 -0
  56. data/lib/quby/compiler/entities/charting/charts.rb +42 -0
  57. data/lib/quby/compiler/entities/charting/line_chart.rb +38 -0
  58. data/lib/quby/compiler/entities/charting/overview_chart.rb +20 -0
  59. data/lib/quby/compiler/entities/charting/plottable.rb +20 -0
  60. data/lib/quby/compiler/entities/charting/radar_chart.rb +17 -0
  61. data/lib/quby/compiler/entities/definition.rb +26 -0
  62. data/lib/quby/compiler/entities/fields.rb +119 -0
  63. data/lib/quby/compiler/entities/flag.rb +55 -0
  64. data/lib/quby/compiler/entities/item.rb +40 -0
  65. data/lib/quby/compiler/entities/lookup_tables.rb +71 -0
  66. data/lib/quby/compiler/entities/outcome_table.rb +31 -0
  67. data/lib/quby/compiler/entities/panel.rb +82 -0
  68. data/lib/quby/compiler/entities/question.rb +365 -0
  69. data/lib/quby/compiler/entities/question_option.rb +96 -0
  70. data/lib/quby/compiler/entities/questionnaire.rb +440 -0
  71. data/lib/quby/compiler/entities/questions/checkbox_question.rb +82 -0
  72. data/lib/quby/compiler/entities/questions/date_question.rb +84 -0
  73. data/lib/quby/compiler/entities/questions/deprecated_question.rb +19 -0
  74. data/lib/quby/compiler/entities/questions/float_question.rb +15 -0
  75. data/lib/quby/compiler/entities/questions/integer_question.rb +15 -0
  76. data/lib/quby/compiler/entities/questions/radio_question.rb +19 -0
  77. data/lib/quby/compiler/entities/questions/select_question.rb +19 -0
  78. data/lib/quby/compiler/entities/questions/string_question.rb +15 -0
  79. data/lib/quby/compiler/entities/questions/text_question.rb +15 -0
  80. data/lib/quby/compiler/entities/score_calculation.rb +35 -0
  81. data/lib/quby/compiler/entities/score_schema.rb +25 -0
  82. data/lib/quby/compiler/entities/subscore_schema.rb +23 -0
  83. data/lib/quby/compiler/entities/table.rb +143 -0
  84. data/lib/quby/compiler/entities/text.rb +71 -0
  85. data/lib/quby/compiler/entities/textvar.rb +23 -0
  86. data/lib/quby/compiler/entities/validation.rb +17 -0
  87. data/lib/quby/compiler/entities/version.rb +23 -0
  88. data/lib/quby/compiler/entities/visibility_rule.rb +71 -0
  89. data/lib/quby/compiler/instance.rb +72 -0
  90. data/lib/quby/compiler/output.rb +13 -0
  91. data/lib/quby/compiler/outputs.rb +4 -0
  92. data/lib/quby/compiler/outputs/quby_frontend_v1_serializer.rb +362 -0
  93. data/lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb +15 -0
  94. data/lib/quby/compiler/outputs/roqua_serializer.rb +108 -0
  95. data/lib/quby/compiler/outputs/seed_serializer.rb +34 -0
  96. data/lib/quby/compiler/services/definition_validator.rb +330 -0
  97. data/lib/quby/compiler/services/quby_proxy.rb +405 -0
  98. data/lib/quby/compiler/services/seed_diff.rb +116 -0
  99. data/lib/quby/compiler/services/text_transformation.rb +30 -0
  100. data/lib/quby/compiler/version.rb +5 -0
  101. data/lib/quby/markdown_parser.rb +38 -0
  102. data/lib/quby/range_categories.rb +38 -0
  103. data/lib/quby/settings.rb +86 -0
  104. data/lib/quby/text_transformation.rb +26 -0
  105. data/lib/quby/type_validator.rb +12 -0
  106. data/quby-compiler.gemspec +39 -0
  107. metadata +277 -0
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'quby/compiler/entities/item'
4
+ require 'quby/markdown_parser'
5
+
6
+ module Quby
7
+ module Compiler
8
+ module Entities
9
+ class Text < Item
10
+ attr_reader :str
11
+ attr_accessor :display_in
12
+ attr_accessor :html_content
13
+
14
+ # In case of being displayed inside a table, amount of columns/rows to span
15
+ attr_accessor :col_span
16
+ attr_accessor :row_span
17
+
18
+ def initialize(str, options = {})
19
+ if options[:html_content]
20
+ options[:raw_content] = "<div class='item text'>" + options[:html_content] + "</div>"
21
+ end
22
+ super(options)
23
+ @str = str
24
+ @html_content = options[:html_content]
25
+ @display_in = options[:display_in] || [:paged]
26
+ @col_span = options[:col_span] || 1
27
+ @row_span = options[:row_span] || 1
28
+ end
29
+
30
+ def as_json(options = {})
31
+ super().merge(text: text)
32
+ end
33
+
34
+ def html
35
+ @html_content || text
36
+ end
37
+
38
+ def text
39
+ @text ||= Quby::MarkdownParser.new(str).to_html
40
+ end
41
+
42
+ def key
43
+ 't0'
44
+ end
45
+
46
+ def type
47
+ "text"
48
+ end
49
+
50
+ def validate_answer(answer_hash)
51
+ true
52
+ end
53
+
54
+ def ==(other)
55
+ case other.class
56
+ when String
57
+ text == other
58
+ when self.class
59
+ text == other.text
60
+ else
61
+ false
62
+ end
63
+ end
64
+
65
+ def to_s
66
+ text
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quby
4
+ module Compiler
5
+ module Entities
6
+ class Textvar < Struct.new(:key, :description, :default, :depends_on_flag)
7
+ # rubocop:disable ParameterLists
8
+ def initialize(key:, description:, default: nil, depends_on_flag: nil)
9
+ default = "{{#{key}}}" unless default
10
+ super(key, description, default, depends_on_flag)
11
+ end
12
+ # rubocop:enable ParameterLists
13
+
14
+ def to_codebook(_options = {})
15
+ output = []
16
+ output << "#{key} Textvariabele"
17
+ output << description
18
+ output.join("\n")
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ module Quby
2
+ module Compiler
3
+ module Entities
4
+ class Validation
5
+ attr_reader :config
6
+
7
+ def initialize(config)
8
+ @config = config
9
+ end
10
+
11
+ def type
12
+ config[:type]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ require 'active_model/model'
2
+
3
+ module Quby
4
+ module Compiler
5
+ module Entities
6
+ class Version
7
+ include ActiveModel::Model
8
+
9
+ # @return [Integer]
10
+ attr_accessor :number
11
+
12
+ # @return [String]
13
+ attr_accessor :release_notes
14
+
15
+ # @return [Boolean]
16
+ attr_accessor :regenerate_outcome
17
+
18
+ # @return [Boolean]
19
+ attr_accessor :deactivate_answers
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,71 @@
1
+ module Quby
2
+ module Compiler
3
+ module Entities
4
+ class VisibilityRule
5
+ def self.from(question)
6
+ [].tap do |rules|
7
+ # Transform "default invisible" into just being hidden by itself,
8
+ # since any other question showing it will take precedence anyway.
9
+ if question.default_invisible
10
+ rules << new(condition: { type: :always, field_key: question.key },
11
+ action: { type: :hide_question, field_key: question.key })
12
+ end
13
+
14
+ case question.type
15
+ when :radio, :scale, :select
16
+ question.options.each do |option|
17
+ rules.concat rules_for_option(question, option, type: :equal)
18
+ end
19
+ when :check_box
20
+ question.options.each do |option|
21
+ rules.concat rules_for_option(question, option, type: :contains)
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ def self.rules_for_option(question, option, type:)
28
+ [].tap do |rules|
29
+ option.shows_questions.each do |shows_question|
30
+ rules << new(condition: { type: type, field_key: question.key, value: option.key },
31
+ action: { type: :show_question, field_key: shows_question })
32
+ end
33
+
34
+ option.hides_questions.each do |hides_question|
35
+ rules << new(condition: { type: type, field_key: question.key, value: option.key },
36
+ action: { type: :hide_question, field_key: hides_question })
37
+ end
38
+ end
39
+ end
40
+
41
+ def self.from_flag(flag)
42
+ condition = { type: "flag_equal", flag_key: flag.key, value: flag.trigger_on }
43
+
44
+ [].tap do |rules|
45
+ flag.shows_questions.each do |question_key|
46
+ rules << VisibilityRule.new(condition: condition, action: { type: "show_question", field_key: question_key })
47
+ end
48
+
49
+ flag.hides_questions.each do |question_key|
50
+ rules << VisibilityRule.new(condition: condition, action: { type: "hide_question", field_key: question_key })
51
+ end
52
+ end
53
+ end
54
+
55
+ attr_reader :condition, :action
56
+
57
+ def initialize(condition:, action:)
58
+ @condition = condition
59
+ @action = action
60
+ end
61
+
62
+ def as_json
63
+ {
64
+ condition: condition.transform_keys { |key| key.to_s.camelize(:lower) },
65
+ action: action.transform_keys { |key| key.to_s.camelize(:lower) },
66
+ }
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,72 @@
1
+ module Quby
2
+ module Compiler
3
+ class Instance
4
+ attr_reader :lookup_tables
5
+
6
+ def initialize(lookup_tables:)
7
+ @lookup_tables = lookup_tables
8
+ end
9
+
10
+ def compile(key:, sourcecode:, seeds:, path: nil, last_update: nil, &block)
11
+ if block # defined in block for tests
12
+ questionnaire = DSL.build(key, path: path, &block)
13
+ else # sourcecode given as string
14
+ tempfile = Tempfile.new(key)
15
+ questionnaire = Entities::Questionnaire.new(key, last_update: last_update)
16
+ Thread.current["quby-questionnaire-loading"] = Quby::Compiler::DSL::QuestionnaireBuilder.new(questionnaire, lookup_tables: lookup_tables)
17
+
18
+ tempfile.puts "Thread.current['quby-questionnaire-loading'].instance_eval do"
19
+ tempfile.puts sourcecode
20
+ tempfile.puts "end"
21
+ tempfile.flush
22
+
23
+ load tempfile.path
24
+ Thread.current['quby-questionnaire-loading'] = nil
25
+
26
+ questionnaire.callback_after_dsl_enhance_on_questions
27
+ end
28
+
29
+ {
30
+ outputs: {
31
+ definition: Output.new(
32
+ key: :definition,
33
+ filename: "definition.rb",
34
+ content: sourcecode,
35
+ ),
36
+ roqua: Output.new(
37
+ key: :roqua,
38
+ filename: "roqua.json",
39
+ content: Outputs::RoquaSerializer.new(questionnaire).to_json,
40
+ ),
41
+ seeds: Output.new(
42
+ key: :seeds,
43
+ filename: "seeds.yml",
44
+ content: YAML.dump(Outputs::SeedSerializer.new(questionnaire, seeds).generate),
45
+ ),
46
+ quby_frontend_v1: Output.new(
47
+ key: :quby_frontend_v1,
48
+ filename: "quby-frontend-v1.json",
49
+ content: Outputs::QubyFrontendV1Serializer.new(questionnaire).to_json,
50
+ ),
51
+ quby_frontend_v2: Output.new(
52
+ key: :quby_frontend_v2,
53
+ filename: "quby-frontend-v2.json",
54
+ content: Outputs::QubyFrontendV2Serializer.new(questionnaire).to_json,
55
+ ),
56
+ },
57
+ }
58
+ ensure
59
+ # We can only close and remove the file once serializers have finished.
60
+ # The serializers need the file in order to grab the source for score blocks
61
+ tempfile&.close
62
+ tempfile&.unlink
63
+ end
64
+
65
+ def validate(key:, sourcecode:)
66
+ definition = Entities::Definition.new(key: key, sourcecode: sourcecode, path: "validating '#{key}'")
67
+ definition.valid?
68
+ definition
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,13 @@
1
+ module Quby
2
+ module Compiler
3
+ class Output
4
+ attr_reader :key, :filename, :content
5
+
6
+ def initialize(key:, filename:, content:)
7
+ @key = key
8
+ @filename = filename
9
+ @content = content
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,4 @@
1
+ require 'quby/compiler/outputs/roqua_serializer'
2
+ require 'quby/compiler/outputs/seed_serializer'
3
+ require 'quby/compiler/outputs/quby_frontend_v1_serializer'
4
+ require 'quby/compiler/outputs/quby_frontend_v2_serializer'
@@ -0,0 +1,362 @@
1
+ require 'method_source'
2
+
3
+ module Quby
4
+ module Compiler
5
+ module Outputs
6
+ class QubyFrontendV1Serializer
7
+ attr_reader :questionnaire
8
+
9
+ def initialize(questionnaire)
10
+ @questionnaire = questionnaire
11
+ end
12
+
13
+ def panels
14
+ questionnaire.panels.map do |panel|
15
+ {
16
+ key: panel.key,
17
+ title: panel.title,
18
+ items: panel.items.reject { |item| item.respond_to?(:table) && item.table }.map { |item| item_as_json(item) }
19
+ }
20
+ end
21
+ end
22
+
23
+ def item_as_json(item)
24
+ case item
25
+ when Quby::Compiler::Entities::Text
26
+ text_as_json(item)
27
+ when Quby::Compiler::Entities::Question
28
+ question_as_json(item)
29
+ when Quby::Compiler::Entities::Table
30
+ table_as_json(item)
31
+ end
32
+ end
33
+
34
+ def text_as_json(text)
35
+ {
36
+ type: 'text',
37
+ str: text.str,
38
+ html_content: text.html_content,
39
+ display_in: text.display_in,
40
+ col_span: text.col_span,
41
+ row_span: text.row_span,
42
+ raw_content: text.raw_content,
43
+ switch_cycle: text.switch_cycle,
44
+ }
45
+ end
46
+
47
+ def table_as_json(table)
48
+ {
49
+ type: 'table',
50
+ columns: table.columns,
51
+ title: table.title,
52
+ description: table.description,
53
+ show_option_desc: table.show_option_desc,
54
+ items: table.items.map { |item| item_as_json(item) }
55
+ }
56
+ end
57
+
58
+ def question_as_json(question)
59
+ base_options = {
60
+ type: 'question',
61
+ question_type: question.type,
62
+ key: question.key,
63
+ title: question.title,
64
+ context_free_title: question.context_free_title,
65
+ description: question.description,
66
+ presentation: question.presentation,
67
+ hidden: question.hidden,
68
+ depends_on: question.depends_on,
69
+ default_position: question.default_position,
70
+ col_span: question.col_span,
71
+ row_span: question.row_span,
72
+ validations: question.validations.map {|validation| validation_as_json(validation) },
73
+ raw_content: question.raw_content,
74
+ switch_cycle: question.switch_cycle,
75
+
76
+ sbg_key: question.sbg_key,
77
+ allow_duplicate_option_values: question.allow_duplicate_option_values,
78
+ allow_blank_titles: question.allow_blank_titles,
79
+ as: question.as,
80
+ display_modes: question.display_modes,
81
+ autocomplete: question.autocomplete,
82
+ show_values: question.show_values,
83
+ deselectable: question.deselectable,
84
+ disallow_bulk: question.disallow_bulk,
85
+ score_header: question.score_header,
86
+ sets_textvar: question.sets_textvar,
87
+ default_invisible: question.default_invisible,
88
+ question_group: question.question_group,
89
+ group_minimum_answered: question.group_minimum_answered,
90
+ group_maximum_answered: question.group_maximum_answered,
91
+ value_tooltip: question.input_data[:value_tooltip],
92
+
93
+ parent_option_key: question.parent_option_key,
94
+ }
95
+
96
+ case question
97
+ when Quby::Compiler::Entities::Questions::CheckboxQuestion
98
+ base_options.merge(
99
+ options: question.options.map { |option| option_as_json(option) },
100
+ check_all_option: question.check_all_option,
101
+ uncheck_all_option: question.uncheck_all_option,
102
+ maximum_checked_allowed: question.maximum_checked_allowed,
103
+ minimum_checked_required: question.minimum_checked_required
104
+ )
105
+ when Quby::Compiler::Entities::Questions::DateQuestion
106
+ base_options.merge(
107
+ components: question.components,
108
+ required_components: question.required_components,
109
+ year_key: question.year_key,
110
+ month_key: question.month_key,
111
+ day_key: question.day_key,
112
+ hour_key: question.hour_key,
113
+ minute_key: question.minute_key,
114
+ )
115
+ when Quby::Compiler::Entities::Questions::DeprecatedQuestion
116
+ base_options.merge(
117
+ options: question.options.map { |option| option_as_json(option) }
118
+ )
119
+ when Quby::Compiler::Entities::Questions::FloatQuestion
120
+ base_options.merge(
121
+ labels: question.labels,
122
+ unit: question.unit,
123
+ size: question.size,
124
+ )
125
+ when Quby::Compiler::Entities::Questions::IntegerQuestion
126
+ base_options.merge(
127
+ labels: question.labels,
128
+ unit: question.unit,
129
+ size: question.size,
130
+ )
131
+ when Quby::Compiler::Entities::Questions::RadioQuestion
132
+ base_options.merge(
133
+ options: question.options.map { |option| option_as_json(option) }
134
+ )
135
+ when Quby::Compiler::Entities::Questions::SelectQuestion
136
+ base_options.merge(
137
+ options: question.options.map { |option| option_as_json(option) }
138
+ )
139
+ when Quby::Compiler::Entities::Questions::StringQuestion
140
+ base_options.merge(
141
+ unit: question.unit,
142
+ size: question.size,
143
+ )
144
+ when Quby::Compiler::Entities::Questions::TextQuestion
145
+ base_options.merge(
146
+ lines: question.lines
147
+ )
148
+ else
149
+ raise "Unknown item type"
150
+ end
151
+ end
152
+
153
+ def validation_as_json(validation)
154
+ case validation[:type]
155
+ when :requires_answer
156
+ validation.slice(:type, :explanation)
157
+ when :answer_group_minimum, :answer_group_maximum
158
+ validation.slice(:type, :explanation, :group, :value)
159
+ when :valid_integer, :valid_float
160
+ validation.slice(:type, :explanation)
161
+ when :valid_date
162
+ validation.slice(:type, :explanation, :subtype).merge(value_type: validation[:value].class.to_s)
163
+ when :minimum, :maximum
164
+ validation.slice(:type, :explanation, :value, :subtype).merge(value_type: validation[:value].class.to_s)
165
+ when :too_many_checked
166
+ validation.slice(:type, :explanation, :uncheck_all_key)
167
+ when :minimum_checked_required
168
+ validation.slice(:type, :explanation, :minimum_checked_value)
169
+ when :maximum_checked_allowed
170
+ validation.slice(:type, :explanation, :maximum_checked_value)
171
+ when :regexp
172
+ validation.slice(:type, :explanation).merge(matcher: validation.fetch(:matcher).source)
173
+ when :not_all_checked
174
+ validation.slice(:type, :explanation, :check_all_key)
175
+ else
176
+ raise "Unknown validation type: #{validation.inspect}"
177
+ end
178
+ end
179
+
180
+ def option_as_json(option)
181
+ {
182
+ key: option.key,
183
+ value: option.value,
184
+ description: option.description,
185
+ context_free_description: option.context_free_description,
186
+ questions: option.questions.map {|question| question_as_json(question)},
187
+ inner_title: option.inner_title,
188
+ hides_questions: option.hides_questions,
189
+ shows_questions: option.shows_questions,
190
+ hidden: option.hidden,
191
+ placeholder: option.placeholder,
192
+ }
193
+ end
194
+
195
+ def score_calculations
196
+ questionnaire.score_calculations.transform_values do |score_calculation|
197
+ {
198
+ key: score_calculation.key,
199
+ label: score_calculation.label,
200
+ sbg_key: score_calculation.sbg_key,
201
+ options: score_calculation.options,
202
+ sourcecode: score_calculation.sourcecode
203
+ }
204
+ end
205
+ end
206
+
207
+ def score_schemas
208
+ questionnaire.score_schemas.transform_values do |schema|
209
+ {
210
+ key: schema.key,
211
+ label: schema.label,
212
+ subscore_schemas: schema.subscore_schemas.map do |subschema|
213
+ {
214
+ key: subschema.key,
215
+ label: subschema.label,
216
+ export_key: subschema.export_key,
217
+ only_for_export: subschema.only_for_export
218
+ }
219
+ end
220
+ }
221
+ end
222
+ end
223
+
224
+ def charts
225
+ {
226
+ overview: questionnaire.charts.overview && {
227
+ subscore: questionnaire.charts.overview.subscore,
228
+ y_max: questionnaire.charts.overview.y_max,
229
+ },
230
+ others: questionnaire.charts.map do |chart|
231
+ case chart
232
+ when Quby::Compiler::Entities::Charting::LineChart
233
+ {
234
+ y_label: chart.y_label,
235
+ tonality: chart.tonality,
236
+ baseline: YAML.dump(chart.baseline),
237
+ clinically_relevant_change: chart.clinically_relevant_change,
238
+ }
239
+ when Quby::Compiler::Entities::Charting::OverviewChart
240
+ {
241
+ subscore: chart.subscore,
242
+ y_max: chart.y_max,
243
+ }
244
+ else
245
+ {}
246
+ end.merge(
247
+ key: chart.key,
248
+ type: chart.type,
249
+ title: chart.title,
250
+ plottables: chart.plottables,
251
+ y_categories: chart.y_categories,
252
+ y_range_categories: chart.y_range_categories,
253
+ chart_type: chart.chart_type,
254
+ y_range: range_as_json(chart.y_range),
255
+ tick_interval: chart.tick_interval,
256
+ plotbands: chart.plotbands,
257
+ plotlines: chart.plotlines
258
+ )
259
+ end
260
+ }
261
+ end
262
+
263
+ def flags
264
+ questionnaire.flags.transform_values do |flag|
265
+ {
266
+ key: flag.key,
267
+ description_true: flag.description_true,
268
+ description_false: flag.description_false,
269
+ description: flag.description,
270
+ internal: flag.internal,
271
+ trigger_on: flag.trigger_on,
272
+ shows_questions: flag.shows_questions,
273
+ hides_questions: flag.hides_questions,
274
+ depends_on: flag.depends_on,
275
+ default_in_interface: flag.default_in_interface,
276
+ }
277
+ end
278
+ end
279
+
280
+ def textvars
281
+ questionnaire.textvars.transform_values do |textvar|
282
+ {
283
+ key: textvar.key,
284
+ description: textvar.description,
285
+ default: textvar.default,
286
+ depends_on_flag: textvar.depends_on_flag,
287
+ }
288
+ end
289
+ end
290
+
291
+ def lookup_tables
292
+ # TODO: Figure out something better. For now, no interest in walking a tree and converting Range objects to something JSON-able.
293
+ YAML.dump(questionnaire.lookup_tables)
294
+ end
295
+
296
+ def outcome_tables
297
+ # TODO DOes not seem to be used in definitions?
298
+ questionnaire.outcome_tables.map do |outcome_table|
299
+ {
300
+ key: outcome_table.key,
301
+ score_keys: outcome_table.score_keys,
302
+ subscore_keys: outcome_table.subscore_keys,
303
+ name: outcome_table.name,
304
+ default_collapsed: outcome_table.default_collapsed,
305
+ }
306
+ end
307
+ end
308
+
309
+ def as_json(options = {})
310
+ {
311
+ key: questionnaire.key,
312
+ title: questionnaire.title,
313
+ description: questionnaire.description,
314
+ outcome_description: questionnaire.outcome_description,
315
+ short_description: questionnaire.short_description,
316
+ abortable: questionnaire.abortable,
317
+ enable_previous_questionnaire_button: questionnaire.enable_previous_questionnaire_button,
318
+ default_answer_value: questionnaire.default_answer_value,
319
+ leave_page_alert: questionnaire.leave_page_alert,
320
+ allow_hotkeys: questionnaire.allow_hotkeys,
321
+ license: questionnaire.license,
322
+ licensor: questionnaire.licensor,
323
+ language: questionnaire.language,
324
+ renderer_version: questionnaire.renderer_version,
325
+ last_update: questionnaire.last_update,
326
+ last_author: questionnaire.last_author,
327
+ extra_css: questionnaire.extra_css,
328
+ allow_switch_to_bulk: questionnaire.allow_switch_to_bulk,
329
+
330
+ panels: panels,
331
+ score_calculations: score_calculations,
332
+ score_schemas: score_schemas,
333
+ flags: flags,
334
+ textvars: textvars,
335
+ lookup_tables: lookup_tables,
336
+
337
+ # belong to roqua domain, but for now they're here too:
338
+ roqua_keys: questionnaire.roqua_keys,
339
+ sbg_key: questionnaire.sbg_key,
340
+ sbg_domains: questionnaire.sbg_domains,
341
+ outcome_regeneration_requested_at: questionnaire.outcome_regeneration_requested_at,
342
+ deactivate_answers_requested_at: questionnaire.deactivate_answers_requested_at,
343
+ respondent_types: questionnaire.respondent_types,
344
+ tags: questionnaire.tags.to_h.keys,
345
+ charts: charts,
346
+ outcome_tables: outcome_tables,
347
+ }
348
+ end
349
+
350
+ def range_as_json(range)
351
+ return unless range
352
+
353
+ {
354
+ begin: range.begin,
355
+ end: range.end,
356
+ exclude_end: range.exclude_end?
357
+ }
358
+ end
359
+ end
360
+ end
361
+ end
362
+ end