labimotion 2.3.4 → 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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +0 -39
  3. data/lib/labimotion/apis/element_variation_api.rb +3 -58
  4. data/lib/labimotion/apis/exporter_api.rb +19 -2
  5. data/lib/labimotion/apis/generic_dataset_api.rb +1 -3
  6. data/lib/labimotion/apis/generic_element_api.rb +38 -14
  7. data/lib/labimotion/apis/segment_api.rb +1 -5
  8. data/lib/labimotion/collection/import.rb +3 -36
  9. data/lib/labimotion/constants.rb +0 -6
  10. data/lib/labimotion/entities/application_entity.rb +1 -5
  11. data/lib/labimotion/entities/generic_klass_entity.rb +0 -11
  12. data/lib/labimotion/entities/properties_entity.rb +28 -0
  13. data/lib/labimotion/helpers/element_helpers.rb +9 -37
  14. data/lib/labimotion/helpers/generic_helpers.rb +4 -9
  15. data/lib/labimotion/helpers/param_helpers.rb +1 -0
  16. data/lib/labimotion/libs/converter.rb +23 -100
  17. data/lib/labimotion/libs/dataset_builder.rb +1 -1
  18. data/lib/labimotion/libs/linked_element.rb +193 -0
  19. data/lib/labimotion/libs/nmr_mapper.rb +4 -34
  20. data/lib/labimotion/models/concerns/attachment_converter.rb +0 -24
  21. data/lib/labimotion/models/concerns/datasetable.rb +5 -16
  22. data/lib/labimotion/models/concerns/element_fetchable.rb +2 -3
  23. data/lib/labimotion/models/concerns/segmentable.rb +5 -24
  24. data/lib/labimotion/models/element.rb +2 -3
  25. data/lib/labimotion/utils/mapper_utils.rb +5 -30
  26. data/lib/labimotion/utils/prop.rb +0 -8
  27. data/lib/labimotion/utils/units.rb +32 -67
  28. data/lib/labimotion/version.rb +1 -1
  29. data/lib/labimotion.rb +1 -13
  30. metadata +5 -26
  31. data/lib/labimotion/helpers/export_authorization_helpers.rb +0 -39
  32. data/lib/labimotion/libs/element_variation_column_set.rb +0 -426
  33. data/lib/labimotion/libs/element_variation_header.rb +0 -119
  34. data/lib/labimotion/libs/export_element_variations.rb +0 -245
  35. data/lib/labimotion/libs/export_tpa_segments.rb +0 -53
  36. data/lib/labimotion/libs/import_element_variations.rb +0 -411
  37. data/lib/labimotion/libs/klass_version.rb +0 -66
  38. data/lib/labimotion/libs/release_metadata.rb +0 -64
@@ -142,20 +142,16 @@ module Labimotion
142
142
  raise e
143
143
  end
144
144
 
145
- # element_type is the class name of the element that id names, so a segment
146
- # lookup cannot reach another element type's segment with the same element_id.
147
- def create_uploads(type, id, files, param_info, user_id, element_type)
145
+ def create_uploads(type, id, files, param_info, user_id)
148
146
  return if files.nil? || param_info.nil? || files.empty? || param_info.empty?
149
147
 
150
148
  attach_ary = []
151
149
  map_info = JSON.parse(param_info)
152
- # Derived here, not taken from param_info, so the request cannot pick the attachable_type
153
- attachable_type = type == Labimotion::Prop::SEGMENT ? Labimotion::Prop::SEGMENTPROPS : Labimotion::Prop::ELEMENTPROPS
154
150
  map_info&.keys&.each do |key|
155
151
  next if map_info[key]['files'].empty?
156
152
 
157
153
  if type == Labimotion::Prop::SEGMENT
158
- element = Labimotion::Segment.find_by(element_type: element_type, element_id: id, segment_klass_id: key)
154
+ element = Labimotion::Segment.find_by(element_id: id, segment_klass_id: key)
159
155
  elsif type == Labimotion::Prop::ELEMENT
