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.
@@ -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
@@ -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
- # A deleted template has already taken its segments with it, so there is nothing to save.
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
- # Like an element update, an existing segment reads the release from its properties alone.
83
- submitted_uuid = segment.nil? ? submitted_segment_uuid(seg, props) : props['klass_uuid']
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
- segment = Labimotion::Segment.create!(properties_release: 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'] || {})
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 — collection_shares hangs off Collection, not off
145
- # Element itself, so join through the collections association.
144
+ # Shared elements
146
145
  shared = apply_filters.call(
147
- left_joins(collections: :collection_shares).where(collection_shares: { shared_with_id: user_id })
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
- # Never close: closing can rewrite the stored file in place
192
- zip_file = Zip::File.new(zip_file_url)
193
- source_map['sourceSelector'].each do |source_name|
194
- result = process_source(zip_file, source_map[source_name], final_parameters)
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 = read_entry(entry).force_encoding(Constants::File::ENCODING)
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
 
@@ -1,7 +1,6 @@
1
1
  module Labimotion
2
2
  ## Converter State
3
3
  class Prop
4
- CONVERTER_FIELD_UINT_PREFIX = '___unit___'.freeze
5
4
  LAYERS = 'layers'.freeze
6
5
  FIELDS = 'fields'.freeze
7
6
  SUBFIELDS = 'sub_fields'.freeze