eco-helpers 3.2.16 → 3.2.17
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 +4 -4
- data/CHANGELOG.md +117 -0
- data/eco-helpers.gemspec +2 -2
- data/lib/eco/api/usecases/graphql/compat/parity/comparison.rb +70 -0
- data/lib/eco/api/usecases/graphql/compat/parity/harness.rb +102 -0
- data/lib/eco/api/usecases/graphql/compat/parity/run_result.rb +96 -0
- data/lib/eco/api/usecases/graphql/compat.rb +5 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/end_points/optimizations.rb +4 -4
- data/lib/eco/api/usecases/graphql/helpers/pages/copying.rb +71 -0
- data/lib/eco/api/usecases/graphql/helpers/pages/creatable.rb +78 -0
- data/lib/eco/api/usecases/graphql/helpers/pages/filters.rb +114 -0
- data/lib/eco/api/usecases/graphql/helpers/pages/ooze_handlers.rb +112 -0
- data/lib/eco/api/usecases/graphql/helpers/pages/rescuable.rb +52 -0
- data/lib/eco/api/usecases/graphql/helpers/pages/shortcuts.rb +186 -0
- data/lib/eco/api/usecases/graphql/helpers/pages/typed_fields_pairing.rb +303 -0
- data/lib/eco/api/usecases/graphql/helpers/pages.rb +21 -0
- data/lib/eco/api/usecases/graphql/helpers.rb +1 -0
- data/lib/eco/api/usecases/graphql/samples/location/command/service/tree_update.rb +1 -1
- data/lib/eco/api/usecases/graphql/samples/pages/register/base.rb +181 -0
- data/lib/eco/api/usecases/graphql/samples/pages/register/migration_case.rb +132 -0
- data/lib/eco/api/usecases/graphql/samples/pages/register/target_oozes_update_case.rb +163 -0
- data/lib/eco/api/usecases/graphql/samples/pages/register.rb +8 -0
- data/lib/eco/api/usecases/graphql/samples/pages/template/base.rb +70 -0
- data/lib/eco/api/usecases/graphql/samples/pages/template/command_emitter.rb +139 -0
- data/lib/eco/api/usecases/graphql/samples/pages/template/csv_build/builder.rb +126 -0
- data/lib/eco/api/usecases/graphql/samples/pages/template/csv_build/format_map.rb +108 -0
- data/lib/eco/api/usecases/graphql/samples/pages/template/csv_build/parser.rb +98 -0
- data/lib/eco/api/usecases/graphql/samples/pages/template/csv_build.rb +17 -0
- data/lib/eco/api/usecases/graphql/samples/pages/template/deploy/applier.rb +141 -0
- data/lib/eco/api/usecases/graphql/samples/pages/template/deploy/drift_report.rb +104 -0
- data/lib/eco/api/usecases/graphql/samples/pages/template/deploy/loop.rb +155 -0
- data/lib/eco/api/usecases/graphql/samples/pages/template/deploy/recording_executor.rb +58 -0
- data/lib/eco/api/usecases/graphql/samples/pages/template/deploy/sync_readiness.rb +178 -0
- data/lib/eco/api/usecases/graphql/samples/pages/template/deploy/verifier.rb +141 -0
- data/lib/eco/api/usecases/graphql/samples/pages/template/deploy.rb +21 -0
- data/lib/eco/api/usecases/graphql/samples/pages/template.rb +11 -0
- data/lib/eco/api/usecases/graphql/samples/pages.rb +2 -0
- data/lib/eco/version.rb +1 -1
- metadata +34 -5
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# rubocop:disable Lint/SymbolConversion, Style/QuotedSymbols
|
|
2
|
+
module Eco::API::UseCases::GraphQL::Helpers
|
|
3
|
+
module Pages
|
|
4
|
+
# Search-filter + time-math helpers, ported from OozeSamples::Helpers::Filters.
|
|
5
|
+
#
|
|
6
|
+
# The time/date/tag builders are pure and ported verbatim. #field_key_name's field branch
|
|
7
|
+
# is re-expressed with duck-typing (was a `V2::Page::Component` case) — see the note there.
|
|
8
|
+
# A `weekday` helper is added because the original relied on one defined outside Filters
|
|
9
|
+
# (undefined in lib/), which made the sunday/monday chain latent-broken.
|
|
10
|
+
module Filters
|
|
11
|
+
FILTER_TIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ'.freeze
|
|
12
|
+
|
|
13
|
+
def tags_filter(tags, any: true, negate: false)
|
|
14
|
+
tags = [tags].flatten.compact
|
|
15
|
+
return nil if tags.empty?
|
|
16
|
+
key, name = field_key_name(:tags)
|
|
17
|
+
{
|
|
18
|
+
"tags": tags,
|
|
19
|
+
"mode": any ? "any" : "all",
|
|
20
|
+
"negate": negate,
|
|
21
|
+
"key": key,
|
|
22
|
+
"name": name,
|
|
23
|
+
"type": "tag_filter"
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def date_range_filter(from: nil, to: nil, key: :updated_at)
|
|
28
|
+
return nil unless from || to
|
|
29
|
+
key, name = field_key_name(key)
|
|
30
|
+
{
|
|
31
|
+
"relstart": "today",
|
|
32
|
+
"time_zone": "Pacific/Auckland",
|
|
33
|
+
"relative": false,
|
|
34
|
+
"key": key,
|
|
35
|
+
"name": name,
|
|
36
|
+
"type": "date_filter"
|
|
37
|
+
}.tap do |out|
|
|
38
|
+
out.merge!("lbound": to_date_filter(from)) if from
|
|
39
|
+
out.merge!("ubound": to_date_filter(to)) if to
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def to_date_filter(date)
|
|
44
|
+
daystart(date).utc.strftime(FILTER_TIME_FORMAT)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def set_time(date, hour, min, sec)
|
|
48
|
+
Time.new(date.year, date.month, date.day, hour, min, sec)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def weeks(num)
|
|
52
|
+
num * days(7)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def days(num)
|
|
56
|
+
num * 60 * 60 * 24
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def today
|
|
60
|
+
Date.today.to_time
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# 0 = Sunday .. 6 = Saturday (Date#wday). Added: the original relied on a `weekday`
|
|
64
|
+
# helper defined outside Filters (not present in lib/), leaving the chain below broken.
|
|
65
|
+
def weekday(date)
|
|
66
|
+
(date.respond_to?(:to_date) ? date.to_date : date).wday
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def sunday(date)
|
|
70
|
+
date + days(7 - weekday(date))
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def midnight(date)
|
|
74
|
+
set_time(date, 23, 59, 59)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def daystart(date)
|
|
78
|
+
set_time(date, 0, 0, 0)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def previous_sunday(date)
|
|
82
|
+
midnight(sunday(date - weeks(1)))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def this_monday(date)
|
|
86
|
+
set_time(previous_sunday(date) + days(1), 0, 0, 0)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Resolve a filter [key, name] pair for the register search.
|
|
90
|
+
#
|
|
91
|
+
# @note **Search-key gotcha:** the register search matches on the field DEFINITION key,
|
|
92
|
+
# not the field's name/label. For a data-field object we therefore prefer its backend
|
|
93
|
+
# reference key. The exact GraphQL accessor is duck-typed here (ref_backend -> key) and
|
|
94
|
+
# must be confirmed against the live field model before relying on field-scoped search
|
|
95
|
+
# filters (a wrong key silently matches 0 rows). See the CANS search-key note.
|
|
96
|
+
def field_key_name(value)
|
|
97
|
+
case value
|
|
98
|
+
when :updated_at then ["updated_at", "Last updated"]
|
|
99
|
+
when :created_at then ["created_at", "Page created"]
|
|
100
|
+
when :tags then ["tags", "Location Tags"]
|
|
101
|
+
else
|
|
102
|
+
if value.respond_to?(:ref_backend)
|
|
103
|
+
[value.ref_backend, (value.respond_to?(:label) ? value.label : nil)]
|
|
104
|
+
elsif value.respond_to?(:key)
|
|
105
|
+
[value.key, (value.respond_to?(:label) ? value.label : nil)]
|
|
106
|
+
else
|
|
107
|
+
[nil, nil]
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
# rubocop:enable Lint/SymbolConversion, Style/QuotedSymbols
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
module Eco::API::UseCases::GraphQL::Helpers
|
|
2
|
+
module Pages
|
|
3
|
+
# Native GraphQL re-expression of OozeSamples::Helpers::OozeHandlers.
|
|
4
|
+
#
|
|
5
|
+
# `merge_values` combines two values of a data field of the *same* type and
|
|
6
|
+
# label into one, matching the v2 semantics but dispatching on the GraphQL
|
|
7
|
+
# DataField TYPE STRING (e.g. 'PlainText', 'Select', 'Date') rather than on
|
|
8
|
+
# `Ecoportal::API::V2::Page::Component::*` classes. The type strings are the
|
|
9
|
+
# `__typename`-derived values in
|
|
10
|
+
# `Ecoportal::API::GraphQL::Base::Page::DataField::TYPE_MAP`.
|
|
11
|
+
#
|
|
12
|
+
# `merge_arrays` / `array_indexes` are pure array utilities — ported verbatim.
|
|
13
|
+
module OozeHandlers
|
|
14
|
+
# Per-type merge strategy.
|
|
15
|
+
# :join → concatenate origin + append with a delimiter (text, select,
|
|
16
|
+
# reference, people, checklist, action list, files, images, geo, law)
|
|
17
|
+
# :origin → keep the origin value untouched (date, number, gauge — no sensible
|
|
18
|
+
# textual concatenation; the v2 case returned `origin` for these)
|
|
19
|
+
#
|
|
20
|
+
# Text-ish types always merge with a NEWLINE delimiter in v2 (RichText /
|
|
21
|
+
# PlainText) regardless of the caller's `delimiter:`; the rest honour it.
|
|
22
|
+
MERGE_STRATEGY = {
|
|
23
|
+
'PlainText' => %i[join newline],
|
|
24
|
+
'RichText' => %i[join newline],
|
|
25
|
+
'Select' => %i[join delimiter],
|
|
26
|
+
'CrossReference' => %i[join delimiter],
|
|
27
|
+
'People' => %i[join delimiter],
|
|
28
|
+
'Checklist' => %i[join delimiter],
|
|
29
|
+
'ActionsList' => %i[join delimiter],
|
|
30
|
+
'FileField' => %i[join delimiter],
|
|
31
|
+
'File' => %i[join delimiter],
|
|
32
|
+
'ImageGallery' => %i[join delimiter],
|
|
33
|
+
'Geo' => %i[join delimiter],
|
|
34
|
+
'Law' => %i[join delimiter],
|
|
35
|
+
'DateField' => %i[origin],
|
|
36
|
+
'Date' => %i[origin],
|
|
37
|
+
'Number' => %i[origin],
|
|
38
|
+
'Gauge' => %i[origin]
|
|
39
|
+
}.freeze
|
|
40
|
+
|
|
41
|
+
# Merge values of fields of the same type and label name.
|
|
42
|
+
#
|
|
43
|
+
# @param origin [Object] the value to keep as the base.
|
|
44
|
+
# @param append [Object] the value to merge onto `origin`.
|
|
45
|
+
# @param type [String, Symbol, nil] the GraphQL DataField type string
|
|
46
|
+
# (e.g. 'PlainText', 'Select'). Nil / unknown → default join. Accepts a
|
|
47
|
+
# field object responding to #type too (convenience).
|
|
48
|
+
# @param delimiter [String] the delimiter for :delimiter-strategy types.
|
|
49
|
+
# @return [Object] the merged value.
|
|
50
|
+
def merge_values(origin, append, type: nil, delimiter: "\n")
|
|
51
|
+
return origin if !append || append.to_s.strip.empty?
|
|
52
|
+
return append if !origin || origin.to_s.strip.empty?
|
|
53
|
+
|
|
54
|
+
type_key = resolve_field_type(type)
|
|
55
|
+
strategy, delim_kind = MERGE_STRATEGY.fetch(type_key, %i[join delimiter])
|
|
56
|
+
|
|
57
|
+
return origin if strategy == :origin
|
|
58
|
+
|
|
59
|
+
used_delimiter = delim_kind == :newline ? "\n" : delimiter
|
|
60
|
+
[origin, append].join(used_delimiter)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Merges arrays `ref` and `plus` by keeping the order of `ref`
|
|
64
|
+
# but inserting additional elements of `plus` in the correct position
|
|
65
|
+
# (i.e. when those elements are between two elements that exist in `ref`).
|
|
66
|
+
# @note this can be used to combine two different header sequences
|
|
67
|
+
# by using `ref` as a reference.
|
|
68
|
+
def merge_arrays(ref, plus) # rubocop:disable Metrics/AbcSize
|
|
69
|
+
return ref if ref == plus
|
|
70
|
+
|
|
71
|
+
href_idx = array_indexes(ref)
|
|
72
|
+
hplus_idx = array_indexes(plus)
|
|
73
|
+
shared = ref & plus
|
|
74
|
+
hshared_ref_idx = href_idx.slice(*shared)
|
|
75
|
+
|
|
76
|
+
shared_plus_idxs = hplus_idx.values_at(*shared).sort.reverse
|
|
77
|
+
|
|
78
|
+
only_plus = plus - ref
|
|
79
|
+
honly_plus_idx = hplus_idx.slice(*only_plus)
|
|
80
|
+
|
|
81
|
+
hplus_ref_idx = honly_plus_idx.each_with_object({}) do |(e, idx), href_idx_plus|
|
|
82
|
+
ps_idx = shared_plus_idxs.bsearch {|i| idx >= i}
|
|
83
|
+
rs_idx = ps_idx ? hshared_ref_idx[plus[ps_idx]] : -1
|
|
84
|
+
href_idx_plus[e] = rs_idx
|
|
85
|
+
end.group_by do |_e, idx|
|
|
86
|
+
idx
|
|
87
|
+
end.to_a.sort_by do |(idx, _pairs)|
|
|
88
|
+
idx
|
|
89
|
+
end.reverse.to_h.transform_values do |pairs|
|
|
90
|
+
pairs.map(&:first)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
hplus_ref_idx.each_with_object(ref.dup) do |(idx, elements), rf|
|
|
94
|
+
rf.insert(idx + 1, *elements)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def array_indexes(ary)
|
|
99
|
+
ary.each_with_index.to_h
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
# Accept either a type string/symbol or a field object that responds to #type.
|
|
105
|
+
def resolve_field_type(type)
|
|
106
|
+
return type.type.to_s if type.respond_to?(:type)
|
|
107
|
+
|
|
108
|
+
type.to_s
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Eco::API::UseCases::GraphQL::Helpers
|
|
2
|
+
module Pages
|
|
3
|
+
# Prevents a script from stopping on a per-item error, prompting the operator to
|
|
4
|
+
# continue or abort. Ported from OozeSamples::Helpers::Rescuable; the only change is
|
|
5
|
+
# the base-type guard, which now requires a native GraphQL::Base case.
|
|
6
|
+
module Rescuable
|
|
7
|
+
module InstanceMethods
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
# Helper to prevent script from stopping
|
|
11
|
+
def with_rescue(reference, trace_count: 1)
|
|
12
|
+
raise ArgumentError, 'Expecting block, but not given' unless block_given?
|
|
13
|
+
yield
|
|
14
|
+
rescue StandardError => e
|
|
15
|
+
str_err = e.patch_full_message(trace_count: trace_count)
|
|
16
|
+
log(:error) { [reference, str_err].join(" => \n") }
|
|
17
|
+
lines = []
|
|
18
|
+
lines << "\nThere was an error. Choose one option:\n"
|
|
19
|
+
lines << ' (C) - Continue/resume. Just ignore this one.'
|
|
20
|
+
lines << ' (A) - Abort. Just break this run.'
|
|
21
|
+
|
|
22
|
+
session.prompt_user('Type one option (C/a):', explanation: lines.join("\n"), default: 'C') do |res|
|
|
23
|
+
res = res.upcase
|
|
24
|
+
raise if res.start_with?('A')
|
|
25
|
+
|
|
26
|
+
# 'C'
|
|
27
|
+
log(:warn) { "Script resumed after error...\n * #{reference}" }
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class << self
|
|
34
|
+
def included(base)
|
|
35
|
+
super
|
|
36
|
+
validate_base_type!(base)
|
|
37
|
+
base.include(InstanceMethods)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def validate_base_type!(base)
|
|
41
|
+
return super if defined?(super)
|
|
42
|
+
|
|
43
|
+
msg = "#{self} can only be included in Eco::API::UseCases::GraphQL::Base"
|
|
44
|
+
msg << "\nCan't be included in #{base}"
|
|
45
|
+
raise LoadError, msg unless base <= Eco::API::UseCases::GraphQL::Base
|
|
46
|
+
|
|
47
|
+
true
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
module Eco::API::UseCases::GraphQL::Helpers
|
|
2
|
+
module Pages
|
|
3
|
+
# String simplification / comparison helpers, ported from
|
|
4
|
+
# OozeSamples::Helpers::Shortcuts. Everything here is pure except #object_reference,
|
|
5
|
+
# which is re-expressed with duck-typing (no Ecoportal::API::V2 constants) so it
|
|
6
|
+
# describes GraphQL page/stage/section/field/force models.
|
|
7
|
+
module Shortcuts
|
|
8
|
+
# Basic simplification pattern (matches all but a-z and blank space)
|
|
9
|
+
def non_letters_regex
|
|
10
|
+
@non_letters_regex ||= /[^a-z ]+/
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Matches anything between two consecutive (), inclusive
|
|
14
|
+
def bracked_regex
|
|
15
|
+
@bracked_regex ||= /(?<bracked>\([^\)]+?\))/ # rubocop:disable Style/RedundantRegexpEscape
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# It always downcase, trim and remove double spaces.
|
|
19
|
+
# @param ignore [Boolean, Regexp, String, Array] ignored when `exact` is `true`
|
|
20
|
+
# * when `false`: it does not do anything additional
|
|
21
|
+
# * when `Regexp`: it removes all characters that match the expression.
|
|
22
|
+
# * when `String`: each character listed is removed.
|
|
23
|
+
# * when `Array`: reduces `str` processing `ignore` in order.
|
|
24
|
+
# * when `true` (or otherwise): it removes all non a-zA-Z characters but blank spaces.
|
|
25
|
+
def simplify_string(str, ignore: false)
|
|
26
|
+
str = str.to_s.strip.downcase.gsub(/\s+/, ' ')
|
|
27
|
+
return str unless ignore
|
|
28
|
+
|
|
29
|
+
sub = non_letters_regex
|
|
30
|
+
case ignore
|
|
31
|
+
when Regexp then sub = ignore
|
|
32
|
+
when String then sub = /[#{ignore}]+/
|
|
33
|
+
when Array
|
|
34
|
+
return ignore.reduce(str) do |out, sub_ignore|
|
|
35
|
+
simplify_string(out, ignore: sub_ignore)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
str.gsub(sub, '').gsub(/\s+/, ' ').strip
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Offers multiple simplification methods to compare two strings
|
|
42
|
+
# @note only one of the values can be a Regexp
|
|
43
|
+
def same_string?(value_1, value_2, exact: false, mild: false, ignore: false) # rubocop:disable Metrics/AbcSize
|
|
44
|
+
return true if value_1.to_s.strip.empty? && value_2.to_s.strip.empty?
|
|
45
|
+
return false if value_1.to_s.strip.empty? || value_2.to_s.strip.empty?
|
|
46
|
+
|
|
47
|
+
val_1 = value_1
|
|
48
|
+
val_2 = value_2
|
|
49
|
+
|
|
50
|
+
unless exact
|
|
51
|
+
if val_1.is_a?(String)
|
|
52
|
+
val_1 = simplify_string(val_1, ignore: ignore) if ignore
|
|
53
|
+
val_1 = simplify_string(val_1, ignore: non_letters_regex) if mild
|
|
54
|
+
end
|
|
55
|
+
if val_2.is_a?(String)
|
|
56
|
+
val_2 = simplify_string(val_2, ignore: ignore) if ignore
|
|
57
|
+
val_2 = simplify_string(val_2, ignore: non_letters_regex) if mild
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if val_1.is_a?(String) && val_2.is_a?(String)
|
|
62
|
+
val_1 == val_2
|
|
63
|
+
elsif val_1.is_a?(Regexp) && val_2.is_a?(String)
|
|
64
|
+
val_2 =~ val_1
|
|
65
|
+
elsif val_1.is_a?(String) && val_2.is_a?(Regexp)
|
|
66
|
+
val_1 =~ val_2
|
|
67
|
+
else
|
|
68
|
+
msg = 'Expected at least one String, and either a String or Regex. '
|
|
69
|
+
msg << "Given: (1: #{val_1.class}) and (2: #{val_2.class})"
|
|
70
|
+
raise ArgumentError, msg
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def titleize(str)
|
|
75
|
+
return nil unless str
|
|
76
|
+
return str if str.strip.empty?
|
|
77
|
+
|
|
78
|
+
str.split(/\s+/).map do |part|
|
|
79
|
+
part[0] = part[0].upcase
|
|
80
|
+
part[1..] = part[1..].downcase
|
|
81
|
+
part
|
|
82
|
+
end.join(' ')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def normalize_string(str)
|
|
86
|
+
return nil unless str
|
|
87
|
+
|
|
88
|
+
str.gsub(/[^[:print:]]/, '').
|
|
89
|
+
gsub(/[[:space:]]+/, ' ').
|
|
90
|
+
gsub(/[[:space:]]$/, '').
|
|
91
|
+
gsub(/[-‑‒–]/, '-').then do |aux|
|
|
92
|
+
aux = yield(aux) if block_given?
|
|
93
|
+
aux
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def clean_question(str)
|
|
98
|
+
return nil unless str
|
|
99
|
+
|
|
100
|
+
normalize_string(str) do |x|
|
|
101
|
+
x.gsub("\r\n", ' ').then do |aux|
|
|
102
|
+
aux = yield(aux) if block_given?
|
|
103
|
+
aux
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Human-readable reference for a GraphQL page-model object, for logs/errors.
|
|
109
|
+
#
|
|
110
|
+
# Re-expressed with duck-typing (was a V2 `case/when` on Page/Stage/Section/Component/
|
|
111
|
+
# Force/Binding). We inspect the object's shape rather than its class so this works
|
|
112
|
+
# across Base::/Model:: GraphQL types without coupling to constants that may move.
|
|
113
|
+
def object_reference(obj)
|
|
114
|
+
return 'No reference' unless obj
|
|
115
|
+
|
|
116
|
+
if binding_like?(obj)
|
|
117
|
+
"Binding '#{safe_name(obj)}' in #{object_reference(obj.force)}"
|
|
118
|
+
elsif force_like?(obj)
|
|
119
|
+
"Force '#{safe_name(obj)}'"
|
|
120
|
+
elsif component_like?(obj)
|
|
121
|
+
label = obj.respond_to?(:label) ? obj.label : nil
|
|
122
|
+
type = obj.respond_to?(:type) ? obj.type : nil
|
|
123
|
+
ref = "Component '#{label || "(unnamed of type '#{type}')"}'"
|
|
124
|
+
ref += " in #{object_reference(obj.section)}" if obj.respond_to?(:section) && obj.section
|
|
125
|
+
ref
|
|
126
|
+
elsif section_like?(obj)
|
|
127
|
+
heading = obj.respond_to?(:heading) ? obj.heading : nil
|
|
128
|
+
"Section '#{heading || '(unnamed)'}'"
|
|
129
|
+
elsif stage_like?(obj)
|
|
130
|
+
named_ref('Stage', obj)
|
|
131
|
+
elsif page_like?(obj)
|
|
132
|
+
named_ref("Page (#{obj.id})", obj)
|
|
133
|
+
else
|
|
134
|
+
named_ref('', obj).strip
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def to_i(value)
|
|
139
|
+
Float(value).to_i
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# https://stackoverflow.com/a/5661695/4352306
|
|
143
|
+
# Kept as `is_number?` for naming parity with OozeSamples::Helpers::Shortcuts.
|
|
144
|
+
def is_number?(value) # rubocop:disable Naming/PredicatePrefix
|
|
145
|
+
true if Float(value)
|
|
146
|
+
rescue ArgumentError, TypeError
|
|
147
|
+
false
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
private
|
|
151
|
+
|
|
152
|
+
def binding_like?(obj)
|
|
153
|
+
obj.respond_to?(:force) && obj.respond_to?(:name) && !obj.respond_to?(:bindings)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def force_like?(obj)
|
|
157
|
+
obj.respond_to?(:bindings) && obj.respond_to?(:name)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def component_like?(obj)
|
|
161
|
+
obj.respond_to?(:label) && (obj.respond_to?(:type) || obj.respond_to?(:section))
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def section_like?(obj)
|
|
165
|
+
obj.respond_to?(:heading) && obj.respond_to?(:components)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def stage_like?(obj)
|
|
169
|
+
obj.respond_to?(:ordering) && obj.respond_to?(:state)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def page_like?(obj)
|
|
173
|
+
obj.respond_to?(:id) && obj.respond_to?(:stages)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def safe_name(obj)
|
|
177
|
+
obj.respond_to?(:name) ? obj.name : nil
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def named_ref(prefix, obj)
|
|
181
|
+
name = safe_name(obj)
|
|
182
|
+
name ? "#{prefix} '#{name}'" : prefix
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|