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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.gitlab-ci.yml +5 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Dockerfile +11 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +133 -0
- data/LICENSE.txt +21 -0
- data/README.md +44 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/rspec +29 -0
- data/bin/setup +8 -0
- data/config/locales/de.yml +58 -0
- data/config/locales/en.yml +57 -0
- data/config/locales/nl.yml +57 -0
- data/config/locales/rails-i18n/README.md +4 -0
- data/config/locales/rails-i18n/de.yml +223 -0
- data/config/locales/rails-i18n/en.yml +216 -0
- data/config/locales/rails-i18n/nl.yml +214 -0
- data/exe/quby-compile +56 -0
- data/lib/quby/array_attribute_valid_validator.rb +15 -0
- data/lib/quby/attribute_valid_validator.rb +14 -0
- data/lib/quby/compiler.rb +50 -0
- data/lib/quby/compiler/dsl.rb +29 -0
- data/lib/quby/compiler/dsl/base.rb +20 -0
- data/lib/quby/compiler/dsl/calls_custom_methods.rb +29 -0
- data/lib/quby/compiler/dsl/charting/bar_chart_builder.rb +14 -0
- data/lib/quby/compiler/dsl/charting/chart_builder.rb +95 -0
- data/lib/quby/compiler/dsl/charting/line_chart_builder.rb +34 -0
- data/lib/quby/compiler/dsl/charting/overview_chart_builder.rb +31 -0
- data/lib/quby/compiler/dsl/charting/radar_chart_builder.rb +14 -0
- data/lib/quby/compiler/dsl/helpers.rb +53 -0
- data/lib/quby/compiler/dsl/panel_builder.rb +80 -0
- data/lib/quby/compiler/dsl/question_builder.rb +40 -0
- data/lib/quby/compiler/dsl/questionnaire_builder.rb +279 -0
- data/lib/quby/compiler/dsl/questions/base.rb +180 -0
- data/lib/quby/compiler/dsl/questions/checkbox_question_builder.rb +20 -0
- data/lib/quby/compiler/dsl/questions/date_question_builder.rb +18 -0
- data/lib/quby/compiler/dsl/questions/deprecated_question_builder.rb +18 -0
- data/lib/quby/compiler/dsl/questions/float_question_builder.rb +21 -0
- data/lib/quby/compiler/dsl/questions/integer_question_builder.rb +21 -0
- data/lib/quby/compiler/dsl/questions/radio_question_builder.rb +20 -0
- data/lib/quby/compiler/dsl/questions/select_question_builder.rb +18 -0
- data/lib/quby/compiler/dsl/questions/string_question_builder.rb +20 -0
- data/lib/quby/compiler/dsl/questions/text_question_builder.rb +22 -0
- data/lib/quby/compiler/dsl/score_builder.rb +22 -0
- data/lib/quby/compiler/dsl/score_schema_builder.rb +53 -0
- data/lib/quby/compiler/dsl/standardized_panel_generators.rb +33 -0
- data/lib/quby/compiler/dsl/table_builder.rb +48 -0
- data/lib/quby/compiler/entities.rb +38 -0
- data/lib/quby/compiler/entities/charting/bar_chart.rb +17 -0
- data/lib/quby/compiler/entities/charting/chart.rb +101 -0
- data/lib/quby/compiler/entities/charting/charts.rb +42 -0
- data/lib/quby/compiler/entities/charting/line_chart.rb +38 -0
- data/lib/quby/compiler/entities/charting/overview_chart.rb +20 -0
- data/lib/quby/compiler/entities/charting/plottable.rb +20 -0
- data/lib/quby/compiler/entities/charting/radar_chart.rb +17 -0
- data/lib/quby/compiler/entities/definition.rb +26 -0
- data/lib/quby/compiler/entities/fields.rb +119 -0
- data/lib/quby/compiler/entities/flag.rb +55 -0
- data/lib/quby/compiler/entities/item.rb +40 -0
- data/lib/quby/compiler/entities/lookup_tables.rb +71 -0
- data/lib/quby/compiler/entities/outcome_table.rb +31 -0
- data/lib/quby/compiler/entities/panel.rb +82 -0
- data/lib/quby/compiler/entities/question.rb +365 -0
- data/lib/quby/compiler/entities/question_option.rb +96 -0
- data/lib/quby/compiler/entities/questionnaire.rb +440 -0
- data/lib/quby/compiler/entities/questions/checkbox_question.rb +82 -0
- data/lib/quby/compiler/entities/questions/date_question.rb +84 -0
- data/lib/quby/compiler/entities/questions/deprecated_question.rb +19 -0
- data/lib/quby/compiler/entities/questions/float_question.rb +15 -0
- data/lib/quby/compiler/entities/questions/integer_question.rb +15 -0
- data/lib/quby/compiler/entities/questions/radio_question.rb +19 -0
- data/lib/quby/compiler/entities/questions/select_question.rb +19 -0
- data/lib/quby/compiler/entities/questions/string_question.rb +15 -0
- data/lib/quby/compiler/entities/questions/text_question.rb +15 -0
- data/lib/quby/compiler/entities/score_calculation.rb +35 -0
- data/lib/quby/compiler/entities/score_schema.rb +25 -0
- data/lib/quby/compiler/entities/subscore_schema.rb +23 -0
- data/lib/quby/compiler/entities/table.rb +143 -0
- data/lib/quby/compiler/entities/text.rb +71 -0
- data/lib/quby/compiler/entities/textvar.rb +23 -0
- data/lib/quby/compiler/entities/validation.rb +17 -0
- data/lib/quby/compiler/entities/version.rb +23 -0
- data/lib/quby/compiler/entities/visibility_rule.rb +71 -0
- data/lib/quby/compiler/instance.rb +72 -0
- data/lib/quby/compiler/output.rb +13 -0
- data/lib/quby/compiler/outputs.rb +4 -0
- data/lib/quby/compiler/outputs/quby_frontend_v1_serializer.rb +362 -0
- data/lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb +15 -0
- data/lib/quby/compiler/outputs/roqua_serializer.rb +108 -0
- data/lib/quby/compiler/outputs/seed_serializer.rb +34 -0
- data/lib/quby/compiler/services/definition_validator.rb +330 -0
- data/lib/quby/compiler/services/quby_proxy.rb +405 -0
- data/lib/quby/compiler/services/seed_diff.rb +116 -0
- data/lib/quby/compiler/services/text_transformation.rb +30 -0
- data/lib/quby/compiler/version.rb +5 -0
- data/lib/quby/markdown_parser.rb +38 -0
- data/lib/quby/range_categories.rb +38 -0
- data/lib/quby/settings.rb +86 -0
- data/lib/quby/text_transformation.rb +26 -0
- data/lib/quby/type_validator.rb +12 -0
- data/quby-compiler.gemspec +39 -0
- metadata +277 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'quby/compiler/entities'
|
|
4
|
+
|
|
5
|
+
module Quby
|
|
6
|
+
module Compiler
|
|
7
|
+
module DSL
|
|
8
|
+
class ChartBuilder
|
|
9
|
+
# rubocop:disable AccessorMethodName
|
|
10
|
+
def self.set_chart_class(chart_class)
|
|
11
|
+
@chart_class = chart_class
|
|
12
|
+
end
|
|
13
|
+
# rubocop:enable AccessorMethodName
|
|
14
|
+
|
|
15
|
+
def self.chart_class
|
|
16
|
+
@chart_class
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize(questionnaire, key, options = {})
|
|
20
|
+
@chart = self.class.chart_class.new(key, options)
|
|
21
|
+
@questionnaire = questionnaire
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def title(title)
|
|
25
|
+
@chart.title = title
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def range(range)
|
|
29
|
+
@chart.y_range = range
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def tick_interval(tick_interval)
|
|
33
|
+
@chart.tick_interval = tick_interval
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def y_categories(y_categories)
|
|
37
|
+
@chart.y_categories = y_categories
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def y_range_categories(*y_range_categories)
|
|
41
|
+
@chart.y_range_categories = RangeCategories.new(*y_range_categories).as_range_hash
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def plotband(from, to, color)
|
|
45
|
+
@chart.plotbands << {from: from, to: to, color: color}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def plotline(value, color)
|
|
49
|
+
@chart.plotlines << {value: value, color: color}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def plot(key, options = {})
|
|
53
|
+
unless plottable = @questionnaire.find_plottable(key)
|
|
54
|
+
fail "Questionnaire #{@questionnaire.key} chart #{@chart.key} references unknown score or question #{key}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
configure_options plottable, options
|
|
58
|
+
|
|
59
|
+
@chart.plottables << Entities::Charting::Plottable.new(plottable.key, options)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def chart_type(type)
|
|
63
|
+
@chart.chart_type = type
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def build(&block)
|
|
67
|
+
instance_eval(&block)
|
|
68
|
+
validate!
|
|
69
|
+
@chart
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def validate!
|
|
73
|
+
if @chart.y_categories.present? && @chart.y_range.present? &&
|
|
74
|
+
@chart.y_range != (0..(@chart.y_categories.count - 1))
|
|
75
|
+
fail ArgumentError, 'Y_categories size and range do not match'
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def configure_options(plottable, options)
|
|
84
|
+
case plottable
|
|
85
|
+
when Entities::ScoreCalculation
|
|
86
|
+
options.reverse_merge! plottable.options
|
|
87
|
+
when Entities::Question
|
|
88
|
+
options[:label] ||= plottable.title
|
|
89
|
+
end
|
|
90
|
+
options[:questionnaire_key] = @questionnaire.key
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'quby/compiler/entities/charting/line_chart'
|
|
4
|
+
require_relative 'chart_builder'
|
|
5
|
+
|
|
6
|
+
module Quby
|
|
7
|
+
module Compiler
|
|
8
|
+
module DSL
|
|
9
|
+
class LineChartBuilder < ChartBuilder
|
|
10
|
+
set_chart_class(Entities::Charting::LineChart)
|
|
11
|
+
|
|
12
|
+
def y_axis_label(label)
|
|
13
|
+
@chart.y_label = label
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def tonality(value)
|
|
17
|
+
@chart.tonality = value
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def baseline(value)
|
|
21
|
+
@chart.baseline = value
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def clinically_relevant_change(value)
|
|
25
|
+
@chart.clinically_relevant_change = value
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def validate!
|
|
29
|
+
fail "Chart #{@chart.key} has no range specified" unless @chart.y_range
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'quby/compiler/entities/charting/overview_chart'
|
|
2
|
+
require_relative 'chart_builder'
|
|
3
|
+
|
|
4
|
+
module Quby
|
|
5
|
+
module Compiler
|
|
6
|
+
module DSL
|
|
7
|
+
class OverviewChartBuilder < ChartBuilder
|
|
8
|
+
set_chart_class(Entities::Charting::OverviewChart)
|
|
9
|
+
|
|
10
|
+
def initialize(questionnaire, options = {})
|
|
11
|
+
@questionnaire = questionnaire
|
|
12
|
+
@chart = self.class.chart_class.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def subscore(key)
|
|
16
|
+
@chart.subscore = key
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def y_max(value)
|
|
20
|
+
@chart.y_max = value
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def validate!
|
|
24
|
+
fail ArgumentError, "subscore not specified" unless @chart.subscore.present?
|
|
25
|
+
fail ArgumentError, "y_max not specified" unless @chart.y_max.present?
|
|
26
|
+
true
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'quby/compiler/entities/charting/radar_chart'
|
|
4
|
+
require_relative 'chart_builder'
|
|
5
|
+
|
|
6
|
+
module Quby
|
|
7
|
+
module Compiler
|
|
8
|
+
module DSL
|
|
9
|
+
class RadarChartBuilder < ChartBuilder
|
|
10
|
+
set_chart_class(Entities::Charting::RadarChart)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Quby
|
|
4
|
+
module Compiler
|
|
5
|
+
module DSL
|
|
6
|
+
module Helpers
|
|
7
|
+
include ActionView::Helpers::TagHelper
|
|
8
|
+
include ActionView::Helpers::OutputSafetyHelper
|
|
9
|
+
|
|
10
|
+
def image_tag(source, options = {})
|
|
11
|
+
tag.img(options.reverse_merge(src: source, alt: image_alt(source)))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Copied from ActionController::Base.helpers.image_alt, because it will be removed from Rails 6.0, but we want
|
|
15
|
+
# to keep using this functionality
|
|
16
|
+
def image_alt(source)
|
|
17
|
+
File.basename(source, ".*").sub(/-[[:xdigit:]]{32,64}\z/, "").tr("-_", " ").capitalize
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def check_question_keys_uniqueness(key, options, questionnaire)
|
|
21
|
+
keys = QuestionBuilder.build(key, options).claimed_keys
|
|
22
|
+
if keys.any? { |k| questionnaire.key_in_use? k }
|
|
23
|
+
fail "#{questionnaire.key}:#{key}: A question or option with input key #{key} is already defined."
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def video_tag(*urls, poster: nil, autoplay: false, loop: false)
|
|
28
|
+
# assume the file extension is the video format
|
|
29
|
+
# otherwise, the host's declared mime type is used to figure out compatibility
|
|
30
|
+
# which is usually wrong
|
|
31
|
+
sources = urls.map { |url| [url, url.match(/[^\.]*\z/)&.[](0).downcase] }
|
|
32
|
+
sources.each { |_url, ext| raise 'unknown video url file extension' unless %w[mp4 webm].include?(ext) }
|
|
33
|
+
|
|
34
|
+
options = {
|
|
35
|
+
width: '100%',
|
|
36
|
+
preload: 'none',
|
|
37
|
+
poster: poster,
|
|
38
|
+
loop: loop,
|
|
39
|
+
autoplay: autoplay,
|
|
40
|
+
muted: autoplay,
|
|
41
|
+
controls: ''
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
content = sources.map { |url, ext| tag.source src: url, type: "video/#{ext}" }
|
|
45
|
+
.append(I18n.t('video_not_supported'))
|
|
46
|
+
.yield_self(&tag.method(:safe_join))
|
|
47
|
+
|
|
48
|
+
content_tag(:video, content, options)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'quby/compiler/entities'
|
|
4
|
+
|
|
5
|
+
module Quby
|
|
6
|
+
module Compiler
|
|
7
|
+
module DSL
|
|
8
|
+
class PanelBuilder < Base
|
|
9
|
+
attr_reader :title
|
|
10
|
+
attr_reader :questionnaire
|
|
11
|
+
|
|
12
|
+
def initialize(title, options = {})
|
|
13
|
+
@panel = Entities::Panel.new(options.merge(title: title, items: []))
|
|
14
|
+
@default_question_options = options[:default_question_options] || {}
|
|
15
|
+
@questionnaire = options[:questionnaire]
|
|
16
|
+
@custom_methods = options[:custom_methods] || {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def build
|
|
20
|
+
@panel
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def title(value)
|
|
24
|
+
@panel.title = value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def text(value, options = {})
|
|
28
|
+
@panel.items << Entities::Text.new(value.to_s, options)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def html(value)
|
|
32
|
+
@panel.items << Entities::Text.new('', html_content: value.to_s)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def raw_html(value)
|
|
36
|
+
@panel.items << Entities::Text.new('', raw_content: value.to_s)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def video(*urls, **options)
|
|
40
|
+
video_html = video_tag *urls, **options
|
|
41
|
+
@panel.items << Entities::Text.new('', raw_content: video_html)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def default_question_options(options = {})
|
|
45
|
+
@default_question_options = @default_question_options.merge(options)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def question(key, options = {}, &block)
|
|
49
|
+
options = @default_question_options.merge(options).merge(questionnaire: @panel.questionnaire)
|
|
50
|
+
|
|
51
|
+
check_question_keys_uniqueness key, options, @questionnaire
|
|
52
|
+
|
|
53
|
+
question = QuestionBuilder.build(key, options, &block)
|
|
54
|
+
|
|
55
|
+
@questionnaire.register_question(question)
|
|
56
|
+
@panel.items << question
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def table(options = {}, &block)
|
|
60
|
+
table_builder = TableBuilder.new(@panel, options.merge(questionnaire: @panel.questionnaire,
|
|
61
|
+
default_question_options: @default_question_options,
|
|
62
|
+
custom_methods: @custom_methods))
|
|
63
|
+
table_builder.instance_eval(&block) if block
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def method_missing(method_sym, *args, &block)
|
|
67
|
+
if @custom_methods.key? method_sym
|
|
68
|
+
instance_exec(*args, &@custom_methods[method_sym])
|
|
69
|
+
else
|
|
70
|
+
super
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
75
|
+
@custom_methods.key?(method_name) || super
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'quby/compiler/entities'
|
|
4
|
+
|
|
5
|
+
require 'quby/compiler/dsl/questions/base'
|
|
6
|
+
require 'quby/compiler/dsl/questions/checkbox_question_builder'
|
|
7
|
+
require 'quby/compiler/dsl/questions/date_question_builder'
|
|
8
|
+
require 'quby/compiler/dsl/questions/deprecated_question_builder'
|
|
9
|
+
require 'quby/compiler/dsl/questions/float_question_builder'
|
|
10
|
+
require 'quby/compiler/dsl/questions/integer_question_builder'
|
|
11
|
+
require 'quby/compiler/dsl/questions/radio_question_builder'
|
|
12
|
+
require 'quby/compiler/dsl/questions/select_question_builder'
|
|
13
|
+
require 'quby/compiler/dsl/questions/string_question_builder'
|
|
14
|
+
require 'quby/compiler/dsl/questions/text_question_builder'
|
|
15
|
+
|
|
16
|
+
module Quby
|
|
17
|
+
module Compiler
|
|
18
|
+
module DSL
|
|
19
|
+
module QuestionBuilder
|
|
20
|
+
include Helpers
|
|
21
|
+
BUILDERS = {
|
|
22
|
+
'string' => Questions::StringQuestionBuilder,
|
|
23
|
+
'textarea' => Questions::TextQuestionBuilder,
|
|
24
|
+
'integer' => Questions::IntegerQuestionBuilder,
|
|
25
|
+
'float' => Questions::FloatQuestionBuilder,
|
|
26
|
+
'radio' => Questions::RadioQuestionBuilder,
|
|
27
|
+
'scale' => Questions::RadioQuestionBuilder,
|
|
28
|
+
'select' => Questions::SelectQuestionBuilder,
|
|
29
|
+
'check_box' => Questions::CheckboxQuestionBuilder,
|
|
30
|
+
'date' => Questions::DateQuestionBuilder,
|
|
31
|
+
'hidden' => Questions::DeprecatedQuestionBuilder
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
def self.build(key, options = {}, &block)
|
|
35
|
+
BUILDERS.fetch(options.fetch(:type).to_s).build(key, options, &block)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'quby/compiler/dsl/panel_builder'
|
|
4
|
+
require 'quby/compiler/dsl/table_builder'
|
|
5
|
+
require 'quby/compiler/dsl/question_builder'
|
|
6
|
+
require 'quby/compiler/dsl/score_builder'
|
|
7
|
+
require 'quby/compiler/dsl/charting/line_chart_builder'
|
|
8
|
+
require 'quby/compiler/dsl/charting/radar_chart_builder'
|
|
9
|
+
require 'quby/compiler/dsl/charting/bar_chart_builder'
|
|
10
|
+
require 'quby/compiler/dsl/charting/overview_chart_builder'
|
|
11
|
+
|
|
12
|
+
require_relative 'standardized_panel_generators'
|
|
13
|
+
|
|
14
|
+
module Quby
|
|
15
|
+
module Compiler
|
|
16
|
+
module DSL
|
|
17
|
+
class QuestionnaireBuilder
|
|
18
|
+
prepend CallsCustomMethods
|
|
19
|
+
include StandardizedPanelGenerators
|
|
20
|
+
include Helpers
|
|
21
|
+
|
|
22
|
+
def initialize(target_instance, lookup_tables: nil)
|
|
23
|
+
@questionnaire = target_instance
|
|
24
|
+
@lookup_tables = lookup_tables
|
|
25
|
+
@default_question_options = {}
|
|
26
|
+
@custom_methods = {}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def leave_page_alert(text)
|
|
30
|
+
@questionnaire.leave_page_alert = text
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def key(key)
|
|
34
|
+
# no-op, key is now passed in to Questionnaire constructor
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def roqua_keys(*keys)
|
|
38
|
+
@questionnaire.roqua_keys = keys
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def seeds_patch(value)
|
|
42
|
+
@questionnaire.seeds_patch = value
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def do_not_check_key_clashes
|
|
46
|
+
@questionnaire.check_key_clashes = false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def do_not_check_score_keys_consistency
|
|
50
|
+
@questionnaire.check_score_keys_consistency = false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def do_not_validate_html
|
|
54
|
+
@questionnaire.validate_html = false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def title(title)
|
|
58
|
+
@questionnaire.title = title
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def description(description)
|
|
62
|
+
@questionnaire.description = description
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def outcome_description(description)
|
|
66
|
+
@questionnaire.outcome_description = description
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def short_description(description)
|
|
70
|
+
@questionnaire.short_description = description
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def sbg_key(sbg_key)
|
|
74
|
+
@questionnaire.sbg_key = sbg_key
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def sbg_domain(sbg_code, outcome:, from: nil, till: nil, sbg_key: nil, primary: false)
|
|
78
|
+
@questionnaire.sbg_domains << {
|
|
79
|
+
sbg_code: sbg_code,
|
|
80
|
+
from: from,
|
|
81
|
+
till: till,
|
|
82
|
+
outcome: outcome,
|
|
83
|
+
sbg_key: sbg_key || @questionnaire.sbg_key,
|
|
84
|
+
primary: primary
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def abortable
|
|
89
|
+
@questionnaire.abortable = true
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def allow_hotkeys(type = :all)
|
|
93
|
+
@questionnaire.allow_hotkeys = type
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def license(type, licensor: nil)
|
|
97
|
+
@questionnaire.license = type
|
|
98
|
+
@questionnaire.licensor = licensor
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def language(language)
|
|
102
|
+
@questionnaire.language = language
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def respondent_types(*respondent_types)
|
|
106
|
+
@questionnaire.respondent_types = respondent_types
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def tags(*tags)
|
|
110
|
+
@questionnaire.tags = tags
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def version(number, release_notes:, regenerate_outcome: false, deactivate_answers: false)
|
|
114
|
+
@questionnaire.versions << Entities::Version.new(
|
|
115
|
+
number: number,
|
|
116
|
+
release_notes: release_notes,
|
|
117
|
+
regenerate_outcome: regenerate_outcome,
|
|
118
|
+
deactivate_answers: deactivate_answers
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def outcome_regeneration_requested_at(timestamp)
|
|
123
|
+
@questionnaire.outcome_regeneration_requested_at = timestamp
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def deactivate_answers_requested_at(timestamp)
|
|
127
|
+
@questionnaire.deactivate_answers_requested_at = timestamp
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def enable_previous_questionnaire_button
|
|
131
|
+
@questionnaire.enable_previous_questionnaire_button = true
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def renderer_version(version)
|
|
135
|
+
@questionnaire.renderer_version = version
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def css(value)
|
|
139
|
+
@questionnaire.extra_css += value
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def allow_switch_to_bulk(value=true)
|
|
143
|
+
@questionnaire.allow_switch_to_bulk = value
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def default_answer_value(value)
|
|
147
|
+
@questionnaire.default_answer_value = value
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def panel(title = nil, options = {}, &block)
|
|
151
|
+
panel = PanelBuilder.build(title, options.merge(default_panel_options), &block)
|
|
152
|
+
@questionnaire.add_panel(panel)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def custom_method(key, &block)
|
|
156
|
+
if PanelBuilder.new(nil, custom_methods: @custom_methods).respond_to? key
|
|
157
|
+
fail 'Custom method trying to override existing method'
|
|
158
|
+
end
|
|
159
|
+
@custom_methods[key] = block
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def import_lookup_tree(key)
|
|
163
|
+
@questionnaire.lookup_tables[key] = @lookup_tables.fetch(key)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def add_lookup_tree(key, levels:, tree:)
|
|
167
|
+
@questionnaire.lookup_tables[key] = {levels: levels, tree: tree}
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def default_question_options(options = {})
|
|
171
|
+
@default_question_options.merge!(options)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Short-circuit the question command to perform an implicit panel
|
|
175
|
+
def question(key, options = {}, &block)
|
|
176
|
+
panel(nil, default_panel_options) do
|
|
177
|
+
question(key, @default_question_options.merge(options).merge(questionnaire: @questionnaire), &block)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Short-circuit the text command to perform an implicit panel
|
|
182
|
+
def text(value, options = {})
|
|
183
|
+
panel(nil, default_panel_options) do
|
|
184
|
+
text(value, options)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Short-circuit the table command to perform an implicit panel
|
|
189
|
+
def table(options = {}, &block)
|
|
190
|
+
panel(nil, default_panel_options) do
|
|
191
|
+
table(options, &block)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# variable :totaal do
|
|
196
|
+
# # Plain old Ruby code here, executed in the scope of the answer
|
|
197
|
+
# # variables are private to the score calculation
|
|
198
|
+
# q01 + q02 + q03
|
|
199
|
+
# end
|
|
200
|
+
def variable(key, options = {}, &block)
|
|
201
|
+
@questionnaire.add_score_calculation ScoreBuilder.new(key, options, &block).build
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def score(key, options = {}, &block)
|
|
205
|
+
@questionnaire.errors.add "Score #{key}", 'misses label in score call' if options[:label].blank?
|
|
206
|
+
schema = options.delete(:schema)
|
|
207
|
+
score_schema(key, options[:label], schema) if schema.present?
|
|
208
|
+
variable(key, options.reverse_merge(score: true), &block)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def score_schema(key, label, options = nil, &block)
|
|
212
|
+
if block
|
|
213
|
+
schema, calculations = ScoreSchemaBuilder.build(key: key, label: label, &block)
|
|
214
|
+
@questionnaire.add_score_schema schema
|
|
215
|
+
calculations.each do |calculation|
|
|
216
|
+
@questionnaire.add_score_calculation calculation
|
|
217
|
+
end
|
|
218
|
+
else
|
|
219
|
+
@questionnaire.add_score_schema Entities::ScoreSchema.new key: key,
|
|
220
|
+
label: label,
|
|
221
|
+
subscore_schemas: options
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def attention(options = {}, &block)
|
|
226
|
+
variable(:attention, options.reverse_merge(action: true), &block)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def alarm(options = {}, &block)
|
|
230
|
+
variable(:alarm, options.reverse_merge(action: true), &block)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def completion(options = {}, &block)
|
|
234
|
+
variable(:completion, options.reverse_merge(completion: true), &block)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def overview_chart(*args, &block)
|
|
238
|
+
raise "Cannot define more than one overview chart" if @questionnaire.charts.overview.present?
|
|
239
|
+
builder = OverviewChartBuilder.new(@questionnaire, *args)
|
|
240
|
+
@questionnaire.charts.overview = builder.build(&block)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def line_chart(*args, &block)
|
|
244
|
+
builder = LineChartBuilder.new(@questionnaire, *args)
|
|
245
|
+
@questionnaire.add_chart(builder.build(&block))
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def bar_chart(*args, &block)
|
|
249
|
+
builder = BarChartBuilder.new(@questionnaire, *args)
|
|
250
|
+
@questionnaire.add_chart(builder.build(&block))
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def radar_chart(*args, &block)
|
|
254
|
+
builder = RadarChartBuilder.new(@questionnaire, *args)
|
|
255
|
+
@questionnaire.add_chart(builder.build(&block))
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def flag(flag_options)
|
|
259
|
+
@questionnaire.add_flag flag_options
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def textvar(textvar_options)
|
|
263
|
+
@questionnaire.add_textvar textvar_options
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def outcome_table(table_options)
|
|
267
|
+
@questionnaire.add_outcome_table table_options
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
private
|
|
271
|
+
|
|
272
|
+
def default_panel_options
|
|
273
|
+
{questionnaire: @questionnaire, default_question_options: @default_question_options,
|
|
274
|
+
custom_methods: @custom_methods}
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|