reactor_sdk 0.1.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 +7 -0
- data/CHANGELOG.md +19 -0
- data/LICENSE.txt +21 -0
- data/README.md +281 -0
- data/lib/reactor_sdk/authentication.rb +137 -0
- data/lib/reactor_sdk/client.rb +186 -0
- data/lib/reactor_sdk/configuration.rb +102 -0
- data/lib/reactor_sdk/connection.rb +342 -0
- data/lib/reactor_sdk/endpoints/app_configurations.rb +42 -0
- data/lib/reactor_sdk/endpoints/audit_events.rb +64 -0
- data/lib/reactor_sdk/endpoints/base_endpoint.rb +207 -0
- data/lib/reactor_sdk/endpoints/builds.rb +62 -0
- data/lib/reactor_sdk/endpoints/callbacks.rb +38 -0
- data/lib/reactor_sdk/endpoints/companies.rb +42 -0
- data/lib/reactor_sdk/endpoints/data_elements.rb +251 -0
- data/lib/reactor_sdk/endpoints/environments.rb +174 -0
- data/lib/reactor_sdk/endpoints/extension_package_usage_authorizations.rb +51 -0
- data/lib/reactor_sdk/endpoints/extension_packages.rb +63 -0
- data/lib/reactor_sdk/endpoints/extensions.rb +181 -0
- data/lib/reactor_sdk/endpoints/hosts.rb +101 -0
- data/lib/reactor_sdk/endpoints/libraries.rb +872 -0
- data/lib/reactor_sdk/endpoints/notes.rb +11 -0
- data/lib/reactor_sdk/endpoints/profiles.rb +14 -0
- data/lib/reactor_sdk/endpoints/properties.rb +123 -0
- data/lib/reactor_sdk/endpoints/revisions.rb +102 -0
- data/lib/reactor_sdk/endpoints/rule_components.rb +218 -0
- data/lib/reactor_sdk/endpoints/rules.rb +240 -0
- data/lib/reactor_sdk/endpoints/search.rb +23 -0
- data/lib/reactor_sdk/endpoints/secrets.rb +76 -0
- data/lib/reactor_sdk/error.rb +115 -0
- data/lib/reactor_sdk/library_comparison_builder.rb +74 -0
- data/lib/reactor_sdk/library_snapshot_builder.rb +66 -0
- data/lib/reactor_sdk/paginator.rb +92 -0
- data/lib/reactor_sdk/rate_limiter.rb +96 -0
- data/lib/reactor_sdk/reference_extractor.rb +34 -0
- data/lib/reactor_sdk/resource_metadata.rb +73 -0
- data/lib/reactor_sdk/resource_normalizer.rb +90 -0
- data/lib/reactor_sdk/resources/app_configuration.rb +20 -0
- data/lib/reactor_sdk/resources/audit_event.rb +45 -0
- data/lib/reactor_sdk/resources/base_resource.rb +181 -0
- data/lib/reactor_sdk/resources/build.rb +64 -0
- data/lib/reactor_sdk/resources/callback.rb +16 -0
- data/lib/reactor_sdk/resources/company.rb +38 -0
- data/lib/reactor_sdk/resources/comprehensive_data_element.rb +28 -0
- data/lib/reactor_sdk/resources/comprehensive_extension.rb +30 -0
- data/lib/reactor_sdk/resources/comprehensive_resource.rb +31 -0
- data/lib/reactor_sdk/resources/comprehensive_rule.rb +26 -0
- data/lib/reactor_sdk/resources/comprehensive_upstream_chain.rb +50 -0
- data/lib/reactor_sdk/resources/comprehensive_upstream_chain_entry.rb +34 -0
- data/lib/reactor_sdk/resources/data_element.rb +108 -0
- data/lib/reactor_sdk/resources/environment.rb +45 -0
- data/lib/reactor_sdk/resources/extension.rb +66 -0
- data/lib/reactor_sdk/resources/extension_package.rb +49 -0
- data/lib/reactor_sdk/resources/extension_package_usage_authorization.rb +26 -0
- data/lib/reactor_sdk/resources/host.rb +68 -0
- data/lib/reactor_sdk/resources/library.rb +67 -0
- data/lib/reactor_sdk/resources/library_comparison.rb +72 -0
- data/lib/reactor_sdk/resources/library_comparison_entry.rb +144 -0
- data/lib/reactor_sdk/resources/library_snapshot.rb +118 -0
- data/lib/reactor_sdk/resources/library_snapshot_extension_index.rb +70 -0
- data/lib/reactor_sdk/resources/library_snapshot_index.rb +169 -0
- data/lib/reactor_sdk/resources/library_with_resources.rb +194 -0
- data/lib/reactor_sdk/resources/note.rb +37 -0
- data/lib/reactor_sdk/resources/profile.rb +22 -0
- data/lib/reactor_sdk/resources/property.rb +44 -0
- data/lib/reactor_sdk/resources/revision.rb +156 -0
- data/lib/reactor_sdk/resources/rule.rb +44 -0
- data/lib/reactor_sdk/resources/rule_component.rb +101 -0
- data/lib/reactor_sdk/resources/search_results.rb +28 -0
- data/lib/reactor_sdk/resources/secret.rb +17 -0
- data/lib/reactor_sdk/resources/upstream_chain.rb +80 -0
- data/lib/reactor_sdk/resources/upstream_chain_entry.rb +55 -0
- data/lib/reactor_sdk/response_parser.rb +160 -0
- data/lib/reactor_sdk/version.rb +5 -0
- data/lib/reactor_sdk.rb +79 -0
- data/reactor_sdk.gemspec +70 -0
- data/sig/reactor_sdk.rbs +346 -0
- metadata +293 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReactorSDK
|
|
4
|
+
module Resources
|
|
5
|
+
class LibraryComparisonEntry
|
|
6
|
+
attr_reader :resource_id,
|
|
7
|
+
:resource_type,
|
|
8
|
+
:current_library_id,
|
|
9
|
+
:baseline_library_id,
|
|
10
|
+
:current_resource,
|
|
11
|
+
:baseline_resource,
|
|
12
|
+
:current_revision_id,
|
|
13
|
+
:baseline_revision_id,
|
|
14
|
+
:current_comprehensive_resource,
|
|
15
|
+
:baseline_comprehensive_resource
|
|
16
|
+
|
|
17
|
+
def initialize(
|
|
18
|
+
resource_id:,
|
|
19
|
+
resource_type:,
|
|
20
|
+
current_library_id:,
|
|
21
|
+
baseline_library_id:,
|
|
22
|
+
current_resource:,
|
|
23
|
+
baseline_resource:,
|
|
24
|
+
current_revision_id:,
|
|
25
|
+
baseline_revision_id:,
|
|
26
|
+
current_comprehensive_resource:,
|
|
27
|
+
baseline_comprehensive_resource:
|
|
28
|
+
)
|
|
29
|
+
@resource_id = resource_id
|
|
30
|
+
@resource_type = resource_type
|
|
31
|
+
@current_library_id = current_library_id
|
|
32
|
+
@baseline_library_id = baseline_library_id
|
|
33
|
+
@current_resource = current_resource
|
|
34
|
+
@baseline_resource = baseline_resource
|
|
35
|
+
@current_revision_id = current_revision_id
|
|
36
|
+
@baseline_revision_id = baseline_revision_id
|
|
37
|
+
@current_comprehensive_resource = current_comprehensive_resource
|
|
38
|
+
@baseline_comprehensive_resource = baseline_comprehensive_resource
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def resource_name
|
|
42
|
+
return current_resource.name if current_resource.respond_to?(:name)
|
|
43
|
+
return baseline_resource.name if baseline_resource.respond_to?(:name)
|
|
44
|
+
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def status
|
|
49
|
+
return 'added' if added?
|
|
50
|
+
return 'removed' if removed?
|
|
51
|
+
return 'unchanged' if unchanged?
|
|
52
|
+
|
|
53
|
+
'modified'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def added?
|
|
57
|
+
present_in_current? && !present_in_baseline?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def removed?
|
|
61
|
+
!present_in_current? && present_in_baseline?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def modified?
|
|
65
|
+
present_in_current? && present_in_baseline? && !unchanged?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def unchanged?
|
|
69
|
+
return false unless present_in_current? && present_in_baseline?
|
|
70
|
+
|
|
71
|
+
same_revision? || same_normalized_payload?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def changed?
|
|
75
|
+
!unchanged?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def present_in_current?
|
|
79
|
+
!current_resource.nil?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def present_in_baseline?
|
|
83
|
+
!baseline_resource.nil?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def current_normalized_payload
|
|
87
|
+
current_comprehensive_resource&.normalized_payload
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def baseline_normalized_payload
|
|
91
|
+
baseline_comprehensive_resource&.normalized_payload
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def current_normalized_json
|
|
95
|
+
current_comprehensive_resource&.normalized_json.to_s
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def baseline_normalized_json
|
|
99
|
+
baseline_comprehensive_resource&.normalized_json.to_s
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def changeset_document(position: nil)
|
|
103
|
+
document = {
|
|
104
|
+
path: changeset_path,
|
|
105
|
+
language: 'json',
|
|
106
|
+
old_content: baseline_normalized_json,
|
|
107
|
+
new_content: current_normalized_json,
|
|
108
|
+
metadata: changeset_metadata
|
|
109
|
+
}
|
|
110
|
+
document[:position] = position unless position.nil?
|
|
111
|
+
document
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def same_revision?
|
|
117
|
+
return false if current_revision_id.nil? || baseline_revision_id.nil?
|
|
118
|
+
|
|
119
|
+
current_revision_id == baseline_revision_id
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def same_normalized_payload?
|
|
123
|
+
current_normalized_payload == baseline_normalized_payload
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def changeset_path
|
|
127
|
+
"reactor/#{resource_type}/#{resource_id}.json"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def changeset_metadata
|
|
131
|
+
{
|
|
132
|
+
resource_id: resource_id,
|
|
133
|
+
resource_type: resource_type,
|
|
134
|
+
resource_name: resource_name,
|
|
135
|
+
status: status,
|
|
136
|
+
current_library_id: current_library_id,
|
|
137
|
+
baseline_library_id: baseline_library_id,
|
|
138
|
+
current_revision_id: current_revision_id,
|
|
139
|
+
baseline_revision_id: baseline_revision_id
|
|
140
|
+
}.compact
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReactorSDK
|
|
4
|
+
module Resources
|
|
5
|
+
class LibrarySnapshot
|
|
6
|
+
attr_reader :property_id,
|
|
7
|
+
:library,
|
|
8
|
+
:rules,
|
|
9
|
+
:data_elements,
|
|
10
|
+
:extensions,
|
|
11
|
+
:rule_components,
|
|
12
|
+
:rule_components_by_rule_id,
|
|
13
|
+
:resource_by_id
|
|
14
|
+
|
|
15
|
+
def initialize(property_id:, library:, rule_components_by_rule_id:)
|
|
16
|
+
@property_id = property_id
|
|
17
|
+
@library = library
|
|
18
|
+
@rules = Array(library.rules)
|
|
19
|
+
@data_elements = Array(library.data_elements)
|
|
20
|
+
@extensions = Array(library.extensions)
|
|
21
|
+
@index = LibrarySnapshotIndex.new(
|
|
22
|
+
rules: @rules,
|
|
23
|
+
data_elements: @data_elements,
|
|
24
|
+
extensions: @extensions,
|
|
25
|
+
rule_components_by_rule_id: rule_components_by_rule_id
|
|
26
|
+
)
|
|
27
|
+
@rule_components_by_rule_id = @index.rule_components_by_rule_id
|
|
28
|
+
@rule_components = @index.rule_components
|
|
29
|
+
@resource_by_id = @index.resource_by_id
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def top_level_resources
|
|
33
|
+
@top_level_resources ||= @rules + @data_elements + @extensions
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def all_resources
|
|
37
|
+
top_level_resources + @rule_components
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def find_resource(resource_id)
|
|
41
|
+
@index.find_resource(resource_id)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def rule_components_for_rule(rule_or_id)
|
|
45
|
+
@index.rule_components_for_rule(rule_or_id)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def referenced_data_elements_for(data_element_or_id)
|
|
49
|
+
@index.referenced_data_elements_for(data_element_or_id)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def impacted_rules_for(data_element_or_id)
|
|
53
|
+
@index.impacted_rules_for(data_element_or_id)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def data_elements_for_extension(extension_or_id)
|
|
57
|
+
@index.data_elements_for_extension(extension_or_id)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def rule_components_for_extension(extension_or_id)
|
|
61
|
+
@index.rule_components_for_extension(extension_or_id)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def rules_for_extension(extension_or_id)
|
|
65
|
+
@index.rules_for_extension(extension_or_id)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def resource_revision_id(resource_or_id)
|
|
69
|
+
resource_id = extract_id(resource_or_id)
|
|
70
|
+
resource = find_resource(resource_id)
|
|
71
|
+
return nil if resource.nil?
|
|
72
|
+
return resource.revision_id if resource.respond_to?(:revision_id)
|
|
73
|
+
return @library.resource_index[resource_id] if @library.resource_index.key?(resource_id)
|
|
74
|
+
return resource.relationship_id('latest_revision') if resource.respond_to?(:relationship_id)
|
|
75
|
+
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def comprehensive_resource(resource_id, resource_type: nil)
|
|
80
|
+
resource = find_resource(resource_id)
|
|
81
|
+
return nil if resource.nil?
|
|
82
|
+
|
|
83
|
+
build_comprehensive_resource(resource, resource_type || resource.type)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def build_comprehensive_resource(resource, resource_type)
|
|
89
|
+
case resource_type
|
|
90
|
+
when 'rules'
|
|
91
|
+
ComprehensiveRule.new(
|
|
92
|
+
resource: resource,
|
|
93
|
+
rule_components: rule_components_for_rule(resource.id)
|
|
94
|
+
)
|
|
95
|
+
when 'data_elements'
|
|
96
|
+
ComprehensiveDataElement.new(
|
|
97
|
+
resource: resource,
|
|
98
|
+
referenced_data_elements: referenced_data_elements_for(resource.id),
|
|
99
|
+
impacted_rules: impacted_rules_for(resource.id)
|
|
100
|
+
)
|
|
101
|
+
when 'extensions'
|
|
102
|
+
ComprehensiveExtension.new(
|
|
103
|
+
resource: resource,
|
|
104
|
+
data_elements: data_elements_for_extension(resource.id),
|
|
105
|
+
rule_components: rule_components_for_extension(resource.id),
|
|
106
|
+
rules: rules_for_extension(resource.id)
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def extract_id(resource_or_id)
|
|
112
|
+
return resource_or_id.id if resource_or_id.respond_to?(:id)
|
|
113
|
+
|
|
114
|
+
resource_or_id
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReactorSDK
|
|
4
|
+
module Resources
|
|
5
|
+
class LibrarySnapshotExtensionIndex
|
|
6
|
+
def initialize(data_elements:, rule_components_by_rule_id:, find_resource:, sort_rule_components:)
|
|
7
|
+
@data_elements = Array(data_elements)
|
|
8
|
+
@rule_components_by_rule_id = rule_components_by_rule_id
|
|
9
|
+
@find_resource = find_resource
|
|
10
|
+
@sort_rule_components = sort_rule_components
|
|
11
|
+
@data_elements_by_extension = build_data_elements_index
|
|
12
|
+
@rule_components_by_extension = build_rule_components_index
|
|
13
|
+
@rules_by_extension = build_rules_index
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def data_elements_for(extension_id)
|
|
17
|
+
@data_elements_by_extension.fetch(extension_id, [])
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def rule_components_for(extension_id)
|
|
21
|
+
@rule_components_by_extension.fetch(extension_id, [])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def rules_for(extension_id)
|
|
25
|
+
@rules_by_extension.fetch(extension_id, [])
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def build_data_elements_index
|
|
31
|
+
group_by_relationship(@data_elements, 'extension').transform_values do |items|
|
|
32
|
+
items.sort_by { |item| [item.name.to_s, item.id] }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def build_rule_components_index
|
|
37
|
+
group_by_relationship(@rule_components_by_rule_id.values.flatten, 'extension')
|
|
38
|
+
.transform_values { |items| @sort_rule_components.call(items) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def build_rules_index
|
|
42
|
+
rules_by_extension = @rule_components_by_rule_id.each_with_object(new_set_index) do |rule_entry, index|
|
|
43
|
+
rule_id, components = rule_entry
|
|
44
|
+
rule = @find_resource.call(rule_id)
|
|
45
|
+
next if rule.nil?
|
|
46
|
+
|
|
47
|
+
components.each do |component|
|
|
48
|
+
extension_id = component.relationship_id('extension')
|
|
49
|
+
index[extension_id] << rule unless extension_id.nil?
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
rules_by_extension.transform_values do |items|
|
|
54
|
+
items.to_a.sort_by { |rule| [rule.name.to_s, rule.id] }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def group_by_relationship(resources, relationship_name)
|
|
59
|
+
resources.each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |resource, grouped_resources|
|
|
60
|
+
relationship_id = resource.relationship_id(relationship_name)
|
|
61
|
+
grouped_resources[relationship_id] << resource unless relationship_id.nil?
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def new_set_index
|
|
66
|
+
Hash.new { |hash, key| hash[key] = Set.new }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReactorSDK
|
|
4
|
+
module Resources
|
|
5
|
+
class LibrarySnapshotIndex
|
|
6
|
+
attr_reader :resource_by_id, :rule_components, :rule_components_by_rule_id
|
|
7
|
+
|
|
8
|
+
def initialize(rules:, data_elements:, extensions:, rule_components_by_rule_id:)
|
|
9
|
+
@rules = Array(rules)
|
|
10
|
+
@data_elements = Array(data_elements)
|
|
11
|
+
@extensions = Array(extensions)
|
|
12
|
+
@rule_components_by_rule_id = normalize_rule_components(rule_components_by_rule_id)
|
|
13
|
+
@rule_components = @rule_components_by_rule_id.values.flatten
|
|
14
|
+
@resource_by_id = build_resource_index
|
|
15
|
+
@data_elements_by_name = build_data_element_name_index
|
|
16
|
+
@data_element_dependency_graph = build_data_element_dependency_graph
|
|
17
|
+
@reverse_data_element_graph = invert_graph(@data_element_dependency_graph)
|
|
18
|
+
@rule_dependency_graph = build_rule_dependency_graph
|
|
19
|
+
@rules_by_data_element = invert_graph(@rule_dependency_graph)
|
|
20
|
+
@extension_index = LibrarySnapshotExtensionIndex.new(
|
|
21
|
+
data_elements: @data_elements,
|
|
22
|
+
rule_components_by_rule_id: @rule_components_by_rule_id,
|
|
23
|
+
find_resource: method(:find_resource),
|
|
24
|
+
sort_rule_components: method(:sort_rule_components)
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def find_resource(resource_id)
|
|
29
|
+
@resource_by_id[extract_id(resource_id)]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def rule_components_for_rule(rule_or_id)
|
|
33
|
+
@rule_components_by_rule_id.fetch(extract_id(rule_or_id), [])
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def referenced_data_elements_for(data_element_or_id)
|
|
37
|
+
referenced_resource_ids(data_element_or_id).filter_map { |resource_id| find_resource(resource_id) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def impacted_rules_for(data_element_or_id)
|
|
41
|
+
impacted_rule_ids_for(data_element_or_id)
|
|
42
|
+
.filter_map { |resource_id| find_resource(resource_id) }
|
|
43
|
+
.sort_by { |rule| [rule.name.to_s, rule.id] }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def data_elements_for_extension(extension_or_id)
|
|
47
|
+
@extension_index.data_elements_for(extract_id(extension_or_id))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def rule_components_for_extension(extension_or_id)
|
|
51
|
+
@extension_index.rule_components_for(extract_id(extension_or_id))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def rules_for_extension(extension_or_id)
|
|
55
|
+
@extension_index.rules_for(extract_id(extension_or_id))
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def normalize_rule_components(rule_components_by_rule_id)
|
|
61
|
+
rule_components_by_rule_id.to_h do |rule_id, components|
|
|
62
|
+
[rule_id, sort_rule_components(Array(components))]
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def sort_rule_components(components)
|
|
67
|
+
components.sort_by do |component|
|
|
68
|
+
[
|
|
69
|
+
sortable_number(component.respond_to?(:rule_order) ? component.rule_order : nil),
|
|
70
|
+
sortable_number(component.respond_to?(:order) ? component.order : nil),
|
|
71
|
+
component.id
|
|
72
|
+
]
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def sortable_number(value)
|
|
77
|
+
value.nil? ? Float::INFINITY : value.to_f
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def build_resource_index
|
|
81
|
+
(@rules + @data_elements + @extensions + @rule_components).each_with_object({}) do |resource, index|
|
|
82
|
+
index[resource.id] ||= resource
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def build_data_element_name_index
|
|
87
|
+
@data_elements.each_with_object({}) do |data_element, index|
|
|
88
|
+
next unless data_element.respond_to?(:name)
|
|
89
|
+
next if data_element.name.nil?
|
|
90
|
+
|
|
91
|
+
index[data_element.name] = data_element
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def build_data_element_dependency_graph
|
|
96
|
+
@data_elements.to_h do |data_element|
|
|
97
|
+
names = ReactorSDK::ReferenceExtractor.extract_data_element_names(data_element)
|
|
98
|
+
[data_element.id, resolve_data_element_names(names, excluding: data_element.id)]
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def build_rule_dependency_graph
|
|
103
|
+
@rules.to_h do |rule|
|
|
104
|
+
component_names = rule_components_for_rule(rule.id).flat_map do |component|
|
|
105
|
+
ReactorSDK::ReferenceExtractor.extract_data_element_names(component)
|
|
106
|
+
end
|
|
107
|
+
[rule.id, resolve_data_element_names(component_names)]
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def invert_graph(graph)
|
|
112
|
+
graph.each_with_object(new_set_index) do |(from_id, to_ids), inverse|
|
|
113
|
+
Array(to_ids).each { |to_id| inverse[to_id] << from_id }
|
|
114
|
+
end.transform_values(&:to_a)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def referenced_resource_ids(data_element_or_id)
|
|
118
|
+
data_element_id = extract_id(data_element_or_id)
|
|
119
|
+
Array(@data_element_dependency_graph[data_element_id])
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def impacted_rule_ids_for(data_element_or_id)
|
|
123
|
+
related_data_element_ids = transitive_data_element_ids(extract_id(data_element_or_id))
|
|
124
|
+
|
|
125
|
+
related_data_element_ids.each_with_object(Set.new) do |data_element_id, impacted_rule_ids|
|
|
126
|
+
Array(@rules_by_data_element[data_element_id]).each { |rule_id| impacted_rule_ids << rule_id }
|
|
127
|
+
end.to_a
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def transitive_data_element_ids(data_element_id)
|
|
131
|
+
related_data_element_ids = Set[data_element_id]
|
|
132
|
+
queue = [data_element_id]
|
|
133
|
+
|
|
134
|
+
until queue.empty?
|
|
135
|
+
current = queue.shift
|
|
136
|
+
|
|
137
|
+
Array(@reverse_data_element_graph[current]).each do |dependent_id|
|
|
138
|
+
next if related_data_element_ids.include?(dependent_id)
|
|
139
|
+
|
|
140
|
+
related_data_element_ids << dependent_id
|
|
141
|
+
queue << dependent_id
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
related_data_element_ids
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def new_set_index
|
|
149
|
+
Hash.new { |hash, key| hash[key] = Set.new }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def resolve_data_element_names(names, excluding: nil)
|
|
153
|
+
names.each_with_object(Set.new) do |name, resolved_ids|
|
|
154
|
+
target = @data_elements_by_name[name]
|
|
155
|
+
next if target.nil?
|
|
156
|
+
next if target.id == excluding
|
|
157
|
+
|
|
158
|
+
resolved_ids << target.id
|
|
159
|
+
end.to_a.sort
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def extract_id(resource_or_id)
|
|
163
|
+
return resource_or_id.id if resource_or_id.respond_to?(:id)
|
|
164
|
+
|
|
165
|
+
resource_or_id
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# @file resources/library_with_resources.rb
|
|
5
|
+
# @description A richer Library resource returned by Libraries#find_with_resources.
|
|
6
|
+
#
|
|
7
|
+
# When fetching GET /libraries/:id?include=rules,data_elements,extensions
|
|
8
|
+
# Adobe returns the library alongside all its associated resources in the
|
|
9
|
+
# JSON:API included array. Each included resource carries a relationships
|
|
10
|
+
# hash containing its current revision ID.
|
|
11
|
+
#
|
|
12
|
+
# This class wraps that response and exposes:
|
|
13
|
+
# - All standard Library attributes (name, state, etc.)
|
|
14
|
+
# - rules — Array of Rule resources with revision_id attached
|
|
15
|
+
# - data_elements — Array of DataElement resources with revision_id attached
|
|
16
|
+
# - extensions — Array of Extension resources with revision_id attached
|
|
17
|
+
#
|
|
18
|
+
# The revision_id on each resource is the key the app uses for upstream
|
|
19
|
+
# resolution — when a resource does not exist in the target library, the
|
|
20
|
+
# app walks upstream (Development → Staging → Production) to find the
|
|
21
|
+
# nearest version and fetches that revision for comparison.
|
|
22
|
+
#
|
|
23
|
+
# This class is never instantiated directly — always created via
|
|
24
|
+
# Libraries#find_with_resources which passes the full API response.
|
|
25
|
+
#
|
|
26
|
+
# @domain Resources
|
|
27
|
+
# @see https://developer.adobe.com/experience-platform/documentation/tags/api/endpoints/libraries/
|
|
28
|
+
#
|
|
29
|
+
|
|
30
|
+
module ReactorSDK
|
|
31
|
+
module Resources
|
|
32
|
+
class LibraryWithResources < BaseResource
|
|
33
|
+
# @return [String] Display name of the library
|
|
34
|
+
attribute :name
|
|
35
|
+
|
|
36
|
+
# @return [String] Current workflow state
|
|
37
|
+
# One of: "development", "submitted", "approved", "rejected", "published"
|
|
38
|
+
attribute :state
|
|
39
|
+
|
|
40
|
+
# @return [Boolean] Whether the library has been published
|
|
41
|
+
attribute :published, as: :boolean
|
|
42
|
+
|
|
43
|
+
# @return [String] ISO8601 timestamp when the library was created
|
|
44
|
+
attribute :created_at
|
|
45
|
+
|
|
46
|
+
# @return [String] ISO8601 timestamp when the library was last updated
|
|
47
|
+
attribute :updated_at
|
|
48
|
+
|
|
49
|
+
# @return [String, nil] ISO8601 timestamp when the library was published
|
|
50
|
+
attribute :published_at
|
|
51
|
+
|
|
52
|
+
# @return [Array<ReactorSDK::Resources::Rule>] Rules in this library
|
|
53
|
+
# Each rule has a revision_id attribute attached from the relationship data
|
|
54
|
+
attr_reader :rules
|
|
55
|
+
|
|
56
|
+
# @return [Array<ReactorSDK::Resources::DataElement>] Data elements in this library
|
|
57
|
+
# Each data element has a revision_id attribute attached
|
|
58
|
+
attr_reader :data_elements
|
|
59
|
+
|
|
60
|
+
# @return [Array<ReactorSDK::Resources::Extension>] Extensions in this library
|
|
61
|
+
# Each extension has a revision_id attribute attached
|
|
62
|
+
attr_reader :extensions
|
|
63
|
+
|
|
64
|
+
##
|
|
65
|
+
# Initializes the library with its included resources.
|
|
66
|
+
#
|
|
67
|
+
# Accepts the standard BaseResource arguments plus an included_resources
|
|
68
|
+
# hash that maps resource type to arrays of raw JSON:API resource hashes
|
|
69
|
+
# extracted from the API response included array.
|
|
70
|
+
#
|
|
71
|
+
# @param id [String] Adobe library ID
|
|
72
|
+
# @param type [String] JSON:API type ("libraries")
|
|
73
|
+
# @param attributes [Hash] Library attribute values
|
|
74
|
+
# @param meta [Hash] Optional metadata
|
|
75
|
+
# @param included_resources [Hash] Keyed by type — raw included resource arrays
|
|
76
|
+
# {
|
|
77
|
+
# "rules" => [ { "id" => "RL123", "type" => "rules",
|
|
78
|
+
# "attributes" => {...},
|
|
79
|
+
# "relationships" => { "latest_revision" => { "data" => { "id" => "RE123" } } } } ],
|
|
80
|
+
# "data_elements" => [ ... ],
|
|
81
|
+
# "extensions" => [ ... ]
|
|
82
|
+
# }
|
|
83
|
+
#
|
|
84
|
+
def initialize(
|
|
85
|
+
id:,
|
|
86
|
+
type:,
|
|
87
|
+
attributes: {},
|
|
88
|
+
meta: {},
|
|
89
|
+
included_resources: {}
|
|
90
|
+
)
|
|
91
|
+
super(id: id, type: type, attributes: attributes, meta: meta)
|
|
92
|
+
@rules = build_resources(included_resources['rules'], Resources::Rule)
|
|
93
|
+
@data_elements = build_resources(included_resources['data_elements'], Resources::DataElement)
|
|
94
|
+
@extensions = build_resources(included_resources['extensions'], Resources::Extension)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
##
|
|
98
|
+
# Returns true if the library is in a state where it can be built.
|
|
99
|
+
#
|
|
100
|
+
# @return [Boolean]
|
|
101
|
+
#
|
|
102
|
+
def buildable?
|
|
103
|
+
state == 'development'
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
##
|
|
107
|
+
# Returns true if the library has been successfully published.
|
|
108
|
+
#
|
|
109
|
+
# @return [Boolean]
|
|
110
|
+
#
|
|
111
|
+
def published?
|
|
112
|
+
state == 'published'
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
##
|
|
116
|
+
# Returns a flat index of all resources keyed by Adobe resource ID.
|
|
117
|
+
# Maps each resource ID to its current revision ID.
|
|
118
|
+
# Covers rules, data elements, and extensions in a single lookup.
|
|
119
|
+
#
|
|
120
|
+
# Used by the app to compare two libraries — call resource_index on
|
|
121
|
+
# both the source and target library, then compare revision IDs to
|
|
122
|
+
# find what changed, what was added, and what needs upstream resolution.
|
|
123
|
+
#
|
|
124
|
+
# @return [Hash] { "RL123" => "RE456", "DE789" => "RE012", ... }
|
|
125
|
+
#
|
|
126
|
+
def resource_index
|
|
127
|
+
(@rules + @data_elements + @extensions).each_with_object({}) do |resource, index|
|
|
128
|
+
index[resource.id] = resource.revision_id if resource.revision_id
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
##
|
|
133
|
+
# Returns all included resources as a flat array regardless of type.
|
|
134
|
+
#
|
|
135
|
+
# @return [Array<BaseResource>]
|
|
136
|
+
#
|
|
137
|
+
def all_resources
|
|
138
|
+
@rules + @data_elements + @extensions
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
##
|
|
142
|
+
# @return [String] Human-readable representation
|
|
143
|
+
#
|
|
144
|
+
def inspect
|
|
145
|
+
'#<ReactorSDK::Resources::LibraryWithResources ' \
|
|
146
|
+
"id=#{id.inspect} " \
|
|
147
|
+
"name=#{name.inspect} " \
|
|
148
|
+
"state=#{state.inspect} " \
|
|
149
|
+
"rules=#{@rules.length} " \
|
|
150
|
+
"data_elements=#{@data_elements.length} " \
|
|
151
|
+
"extensions=#{@extensions.length}>"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
##
|
|
157
|
+
# Builds an array of typed resource objects from raw included resource hashes.
|
|
158
|
+
# Attaches revision_id to each resource extracted from its relationships.
|
|
159
|
+
#
|
|
160
|
+
# @param raw_resources [Array<Hash>, nil] Raw JSON:API resource hashes
|
|
161
|
+
# @param resource_class [Class] Resource class to instantiate
|
|
162
|
+
# @return [Array<BaseResource>] Typed resource objects with revision_id attached
|
|
163
|
+
#
|
|
164
|
+
def build_resources(raw_resources, resource_class)
|
|
165
|
+
Array(raw_resources).map do |raw|
|
|
166
|
+
resource = resource_class.new(
|
|
167
|
+
id: raw.fetch('id'),
|
|
168
|
+
type: raw.fetch('type'),
|
|
169
|
+
attributes: raw.fetch('attributes', {}),
|
|
170
|
+
meta: raw.fetch('meta', {}),
|
|
171
|
+
relationships: raw.fetch('relationships', {})
|
|
172
|
+
)
|
|
173
|
+
resource.instance_variable_set(
|
|
174
|
+
:@revision_id,
|
|
175
|
+
extract_revision_id(raw)
|
|
176
|
+
)
|
|
177
|
+
resource.singleton_class.attr_reader :revision_id
|
|
178
|
+
resource
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
##
|
|
183
|
+
# Extracts the latest revision ID from a resource's relationships hash.
|
|
184
|
+
# Adobe stores it under relationships.latest_revision.data.id.
|
|
185
|
+
#
|
|
186
|
+
# @param raw [Hash] Raw JSON:API resource hash
|
|
187
|
+
# @return [String, nil] Revision ID or nil if not present
|
|
188
|
+
#
|
|
189
|
+
def extract_revision_id(raw)
|
|
190
|
+
raw.dig('relationships', 'latest_revision', 'data', 'id')
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|