Class: Quby::Compiler::Outputs::QubyFrontendV2Serializer
- Inherits:
-
Object
- Object
- Quby::Compiler::Outputs::QubyFrontendV2Serializer
- Defined in:
- lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb
Instance Method Summary collapse
- #as_json(options = {}) ⇒ Object
- #base_question(question) ⇒ Object
- #check_box_question(question) ⇒ Object
- #children(question) ⇒ Object
- #date_parts_question(question) ⇒ Object
- #depends_on(validation_hsh) ⇒ Object
- #depends_on_options(validation_hsh) ⇒ Object
- #float_question(question) ⇒ Object
- #footer ⇒ Object
- #handle_html(html, type: :simple, v1_markdown: true) ⇒ Object
-
#hidden_question(question) ⇒ Object
deprecated.
- #html_sanitizer ⇒ Object
- #info_block_item(info_block) ⇒ Object
-
#initialize(questionnaire, translations: {}) ⇒ QubyFrontendV2Serializer
constructor
A new instance of QubyFrontendV2Serializer.
- #inner_title_as_json(option, idx) ⇒ Object
- #integer_question(question) ⇒ Object
- #number_question(question) ⇒ Object
- #option_as_json(option) ⇒ Object
- #panel(panel) ⇒ Object
- #panel_item(item) ⇒ Object
- #question(question) ⇒ Object
- #question_keys_for_panel(panel) ⇒ Object
- #question_keys_for_question(question) ⇒ Object
- #question_type(question) ⇒ Object
- #questions ⇒ Object
- #radio_question(question) ⇒ Object
- #scale_question(question) ⇒ Object
- #select_question(question) ⇒ Object
- #size(question) ⇒ Object
- #slider_question(question) ⇒ Object
- #split_to_units_question(question) ⇒ Object
- #string_question(question) ⇒ Object
- #textarea_question(question) ⇒ Object
- #textvars ⇒ Object
- #translations ⇒ Object
-
#validate_all_questions_in_a_panel_question_keys(json) ⇒ Object
safeguard that question_keys_for_panel is considering all options.
- #validations ⇒ Object
Constructor Details
#initialize(questionnaire, translations: {}) ⇒ QubyFrontendV2Serializer
Returns a new instance of QubyFrontendV2Serializer.
7 8 9 10 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 7 def initialize(questionnaire, translations: {}) @questionnaire = questionnaire @translations = translations end |
Instance Method Details
#as_json(options = {}) ⇒ Object
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 14 def as_json( = {}) { key: key, layoutVersion: layout_version || :v1, title: title, language: language || "nl", description: description, shortDescription: short_description, footer: , defaultAnswerValue: Services::TransformQuby1ValuesIntoQuby2Values.run!(@questionnaire, default_answer_value), panels: panels.map { panel(_1) }, questions: questions, textvars: textvars, validations: validations, visibilityRules: visibility_rules.as_json, sexpVariables: sexp_variables, cssVars: css_vars, versionNumber: version_number, translations: translations, }.compact.tap do |json| validate_all_questions_in_a_panel_question_keys(json) end end |
#base_question(question) ⇒ Object
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 207 def base_question(question) { key: question.key, title: handle_html(question.title), description: handle_html(question.description, type: :question_description), contextDescription: handle_html(question.context_description, type: :prose, v1_markdown: false), type: question_type(question), cssVars: question.css_vars, indent: question.indent, hidden: question.hidden?, displayModes: question.display_modes, viewSelector: question.view_selector, parentKey: question.parent&.key, parentOptionKey: question.parent_option_key, deselectable: question.deselectable, presentation: question.presentation, as: question.as || question_type(question), # default to type so typescript can narrow on it. questionGroup: question.question_group, } end |
#check_box_question(question) ⇒ Object
118 119 120 121 122 123 124 125 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 118 def check_box_question(question) { **base_question(question), children: children(question), checkAllOption: question.check_all_option, uncheckAllOption: question.uncheck_all_option, }.compact end |
#children(question) ⇒ Object
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 258 def children(question) question..map.with_index { |child, idx| if child.is_a?(Quby::Compiler::Entities::QuestionOptgroup) { type: 'optgroup', key: child.key, label: child.label, options: child..map { option_as_json(_1) } } elsif child.inner_title inner_title_as_json(child, idx) elsif child.placeholder nil # placeholder attr on question. else option_as_json(child) end }.compact end |
#date_parts_question(question) ⇒ Object
127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 127 def date_parts_question(question) { **base_question(question), dateParts: question.components.map { |component| { part: component, key: question.send("#{component}_key"), } }, }.compact end |
#depends_on(validation_hsh) ⇒ Object
339 340 341 342 343 344 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 339 def depends_on(validation_hsh) question = @questionnaire.question_hash[validation_hsh['fieldKey']] return unless question.depends_on.present? question.depends_on.select { @questionnaire.question_hash.key?(_1) } end |
#depends_on_options(validation_hsh) ⇒ Object
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 346 def (validation_hsh) question = @questionnaire.question_hash[validation_hsh['fieldKey']] return unless question.depends_on.present? question.depends_on .map(&:to_s) .reject { @questionnaire.question_hash.key?(_1) } .map { |key| question_key, option_key = key.split(/_(?=[^_]+$)/) # split on last _, works for all existing questionnaires. if @questionnaire.question_hash.key?(question_key) if @questionnaire.question_hash[question_key]..any? { _1.key.to_s == option_key } # single_select next [question_key, option_key] elsif @questionnaire.question_hash[question_key]..any? { _1.key.to_s == key } # multi_select next [question_key, key] end end raise "#{validation_hsh['fieldKey']} depends_on: can't find key '#{question_key}_#{option_key}'." }.group_by(&:first).transform_values{ _1.map(&:last) } end |
#float_question(question) ⇒ Object
144 145 146 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 144 def float_question(question) number_question(question) end |
#footer ⇒ Object
46 47 48 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 46 def handle_html(@questionnaire., type: :prose, v1_markdown: false) end |
#handle_html(html, type: :simple, v1_markdown: true) ⇒ Object
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 366 def handle_html(html, type: :simple, v1_markdown: true) if layout_version == :v2 case type when :simple html_sanitizer.sanitize(html, tags: %w[strong em sup sub br span img], attributes: %w[class src alt width height]) when :question_description html_sanitizer.sanitize(html, tags: %w[strong em b i u sup sub pre blockquote p span br ul ol li a img], attributes: %w[class href target src alt width height]) when :prose html_sanitizer.sanitize(html, tags: %w[strong em b i u sup sub pre blockquote p span br ul ol li a img h1 h2 h3 h4 hr], attributes: %w[href class target src alt width height]) end elsif v1_markdown Quby::Compiler::MarkdownParser.new(html).to_html else html end end |
#hidden_question(question) ⇒ Object
deprecated
140 141 142 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 140 def hidden_question(question) nil end |
#html_sanitizer ⇒ Object
383 384 385 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 383 def html_sanitizer @html_sanitize ||= Rails::HTML5::SafeListSanitizer.new end |
#info_block_item(info_block) ⇒ Object
97 98 99 100 101 102 103 104 105 106 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 97 def info_block_item(info_block) { type: 'info', subtype: info_block.subtype, key: info_block.key, html: handle_html(info_block.html, type: :prose, v1_markdown: false), startOpen: info_block.start_open, items: info_block.items.map { panel_item(_1) }.compact } end |
#inner_title_as_json(option, idx) ⇒ Object
276 277 278 279 280 281 282 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 276 def inner_title_as_json(option, idx) { type: 'html', key: option.key, html: handle_html(option.description) } end |
#integer_question(question) ⇒ Object
148 149 150 151 152 153 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 148 def integer_question(question) { **number_question(question), **split_to_units_question(question), }.compact end |
#number_question(question) ⇒ Object
155 156 157 158 159 160 161 162 163 164 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 155 def number_question(question) { **base_question(question), **(question), minimum: question.minimum, maximum: question.maximum, size: size(question), unit: question.as != :slider && question.unit, }.compact end |
#option_as_json(option) ⇒ Object
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 284 def option_as_json(option) label = option.label || option.description label = handle_html(label) if option.question.type != :select description = !option.label ? nil : option.description description = handle_html(description) if option.question.type != :select { type: 'option', key: option.key, value: option.question.type != :check_box && option.value, label:, description:, questions: option.question.type != :select ? option.questions.map{ question(_1) } : nil, hidden: option.hidden.presence, viewId: option.view_id }.compact end |
#panel(panel) ⇒ Object
50 51 52 53 54 55 56 57 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 50 def panel(panel) { key: panel.key, title: panel.title, items: panel.items.map { panel_item(_1) }.compact, questionKeys: question_keys_for_panel(panel) # added instead of calculated in js, so we can validate completeness. }.compact end |
#panel_item(item) ⇒ Object
59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 59 def panel_item(item) case item when Quby::Compiler::Entities::Text { type: 'html', key: item.key, html: handle_html(item.html, type: :prose, v1_markdown: false) } when Quby::Compiler::Entities::Question return if item.table # things inside a table are added to the table, AND ALSO to the panel. skip them. { type: 'question', key: item.key } when Quby::Compiler::Entities::InfoBlock info_block_item(item) when Quby::Compiler::Entities::Table { type: "table" } end end |
#question(question) ⇒ Object
114 115 116 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 114 def question(question) send(:"#{question_type(question)}_question", question) end |
#question_keys_for_panel(panel) ⇒ Object
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 73 def question_keys_for_panel(panel) panel.items.flat_map do |item| case item when Quby::Compiler::Entities::Question question_keys_for_question(item) when Quby::Compiler::Entities::InfoBlock, Quby::Compiler::Entities::Table item.items.flat_map do |nested_item| next unless nested_item.is_a?(Quby::Compiler::Entities::Question) question_keys_for_question(nested_item) end else [] end end.compact end |
#question_keys_for_question(question) ⇒ Object
89 90 91 92 93 94 95 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 89 def question_keys_for_question(question) keys = [question.key] keys << question.title_question.key if question.title_question keys + question.&.flat_map do |option| option.questions&.map(&:key) # We only allow subquestions one level deep. end end |
#question_type(question) ⇒ Object
248 249 250 251 252 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 248 def question_type(question) { date: 'date_parts', }[question.type] || question.type end |
#questions ⇒ Object
108 109 110 111 112 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 108 def questions fields.question_hash \ .to_h { |k, question| [k, question(question)] } \ .compact end |
#radio_question(question) ⇒ Object
166 167 168 169 170 171 172 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 166 def radio_question(question) { **base_question(question), children: children(question), showValues: [true, :all].include?(question.show_values), }.compact end |
#scale_question(question) ⇒ Object
174 175 176 177 178 179 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 174 def scale_question(question) { **radio_question(question), presentation: :horizontal } end |
#select_question(question) ⇒ Object
181 182 183 184 185 186 187 188 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 181 def select_question(question) placeholder = question..find(&:placeholder) { **base_question(question), children: children(question), placeholder: placeholder&.label || placeholder&.description, }.compact end |
#size(question) ⇒ Object
254 255 256 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 254 def size(question) question.size.presence && Integer(question.size) # 2022-11: 4k string and 7k integer end |
#slider_question(question) ⇒ Object
229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 229 def (question) return {} unless question.as == :slider { step: question.step, defaultPosition: question.default_position.is_a?(Numeric) ? question.default_position : question.minimum, startThumbHidden: question.default_position == :hidden, valueTooltip: question.input_data[:value_tooltip] || false, labels: question.labels, } end |
#split_to_units_question(question) ⇒ Object
241 242 243 244 245 246 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 241 def split_to_units_question(question) { units: question.units, conversions: question.conversions, } end |
#string_question(question) ⇒ Object
190 191 192 193 194 195 196 197 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 190 def string_question(question) { **base_question(question), setsTextvar: question.sets_textvar, size: size(question), unit: question.as != :slider && question.unit, }.compact end |
#textarea_question(question) ⇒ Object
199 200 201 202 203 204 205 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 199 def textarea_question(question) { **base_question(question), autocomplete: question.autocomplete, lines: question.lines, }.compact end |
#textvars ⇒ Object
301 302 303 304 305 306 307 308 309 310 311 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 301 def textvars @questionnaire.textvars.to_h { |key, textvar| [ key, { key: textvar.key, default: textvar.default, } ] } end |
#translations ⇒ Object
328 329 330 331 332 333 334 335 336 337 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 328 def translations @translations.clone \ .transform_values { |translations| translations.clone.delete_if { |key, _v| key.ends_with?("context_free_title") \ || key.ends_with?("context_free_description") \ || key == "short_description" } } end |
#validate_all_questions_in_a_panel_question_keys(json) ⇒ Object
safeguard that question_keys_for_panel is considering all options.
39 40 41 42 43 44 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 39 def validate_all_questions_in_a_panel_question_keys(json) missings = @questionnaire.question_hash.values.map(&:key) - json[:panels].flat_map{_1[:questionKeys]} return if missings.empty? raise "Not all questions are listed in a panel['questionKeys']: #{missings.join(",")}." end |
#validations ⇒ Object
313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb', line 313 def validations @questionnaire.validations.map do |validation| validation.config.compact .transform_keys{ _1.to_s.camelize(:lower) } .tap { |validation_hsh| # otherwise ruby will put a (?-mix around the regex, which js errors on. validation_hsh['matcher'] = validation_hsh['matcher'].source.to_s if validation_hsh['matcher'] validation_hsh['type'] = 'minimum_date' if validation_hsh['type'] == :minimum && validation_hsh['subtype'] == :date validation_hsh['type'] = 'maximum_date' if validation_hsh['type'] == :maximum && validation_hsh['subtype'] == :date validation_hsh['dependsOnQuestions'] = depends_on(validation_hsh) if validation_hsh['type'] == :requires_answer validation_hsh['dependsOnOptions'] = (validation_hsh) if validation_hsh['type'] == :requires_answer }.compact.as_json end end |