openc3 7.4.0 → 7.4.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 +4 -4
- data/bin/openc3cli +4 -3
- data/lib/openc3/api/tlm_api.rb +15 -11
- data/lib/openc3/interfaces/protocols/protocol.rb +8 -0
- data/lib/openc3/io/json_api_object.rb +24 -3
- data/lib/openc3/io/json_drb_object.rb +3 -2
- data/lib/openc3/models/reaction_model.rb +6 -3
- data/lib/openc3/models/setting_model.rb +5 -3
- data/lib/openc3/packets/packet_config.rb +36 -1
- data/lib/openc3/packets/parsers/xtce_parser.rb +8 -1
- data/lib/openc3/script/tables.rb +5 -1
- data/lib/openc3/utilities/questdb_client.rb +29 -46
- data/lib/openc3/version.rb +5 -5
- data/templates/tool_angular/package.json +2 -2
- data/templates/tool_react/package.json +1 -1
- data/templates/tool_svelte/package.json +1 -1
- data/templates/tool_vue/package.json +3 -3
- data/templates/widget/package.json +2 -2
- metadata +21 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 38237f07ec722ff8f92d12fd8b1219f0f90fe58c7e37e30e8d50fd9107cc373e
|
|
4
|
+
data.tar.gz: 5b2d61f7c5cfc095fc554099d4b4854d147b7c9335dc413a024afc14d09b6713
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5cf34d15ab53eee6dde3268c9735c51ebe8ed39ee910a34388549bde4e6c5abb9e60afd88a0698937e2c5f5ff16b106dd0633c268f62fdbf79be0f6b02e1fc22
|
|
7
|
+
data.tar.gz: b9e56c46a90420c6a8c20e5dbcae3b49d30686c4453d640e5bfbca90d5391949eb3054a8d11380ef1735f1b05f830973b1dd79a70b3ce7b7178b17b0f892c673
|
data/bin/openc3cli
CHANGED
|
@@ -1541,7 +1541,7 @@ if not ARGV[0].nil? # argument(s) given
|
|
|
1541
1541
|
puts " OPENC3_SETTING_<NAME> One variable per setting, e.g."
|
|
1542
1542
|
puts " OPENC3_SETTING_TIME_ZONE=UTC sets 'time_zone'"
|
|
1543
1543
|
puts " OPENC3_SETTINGS_OVERWRITE true to overwrite existing settings on every"
|
|
1544
|
-
puts " run. false/unset
|
|
1544
|
+
puts " run. false/unset keeps Admin Console edits"
|
|
1545
1545
|
puts " OPENC3_SETTINGS_ALLOW_UNKNOWN true to allow setting names COSMOS doesn't"
|
|
1546
1546
|
puts " recognize. Without it an unknown name is"
|
|
1547
1547
|
puts " reported and skipped, so a typo can't"
|
|
@@ -1551,8 +1551,9 @@ if not ARGV[0].nil? # argument(s) given
|
|
|
1551
1551
|
puts ""
|
|
1552
1552
|
puts "These three accept true/false (1/0 also work)."
|
|
1553
1553
|
puts ""
|
|
1554
|
-
puts "A setting
|
|
1555
|
-
puts "
|
|
1554
|
+
puts "A new setting is written. A setting still holding the value last seeded is"
|
|
1555
|
+
puts "updated, so changing a variable and restarting works. A setting changed in"
|
|
1556
|
+
puts "the Admin Console is left alone. Settings written by a release before this"
|
|
1556
1557
|
puts "command existed carry no record of having been seeded, so they all count as"
|
|
1557
1558
|
puts "edited: on such an upgrade run one init with OPENC3_SETTINGS_OVERWRITE=true"
|
|
1558
1559
|
puts "to adopt the environment values, then turn it back off."
|
data/lib/openc3/api/tlm_api.rb
CHANGED
|
@@ -329,19 +329,23 @@ module OpenC3
|
|
|
329
329
|
end
|
|
330
330
|
packets = []
|
|
331
331
|
cvt_items = []
|
|
332
|
-
items.
|
|
332
|
+
items.each do |item|
|
|
333
|
+
# get_tlm_available returns nil for items which don't exist and its result is
|
|
334
|
+
# passed directly here, so nil is a placeholder which returns a nil value
|
|
333
335
|
if item.nil?
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
336
|
+
cvt_items << [nil, nil, nil, nil, nil]
|
|
337
|
+
next
|
|
338
|
+
end
|
|
339
|
+
# get_tlm_available tacks on __LIMITS to indicate a limits value is available
|
|
340
|
+
# so accept both TGT__PKT__ITEM__TYPE and TGT__PKT__ITEM__TYPE__LIMITS
|
|
341
|
+
parts = item.to_s.upcase.split('__')
|
|
342
|
+
raise ArgumentError, "items must be formatted as TGT__PKT__ITEM__TYPE" if parts.length < 4 || parts.length > 5
|
|
343
|
+
target_name, packet_name, item_name, value_type, limits = parts
|
|
344
|
+
if packet_name == 'LATEST' # Lookup packet_name in case of LATEST
|
|
345
|
+
packet_name = CvtModel.determine_latest_packet_for_item(target_name, item_name, cache_timeout: cache_timeout, scope: scope)
|
|
343
346
|
end
|
|
344
|
-
|
|
347
|
+
# NOTE: limits is required by the historical (start_time) QuestDB lookup
|
|
348
|
+
cvt_items << [target_name, packet_name, item_name, value_type, limits]
|
|
345
349
|
packets << [target_name, packet_name]
|
|
346
350
|
end
|
|
347
351
|
packets.uniq!
|
|
@@ -36,6 +36,14 @@ module OpenC3
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def reset
|
|
39
|
+
@read_data_input_time = nil
|
|
40
|
+
@read_data_input = ''
|
|
41
|
+
@read_data_output_time = nil
|
|
42
|
+
@read_data_output = ''
|
|
43
|
+
@write_data_input_time = nil
|
|
44
|
+
@write_data_input = ''
|
|
45
|
+
@write_data_output_time = nil
|
|
46
|
+
@write_data_output = ''
|
|
39
47
|
@extra = nil
|
|
40
48
|
end
|
|
41
49
|
|
|
@@ -53,10 +53,19 @@ module OpenC3
|
|
|
53
53
|
|
|
54
54
|
USER_AGENT = 'OpenC3 / v7 (ruby/openc3/lib/io/json_api_object)'.freeze
|
|
55
55
|
|
|
56
|
+
# Time limit for the first response byte from the server. This must be an
|
|
57
|
+
# explicit number rather than nil, see connect() for why. Requests can
|
|
58
|
+
# legitimately block for a long time, e.g. cmd() with a large timeout waiting
|
|
59
|
+
# on an interface ack, so this is effectively "wait forever" while still
|
|
60
|
+
# bounded so a wedged connection eventually releases the thread.
|
|
61
|
+
DEFAULT_READ_TIMEOUT_S = 86400
|
|
62
|
+
|
|
56
63
|
# @param url [String] The url of openc3-cosmos-cmd-tlm-api http://openc3-cosmos-cmd-tlm-api:2901
|
|
57
64
|
# @param timeout [Float] The time to wait before disconnecting 1.0
|
|
58
65
|
# @param authentication [OpenC3Authentication] The authentication object if nill initialize will generate
|
|
59
|
-
|
|
66
|
+
# @param read_timeout [Float] The time to wait for the first response byte from the server.
|
|
67
|
+
# Defaults to the OPENC3_API_READ_TIMEOUT environment variable or DEFAULT_READ_TIMEOUT_S.
|
|
68
|
+
def initialize(url:, timeout: 1.0, authentication: nil, read_timeout: nil)
|
|
60
69
|
@http = nil
|
|
61
70
|
@mutex = Mutex.new
|
|
62
71
|
@request_data = ""
|
|
@@ -65,6 +74,7 @@ module OpenC3
|
|
|
65
74
|
@log = [nil, nil, nil]
|
|
66
75
|
@authentication = authentication.nil? ? generate_auth() : authentication
|
|
67
76
|
@timeout = timeout
|
|
77
|
+
@read_timeout = (read_timeout || ENV.fetch('OPENC3_API_READ_TIMEOUT', DEFAULT_READ_TIMEOUT_S)).to_f
|
|
68
78
|
@shutdown = false
|
|
69
79
|
# JsonDRb.debug = true # Enable for debugging
|
|
70
80
|
end
|
|
@@ -120,7 +130,11 @@ module OpenC3
|
|
|
120
130
|
# :open_timeout - time limit for just the connection phase (e.g. handshake) (Integer in seconds)
|
|
121
131
|
# :read_timeout - time limit for the first response byte received from the server (Integer in seconds)
|
|
122
132
|
# :write_timeout - time limit for the client to send the request to the server (Integer in seconds)
|
|
123
|
-
|
|
133
|
+
# NOTE: read_timeout must be an explicit number. Faraday only assigns it to Net::HTTP
|
|
134
|
+
# when the value is truthy (see Faraday::Adapter::NetHttp#configure_request), so passing
|
|
135
|
+
# nil does NOT mean "wait forever", it silently leaves Net::HTTP's 60s default in place.
|
|
136
|
+
# That 60s then caps every request regardless of any longer application level timeout.
|
|
137
|
+
@http = Faraday.new(request: { open_timeout: @timeout.to_i, read_timeout: @read_timeout }) do |f|
|
|
124
138
|
f.adapter :net_http # adds the adapter to the connection, defaults to `Faraday.default_adapter`
|
|
125
139
|
end
|
|
126
140
|
rescue => e
|
|
@@ -212,7 +226,9 @@ module OpenC3
|
|
|
212
226
|
@response_data = resp.body
|
|
213
227
|
return resp
|
|
214
228
|
rescue Faraday::ConnectionFailed, Errno::ECONNRESET, Errno::EPIPE, IOError => e
|
|
215
|
-
#
|
|
229
|
+
# Retryable. Nothing was successfully sent, so replaying is safe. This includes
|
|
230
|
+
# Net::OpenTimeout, which Faraday maps to ConnectionFailed and which covers a
|
|
231
|
+
# service that is still starting up.
|
|
216
232
|
retry_count += 1
|
|
217
233
|
@log[2] = "#{method} Exception: #{e.class}, #{e.message}, #{e.backtrace}"
|
|
218
234
|
if retry_count <= RETRY_COUNT
|
|
@@ -225,6 +241,11 @@ module OpenC3
|
|
|
225
241
|
raise error
|
|
226
242
|
end
|
|
227
243
|
rescue StandardError => e
|
|
244
|
+
# Everything else, including Faraday::TimeoutError from a read timeout, is NOT
|
|
245
|
+
# retryable. A read timeout means the request was fully sent and the server may
|
|
246
|
+
# have already acted on it. Many endpoints are not idempotent (create_activity,
|
|
247
|
+
# script_run, cmd), so a retry risks duplicating the operation. It would also blow
|
|
248
|
+
# past the caller's deadline by RETRY_COUNT times.
|
|
228
249
|
@log[2] = "#{method} Exception: #{e.class}, #{e.message}, #{e.backtrace}"
|
|
229
250
|
disconnect()
|
|
230
251
|
error = "Api Exception: #{@log[0]} ::: #{@log[1]} ::: #{@log[2]}"
|
|
@@ -38,8 +38,9 @@ module OpenC3
|
|
|
38
38
|
# @param url [String] The url of openc3-cosmos-cmd-tlm-api http://openc3-cosmos-cmd-tlm-api:2901
|
|
39
39
|
# @param timeout [Float] The time to wait before disconnecting 1.0
|
|
40
40
|
# @param authentication [OpenC3Authentication] The authentication object if nill initialize will generate
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
# @param read_timeout [Float] The time to wait for the first response byte from the server
|
|
42
|
+
def initialize(url:, timeout: 1.0, authentication: nil, read_timeout: nil)
|
|
43
|
+
super(url: url, timeout: timeout, authentication: authentication, read_timeout: read_timeout)
|
|
43
44
|
@uri = URI("#{url}/openc3-api/api")
|
|
44
45
|
end
|
|
45
46
|
|
|
@@ -150,11 +150,14 @@ module OpenC3
|
|
|
150
150
|
if trigger['name'].nil? || trigger['group'].nil?
|
|
151
151
|
raise ReactionInputError.new "invalid trigger, must contain 'name' and 'group' keys: #{trigger}"
|
|
152
152
|
end
|
|
153
|
-
|
|
154
|
-
|
|
153
|
+
# Trigger names are only unique within a group so both parts identify
|
|
154
|
+
# the trigger. Keying on name alone rejects a reaction which references
|
|
155
|
+
# the same trigger name in two different groups.
|
|
156
|
+
trigger_key = "#{trigger['group']}__#{trigger['name']}"
|
|
157
|
+
unless trigger_hash[trigger_key].nil?
|
|
155
158
|
raise ReactionInputError.new "no duplicate triggers allowed: #{triggers}"
|
|
156
159
|
else
|
|
157
|
-
trigger_hash[
|
|
160
|
+
trigger_hash[trigger_key] = 1
|
|
158
161
|
end
|
|
159
162
|
end
|
|
160
163
|
return triggers
|
|
@@ -182,9 +182,11 @@ module OpenC3
|
|
|
182
182
|
# Seed settings from OPENC3_SETTING_<NAME> environment variables. Called by
|
|
183
183
|
# `openc3cli initsettings` during init container startup.
|
|
184
184
|
#
|
|
185
|
-
# By default a setting is
|
|
186
|
-
# value
|
|
187
|
-
#
|
|
185
|
+
# By default a setting is written when it does not exist yet, or when it
|
|
186
|
+
# still holds the value last seeded (see seeded_value?), so a changed env
|
|
187
|
+
# var takes effect but a value changed in the Admin Console survives a
|
|
188
|
+
# container restart. Set OPENC3_SETTINGS_OVERWRITE to write on every run
|
|
189
|
+
# instead.
|
|
188
190
|
#
|
|
189
191
|
# Nothing here aborts init by default. A bad setting name, a bad value, or a
|
|
190
192
|
# malformed control variable is reported on stdout and that one setting is
|
|
@@ -145,7 +145,12 @@ module OpenC3
|
|
|
145
145
|
# Handle .xtce files
|
|
146
146
|
extension = File.extname(filename).to_s.downcase
|
|
147
147
|
if extension == ".xtce" or extension == ".xml"
|
|
148
|
-
XtceParser.process(@commands, @telemetry, @warnings, filename, process_target_name)
|
|
148
|
+
target_name = XtceParser.process(@commands, @telemetry, @warnings, filename, process_target_name)
|
|
149
|
+
# XtceParser adds packets straight into @commands / @telemetry without
|
|
150
|
+
# going through finish_packet, so the ID lookup metadata finish_packet
|
|
151
|
+
# builds has to be built here instead. Without it Telemetry#identify
|
|
152
|
+
# and Commands#identify find no hash for the target and raise on nil.
|
|
153
|
+
build_xtce_id_metadata(target_name)
|
|
149
154
|
return
|
|
150
155
|
end
|
|
151
156
|
|
|
@@ -430,6 +435,36 @@ module OpenC3
|
|
|
430
435
|
|
|
431
436
|
protected
|
|
432
437
|
|
|
438
|
+
# Build the ID lookup metadata for every packet an XTCE file just added to
|
|
439
|
+
# the given target. Packets are walked in the order XtceParser left them in,
|
|
440
|
+
# which is the order the rest of COSMOS sees them, so duplicate ID values
|
|
441
|
+
# resolve the same way here as they do everywhere else.
|
|
442
|
+
#
|
|
443
|
+
# @param target_name [String] Target the XTCE file defined, or nil if the
|
|
444
|
+
# file defined no target
|
|
445
|
+
def build_xtce_id_metadata(target_name)
|
|
446
|
+
return unless target_name
|
|
447
|
+
|
|
448
|
+
@commands[target_name]&.each_value do |packet|
|
|
449
|
+
next if packet.virtual
|
|
450
|
+
|
|
451
|
+
if packet.subpacket
|
|
452
|
+
build_id_metadata(packet, @cmd_subpacket_id_value_hash, @cmd_subpacket_id_signature, @cmd_subpacket_unique_id_mode)
|
|
453
|
+
else
|
|
454
|
+
build_id_metadata(packet, @cmd_id_value_hash, @cmd_id_signature, @cmd_unique_id_mode)
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
@telemetry[target_name]&.each_value do |packet|
|
|
458
|
+
next if packet.virtual
|
|
459
|
+
|
|
460
|
+
if packet.subpacket
|
|
461
|
+
build_id_metadata(packet, @tlm_subpacket_id_value_hash, @tlm_subpacket_id_signature, @tlm_subpacket_unique_id_mode)
|
|
462
|
+
else
|
|
463
|
+
build_id_metadata(packet, @tlm_id_value_hash, @tlm_id_signature, @tlm_unique_id_mode)
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
|
|
433
468
|
def build_id_metadata(packet, id_value_hash, id_signature_hash, unique_id_mode_hash)
|
|
434
469
|
target_id_value_hash = id_value_hash[packet.target_name]
|
|
435
470
|
target_id_value_hash = {} unless target_id_value_hash
|
|
@@ -22,6 +22,10 @@ module OpenC3
|
|
|
22
22
|
class XtceParser
|
|
23
23
|
attr_accessor :current_target_name
|
|
24
24
|
|
|
25
|
+
# The target the file defined, still readable after parsing finishes.
|
|
26
|
+
# current_target_name is cleared by reset_processing_variables.
|
|
27
|
+
attr_reader :parsed_target_name
|
|
28
|
+
|
|
25
29
|
# Processes a XTCE formatted OpenC3 configuration file
|
|
26
30
|
#
|
|
27
31
|
# @param commands [Hash<String=>Packet>] Hash of all the command packets
|
|
@@ -32,8 +36,10 @@ module OpenC3
|
|
|
32
36
|
# that were created while parsing the configuration
|
|
33
37
|
# @param filename [String] The name of the configuration file
|
|
34
38
|
# @param target_name [String] Override the target name found in the XTCE file
|
|
39
|
+
# @return [String] The target name the packets were added under. The caller
|
|
40
|
+
# needs this to build the ID lookup metadata for those packets.
|
|
35
41
|
def self.process(commands, telemetry, warnings, filename, target_name = nil)
|
|
36
|
-
XtceParser.new(commands, telemetry, warnings, filename, target_name)
|
|
42
|
+
XtceParser.new(commands, telemetry, warnings, filename, target_name).parsed_target_name
|
|
37
43
|
end
|
|
38
44
|
|
|
39
45
|
def self.reverse_packet_order(target_name, cmd_or_tlm_hash)
|
|
@@ -91,6 +97,7 @@ module OpenC3
|
|
|
91
97
|
XtceParser.reverse_packet_order(@current_target_name, @commands)
|
|
92
98
|
XtceParser.reverse_packet_order(@current_target_name, @telemetry)
|
|
93
99
|
|
|
100
|
+
@parsed_target_name = @current_target_name
|
|
94
101
|
reset_processing_variables()
|
|
95
102
|
end
|
|
96
103
|
|
data/lib/openc3/script/tables.rb
CHANGED
|
@@ -22,11 +22,15 @@ module OpenC3
|
|
|
22
22
|
return _tables_handle_response(response, 'Failed to create binary')
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
# save: true (the default) writes the report into the target's storage next to
|
|
26
|
+
# the binary so it can be read back with get_target_file(). Pass save: false to
|
|
27
|
+
# get the contents in the return value without creating a file.
|
|
28
|
+
def table_create_report(filename, definition, table_name: nil, save: true, scope: $openc3_scope)
|
|
26
29
|
post_data = {}
|
|
27
30
|
post_data['binary'] = filename
|
|
28
31
|
post_data['definition'] = definition
|
|
29
32
|
post_data['table_name'] = table_name if table_name
|
|
33
|
+
post_data['save'] = save
|
|
30
34
|
response = $api_server.request('post', '/openc3-api/tables/report', json: true, data: post_data, scope: scope)
|
|
31
35
|
return _tables_handle_response(response, 'Failed to create report')
|
|
32
36
|
end
|
|
@@ -928,6 +928,10 @@ module OpenC3
|
|
|
928
928
|
# @return [Array, Hash] Array of [value, limits_state] pairs per row, or {} if no results.
|
|
929
929
|
# Single-row results return a flat array; multi-row results return array of arrays.
|
|
930
930
|
def self.tsdb_lookup(items, start_time:, end_time: nil, scope: "DEFAULT")
|
|
931
|
+
# Every item is a placeholder for an item which doesn't exist, so there's
|
|
932
|
+
# nothing to query. Return a single row of nil values, one per item.
|
|
933
|
+
return Array.new(items.length) { [nil, nil] } if items.all? { |item| item[2].nil? }
|
|
934
|
+
|
|
931
935
|
# Group items by db_shard number while preserving their original positions
|
|
932
936
|
db_shard_groups = {} # db_shard => { positions: [], items: [] }
|
|
933
937
|
items.each_with_index do |item, pos|
|
|
@@ -944,62 +948,36 @@ module OpenC3
|
|
|
944
948
|
return tsdb_lookup_single_db_shard(group[:items], start_time: start_time, end_time: end_time, scope: scope, db_shard: db_shard)
|
|
945
949
|
end
|
|
946
950
|
|
|
947
|
-
# Cross-db_shard: execute per-db_shard queries and merge results
|
|
948
|
-
|
|
951
|
+
# Cross-db_shard: execute per-db_shard queries (as arrays of rows) and merge results
|
|
952
|
+
db_shard_rows = {} # db_shard => rows
|
|
949
953
|
db_shard_groups.each do |db_shard, group|
|
|
950
|
-
result = tsdb_lookup_single_db_shard(group[:items], start_time: start_time, end_time: end_time, scope: scope, db_shard: db_shard)
|
|
951
|
-
|
|
954
|
+
result = tsdb_lookup_single_db_shard(group[:items], start_time: start_time, end_time: end_time, scope: scope, db_shard: db_shard, flatten: false)
|
|
955
|
+
db_shard_rows[db_shard] = result.is_a?(Array) ? result : []
|
|
952
956
|
end
|
|
953
957
|
|
|
954
|
-
#
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
# Merge them into the original item order.
|
|
964
|
-
merged = Array.new(items.length) { [nil, nil] }
|
|
965
|
-
db_shard_groups.each do |db_shard, group|
|
|
966
|
-
result = db_shard_results[db_shard]
|
|
967
|
-
next if result == {} || !result.is_a?(Array)
|
|
958
|
+
# Merge results positionally back into the original item order. Each db_shard
|
|
959
|
+
# may have different row counts so use the maximum row count and fill missing
|
|
960
|
+
# positions with [nil, nil]. If all db_shards returned empty, return empty.
|
|
961
|
+
max_rows = db_shard_rows.values.map(&:length).max
|
|
962
|
+
return {} if max_rows == 0
|
|
963
|
+
|
|
964
|
+
merged = Array.new(max_rows) { Array.new(items.length) { [nil, nil] } }
|
|
965
|
+
db_shard_groups.each do |db_shard, group|
|
|
966
|
+
db_shard_rows[db_shard].each_with_index do |row, row_num|
|
|
968
967
|
group[:positions].each_with_index do |orig_pos, db_shard_idx|
|
|
969
|
-
merged[orig_pos] =
|
|
968
|
+
merged[row_num][orig_pos] = row[db_shard_idx] if row[db_shard_idx]
|
|
970
969
|
end
|
|
971
970
|
end
|
|
972
|
-
merged
|
|
973
|
-
else
|
|
974
|
-
# Multi-row mode: find max row count across db_shards
|
|
975
|
-
max_rows = 0
|
|
976
|
-
db_shard_groups.each do |db_shard, _group|
|
|
977
|
-
result = db_shard_results[db_shard]
|
|
978
|
-
next if result == {}
|
|
979
|
-
count = result.is_a?(Array) ? result.length : 0
|
|
980
|
-
max_rows = count if count > max_rows
|
|
981
|
-
end
|
|
982
|
-
return {} if max_rows == 0
|
|
983
|
-
|
|
984
|
-
merged = Array.new(max_rows) { Array.new(items.length) { [nil, nil] } }
|
|
985
|
-
db_shard_groups.each do |db_shard, group|
|
|
986
|
-
result = db_shard_results[db_shard]
|
|
987
|
-
next if result == {}
|
|
988
|
-
rows = result.is_a?(Array) ? result : []
|
|
989
|
-
rows.each_with_index do |row, row_num|
|
|
990
|
-
next unless row.is_a?(Array)
|
|
991
|
-
group[:positions].each_with_index do |orig_pos, db_shard_idx|
|
|
992
|
-
merged[row_num][orig_pos] = row[db_shard_idx] if row[db_shard_idx]
|
|
993
|
-
end
|
|
994
|
-
end
|
|
995
|
-
end
|
|
996
|
-
merged
|
|
997
971
|
end
|
|
972
|
+
# Match the single db_shard behavior of returning a flat array for a single row
|
|
973
|
+
return merged[0] if max_rows == 1
|
|
974
|
+
merged
|
|
998
975
|
end
|
|
999
976
|
|
|
1000
977
|
# Execute a tsdb_lookup query against a single db_shard.
|
|
1001
978
|
# This contains the original ASOF JOIN logic for items all on the same QuestDB instance.
|
|
1002
|
-
|
|
979
|
+
# When flatten is true a single row result is returned as a flat array rather than an array of rows.
|
|
980
|
+
def self.tsdb_lookup_single_db_shard(items, start_time:, end_time: nil, scope: "DEFAULT", db_shard: 0, flatten: true)
|
|
1003
981
|
tables = {}
|
|
1004
982
|
names = []
|
|
1005
983
|
nil_count = 0
|
|
@@ -1060,6 +1038,11 @@ module OpenC3
|
|
|
1060
1038
|
end
|
|
1061
1039
|
end
|
|
1062
1040
|
|
|
1041
|
+
# Every item in this db_shard is a placeholder so there's no table to query.
|
|
1042
|
+
# Return no results and let tsdb_lookup fill these positions with [nil, nil]
|
|
1043
|
+
# when it merges the db_shards (an all placeholder lookup returns before this)
|
|
1044
|
+
return {} if tables.empty?
|
|
1045
|
+
|
|
1063
1046
|
# Add needed timestamp columns to the SELECT for calculated items
|
|
1064
1047
|
needed_timestamps.each do |table_index, ts_columns|
|
|
1065
1048
|
ts_columns.each do |ts_col|
|
|
@@ -1147,7 +1130,7 @@ module OpenC3
|
|
|
1147
1130
|
data[row_num].insert(position, [calculated_value, nil])
|
|
1148
1131
|
end
|
|
1149
1132
|
end
|
|
1150
|
-
if result.ntuples == 1
|
|
1133
|
+
if flatten and result.ntuples == 1
|
|
1151
1134
|
data = data[0]
|
|
1152
1135
|
end
|
|
1153
1136
|
data
|
data/lib/openc3/version.rb
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# encoding: ascii-8bit
|
|
2
2
|
|
|
3
|
-
OPENC3_VERSION = '7.4.
|
|
3
|
+
OPENC3_VERSION = '7.4.1'
|
|
4
4
|
module OpenC3
|
|
5
5
|
module Version
|
|
6
6
|
MAJOR = '7'
|
|
7
7
|
MINOR = '4'
|
|
8
|
-
PATCH = '
|
|
8
|
+
PATCH = '1'
|
|
9
9
|
OTHER = ''
|
|
10
|
-
BUILD = '
|
|
10
|
+
BUILD = '2d4089e0687654512c2eb84722a5a4fa699dd5c3'
|
|
11
11
|
end
|
|
12
|
-
VERSION = '7.4.
|
|
13
|
-
GEM_VERSION = '7.4.
|
|
12
|
+
VERSION = '7.4.1'
|
|
13
|
+
GEM_VERSION = '7.4.1'
|
|
14
14
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "<%= tool_name %>",
|
|
3
|
-
"version": "7.4.
|
|
3
|
+
"version": "7.4.1",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"ng": "ng",
|
|
6
6
|
"start": "ng serve",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"@angular/platform-browser-dynamic": "^18.2.6",
|
|
24
24
|
"@angular/router": "^18.2.6",
|
|
25
25
|
"@astrouxds/astro-web-components": "^7.24.0",
|
|
26
|
-
"@openc3/js-common": "7.4.
|
|
26
|
+
"@openc3/js-common": "7.4.1",
|
|
27
27
|
"rxjs": "~7.8.0",
|
|
28
28
|
"single-spa": "^5.9.5",
|
|
29
29
|
"single-spa-angular": "^9.2.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "<%= tool_name %>",
|
|
3
|
-
"version": "7.4.
|
|
3
|
+
"version": "7.4.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@astrouxds/astro-web-components": "^7.24.0",
|
|
14
|
-
"@openc3/js-common": "7.4.
|
|
15
|
-
"@openc3/vue-common": "7.4.
|
|
14
|
+
"@openc3/js-common": "7.4.1",
|
|
15
|
+
"@openc3/vue-common": "7.4.1",
|
|
16
16
|
"axios": "^1.7.7",
|
|
17
17
|
"date-fns": "^4.1.0",
|
|
18
18
|
"lodash": "^4.17.21",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "<%= widget_name %>",
|
|
3
|
-
"version": "7.4.
|
|
3
|
+
"version": "7.4.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@astrouxds/astro-web-components": "^7.24.0",
|
|
11
|
-
"@openc3/vue-common": "7.4.
|
|
11
|
+
"@openc3/vue-common": "7.4.1",
|
|
12
12
|
"vuetify": "^3.7.1"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openc3
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.4.
|
|
4
|
+
version: 7.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ryan Melton
|
|
@@ -562,6 +562,26 @@ dependencies:
|
|
|
562
562
|
- - "~>"
|
|
563
563
|
- !ruby/object:Gem::Version
|
|
564
564
|
version: '1.2'
|
|
565
|
+
- !ruby/object:Gem::Dependency
|
|
566
|
+
name: resolv
|
|
567
|
+
requirement: !ruby/object:Gem::Requirement
|
|
568
|
+
requirements:
|
|
569
|
+
- - "~>"
|
|
570
|
+
- !ruby/object:Gem::Version
|
|
571
|
+
version: '0.7'
|
|
572
|
+
- - ">="
|
|
573
|
+
- !ruby/object:Gem::Version
|
|
574
|
+
version: 0.7.2
|
|
575
|
+
type: :runtime
|
|
576
|
+
prerelease: false
|
|
577
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
578
|
+
requirements:
|
|
579
|
+
- - "~>"
|
|
580
|
+
- !ruby/object:Gem::Version
|
|
581
|
+
version: '0.7'
|
|
582
|
+
- - ">="
|
|
583
|
+
- !ruby/object:Gem::Version
|
|
584
|
+
version: 0.7.2
|
|
565
585
|
- !ruby/object:Gem::Dependency
|
|
566
586
|
name: resolv-replace
|
|
567
587
|
requirement: !ruby/object:Gem::Requirement
|