inferno_core 1.2.1 → 1.3.0
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/lib/inferno/apps/cli/execute_script.rb +1 -0
- data/lib/inferno/apps/cli/main.rb +5 -0
- data/lib/inferno/apps/cli/session/session_compare.rb +88 -17
- data/lib/inferno/apps/web/serializers/input.rb +1 -0
- data/lib/inferno/config/application.rb +1 -0
- data/lib/inferno/dsl/input_output_handling.rb +4 -0
- data/lib/inferno/entities/input.rb +31 -6
- data/lib/inferno/public/bundle.js +13 -13
- data/lib/inferno/version.rb +1 -1
- metadata +12 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e3ab1df7a5983dabeaaac0fdfa75d5235815b48841dc94db70c55438f91c438d
|
|
4
|
+
data.tar.gz: 30da5ad7c4c0b379b91c49197b9d458fdfa912dbd5058b75793d46ebf87be46a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4397d0025d25280991e60ffe4c2ff1b660b64ebcb067f5988b1edbafd4b51d06c07639508bf1c9590718ae597ae6f9e4e1586348a83a84e6baff86de18e0fba9
|
|
7
|
+
data.tar.gz: c2c18fef0b14842503aba9367c7faec52f5c68bbda66694ddf22a99fd927eabcd53503d1b724430534f3f2ab06d662be3dcd1496822c14b49463a440a23d3516
|
|
@@ -909,6 +909,7 @@ module Inferno
|
|
|
909
909
|
expected_results_file: expected_file,
|
|
910
910
|
compare_messages: options[:compare_messages],
|
|
911
911
|
compare_result_message: options[:compare_result_message],
|
|
912
|
+
only_different_messages: options[:only_different_messages],
|
|
912
913
|
inferno_base_url: options[:inferno_base_url],
|
|
913
914
|
normalized_strings: Array(comparison_config['normalized_strings'])
|
|
914
915
|
}
|
|
@@ -104,6 +104,11 @@ module Inferno
|
|
|
104
104
|
type: :numeric,
|
|
105
105
|
default: 120,
|
|
106
106
|
desc: 'Default seconds to wait for a matching step before timing out.'
|
|
107
|
+
option :only_different_messages,
|
|
108
|
+
aliases: ['-d'],
|
|
109
|
+
type: :boolean,
|
|
110
|
+
default: true,
|
|
111
|
+
desc: 'Only show messages that differ when comparing results.'
|
|
107
112
|
option :allow_commands,
|
|
108
113
|
type: :boolean,
|
|
109
114
|
default: false,
|
|
@@ -40,6 +40,13 @@ module Inferno
|
|
|
40
40
|
type: :array,
|
|
41
41
|
desc: 'Literal strings or regexes to normalize away before comparing ' \
|
|
42
42
|
'(URL-encoded form of literal strings will also be normalized).'
|
|
43
|
+
},
|
|
44
|
+
only_different_messages: {
|
|
45
|
+
aliases: ['-d'],
|
|
46
|
+
type: :boolean,
|
|
47
|
+
default: false,
|
|
48
|
+
desc: 'When displaying messages in CSV output, only show mismatched messages ' \
|
|
49
|
+
'(hide matching ones).'
|
|
43
50
|
}
|
|
44
51
|
}.freeze
|
|
45
52
|
def run
|
|
@@ -270,10 +277,66 @@ module Inferno
|
|
|
270
277
|
UNKNOWN_MESSAGE_TYPE_ORDER = 99
|
|
271
278
|
|
|
272
279
|
def build_message_comparisons
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
280
|
+
lcs_align(sorted_messages(expected_result), sorted_messages(actual_result))
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def lcs_align(expected, actual)
|
|
284
|
+
lcs_backtrack(lcs_matrix(expected, actual), expected, actual)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def lcs_matrix(expected, actual)
|
|
288
|
+
matrix = Array.new(expected.size + 1) { Array.new(actual.size + 1, 0) }
|
|
289
|
+
(1..expected.size).each do |expected_index|
|
|
290
|
+
(1..actual.size).each do |actual_index|
|
|
291
|
+
matrix[expected_index][actual_index] =
|
|
292
|
+
if same_message?(expected[expected_index - 1], actual[actual_index - 1])
|
|
293
|
+
matrix[expected_index - 1][actual_index - 1] + 1
|
|
294
|
+
else
|
|
295
|
+
[matrix[expected_index - 1][actual_index],
|
|
296
|
+
matrix[expected_index][actual_index - 1]].max
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
matrix
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def lcs_backtrack(matrix, expected, actual)
|
|
304
|
+
alignment = []
|
|
305
|
+
expected_index = expected.size
|
|
306
|
+
actual_index = actual.size
|
|
307
|
+
|
|
308
|
+
while expected_index.positive? || actual_index.positive?
|
|
309
|
+
step = lcs_backtrack_step(matrix, expected, actual, expected_index, actual_index)
|
|
310
|
+
alignment.unshift(step[:entry])
|
|
311
|
+
expected_index = step[:next_expected_index]
|
|
312
|
+
actual_index = step[:next_actual_index]
|
|
313
|
+
end
|
|
314
|
+
alignment
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Each step returns { entry:, next_expected_index:, next_actual_index: }.
|
|
318
|
+
# entry is { expected: msg_or_nil, actual: msg_or_nil, match: bool }.
|
|
319
|
+
def lcs_backtrack_step(matrix, expected, actual, expected_index, actual_index)
|
|
320
|
+
if expected_index.positive? &&
|
|
321
|
+
actual_index.positive? &&
|
|
322
|
+
same_message?(expected[expected_index - 1], actual[actual_index - 1])
|
|
323
|
+
|
|
324
|
+
# Items match: consume both
|
|
325
|
+
entry = { expected: expected[expected_index - 1], actual: actual[actual_index - 1], match: true }
|
|
326
|
+
{ entry:, next_expected_index: expected_index - 1, next_actual_index: actual_index - 1 }
|
|
327
|
+
elsif actual_index.positive? &&
|
|
328
|
+
(expected_index.zero? ||
|
|
329
|
+
matrix[expected_index][actual_index - 1] >= matrix[expected_index - 1][actual_index])
|
|
330
|
+
# Item in actual is "Additional": consume only actual
|
|
331
|
+
{ entry: { expected: nil, actual: actual[actual_index - 1], match: false },
|
|
332
|
+
next_expected_index: expected_index,
|
|
333
|
+
next_actual_index: actual_index - 1 }
|
|
334
|
+
else
|
|
335
|
+
# Item in expected is "Missing": consume only expected
|
|
336
|
+
{ entry: { expected: expected[expected_index - 1], actual: nil, match: false },
|
|
337
|
+
next_expected_index: expected_index - 1,
|
|
338
|
+
next_actual_index: actual_index }
|
|
339
|
+
end
|
|
277
340
|
end
|
|
278
341
|
|
|
279
342
|
def sorted_messages(result)
|
|
@@ -283,15 +346,8 @@ module Inferno
|
|
|
283
346
|
end
|
|
284
347
|
end
|
|
285
348
|
|
|
286
|
-
def messages_match?(expected_message, actual_message)
|
|
287
|
-
expected_message.present? && actual_message.present? && same_message?(expected_message, actual_message)
|
|
288
|
-
end
|
|
289
|
-
|
|
290
349
|
def same_messages?
|
|
291
|
-
|
|
292
|
-
return true unless expected_result['messages'].present?
|
|
293
|
-
|
|
294
|
-
message_comparisons.all?
|
|
350
|
+
message_comparisons.all? { |entry| entry[:match] }
|
|
295
351
|
end
|
|
296
352
|
|
|
297
353
|
def same_message?(expected_message, actual_message)
|
|
@@ -370,13 +426,28 @@ module Inferno
|
|
|
370
426
|
def format_messages_for_csv(results)
|
|
371
427
|
return '' unless results&.dig('messages').present?
|
|
372
428
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
429
|
+
is_expected = results == expected_result
|
|
430
|
+
lines = message_comparisons.filter_map { |entry| message_line_for_csv(entry, is_expected) }
|
|
431
|
+
collapse_message_lines(lines).join("\n")
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def message_line_for_csv(entry, is_expected)
|
|
435
|
+
return if options[:only_different_messages] && entry[:match]
|
|
436
|
+
|
|
437
|
+
msg = is_expected ? entry[:expected] : entry[:actual]
|
|
438
|
+
return if msg.nil?
|
|
439
|
+
|
|
440
|
+
message_text_for_csv(msg, entry[:match])
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def collapse_message_lines(lines)
|
|
444
|
+
lines.chunk_while { |a, b| a == b }.map do |group|
|
|
445
|
+
group.size > 1 ? "(#{group.size}) #{group.first}" : group.first
|
|
446
|
+
end
|
|
376
447
|
end
|
|
377
448
|
|
|
378
|
-
def message_text_for_csv(message,
|
|
379
|
-
prefix =
|
|
449
|
+
def message_text_for_csv(message, matches)
|
|
450
|
+
prefix = matches ? '- ' : '! '
|
|
380
451
|
text = normalize_string(message['message'].to_s)
|
|
381
452
|
.gsub("\r\n", '\n')
|
|
382
453
|
.gsub("\n", '\n')
|
|
@@ -16,6 +16,10 @@ module Inferno
|
|
|
16
16
|
# @option input_params [Hash] :options Possible input option formats based on input type
|
|
17
17
|
# @option options [Array] :list_options Array of options for input formats
|
|
18
18
|
# that require a list of possible values (radio and checkbox)
|
|
19
|
+
# @option input_params [Hash] :enable_when Conditions for showing the input. Must be a Hash
|
|
20
|
+
# with String :input_name (the name of the controlling input) and String :value (the value
|
|
21
|
+
# that triggers visibility). For checkbox inputs the value must be a JSON-encoded sorted
|
|
22
|
+
# array, e.g. '["a","b"]'.
|
|
19
23
|
# @return [void]
|
|
20
24
|
# @example
|
|
21
25
|
# input :patient_id, title: 'Patient ID', description: 'The ID of the patient being searched for',
|
|
@@ -15,22 +15,25 @@ module Inferno
|
|
|
15
15
|
:options,
|
|
16
16
|
:locked,
|
|
17
17
|
:hidden,
|
|
18
|
-
:value
|
|
18
|
+
:value,
|
|
19
|
+
:enable_when
|
|
19
20
|
].freeze
|
|
20
21
|
include Entities::Attributes
|
|
21
22
|
|
|
22
23
|
# These attributes require special handling when merging input
|
|
23
24
|
# definitions.
|
|
24
25
|
UNINHERITABLE_ATTRIBUTES = [
|
|
25
|
-
# Locking or
|
|
26
|
-
# Consider:
|
|
26
|
+
# Locking, hiding, or conditional display only have meaning at the level
|
|
27
|
+
# they are applied. Consider:
|
|
27
28
|
# - ParentGroup
|
|
28
29
|
# - Group 1, input :a
|
|
29
|
-
# - Group 2, input :a, locked: true, hidden: true, optional: true
|
|
30
|
-
# The input 'a' should only be locked
|
|
31
|
-
# It should not
|
|
30
|
+
# - Group 2, input :a, locked: true, hidden: true, enable_when: {...}, optional: true
|
|
31
|
+
# The input 'a' should only be locked, hidden, or conditionally shown when
|
|
32
|
+
# running Group 2 in isolation. It should not inherit those when running
|
|
33
|
+
# Group 1 or the ParentGroup.
|
|
32
34
|
:locked,
|
|
33
35
|
:hidden,
|
|
36
|
+
:enable_when,
|
|
34
37
|
# Input type is sometimes only a UI concern (e.g. text vs. textarea), so
|
|
35
38
|
# it is common to not redeclare the type everywhere it's used and needs
|
|
36
39
|
# special handling to avoid clobbering the type with the default (text)
|
|
@@ -59,6 +62,8 @@ module Inferno
|
|
|
59
62
|
)
|
|
60
63
|
end
|
|
61
64
|
|
|
65
|
+
assert_enable_when_shape!(params)
|
|
66
|
+
|
|
62
67
|
params
|
|
63
68
|
.compact
|
|
64
69
|
.each { |key, value| send("#{key}=", value) }
|
|
@@ -66,6 +71,26 @@ module Inferno
|
|
|
66
71
|
self.name = name.to_s if params[:name].present?
|
|
67
72
|
end
|
|
68
73
|
|
|
74
|
+
def assert_enable_when_shape!(params)
|
|
75
|
+
enable_when = params[:enable_when]
|
|
76
|
+
return if enable_when.blank?
|
|
77
|
+
return if enable_when_valid?(enable_when)
|
|
78
|
+
|
|
79
|
+
raise Exceptions::InvalidAttributeException.new(
|
|
80
|
+
:enable_when,
|
|
81
|
+
self.class,
|
|
82
|
+
'must be a Hash with a non-empty String :input_name and a String :value'
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def enable_when_valid?(enable_when)
|
|
87
|
+
type_is_hash = enable_when.is_a?(Hash)
|
|
88
|
+
input_name_string_exists = enable_when[:input_name].is_a?(String) && enable_when[:input_name].present?
|
|
89
|
+
value_string_exists = enable_when.key?(:value) && enable_when[:value].is_a?(String)
|
|
90
|
+
|
|
91
|
+
type_is_hash && input_name_string_exists && value_string_exists
|
|
92
|
+
end
|
|
93
|
+
|
|
69
94
|
# @private
|
|
70
95
|
# Merge this input with an input belonging to a child. Fields defined on
|
|
71
96
|
# this input take precedence over those defined on the child input.
|