kinetic_sdk 5.0.30 → 7.0.0.rc1
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 +24 -0
- data/kinetic_sdk.gemspec +1 -1
- data/lib/kinetic_sdk/core/lib/form.rb +22 -1
- data/lib/kinetic_sdk/core/lib/jwt.rb +4 -2
- data/lib/kinetic_sdk/core/lib/space.rb +70 -40
- data/lib/kinetic_sdk/task/lib/handlers.rb +22 -1
- data/lib/kinetic_sdk/task/lib/health.rb +3 -1
- data/lib/kinetic_sdk/task/lib/trees.rb +79 -2
- data/lib/kinetic_sdk/task/task-sdk.rb +2 -0
- data/lib/kinetic_sdk/utils/kinetic-export-utils.rb +104 -7
- data/lib/kinetic_sdk/version.rb +1 -1
- data/lib/kinetic_sdk.rb +1 -0
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c479e6e6a7733c2bfa20d92f03230037c9733f6c58c804c38695776572d1e1d6
|
|
4
|
+
data.tar.gz: fc8d8219f4bb8e7f3f1655d259056f19cdb1e7d0606e7b315e775f4a161a041b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fd3128429fdd4e411786cbbf5b3d99bd113897f999f35c24624d19477b7eb0174ee289f3b7a4a356ae6367fc5f6d305c5edcd64c47bc8a2de123474b8bbe6ff9
|
|
7
|
+
data.tar.gz: 79cd820bac2d3bc3b38bd24cd079f1c01a0e3116b3d6e8caef5988535e9b3c3c46562bf540d695de6a5586646dcc9c71632cdecd26095a8fdb5cb44a0a96c6be
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
## [7.0.0-rc1](https://github.com/kineticdata/kinetic-sdk-rb/tree/7.0.0-rc1) (2026-08-31)
|
|
5
|
+
|
|
6
|
+
**Breaking changes:**
|
|
7
|
+
|
|
8
|
+
- This release is tied to version 7 of the Kinetic Platform, and is not compatible with
|
|
9
|
+
earlier platform versions.
|
|
10
|
+
- The Core OAuth authorization routes moved from `/app/oauth` to `/app/oauth2`. The
|
|
11
|
+
authorization code and token requests made by `KineticSdk::Core#jwt_token` - used by the
|
|
12
|
+
Discussions and Integrator SDKs to authenticate - now use the `/app/oauth2` routes.
|
|
13
|
+
|
|
14
|
+
**Implemented enhancements:**
|
|
15
|
+
|
|
16
|
+
- Raise a descriptive error when the authorize endpoint redirects without an authorization
|
|
17
|
+
code, instead of passing an invalid code along to the token endpoint.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## [5.0.31](https://github.com/kineticdata/kinetic-sdk-rb/tree/5.0.31) (2026-03-06)
|
|
21
|
+
|
|
22
|
+
**Implemented enhancements:**
|
|
23
|
+
|
|
24
|
+
- Added thread support for exporting.
|
|
25
|
+
|
|
26
|
+
|
|
3
27
|
## [5.0.30](https://github.com/kineticdata/kinetic-sdk-rb/tree/5.0.30) (2025-10-02)
|
|
4
28
|
|
|
5
29
|
**Implemented enhancements:**
|
data/kinetic_sdk.gemspec
CHANGED
|
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
|
|
|
23
23
|
|
|
24
24
|
spec.add_development_dependency "bundler", ">= 1.17"
|
|
25
25
|
spec.add_development_dependency "rake", ">= 13.0.1"
|
|
26
|
-
spec.add_development_dependency "yard", "~> 0.9.
|
|
26
|
+
spec.add_development_dependency "yard", "~> 0.9.45"
|
|
27
27
|
|
|
28
28
|
spec.metadata["yard.run"] = "yri"
|
|
29
29
|
end
|
|
@@ -14,7 +14,7 @@ module KineticSdk
|
|
|
14
14
|
# - +status+
|
|
15
15
|
# - +submissionLabelExpression+
|
|
16
16
|
# - +type+
|
|
17
|
-
# - +attributes
|
|
17
|
+
# - +attributes
|
|
18
18
|
# - +bridgedResources+
|
|
19
19
|
# - +pages+
|
|
20
20
|
# - +securityPolicies+
|
|
@@ -95,6 +95,27 @@ module KineticSdk
|
|
|
95
95
|
put("#{@api_url}/kapps/#{kapp_slug}/forms/#{form_slug}", properties, headers)
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
+
# Update a Form
|
|
99
|
+
#
|
|
100
|
+
# @param kapp_slug [String] slug of the Kapp the form belongs to
|
|
101
|
+
# @param form_slug [String] slug of the form
|
|
102
|
+
# @param properties [Hash] form properties to update
|
|
103
|
+
# - +anonymous+
|
|
104
|
+
# - +customHeadContent+
|
|
105
|
+
# - +description+
|
|
106
|
+
# - +name+
|
|
107
|
+
# - +notes+
|
|
108
|
+
# - +slug+
|
|
109
|
+
# - +status+
|
|
110
|
+
# - +submissionLabelExpression+
|
|
111
|
+
# - +type+
|
|
112
|
+
# - +attributes+
|
|
113
|
+
# - +bridgedResources+
|
|
114
|
+
# - +pages+
|
|
115
|
+
# - +securityPolicies+
|
|
116
|
+
# @param headers [Hash] hash of headers to send, default is basic authentication and accept JSON content type
|
|
117
|
+
# @return [KineticSdk::Utils::KineticHttpResponse] object, with +code+, +message+, +content_string+, and +content+ properties
|
|
118
|
+
|
|
98
119
|
|
|
99
120
|
# Find Form workflows
|
|
100
121
|
#
|
|
@@ -12,7 +12,7 @@ module KineticSdk
|
|
|
12
12
|
jwt_code = jwt_code(client_id, headers)
|
|
13
13
|
# retrieve the jwt token
|
|
14
14
|
@logger.info("Retrieving JWT authorization token")
|
|
15
|
-
url = "#{@server}/app/
|
|
15
|
+
url = "#{@server}/app/oauth2/token?grant_type=authorization_code&response_type=token&client_id=#{client_id}&code=#{jwt_code}"
|
|
16
16
|
token_headers = header_accept_json.merge(header_basic_auth(client_id, client_secret))
|
|
17
17
|
response = post(url, {}, token_headers, { :max_redirects => 0 })
|
|
18
18
|
|
|
@@ -34,7 +34,7 @@ module KineticSdk
|
|
|
34
34
|
# @return [KineticSdk::Utils::KineticHttpResponse] object, with +code+, +message+, +content_string+, and +content+ properties
|
|
35
35
|
def jwt_code(client_id, headers = default_headers)
|
|
36
36
|
@logger.info("Retrieving JWT authorization code")
|
|
37
|
-
url = "#{@server}/app/
|
|
37
|
+
url = "#{@server}/app/oauth2/authorize?grant_type=authorization_code&response_type=code&client_id=#{client_id}"
|
|
38
38
|
response = post(url, {}, headers, { :max_redirects => -1 })
|
|
39
39
|
|
|
40
40
|
if response.status == 401
|
|
@@ -43,6 +43,8 @@ module KineticSdk
|
|
|
43
43
|
location = response.headers["location"]
|
|
44
44
|
if location.nil?
|
|
45
45
|
raise StandardError.new "Unable to retrieve code: #{response.inspect}"
|
|
46
|
+
elsif !location.include?("?code=")
|
|
47
|
+
raise StandardError.new "Unable to retrieve code, the authorize endpoint redirected to #{location} without an authorization code."
|
|
46
48
|
else
|
|
47
49
|
location.split("?code=").last.split("#/").first
|
|
48
50
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
module KineticSdk
|
|
2
|
+
MAX_THREADS = 30
|
|
2
3
|
class Core
|
|
3
4
|
|
|
4
5
|
# Add an attribute value to the space, or update an attribute if it already exists
|
|
@@ -86,7 +87,14 @@ module KineticSdk
|
|
|
86
87
|
"space.webhooks.{name}",
|
|
87
88
|
)
|
|
88
89
|
core_data = find_space({ 'export' => true }, headers).content
|
|
89
|
-
|
|
90
|
+
pool = Concurrent::FixedThreadPool.new(MAX_THREADS) # Adjust thread count based on your needs
|
|
91
|
+
begin
|
|
92
|
+
process_export(@options[:export_directory], export_shape, core_data, '', executor: pool)
|
|
93
|
+
ensure
|
|
94
|
+
pool.shutdown
|
|
95
|
+
pool.wait_for_termination
|
|
96
|
+
end
|
|
97
|
+
|
|
90
98
|
export_workflows(headers)
|
|
91
99
|
@logger.info("Finished exporting space definition to #{@options[:export_directory]}.")
|
|
92
100
|
end
|
|
@@ -106,50 +114,68 @@ module KineticSdk
|
|
|
106
114
|
raise StandardError.new "An export directory must be defined to export workflows." if @options[:export_directory].nil?
|
|
107
115
|
@logger.info("Exporting workflows to #{@options[:export_directory]}.")
|
|
108
116
|
|
|
109
|
-
#
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
kapp_workflows.select { |wf| !wf["event"].nil? }.each do |workflow|
|
|
129
|
-
evt = workflow["event"].to_s.slugify
|
|
130
|
-
name = workflow["name"].to_s.slugify
|
|
131
|
-
if evt.empty? || name.empty?
|
|
132
|
-
raise "Some workflows are currently in an orphaned or missing state. You can open the Workflows tab for the #{kapp["name"]} kapp in the space console, and run the repair to attempt to resolve this issue."
|
|
117
|
+
#Define Thread settings
|
|
118
|
+
pool = Concurrent::FixedThreadPool.new(MAX_THREADS)
|
|
119
|
+
futures = []
|
|
120
|
+
begin
|
|
121
|
+
# space workflows
|
|
122
|
+
space_workflows = find_space_workflows({ "include" => "details" }, headers).content["workflows"] || []
|
|
123
|
+
space_workflows.select { |wf| !wf["event"].nil? }.each do |workflow|
|
|
124
|
+
futures << Concurrent::Future.execute(executor: pool) do
|
|
125
|
+
begin
|
|
126
|
+
evt = workflow["event"].to_s.slugify
|
|
127
|
+
name = workflow["name"].to_s.slugify
|
|
128
|
+
if evt.empty? || name.empty?
|
|
129
|
+
raise "Some workflows are currently in an orphaned or missing state. You can open the Workflows tab for the space in the space console, and run the repair to attempt to resolve this issue."
|
|
130
|
+
end
|
|
131
|
+
@logger.info(workflow)
|
|
132
|
+
filename = "#{File.join(@options[:export_directory], "space", "workflows", evt, name)}.json"
|
|
133
|
+
workflow_json = find_space_workflow(workflow["id"], {}, headers).content["treeJson"]
|
|
134
|
+
write_object_to_file(filename, workflow_json)
|
|
135
|
+
end
|
|
133
136
|
end
|
|
134
|
-
filename = "#{File.join(@options[:export_directory], "space", "kapps", kapp["slug"], "workflows", evt, name)}.json"
|
|
135
|
-
workflow_json = find_kapp_workflow(kapp["slug"], workflow["id"], {}, headers).content["treeJson"]
|
|
136
|
-
write_object_to_file(filename, workflow_json)
|
|
137
137
|
end
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
138
|
+
futures.each(&:value)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
space_content = find_space({ 'include' => "kapps.details,kapps.forms.details" }).content["space"]
|
|
142
|
+
|
|
143
|
+
# kapp workflows
|
|
144
|
+
space_content["kapps"].each do |kapp|
|
|
145
|
+
futures << Concurrent::Future.execute(executor: pool) do
|
|
146
|
+
kapp_workflows = find_kapp_workflows(kapp["slug"], {}, headers).content["workflows"] || []
|
|
147
|
+
kapp_workflows.select { |wf| !wf["event"].nil? }.each do |workflow|
|
|
148
|
+
evt = workflow["event"].to_s.slugify
|
|
149
|
+
name = workflow["name"].to_s.slugify
|
|
150
|
+
if evt.empty? || name.empty?
|
|
151
|
+
raise "Some workflows are currently in an orphaned or missing state. You can open the Workflows tab for the #{kapp["name"]} kapp in the space console, and run the repair to attempt to resolve this issue."
|
|
152
|
+
end
|
|
153
|
+
filename = "#{File.join(@options[:export_directory], "space", "kapps", kapp["slug"], "workflows", evt, name)}.json"
|
|
154
|
+
workflow_json = find_kapp_workflow(kapp["slug"], workflow["id"], {}, headers).content["treeJson"]
|
|
155
|
+
write_object_to_file(filename, workflow_json)
|
|
147
156
|
end
|
|
148
|
-
filename = "#{File.join(@options[:export_directory], "space", "kapps", kapp["slug"], "forms", form["slug"], "workflows", evt, name)}.json"
|
|
149
|
-
workflow_json = find_form_workflow(kapp["slug"], form["slug"], workflow["id"], {}, headers).content["treeJson"]
|
|
150
|
-
write_object_to_file(filename, workflow_json)
|
|
151
157
|
end
|
|
158
|
+
futures << Concurrent::Future.execute(executor: pool) do
|
|
159
|
+
# form workflows
|
|
160
|
+
kapp["forms"].each do |form|
|
|
161
|
+
form_workflows = find_form_workflows(kapp["slug"], form["slug"], {}, headers).content["workflows"] || []
|
|
162
|
+
form_workflows.select { |wf| !wf["event"].nil? }.each do |workflow|
|
|
163
|
+
evt = workflow["event"].to_s.slugify
|
|
164
|
+
name = workflow["name"].to_s.slugify
|
|
165
|
+
if evt.empty? || name.empty?
|
|
166
|
+
raise "Some workflows are currently in an orphaned or missing state. You can open the Workflows tab for the #{kapp["name"]} > #{form["name"]} form in the space console, and run the repair to attempt to resolve this issue."
|
|
167
|
+
end
|
|
168
|
+
filename = "#{File.join(@options[:export_directory], "space", "kapps", kapp["slug"], "forms", form["slug"], "workflows", evt, name)}.json"
|
|
169
|
+
workflow_json = find_form_workflow(kapp["slug"], form["slug"], workflow["id"], {}, headers).content["treeJson"]
|
|
170
|
+
write_object_to_file(filename, workflow_json)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end #End thread
|
|
152
174
|
end
|
|
175
|
+
ensure
|
|
176
|
+
futures.each(&:value)
|
|
177
|
+
pool.shutdown
|
|
178
|
+
pool.wait_for_termination
|
|
153
179
|
end
|
|
154
180
|
end
|
|
155
181
|
|
|
@@ -163,6 +189,7 @@ module KineticSdk
|
|
|
163
189
|
get("#{@api_url}/space", params, headers)
|
|
164
190
|
end
|
|
165
191
|
|
|
192
|
+
|
|
166
193
|
# Imports a full space definition from the export_directory, except for workflows. Those must be imported separately after
|
|
167
194
|
# the Kinetic Platform source exists in task.
|
|
168
195
|
#
|
|
@@ -361,5 +388,8 @@ module KineticSdk
|
|
|
361
388
|
post("#{@api_url}/workflows/#{workflow_id}", payload, headers)
|
|
362
389
|
end
|
|
363
390
|
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
|
|
364
394
|
end
|
|
365
395
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
module KineticSdk
|
|
2
2
|
class Task
|
|
3
|
-
|
|
4
3
|
# Delete a Handler
|
|
5
4
|
#
|
|
6
5
|
# @param definition_id [String] the handler definition id
|
|
@@ -73,6 +72,28 @@ module KineticSdk
|
|
|
73
72
|
end
|
|
74
73
|
end
|
|
75
74
|
|
|
75
|
+
def import_handlers_threaded(force_overwrite=false, headers=header_basic_auth)
|
|
76
|
+
raise StandardError.new "An export directory must be defined to import handlers from." if @options[:export_directory].nil?
|
|
77
|
+
@logger.info("Importing all Handlers from Export Directory")
|
|
78
|
+
pool = Concurrent::FixedThreadPool.new(MAX_THREADS)
|
|
79
|
+
mutex = Mutex.new
|
|
80
|
+
promises = []
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
Dir["#{@options[:export_directory]}/handlers/*.zip"].sort.each do |file|
|
|
84
|
+
promises << Concurrent::Promise.execute(executor: pool) do
|
|
85
|
+
begin
|
|
86
|
+
handler_file = File.new(file, "rb")
|
|
87
|
+
import_handler(handler_file, force_overwrite, headers)
|
|
88
|
+
rescue
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
promises.each(&:wait!)
|
|
93
|
+
pool.shutdown
|
|
94
|
+
pool.wait_for_termination
|
|
95
|
+
end
|
|
96
|
+
|
|
76
97
|
# Modifies the properties and info values for a handler
|
|
77
98
|
#
|
|
78
99
|
# @param definition_id [String] the definition id of the handler
|
|
@@ -29,7 +29,9 @@ module KineticSdk
|
|
|
29
29
|
# @param headers [Hash] hash of headers to send, default is none
|
|
30
30
|
# @return [KineticSdk::Utils::KineticHttpResponse] object, with +code+, +message+, +content_string+, and +content+ properties
|
|
31
31
|
def server_info(headers={})
|
|
32
|
-
|
|
32
|
+
@server_info_mutex.synchronize do
|
|
33
|
+
@cached_server_info ||= get(@api_url, {}, headers)
|
|
34
|
+
end
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
module KineticSdk
|
|
2
2
|
class Task
|
|
3
|
-
|
|
4
3
|
# Delete a tree.
|
|
5
4
|
#
|
|
6
5
|
# @param tree [String|Hash] either the tree title, or a hash consisting of component names
|
|
@@ -147,7 +146,63 @@ module KineticSdk
|
|
|
147
146
|
@logger.info("Importing all Trees from Export Directory")
|
|
148
147
|
Dir["#{@options[:export_directory]}/sources/**/*.xml"].sort.each do |file|
|
|
149
148
|
tree_file = File.new(file, "rb")
|
|
150
|
-
|
|
149
|
+
match = compare_trees(tree_file)
|
|
150
|
+
if !match
|
|
151
|
+
import_tree(tree_file, force_overwrite, headers)
|
|
152
|
+
else
|
|
153
|
+
#Matching trees, skipping
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
def import_trees_threaded(force_overwrite=false, headers=header_basic_auth)
|
|
158
|
+
raise StandardError.new "An export directory must be defined to import trees from." if @options[:export_directory].nil?
|
|
159
|
+
@logger.info("Importing all Trees from Export Directory")
|
|
160
|
+
pool = Concurrent::FixedThreadPool.new(MAX_THREADS)
|
|
161
|
+
mutex = Mutex.new
|
|
162
|
+
promises = []
|
|
163
|
+
|
|
164
|
+
Dir["#{@options[:export_directory]}/sources/**/*.xml"].sort.each do |file|
|
|
165
|
+
promises << Concurrent::Promise.execute(executor: pool) do
|
|
166
|
+
begin
|
|
167
|
+
|
|
168
|
+
tree_file = File.new(file, "rb")
|
|
169
|
+
match = compare_trees(tree_file)
|
|
170
|
+
if !match
|
|
171
|
+
import_tree(tree_file, force_overwrite, headers)
|
|
172
|
+
else
|
|
173
|
+
#Matching trees, skipping
|
|
174
|
+
end
|
|
175
|
+
rescue
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
promises.each(&:wait!)
|
|
181
|
+
pool.shutdown
|
|
182
|
+
pool.wait_for_termination
|
|
183
|
+
end
|
|
184
|
+
# Import a tree
|
|
185
|
+
#
|
|
186
|
+
# If the tree already exists on the server, this will fail unless forced
|
|
187
|
+
# to overwrite.
|
|
188
|
+
#
|
|
189
|
+
# The source named in the tree content must also exist on the server, or
|
|
190
|
+
# the import will fail.
|
|
191
|
+
#
|
|
192
|
+
# @param tree [String] content from tree file
|
|
193
|
+
# @param force_overwrite [Boolean] whether to overwrite a tree if it exists, default is false
|
|
194
|
+
# @param headers [Hash] hash of headers to send, default is basic authentication
|
|
195
|
+
# @return [KineticSdk::Utils::KineticHttpResponse] object, with +code+, +message+, +content_string+, and +content+ properties
|
|
196
|
+
def compare_trees(tree, headers=header_basic_auth)
|
|
197
|
+
definitionId = REXML::XPath.first(tree, "//definitionId")&.text
|
|
198
|
+
@logger.info("Querying for Tree #{definitionId}")
|
|
199
|
+
response = find_tree(definitionId, { "include" => "details,export" })
|
|
200
|
+
@logger.info("Comparing new and old tree")
|
|
201
|
+
prev_tree = response.content
|
|
202
|
+
if prev_tree == tree
|
|
203
|
+
return true
|
|
204
|
+
else
|
|
205
|
+
return false
|
|
151
206
|
end
|
|
152
207
|
end
|
|
153
208
|
|
|
@@ -183,6 +238,28 @@ module KineticSdk
|
|
|
183
238
|
end
|
|
184
239
|
end
|
|
185
240
|
|
|
241
|
+
def import_routines_threaded(force_overwrite=false, headers=header_basic_auth)
|
|
242
|
+
raise StandardError.new "An export directory must be defined to import trees from." if @options[:export_directory].nil?
|
|
243
|
+
@logger.info("Importing all Routines from Export Directory")
|
|
244
|
+
pool = Concurrent::FixedThreadPool.new(MAX_THREADS)
|
|
245
|
+
mutex = Mutex.new
|
|
246
|
+
promises = []
|
|
247
|
+
|
|
248
|
+
Dir["#{@options[:export_directory]}/routines/*.xml"].sort.each do |file|
|
|
249
|
+
promises << Concurrent::Promise.execute(executor: pool) do
|
|
250
|
+
begin
|
|
251
|
+
routine_file = File.new(file, "rb")
|
|
252
|
+
import_routine(routine_file, force_overwrite, headers)
|
|
253
|
+
rescue
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
promises.each(&:wait!)
|
|
258
|
+
pool.shutdown
|
|
259
|
+
pool.wait_for_termination
|
|
260
|
+
|
|
261
|
+
end
|
|
262
|
+
|
|
186
263
|
# Find a single tree by title (Source Name :: Group Name :: Tree Name)
|
|
187
264
|
#
|
|
188
265
|
# @param title [String] The tree title
|
|
@@ -97,6 +97,18 @@ module KineticSdk
|
|
|
97
97
|
File.open(filename, 'w') { |file| file.write(JSON.pretty_generate(file_contents)) }
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
+
def write_object_to_file_threaded(filename, file_contents, mutex=nil)
|
|
101
|
+
# Create Folder if not exists
|
|
102
|
+
dir_path = File.dirname(filename)
|
|
103
|
+
if mutex
|
|
104
|
+
mutex.synchronize { FileUtils.mkdir_p(dir_path, :mode => 0700) }
|
|
105
|
+
else
|
|
106
|
+
FileUtils.mkdir_p(dir_path, :mode => 0700)
|
|
107
|
+
end
|
|
108
|
+
# Write File
|
|
109
|
+
File.open(filename, 'w') { |file| file.write(JSON.pretty_generate(file_contents)) }
|
|
110
|
+
end
|
|
111
|
+
|
|
100
112
|
# Processes and writes data exported from the core service
|
|
101
113
|
#
|
|
102
114
|
# @param core_path [String] the root folder path to write the data to
|
|
@@ -104,7 +116,84 @@ module KineticSdk
|
|
|
104
116
|
# @param object [Hash] the object being processed
|
|
105
117
|
# @param object_path [String] the path of the object being processed (used recursively)
|
|
106
118
|
# @return nil
|
|
107
|
-
|
|
119
|
+
|
|
120
|
+
def process_export(core_path, export_shape, object, object_path='', executor: nil)
|
|
121
|
+
# Prepare metadata (same as before)
|
|
122
|
+
child_objects = {}
|
|
123
|
+
file_contents = {}
|
|
124
|
+
|
|
125
|
+
if object.kind_of?(Array)
|
|
126
|
+
file_contents = object
|
|
127
|
+
else
|
|
128
|
+
object.each do |key, value|
|
|
129
|
+
child_object_path = object_path.empty? ? key : "#{object_path}.#{key}"
|
|
130
|
+
|
|
131
|
+
if should_extract(child_object_path, export_shape)
|
|
132
|
+
if value.kind_of?(Array)
|
|
133
|
+
variable = get_variable_property(export_shape, child_object_path)
|
|
134
|
+
if variable.nil?
|
|
135
|
+
child_objects[child_object_path] = value
|
|
136
|
+
else
|
|
137
|
+
value.each do |item|
|
|
138
|
+
child_objects["#{child_object_path}.#{item[variable]}"] = item
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
else
|
|
142
|
+
child_objects[child_object_path] = value
|
|
143
|
+
end
|
|
144
|
+
else
|
|
145
|
+
file_contents[key] = value
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
if object_path != '' && !file_contents.empty?
|
|
151
|
+
filename = "#{core_path}/#{object_path.gsub('/', '').gsub('\\', '').gsub('.', '/').gsub(/::/, '-').gsub(/[^ a-zA-Z0-9_\-\/]/, '')}.json"
|
|
152
|
+
write_object_to_file(filename, file_contents)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Thread at key levels: root, kapps, and forms
|
|
156
|
+
should_thread = executor && child_objects.size > 1 && should_parallelize?(object_path)
|
|
157
|
+
|
|
158
|
+
if should_thread
|
|
159
|
+
puts " -> Spawning #{child_objects.size} threads for: #{object_path}"
|
|
160
|
+
futures = child_objects.map do |key, child_object|
|
|
161
|
+
Concurrent::Future.execute(executor: executor) do
|
|
162
|
+
puts " [Thread #{Thread.current.object_id}] Starting: #{key}"
|
|
163
|
+
process_export(core_path, export_shape, child_object, key, executor: executor)
|
|
164
|
+
puts " [Thread #{Thread.current.object_id}] Finished: #{key}"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
futures.each(&:value)
|
|
168
|
+
else
|
|
169
|
+
child_objects.each do |key, child_object|
|
|
170
|
+
process_export(core_path, export_shape, child_object, key, executor: executor)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def should_parallelize?(object_path)
|
|
176
|
+
# Thread at these specific levels
|
|
177
|
+
parallelize_patterns = [
|
|
178
|
+
/^$/, # Root level
|
|
179
|
+
/^space\.kapps$/, # All kapps
|
|
180
|
+
/^space\.kapps\.[^.]+$/, # Within each kapp (forms, webhooks, etc)
|
|
181
|
+
/^space\.kapps\.[^.]+\.forms$/, # All forms within a kapp
|
|
182
|
+
/^space\.teams$/, # All teams
|
|
183
|
+
/^space\.bridges$/, # All bridges
|
|
184
|
+
/^space\.webApis$/, # All webApis
|
|
185
|
+
/^space\.webhooks$/, # All webhooks
|
|
186
|
+
]
|
|
187
|
+
|
|
188
|
+
matches = parallelize_patterns.any? { |pattern| object_path =~ pattern }
|
|
189
|
+
|
|
190
|
+
# DEBUG
|
|
191
|
+
puts " Checking parallelize for: '#{object_path}' -> #{matches}"
|
|
192
|
+
|
|
193
|
+
matches
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def process_export_old(core_path, export_shape, object, object_path='', executor: nil)
|
|
108
197
|
# Prepare metadata
|
|
109
198
|
child_objects = {}
|
|
110
199
|
file_contents = {}
|
|
@@ -150,14 +239,22 @@ module KineticSdk
|
|
|
150
239
|
write_object_to_file(filename, file_contents)
|
|
151
240
|
end
|
|
152
241
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
242
|
+
if executor && child_objects.size > 0
|
|
243
|
+
futures = child_objects.map do |key, child_object|
|
|
244
|
+
Concurrent::Future.execute(executor: executor) do
|
|
245
|
+
# Recursive calls don't use threading
|
|
246
|
+
process_export(core_path, export_shape, child_object, key)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
futures.each do |future|
|
|
250
|
+
future.value # This will re-raise any exceptions that occurred
|
|
251
|
+
end
|
|
252
|
+
else
|
|
253
|
+
child_objects.each do |key, child_object|
|
|
254
|
+
process_export(core_path, export_shape, child_object, key)
|
|
255
|
+
end
|
|
158
256
|
end
|
|
159
257
|
end
|
|
160
|
-
|
|
161
258
|
end
|
|
162
259
|
end
|
|
163
260
|
end
|
data/lib/kinetic_sdk/version.rb
CHANGED
data/lib/kinetic_sdk.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kinetic_sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 7.0.0.rc1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kinetic Data
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-08-31 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: slugify
|
|
@@ -86,14 +86,14 @@ dependencies:
|
|
|
86
86
|
requirements:
|
|
87
87
|
- - "~>"
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: 0.9.
|
|
89
|
+
version: 0.9.45
|
|
90
90
|
type: :development
|
|
91
91
|
prerelease: false
|
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
93
|
requirements:
|
|
94
94
|
- - "~>"
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: 0.9.
|
|
96
|
+
version: 0.9.45
|
|
97
97
|
description:
|
|
98
98
|
email:
|
|
99
99
|
- support@kineticdata.com
|
|
@@ -708,9 +708,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
708
708
|
version: '0'
|
|
709
709
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
710
710
|
requirements:
|
|
711
|
-
- - "
|
|
711
|
+
- - ">"
|
|
712
712
|
- !ruby/object:Gem::Version
|
|
713
|
-
version:
|
|
713
|
+
version: 1.3.1
|
|
714
714
|
requirements: []
|
|
715
715
|
rubygems_version: 3.3.3
|
|
716
716
|
signing_key:
|