quby-compiler 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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