160
156
  element = Labimotion::Element.find_by(id: id)
161
157
  end
@@ -176,7 +172,7 @@ module Labimotion
176
172
  created_by: user_id,
177
173
  created_for: user_id,
178
174
  content_type: file[:type],
179
- attachable_type: attachable_type,
175
+ attachable_type: map_info[key]['type'],
180
176
  attachable_id: element.id,
181
177
  )
182
178
  begin
@@ -225,8 +221,7 @@ module Labimotion
225
221
  end
226
222
  end
227
223
  unless (del_files || []).empty?
228
- # Scoped to the element, so ids of another element's attachments are left alone
229
- Attachment.where(id: del_files.map(&:to_i), attachable_type: type, attachable_id: id).update_all(attachable_id: nil)
224
+ Attachment.where('id IN (?) AND attachable_type = (?)', del_files.map!(&:to_i), type).update_all(attachable_id: nil)
230
225
  end
231
226
  attach_ary
232
227
  rescue StandardError => e
@@ -91,6 +91,7 @@ module Labimotion
91
91
  optional :desc, type: String, desc: 'Segment Klass Desc'
92
92
  optional :place, type: String, desc: 'Segment Klass Place', default: '100'
93
93
  optional :identifier, type: String, desc: 'Segment Identifier'
94
+ optional :metadata, type: Hash, desc: 'Klass metadata'
94
95
  end
95
96
 
96
97
  params :create_segment_klass_params do
@@ -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
- ctx = { att: nil, ols: nil, dsr: [] }
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
- # Never close: closing can rewrite the stored file in place
43
- zip_file = Zip::File.new(att.attachment_attacher.file.url)
44
- res = Labimotion::Converter.collect_metadata(zip_file, current_user)
45
- Labimotion::Converter.collect_reaction_converter_metadata(att, zip_file)
46
- ctx[:ols] = res[:o] unless res&.dig(:o).nil?
47
- ctx[:dsr].push(res[:d]) unless res&.dig(:d).nil?
48
- ctx[:dsr].flatten!
49
- dataset = build_ds(att.attachable_id, ctx[:ols])
50
- update_ds(dataset, ctx[:dsr], current_user) if dataset.present?
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
- Labimotion::ConState::COMPLETED
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 = Labimotion::MapperUtils.read_entry(entry).force_encoding('UTF-8')
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
- state = Labimotion::Converter.handle_response(data[:a], response, current_user)
206
- # A failed dataset mapping also marks the source attachment as ERROR
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
@@ -262,7 +224,7 @@ module Labimotion
262
224
  properties: props,
263
225
  properties_release: klass.properties_release,
264
226
  klass_uuid: klass.uuid,
265
- metadata: Labimotion::ReleaseMetadata.for_klass(klass)
227
+ metadata: klass.metadata
266
228
  )
267
229
  end
268
230
 
@@ -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'] == field_name }
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
- if is_unit
299
- fi['value_system'] = ds[:value]
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
@@ -45,7 +45,7 @@ module Labimotion
45
45
  properties: props,
46
46
  properties_release: klass.properties_release,
47
47
  klass_uuid: klass.uuid,
48
- metadata: Labimotion::ReleaseMetadata.for_klass(klass)
48
+ metadata: klass.metadata
49
49
  )
50
50
  end
51
51
 
@@ -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
- ctx = { att: nil }
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
- attacher = att&.attachment_attacher
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
- # Return the state Converter.metadata stored; CONVERTED means still waiting for an analysis dataset
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
- # A deleted template has already taken its datasets with it, so there is nothing to save.
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
- # A dataset whose klass changed follows the new klass, not the release stored for the old
57
- # one, so the switch resolves as a new record does. The release the client sent counts only
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: 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)
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 — collection_shares hangs off Collection, not off the
30
- # element models themselves, so join through the collections association.
29
+ # Shared records
31
30
  shared = apply_filters.call(
32
- left_joins(collections: :collection_shares).where(collection_shares: { shared_with_id: user_id })
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