labimotion 2.3.3 → 2.4.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 +0 -23
- data/lib/labimotion/apis/element_variation_api.rb +3 -58
- data/lib/labimotion/apis/generic_dataset_api.rb +1 -3
- data/lib/labimotion/apis/generic_element_api.rb +24 -9
- data/lib/labimotion/apis/segment_api.rb +1 -5
- data/lib/labimotion/collection/import.rb +3 -36
- data/lib/labimotion/constants.rb +0 -6
- data/lib/labimotion/entities/application_entity.rb +1 -5
- data/lib/labimotion/entities/generic_klass_entity.rb +0 -11
- data/lib/labimotion/entities/properties_entity.rb +28 -0
- data/lib/labimotion/helpers/element_helpers.rb +5 -27
- data/lib/labimotion/helpers/param_helpers.rb +1 -0
- data/lib/labimotion/libs/converter.rb +22 -99
- data/lib/labimotion/libs/linked_element.rb +193 -0
- data/lib/labimotion/libs/nmr_mapper.rb +4 -34
- data/lib/labimotion/models/concerns/attachment_converter.rb +0 -24
- data/lib/labimotion/models/concerns/datasetable.rb +5 -16
- data/lib/labimotion/models/concerns/element_fetchable.rb +2 -3
- data/lib/labimotion/models/concerns/segmentable.rb +5 -24
- data/lib/labimotion/models/element.rb +2 -3
- data/lib/labimotion/utils/mapper_utils.rb +5 -30
- data/lib/labimotion/utils/prop.rb +0 -1
- data/lib/labimotion/utils/units.rb +32 -67
- data/lib/labimotion/version.rb +1 -1
- data/lib/labimotion.rb +1 -12
- metadata +5 -25
- data/lib/labimotion/libs/element_variation_column_set.rb +0 -426
- data/lib/labimotion/libs/element_variation_header.rb +0 -119
- data/lib/labimotion/libs/export_element_variations.rb +0 -245
- data/lib/labimotion/libs/export_tpa_segments.rb +0 -53
- data/lib/labimotion/libs/import_element_variations.rb +0 -411
- data/lib/labimotion/libs/klass_version.rb +0 -66
- data/lib/labimotion/libs/release_metadata.rb +0 -56
|
@@ -6,7 +6,6 @@ require 'json'
|
|
|
6
6
|
require 'date'
|
|
7
7
|
require 'labimotion/version'
|
|
8
8
|
require 'labimotion/utils/utils'
|
|
9
|
-
require 'labimotion/utils/mapper_utils'
|
|
10
9
|
require 'labimotion/helpers/converter_helpers'
|
|
11
10
|
|
|
12
11
|
# rubocop: disable Metrics/AbcSize
|
|
@@ -21,37 +20,27 @@ module Labimotion
|
|
|
21
20
|
end
|
|
22
21
|
|
|
23
22
|
def self.process_ds(id, current_user = {})
|
|
24
|
-
|
|
25
|
-
# Savepoint: a failure leaves no half-built dataset and keeps the caller's transaction usable
|
|
26
|
-
Attachment.transaction(requires_new: true) { map_ds(id, current_user, ctx) }
|
|
27
|
-
rescue StandardError => e
|
|
28
|
-
Labimotion::Converter.logger.error ["Att ID: #{ctx[:att]&.id}, OLS: #{ctx[:ols]}", "DSR: #{ctx[:dsr]}", e.message, *e.backtrace].join($INPUT_RECORD_SEPARATOR)
|
|
29
|
-
Labimotion.report_exception(e)
|
|
30
|
-
# Terminal state: do not fail the request, and do not retry on the next save
|
|
31
|
-
ctx[:att]&.update_column(:con_state, Labimotion::ConState::ERROR)
|
|
32
|
-
Labimotion::ConState::ERROR
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def self.map_ds(id, current_user, ctx)
|
|
36
|
-
att = ctx[:att] = Attachment.find_by(id: id, con_state: Labimotion::ConState::CONVERTED)
|
|
23
|
+
att = Attachment.find_by(id: id, con_state: Labimotion::ConState::CONVERTED)
|
|
37
24
|
return if att.nil? || att.attachable_id.nil? || att.attachable_type != 'Container' || att.filename.split('.')&.last != 'zip'
|
|
38
25
|
|
|
39
26
|
eln_ds = Container.find_by(id: att.attachable_id, container_type: 'dataset')
|
|
40
27
|
return if eln_ds.nil? || eln_ds.parent.nil? || eln_ds.parent&.container_type != 'analysis'
|
|
41
28
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
29
|
+
dsr = []
|
|
30
|
+
ols = nil
|
|
31
|
+
Zip::File.open(att.attachment_attacher.file.url) do |zip_file|
|
|
32
|
+
res = Labimotion::Converter.collect_metadata(zip_file, current_user) if att.filename.split('.')&.last == 'zip'
|
|
33
|
+
ols = res[:o] unless res&.dig(:o).nil?
|
|
34
|
+
dsr.push(res[:d]) unless res&.dig(:d).nil?
|
|
35
|
+
end
|
|
36
|
+
dsr.flatten!
|
|
37
|
+
dataset = build_ds(att.attachable_id, ols)
|
|
38
|
+
update_ds(dataset, dsr, current_user) if dataset.present?
|
|
51
39
|
att.update_column(:con_state, Labimotion::ConState::COMPLETED)
|
|
52
|
-
|
|
40
|
+
rescue StandardError => e
|
|
41
|
+
Labimotion::Converter.logger.error ["Att ID: #{att&.id}, OLS: #{ols}", "DSR: #{dsr}", e.message, *e.backtrace].join($INPUT_RECORD_SEPARATOR)
|
|
42
|
+
raise e
|
|
53
43
|
end
|
|
54
|
-
private_class_method :map_ds
|
|
55
44
|
|
|
56
45
|
def self.uri(api_name)
|
|
57
46
|
url = Rails.configuration.converter.url
|
|
@@ -104,40 +93,13 @@ module Labimotion
|
|
|
104
93
|
{ a: oa, f: folder }
|
|
105
94
|
end
|
|
106
95
|
|
|
107
|
-
def self.collect_reaction_converter_metadata(oat, zip_file)
|
|
108
|
-
zip_file.each do |entry|
|
|
109
|
-
next unless entry.name == 'metadata/reaction_variation.json'
|
|
110
|
-
|
|
111
|
-
# Create temp file
|
|
112
|
-
tmp_file = Tempfile.new(['reaction_variation', '.json'])
|
|
113
|
-
tmp_file.binmode
|
|
114
|
-
|
|
115
|
-
# Write ZIP entry content into temp file
|
|
116
|
-
entry.extract(tmp_file.path) { true }
|
|
117
|
-
|
|
118
|
-
# Create attachment
|
|
119
|
-
att = Attachment.create!(
|
|
120
|
-
filename: 'reaction_variation.json',
|
|
121
|
-
file_path: tmp_file.path,
|
|
122
|
-
content_type: 'application/json',
|
|
123
|
-
attachable_id: oat.attachable_id,
|
|
124
|
-
attachable_type: 'Container',
|
|
125
|
-
con_state: Labimotion::ConState::CONVERTED,
|
|
126
|
-
created_by: oat.created_by,
|
|
127
|
-
created_for: oat.created_for
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
att.save! if att.valid?
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
96
|
def self.collect_metadata(zip_file, current_user = {}) # rubocop: disable Metrics/PerceivedComplexity
|
|
135
97
|
dsr = []
|
|
136
98
|
ols = nil
|
|
137
99
|
zip_file.each do |entry|
|
|
138
100
|
next unless entry.name == 'metadata/converter.json'
|
|
139
101
|
|
|
140
|
-
metadata =
|
|
102
|
+
metadata = entry.get_input_stream.read.force_encoding('UTF-8')
|
|
141
103
|
jdata = JSON.parse(metadata)
|
|
142
104
|
|
|
143
105
|
ols = jdata['ols']
|
|
@@ -163,6 +125,7 @@ module Labimotion
|
|
|
163
125
|
tmp_file.rewind
|
|
164
126
|
|
|
165
127
|
filename = oat.filename
|
|
128
|
+
# name = "#{File.basename(filename, '.*')}.zip"
|
|
166
129
|
name = "#{File.basename(filename, '.*')}#{File.extname(filename) == '.zip' ? '.bagit.zip' : '.zip'}"
|
|
167
130
|
att = Attachment.new(
|
|
168
131
|
filename: name,
|
|
@@ -202,9 +165,8 @@ module Labimotion
|
|
|
202
165
|
)
|
|
203
166
|
end
|
|
204
167
|
if response.ok?
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
state == Labimotion::ConState::ERROR ? state : Labimotion::ConState::PROCESSED
|
|
168
|
+
Labimotion::Converter.handle_response(data[:a], response, current_user)
|
|
169
|
+
Labimotion::ConState::PROCESSED
|
|
208
170
|
else
|
|
209
171
|
Labimotion::Converter.logger.error ["Converter Response Error: id: [#{data[:a]&.id}], filename: [#{data[:a]&.filename}], response: #{response}"].join($INPUT_RECORD_SEPARATOR)
|
|
210
172
|
Labimotion::ConState::ERROR
|
|
@@ -285,22 +247,14 @@ module Labimotion
|
|
|
285
247
|
dsr.each do |ds|
|
|
286
248
|
layer = layers[ds[:layer]]
|
|
287
249
|
next if layer.blank? || layer[Labimotion::Prop::FIELDS].blank?
|
|
288
|
-
is_unit = ds[:field].start_with?(Prop::CONVERTER_FIELD_UINT_PREFIX)
|
|
289
|
-
next if is_unit && ds[:value].nil?
|
|
290
|
-
|
|
291
|
-
field_name = ds[:field].delete_prefix(Prop::CONVERTER_FIELD_UINT_PREFIX)
|
|
292
250
|
|
|
293
|
-
fields = layer[Labimotion::Prop::FIELDS].select{ |f| f['field'] ==
|
|
251
|
+
fields = layer[Labimotion::Prop::FIELDS].select{ |f| f['field'] == ds[:field] }
|
|
294
252
|
fi = fields&.first
|
|
295
253
|
next if fi.blank?
|
|
296
254
|
|
|
297
255
|
idx = layer[Labimotion::Prop::FIELDS].find_index(fi)
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
else
|
|
301
|
-
fi['value'] = ds[:value]
|
|
302
|
-
fi['device'] = ds[:device] || ds[:value]
|
|
303
|
-
end
|
|
256
|
+
fi['value'] = ds[:value]
|
|
257
|
+
fi['device'] = ds[:device] || ds[:value]
|
|
304
258
|
new_prop[Labimotion::Prop::LAYERS][ds[:layer]][Labimotion::Prop::FIELDS][idx] = fi
|
|
305
259
|
end
|
|
306
260
|
element = Container.find(dataset.element_id)&.root_element
|
|
@@ -366,37 +320,6 @@ module Labimotion
|
|
|
366
320
|
res
|
|
367
321
|
end
|
|
368
322
|
|
|
369
|
-
def self.test_conversions(tmpfile, format)
|
|
370
|
-
res = {}
|
|
371
|
-
File.open(tmpfile.path, 'r') do |file|
|
|
372
|
-
body = { file: file, format: format }
|
|
373
|
-
response = HTTParty.post(
|
|
374
|
-
uri('conversions'),
|
|
375
|
-
basic_auth: auth,
|
|
376
|
-
body: body,
|
|
377
|
-
timeout: timeout,
|
|
378
|
-
)
|
|
379
|
-
res = response.parsed_response
|
|
380
|
-
end
|
|
381
|
-
res
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
def self.restore(profile_id, version, hard)
|
|
385
|
-
body = { hard: hard }
|
|
386
|
-
response = HTTParty.post(
|
|
387
|
-
uri("profiles/restore/#{profile_id}/#{version}"),
|
|
388
|
-
headers: {
|
|
389
|
-
"Content-Type" => "application/json"
|
|
390
|
-
},
|
|
391
|
-
basic_auth: auth,
|
|
392
|
-
body: body.to_json,
|
|
393
|
-
timeout: timeout,
|
|
394
|
-
)
|
|
395
|
-
res = response.parsed_response
|
|
396
|
-
|
|
397
|
-
res
|
|
398
|
-
end
|
|
399
|
-
|
|
400
323
|
def self.metadata(id, current_user)
|
|
401
324
|
att = Attachment.find(id)
|
|
402
325
|
return if att.nil? || att.attachable_id.nil? || att.attachable_type != Labimotion::Prop::CONTAINER
|
|
@@ -411,4 +334,4 @@ end
|
|
|
411
334
|
# rubocop: enable Metrics/AbcSize
|
|
412
335
|
# rubocop: enable Metrics/MethodLength
|
|
413
336
|
# rubocop: enable Metrics/ClassLength
|
|
414
|
-
# rubocop: enable Metrics/CyclomaticComplexity
|
|
337
|
+
# rubocop: enable Metrics/CyclomaticComplexity
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Labimotion
|
|
4
|
+
# Resolves the "inline attributes" of an element linked from a generic
|
|
5
|
+
# drag_element field.
|
|
6
|
+
#
|
|
7
|
+
# A drag_element link target is identified by the stored value's +el_klass+:
|
|
8
|
+
# * a generic element klass name -> the target is a Labimotion::Element and
|
|
9
|
+
# its selectable attributes are its own property fields (layer + field);
|
|
10
|
+
# * a permit-target key (reaction, sample, molecule, wellplate, screen,
|
|
11
|
+
# research_plan, device_description) -> the target is a standard ELN model
|
|
12
|
+
# and its selectable attributes are a curated set of database columns.
|
|
13
|
+
#
|
|
14
|
+
# Used both to list the attributes a user can pick (#available) and to refresh
|
|
15
|
+
# the current values of a previously stored selection (#resolve).
|
|
16
|
+
class LinkedElement
|
|
17
|
+
SOURCE_PROPERTY = 'property'
|
|
18
|
+
SOURCE_COLUMN = 'column'
|
|
19
|
+
|
|
20
|
+
# el_klass (permit-target key) => model class name
|
|
21
|
+
MODELS = {
|
|
22
|
+
'reaction' => '::Reaction',
|
|
23
|
+
'sample' => '::Sample',
|
|
24
|
+
'molecule' => '::Molecule',
|
|
25
|
+
'wellplate' => '::Wellplate',
|
|
26
|
+
'screen' => '::Screen',
|
|
27
|
+
'research_plan' => '::ResearchPlan',
|
|
28
|
+
'device_description' => '::DeviceDescription'
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
# Curated, scalar columns exposed per type: [column/method, label, unit_column?]
|
|
32
|
+
COLUMNS = {
|
|
33
|
+
'reaction' => [
|
|
34
|
+
%w[name Name], %w[short_label], ['status', 'Status'], ['role', 'Role'],
|
|
35
|
+
['rxno', 'Reaction Type'], ['conditions', 'Conditions'],
|
|
36
|
+
['duration', 'Duration'], %w[solvent Solvent]
|
|
37
|
+
],
|
|
38
|
+
'sample' => [
|
|
39
|
+
%w[name Name], %w[short_label], ['external_label', 'External Label'],
|
|
40
|
+
['sum_formula', 'Sum Formula'], ['molecular_mass', 'Molecular Mass'],
|
|
41
|
+
%w[purity Purity], %w[density Density],
|
|
42
|
+
['real_amount_value', 'Real Amount', 'real_amount_unit'],
|
|
43
|
+
['target_amount_value', 'Target Amount', 'target_amount_unit'],
|
|
44
|
+
%w[location Location]
|
|
45
|
+
],
|
|
46
|
+
'molecule' => [
|
|
47
|
+
['iupac_name', 'IUPAC Name'], ['sum_formular', 'Sum Formula'],
|
|
48
|
+
['molecular_weight', 'Molecular Weight'],
|
|
49
|
+
['exact_molecular_weight', 'Exact Molecular Weight'],
|
|
50
|
+
['cano_smiles', 'Canonical SMILES'], ['melting_point', 'Melting Point'],
|
|
51
|
+
['boiling_point', 'Boiling Point'], %w[density Density]
|
|
52
|
+
],
|
|
53
|
+
'wellplate' => [
|
|
54
|
+
%w[name Name], %w[short_label], ['description', 'Description'],
|
|
55
|
+
%w[width Width], %w[height Height]
|
|
56
|
+
],
|
|
57
|
+
'screen' => [
|
|
58
|
+
%w[name Name], ['description', 'Description'], %w[result Result],
|
|
59
|
+
%w[collaborator Collaborator], ['conditions', 'Conditions'],
|
|
60
|
+
%w[requirements Requirements]
|
|
61
|
+
],
|
|
62
|
+
'research_plan' => [
|
|
63
|
+
%w[name Name], %w[short_label]
|
|
64
|
+
],
|
|
65
|
+
'device_description' => [
|
|
66
|
+
%w[name Name], %w[short_label], ['serial_number', 'Serial Number'],
|
|
67
|
+
['device_class', 'Device Class'], ['operation_mode', 'Operation Mode'],
|
|
68
|
+
['application_name', 'Application Name'], ['vendor_url', 'Vendor URL'],
|
|
69
|
+
%w[institute Institute], %w[building Building], %w[room Room]
|
|
70
|
+
]
|
|
71
|
+
}.freeze
|
|
72
|
+
|
|
73
|
+
# Property field types whose value is a scalar worth surfacing inline.
|
|
74
|
+
PROPERTY_TYPES = %w[
|
|
75
|
+
text textarea number integer select select-multi checkbox
|
|
76
|
+
datetime system-defined formula-field text-formula ontology-select
|
|
77
|
+
].freeze
|
|
78
|
+
|
|
79
|
+
def initialize(el_klass, el_id, el_type = nil)
|
|
80
|
+
@el_klass = el_klass.to_s
|
|
81
|
+
@el_type = el_type.to_s
|
|
82
|
+
@el_id = el_id
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# drag_element keeps the linked class in +el_klass+; drag_sample / drag_molecule
|
|
86
|
+
# keep it in +el_type+ (and may omit el_klass). Resolve one effective dispatch key.
|
|
87
|
+
def target_key
|
|
88
|
+
return @target_key if defined?(@target_key)
|
|
89
|
+
|
|
90
|
+
@target_key =
|
|
91
|
+
if MODELS.key?(@el_klass) || generic_klass?(@el_klass)
|
|
92
|
+
@el_klass
|
|
93
|
+
elsif MODELS.key?(@el_type)
|
|
94
|
+
@el_type
|
|
95
|
+
else
|
|
96
|
+
''
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def column_based?
|
|
101
|
+
MODELS.key?(target_key)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def generic?
|
|
105
|
+
generic_klass?(target_key)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def record
|
|
109
|
+
return @record if defined?(@record)
|
|
110
|
+
|
|
111
|
+
@record =
|
|
112
|
+
if column_based?
|
|
113
|
+
MODELS[target_key].safe_constantize&.find_by(id: @el_id)
|
|
114
|
+
elsif generic?
|
|
115
|
+
Labimotion::Element.find_by(id: @el_id)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# All attributes the user can pick, each with its current value.
|
|
120
|
+
def available
|
|
121
|
+
return generic_attributes if generic?
|
|
122
|
+
return column_attributes if column_based?
|
|
123
|
+
|
|
124
|
+
[]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Refresh the values/labels of a previously stored selection. Selections whose
|
|
128
|
+
# source attribute no longer exists are kept but flagged as missing; for an
|
|
129
|
+
# unknown/unsupported el_klass the selection is returned untouched.
|
|
130
|
+
def resolve(selected)
|
|
131
|
+
return selected unless selected.is_a?(Array)
|
|
132
|
+
return selected unless generic? || column_based?
|
|
133
|
+
|
|
134
|
+
index = available.index_by { |a| [a['source'], a['key']] }
|
|
135
|
+
selected.map do |attr|
|
|
136
|
+
next attr unless attr.is_a?(Hash)
|
|
137
|
+
|
|
138
|
+
index[[attr['source'], attr['key']]] || attr.merge('value' => nil, 'missing' => true)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
private
|
|
143
|
+
|
|
144
|
+
def generic_klass?(name)
|
|
145
|
+
name.present? && !MODELS.key?(name) && Labimotion::ElementKlass.exists?(name: name)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def generic_attributes
|
|
149
|
+
el = record
|
|
150
|
+
return [] unless el&.properties.is_a?(Hash)
|
|
151
|
+
|
|
152
|
+
attrs = []
|
|
153
|
+
layers = el.properties[Labimotion::Prop::LAYERS] || {}
|
|
154
|
+
layers.values.sort_by { |layer| layer['position'] || 0 }.each do |layer|
|
|
155
|
+
(layer[Labimotion::Prop::FIELDS] || []).each do |field|
|
|
156
|
+
next unless PROPERTY_TYPES.include?(field['type'])
|
|
157
|
+
|
|
158
|
+
attr = {
|
|
159
|
+
'source' => SOURCE_PROPERTY,
|
|
160
|
+
'key' => "#{layer['key']}::#{field['field']}",
|
|
161
|
+
'label' => field['label'].presence || field['field'],
|
|
162
|
+
'type' => field['type'],
|
|
163
|
+
'value' => field['value'],
|
|
164
|
+
'group' => layer['label'].presence || layer['key']
|
|
165
|
+
}
|
|
166
|
+
attr['value_system'] = field['value_system'] if field['value_system'].present?
|
|
167
|
+
attrs << attr
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
attrs
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def column_attributes
|
|
174
|
+
rec = record
|
|
175
|
+
return [] if rec.blank?
|
|
176
|
+
|
|
177
|
+
(COLUMNS[target_key] || []).filter_map do |col, label, unit_col|
|
|
178
|
+
next unless rec.respond_to?(col)
|
|
179
|
+
|
|
180
|
+
attr = {
|
|
181
|
+
'source' => SOURCE_COLUMN,
|
|
182
|
+
'key' => col,
|
|
183
|
+
'label' => label.presence || col.to_s.humanize,
|
|
184
|
+
'type' => SOURCE_COLUMN,
|
|
185
|
+
'value' => rec.public_send(col),
|
|
186
|
+
'group' => target_key
|
|
187
|
+
}
|
|
188
|
+
attr['value_system'] = rec.public_send(unit_col) if unit_col && rec.respond_to?(unit_col) && rec.public_send(unit_col).present?
|
|
189
|
+
attr
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
@@ -30,21 +30,7 @@ module Labimotion
|
|
|
30
30
|
|
|
31
31
|
class << self
|
|
32
32
|
def process_ds(id, current_user = {})
|
|
33
|
-
|
|
34
|
-
# Savepoint: a failure leaves no half-built dataset and keeps the caller's transaction usable
|
|
35
|
-
Attachment.transaction(requires_new: true) { map_ds(id, current_user, ctx) }
|
|
36
|
-
rescue StandardError => e
|
|
37
|
-
Labimotion.log_exception(e, current_user)
|
|
38
|
-
# Not a readable zip: nothing to map, and not an error
|
|
39
|
-
return Labimotion::ConState::NONE if invalid_archive?(e, ctx[:att])
|
|
40
|
-
|
|
41
|
-
# Terminal state: do not fail the request, and do not retry on the next save
|
|
42
|
-
Labimotion.report_exception(e)
|
|
43
|
-
Labimotion::ConState::ERROR
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def map_ds(id, current_user, ctx)
|
|
47
|
-
att = ctx[:att] = find_attachment(id)
|
|
33
|
+
att = find_attachment(id)
|
|
48
34
|
return Labimotion::ConState::NONE if att.nil?
|
|
49
35
|
|
|
50
36
|
result = process(att)
|
|
@@ -55,10 +41,9 @@ module Labimotion
|
|
|
55
41
|
|
|
56
42
|
def process(att)
|
|
57
43
|
config = Labimotion::MapperUtils.load_brucker_config
|
|
58
|
-
|
|
59
|
-
return Labimotion::MapperUtils.check_if_bagit(attacher&.file&.url) if config.nil?
|
|
60
|
-
|
|
44
|
+
return if config.nil?
|
|
61
45
|
|
|
46
|
+
attacher = att&.attachment_attacher
|
|
62
47
|
extracted_data = Labimotion::MapperUtils.extract_data_from_zip(attacher&.file&.url, config['sourceMap'])
|
|
63
48
|
return if extracted_data.nil?
|
|
64
49
|
|
|
@@ -147,20 +132,6 @@ module Labimotion
|
|
|
147
132
|
Attachment.find_by(id: id, con_state: Labimotion::ConState::NMR)
|
|
148
133
|
end
|
|
149
134
|
|
|
150
|
-
# True when the upload itself is not a readable zip. A missing or changed file, an encrypted zip
|
|
151
|
-
# or an unsupported compression method is not covered and stays ERROR.
|
|
152
|
-
def invalid_archive?(error, att)
|
|
153
|
-
return false unless error.instance_of?(Zip::Error) || error.is_a?(Zip::DecompressionError)
|
|
154
|
-
|
|
155
|
-
file = att&.attachment_attacher&.file
|
|
156
|
-
path = file&.url
|
|
157
|
-
on_disk = path && File.size?(path)
|
|
158
|
-
return false if on_disk.nil?
|
|
159
|
-
|
|
160
|
-
recorded = file.size
|
|
161
|
-
recorded.nil? || recorded == on_disk
|
|
162
|
-
end
|
|
163
|
-
|
|
164
135
|
def handle_process_result(result, att, id, current_user)
|
|
165
136
|
if result[:is_bagit]
|
|
166
137
|
handle_bagit_result(att, id, current_user)
|
|
@@ -174,8 +145,7 @@ module Labimotion
|
|
|
174
145
|
def handle_bagit_result(att, id, current_user)
|
|
175
146
|
att.update_column(:con_state, Labimotion::ConState::CONVERTED)
|
|
176
147
|
Labimotion::Converter.metadata(id, current_user)
|
|
177
|
-
|
|
178
|
-
Attachment.find_by(id: id)&.con_state || Labimotion::ConState::NONE
|
|
148
|
+
Labimotion::ConState::COMPLETED
|
|
179
149
|
end
|
|
180
150
|
|
|
181
151
|
def invalid_metadata?(result)
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'zip'
|
|
4
|
-
|
|
5
3
|
module Labimotion
|
|
6
4
|
module AttachmentConverter
|
|
7
5
|
ACCEPTED_FORMATS = (Rails.configuration.try(:converter).try(:ext) || []).freeze
|
|
@@ -29,8 +27,6 @@ module Labimotion
|
|
|
29
27
|
|
|
30
28
|
return if attachable_id.nil? && con_state != Labimotion::ConState::WAIT
|
|
31
29
|
|
|
32
|
-
# No attachable_id-changed guard: an attachment created already attached must still be processed.
|
|
33
|
-
# Each branch leaves its pending state, so the work runs once.
|
|
34
30
|
current_user = User.find_by(id: created_by)
|
|
35
31
|
case con_state
|
|
36
32
|
when Labimotion::ConState::NMR
|
|
@@ -44,26 +40,6 @@ module Labimotion
|
|
|
44
40
|
Labimotion::Converter.metadata(id, current_user)
|
|
45
41
|
end
|
|
46
42
|
end
|
|
47
|
-
|
|
48
|
-
# Run the zip mapping again for an attachment in ERROR, e.g. after its file was restored.
|
|
49
|
-
# Returns the new con_state.
|
|
50
|
-
def reprocess_converter!
|
|
51
|
-
return con_state unless has_attribute?(:con_state) && File.extname(filename.to_s.downcase) == '.zip'
|
|
52
|
-
|
|
53
|
-
path = attachment_attacher&.file&.url
|
|
54
|
-
return con_state unless path && File.size?(path)
|
|
55
|
-
|
|
56
|
-
self.con_state = converter_output?(path) ? Labimotion::ConState::CONVERTED : Labimotion::ConState::NMR
|
|
57
|
-
update_column(:con_state, con_state)
|
|
58
|
-
exec_converter
|
|
59
|
-
reload.con_state
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
private def converter_output?(path)
|
|
63
|
-
!Zip::File.new(path).find_entry('metadata/converter.json').nil?
|
|
64
|
-
rescue Zip::Error
|
|
65
|
-
false # not a readable zip: the NMR mapper classifies it
|
|
66
|
-
end
|
|
67
43
|
end
|
|
68
44
|
end
|
|
69
45
|
end
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
# Datasetable concern
|
|
4
4
|
require 'labimotion/utils/utils'
|
|
5
|
-
require 'labimotion/libs/klass_version'
|
|
6
5
|
|
|
7
6
|
module Labimotion
|
|
8
7
|
## Datasetable concern
|
|
@@ -39,10 +38,7 @@ module Labimotion
|
|
|
39
38
|
|
|
40
39
|
args = dataset_args[:dataset]
|
|
41
40
|
dataset_klass_id = args[:dataset_klass_id]
|
|
42
|
-
|
|
43
|
-
klass = Labimotion::DatasetKlass.with_deleted.find_by(id: dataset_klass_id)
|
|
44
|
-
return if klass.nil? || klass.deleted_at.present?
|
|
45
|
-
|
|
41
|
+
klass = Labimotion::DatasetKlass.find_by(id: dataset_klass_id)
|
|
46
42
|
uuid = SecureRandom.uuid
|
|
47
43
|
metadata = args[:metadata] || {}
|
|
48
44
|
props = args[:properties]
|
|
@@ -50,23 +46,16 @@ module Labimotion
|
|
|
50
46
|
props['identifier'] = klass.identifier if klass.identifier.present?
|
|
51
47
|
props['uuid'] = uuid
|
|
52
48
|
props['klass'] = 'Dataset'
|
|
49
|
+
props['klass_uuid'] = klass.uuid
|
|
53
50
|
props = Labimotion::VocabularyHandler.update_vocabularies(props, dataset_args[:current_user], dataset_args[:element])
|
|
54
51
|
|
|
55
52
|
ds = Labimotion::Dataset.find_by(element_type: self.class.name, element_id: id)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
# for a new dataset; on a switch it may still describe the old klass.
|
|
59
|
-
on_same_klass = ds if ds.present? && ds.dataset_klass_id == klass.id
|
|
60
|
-
klass_uuid, release =
|
|
61
|
-
Labimotion::KlassVersion.for_record(klass, klass.dataset_klasses_revisions, props['klass_uuid'], on_same_klass,
|
|
62
|
-
client_release: (args[:properties_release] if ds.nil?))
|
|
63
|
-
props['klass_uuid'] = klass_uuid if klass_uuid.present?
|
|
64
|
-
if ds.present? && (ds.klass_uuid != klass_uuid || ds.properties != props || ds.metadata != metadata)
|
|
65
|
-
ds.update!(properties_release: release, uuid: uuid, dataset_klass_id: dataset_klass_id, properties: props, klass_uuid: klass_uuid, metadata: metadata)
|
|
53
|
+
if ds.present? && (ds.klass_uuid != klass.uuid || ds.properties != props || ds.metadata != metadata)
|
|
54
|
+
ds.update!(properties_release: klass.properties_release, uuid: uuid, dataset_klass_id: dataset_klass_id, properties: props, klass_uuid: klass.uuid, metadata: metadata)
|
|
66
55
|
end
|
|
67
56
|
return if ds.present?
|
|
68
57
|
|
|
69
|
-
Labimotion::Dataset.create!(properties_release:
|
|
58
|
+
Labimotion::Dataset.create!(properties_release: klass.properties_release, uuid: uuid, dataset_klass_id: dataset_klass_id, element_type: self.class.name, element_id: id, properties: props, klass_uuid: klass.uuid, metadata: metadata)
|
|
70
59
|
end
|
|
71
60
|
|
|
72
61
|
def destroy_datasetable
|
|
@@ -26,10 +26,9 @@ module Labimotion
|
|
|
26
26
|
joins(collections: :user).where(collections: { user_id: user_id })
|
|
27
27
|
)
|
|
28
28
|
|
|
29
|
-
# Shared records
|
|
30
|
-
# element models themselves, so join through the collections association.
|
|
29
|
+
# Shared records
|
|
31
30
|
shared = apply_filters.call(
|
|
32
|
-
left_joins(
|
|
31
|
+
left_joins(:collection_shares).where(collection_shares: { shared_with_id: user_id })
|
|
33
32
|
)
|
|
34
33
|
|
|
35
34
|
# Combine (remove duplicates), order, and limit
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require 'labimotion/utils/utils'
|
|
4
4
|
require 'labimotion/libs/attachment_handler'
|
|
5
|
-
require 'labimotion/libs/klass_version'
|
|
6
5
|
|
|
7
6
|
module Labimotion
|
|
8
7
|
# Segmentable concern
|
|
@@ -47,14 +46,6 @@ module Labimotion
|
|
|
47
46
|
end
|
|
48
47
|
end
|
|
49
48
|
|
|
50
|
-
# The release a new segment was built on: its properties first, then the template the client
|
|
51
|
-
# loaded, then the klass_uuid a copied row carried.
|
|
52
|
-
def submitted_segment_uuid(seg, props)
|
|
53
|
-
segment_klass = seg['segment_klass']
|
|
54
|
-
[props['klass_uuid'], (segment_klass['uuid'] if segment_klass.is_a?(Hash)), seg['klass_uuid']]
|
|
55
|
-
.map { |uuid| Labimotion::KlassVersion.string_uuid(uuid) }.find(&:present?)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
49
|
def touch_vocabulary(current_user)
|
|
59
50
|
touch_element_properties(current_user) if instance_of?(::Labimotion::Element)
|
|
60
51
|
touch_segments_properties(current_user)
|
|
@@ -66,10 +57,7 @@ module Labimotion
|
|
|
66
57
|
|
|
67
58
|
current_user = User.find_by(id: args[:current_user_id])
|
|
68
59
|
args_segments.each do |seg|
|
|
69
|
-
|
|
70
|
-
klass = Labimotion::SegmentKlass.with_deleted.find_by(id: seg['segment_klass_id'])
|
|
71
|
-
next if klass.nil? || klass.deleted_at.present?
|
|
72
|
-
|
|
60
|
+
klass = Labimotion::SegmentKlass.find_by(id: seg['segment_klass_id'])
|
|
73
61
|
uuid = SecureRandom.uuid
|
|
74
62
|
props = seg['properties']
|
|
75
63
|
props['pkg'] = Labimotion::Utils.pkg(props['pkg'])
|
|
@@ -79,22 +67,15 @@ module Labimotion
|
|
|
79
67
|
props = Labimotion::SampleAssociation.update_sample_association(props, args[:current_user_id])
|
|
80
68
|
# props = Labimotion::VocabularyHandler.update_vocabularies(props, current_user, self)
|
|
81
69
|
segment = Labimotion::Segment.where(element_type: self.class.name, element_id: self.id, segment_klass_id: seg['segment_klass_id']).order(id: :desc).first
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
klass_uuid, release =
|
|
85
|
-
Labimotion::KlassVersion.for_record(klass, klass.segment_klasses_revisions, submitted_uuid, segment,
|
|
86
|
-
client_release: seg['properties_release'])
|
|
87
|
-
# props is persisted as submitted, so the resolved value goes back in: otherwise a
|
|
88
|
-
# payload without klass_uuid stores a segment whose column and properties disagree.
|
|
89
|
-
props['klass_uuid'] = klass_uuid if klass_uuid.present?
|
|
90
|
-
if segment.present? && (segment.klass_uuid != klass_uuid || segment.properties != props)
|
|
91
|
-
segment.update!(properties_release: release, properties: props, uuid: uuid, klass_uuid: klass_uuid, metadata: seg['metadata'] || {})
|
|
70
|
+
if segment.present? && (segment.klass_uuid != props['klass_uuid'] || segment.properties != props)
|
|
71
|
+
segment.update!(properties_release: klass.properties_release, properties: props, uuid: uuid, klass_uuid: props['klass_uuid'], metadata: seg['metadata'] || {})
|
|
92
72
|
# segments.push(segment)
|
|
93
73
|
Labimotion::Segment.where(element_type: self.class.name, element_id: self.id, segment_klass_id: seg['segment_klass_id']).where.not(id: segment.id).destroy_all
|
|
94
74
|
end
|
|
95
75
|
next if segment.present?
|
|
96
76
|
|
|
97
|
-
|
|
77
|
+
props['klass_uuid'] = klass.uuid
|
|
78
|
+
segment = Labimotion::Segment.create!(properties_release: klass.properties_release, segment_klass_id: seg['segment_klass_id'], element_type: self.class.name, element_id: self.id, properties: props, created_by: args[:current_user_id], uuid: uuid, klass_uuid: klass.uuid, metadata: seg['metadata'] || {})
|
|
98
79
|
# segments.push(segment)
|
|
99
80
|
end
|
|
100
81
|
|
|
@@ -141,10 +141,9 @@ module Labimotion
|
|
|
141
141
|
joins(collections: :user).where(collections: { user_id: user_id })
|
|
142
142
|
)
|
|
143
143
|
|
|
144
|
-
# Shared elements
|
|
145
|
-
# Element itself, so join through the collections association.
|
|
144
|
+
# Shared elements
|
|
146
145
|
shared = apply_filters.call(
|
|
147
|
-
left_joins(
|
|
146
|
+
left_joins(:collection_shares).where(collection_shares: { shared_with_id: user_id })
|
|
148
147
|
)
|
|
149
148
|
|
|
150
149
|
# Combine (remove duplicates), order, and limit
|