labimotion 2.3.2 → 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 -22
- data/lib/labimotion/apis/element_variation_api.rb +3 -58
- data/lib/labimotion/apis/generic_element_api.rb +22 -0
- 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/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 -11
- metadata +5 -24
- 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
|
@@ -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
|
|
@@ -6,18 +6,8 @@ require 'zip'
|
|
|
6
6
|
require 'labimotion/constants'
|
|
7
7
|
|
|
8
8
|
module Labimotion
|
|
9
|
-
# rubyzip 2.x does not detect encrypted entries and would inflate them as garbage
|
|
10
|
-
class EncryptedZipError < Zip::Error; end
|
|
11
|
-
|
|
12
9
|
class MapperUtils
|
|
13
10
|
class << self
|
|
14
|
-
# Read an entry and close its stream, so no handle on the stored file stays open
|
|
15
|
-
def read_entry(entry)
|
|
16
|
-
raise EncryptedZipError, "Encrypted zip entry: #{entry.name}" if entry.encrypted?
|
|
17
|
-
|
|
18
|
-
entry.get_input_stream(&:read)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
11
|
def load_config(config_json)
|
|
22
12
|
JSON.parse(config_json)
|
|
23
13
|
rescue JSON::ParserError => e
|
|
@@ -47,18 +37,7 @@ module Labimotion
|
|
|
47
37
|
def extract_data_from_zip(zip_file_url, source_map)
|
|
48
38
|
return nil if zip_file_url.nil?
|
|
49
39
|
|
|
50
|
-
# nil means nothing to map; errors are raised so the caller can record ERROR
|
|
51
40
|
process_zip_file(zip_file_url, source_map)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def check_if_bagit(zip_file_url)
|
|
55
|
-
return nil if zip_file_url.nil?
|
|
56
|
-
Zip::File.open(zip_file_url) do |zip_file|
|
|
57
|
-
zip_file.each do |entry|
|
|
58
|
-
return { is_bagit: true, metadata: nil } if bagit_metadata_file?(entry)
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
41
|
rescue Zip::Error => e
|
|
63
42
|
Rails.logger.error "Zip file error: #{e.message}"
|
|
64
43
|
nil
|
|
@@ -188,12 +167,10 @@ module Labimotion
|
|
|
188
167
|
def process_zip_file(zip_file_url, source_map)
|
|
189
168
|
final_parameters = {}
|
|
190
169
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
# A BagIt zip is mapped by the converter instead
|
|
196
|
-
return result if result&.dig(:is_bagit)
|
|
170
|
+
Zip::File.open(zip_file_url) do |zip_file|
|
|
171
|
+
source_map['sourceSelector'].each do |source_name|
|
|
172
|
+
process_source(zip_file, source_map[source_name], final_parameters)
|
|
173
|
+
end
|
|
197
174
|
end
|
|
198
175
|
|
|
199
176
|
return { is_bagit: false, metadata: final_parameters } if final_parameters.present?
|
|
@@ -205,8 +182,6 @@ module Labimotion
|
|
|
205
182
|
return if invalid_source_config?(source_config)
|
|
206
183
|
|
|
207
184
|
zip_file.each do |entry|
|
|
208
|
-
next if entry.directory?
|
|
209
|
-
|
|
210
185
|
if source_file?(entry, source_config)
|
|
211
186
|
process_file_entry(entry, source_config, final_parameters)
|
|
212
187
|
elsif bagit_metadata_file?(entry)
|
|
@@ -216,7 +191,7 @@ module Labimotion
|
|
|
216
191
|
end
|
|
217
192
|
|
|
218
193
|
def process_file_entry(entry, source_config, final_parameters)
|
|
219
|
-
file_content =
|
|
194
|
+
file_content = entry.get_input_stream.read.force_encoding(Constants::File::ENCODING)
|
|
220
195
|
extracted_parameters = extract_parameters(file_content, source_config['parameters'])
|
|
221
196
|
final_parameters.merge!(extracted_parameters) if extracted_parameters.present?
|
|
222
197
|
|