labimotion 2.0.0 → 2.1.0.rc11

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/lib/labimotion/apis/exporter_api.rb +271 -0
  3. data/lib/labimotion/apis/generic_dataset_api.rb +37 -3
  4. data/lib/labimotion/apis/generic_element_api.rb +106 -4
  5. data/lib/labimotion/apis/generic_klass_api.rb +42 -2
  6. data/lib/labimotion/apis/labimotion_api.rb +1 -0
  7. data/lib/labimotion/apis/segment_api.rb +5 -3
  8. data/lib/labimotion/entities/application_entity.rb +7 -80
  9. data/lib/labimotion/entities/dataset_entity.rb +8 -3
  10. data/lib/labimotion/entities/dataset_klass_entity.rb +3 -2
  11. data/lib/labimotion/entities/element_entity.rb +1 -1
  12. data/lib/labimotion/entities/element_klass_entity.rb +2 -2
  13. data/lib/labimotion/entities/element_revision_entity.rb +3 -1
  14. data/lib/labimotion/entities/eln_element_entity.rb +4 -2
  15. data/lib/labimotion/entities/generic_klass_entity.rb +8 -9
  16. data/lib/labimotion/entities/generic_public_entity.rb +5 -3
  17. data/lib/labimotion/entities/klass_revision_entity.rb +2 -1
  18. data/lib/labimotion/entities/properties_entity.rb +5 -0
  19. data/lib/labimotion/entities/segment_entity.rb +5 -2
  20. data/lib/labimotion/entities/segment_revision_entity.rb +4 -2
  21. data/lib/labimotion/entities/vocabulary_entity.rb +2 -2
  22. data/lib/labimotion/helpers/converter_helpers.rb +16 -3
  23. data/lib/labimotion/helpers/dataset_helpers.rb +31 -6
  24. data/lib/labimotion/helpers/element_helpers.rb +7 -4
  25. data/lib/labimotion/helpers/exporter_helpers.rb +139 -0
  26. data/lib/labimotion/helpers/param_helpers.rb +6 -0
  27. data/lib/labimotion/helpers/segment_helpers.rb +2 -2
  28. data/lib/labimotion/libs/converter.rb +14 -0
  29. data/lib/labimotion/libs/data/layer/StdDataset.json +212 -0
  30. data/lib/labimotion/libs/data/mapper/Chemwiki.json +2 -2
  31. data/lib/labimotion/libs/dataset_builder.rb +2 -1
  32. data/lib/labimotion/libs/export_element.rb +11 -2
  33. data/lib/labimotion/libs/nmr_mapper.rb +13 -0
  34. data/lib/labimotion/libs/properties_handler.rb +12 -2
  35. data/lib/labimotion/libs/sample_association.rb +7 -4
  36. data/lib/labimotion/libs/template_matcher.rb +39 -0
  37. data/lib/labimotion/libs/vocabulary_handler.rb +8 -6
  38. data/lib/labimotion/libs/xlsx_exporter.rb +285 -0
  39. data/lib/labimotion/models/concerns/datasetable.rb +3 -3
  40. data/lib/labimotion/models/concerns/element_fetchable.rb +53 -0
  41. data/lib/labimotion/models/concerns/generic_klass.rb +16 -0
  42. data/lib/labimotion/models/concerns/segmentable.rb +44 -7
  43. data/lib/labimotion/models/dataset_klass.rb +30 -2
  44. data/lib/labimotion/models/device_description.rb +36 -0
  45. data/lib/labimotion/models/element.rb +37 -1
  46. data/lib/labimotion/models/element_klass.rb +12 -1
  47. data/lib/labimotion/models/reaction.rb +36 -0
  48. data/lib/labimotion/models/research_plan.rb +40 -0
  49. data/lib/labimotion/models/sample.rb +36 -0
  50. data/lib/labimotion/models/screen.rb +36 -0
  51. data/lib/labimotion/models/segment_klass.rb +12 -4
  52. data/lib/labimotion/models/wellplate.rb +40 -0
  53. data/lib/labimotion/utils/serializer.rb +2 -0
  54. data/lib/labimotion/utils/units.rb +609 -468
  55. data/lib/labimotion/utils/utils.rb +42 -0
  56. data/lib/labimotion/version.rb +1 -1
  57. data/lib/labimotion.rb +12 -0
  58. metadata +45 -8
@@ -0,0 +1,212 @@
1
+ {
2
+ "layers": {
3
+ "std_general": {
4
+ "wf": false,
5
+ "key": "std_general",
6
+ "std": true,
7
+ "cols": 4,
8
+ "color": "default",
9
+ "label": "Standard General description",
10
+ "style": "panel_generic_heading",
11
+ "fields": [
12
+ {
13
+ "type": "text",
14
+ "field": "title",
15
+ "label": "title",
16
+ "default": "",
17
+ "ontology": {
18
+ "id": "afo:class:http://purl.allotrope.org/ontologies/result#AFR_0001080",
19
+ "iri": "http://purl.allotrope.org/ontologies/result#AFR_0001080",
20
+ "type": "class",
21
+ "label": "title",
22
+ "obo_id": "AFR:0001080",
23
+ "short_form": "AFR_0001080",
24
+ "description": [
25
+ "A title is a prefix or suffix added to a personal name. A title can be a title of honor, that is a title bestowed as an award, a honorary title (academic), that is a recognition of contributions by a non-employee or employee, a hereditary title, that is a title that remains in a family or a job title, that is a designation of a person's position in an organization. [Wikipedia]"
26
+ ],
27
+ "ontology_name": "afo",
28
+ "ontology_prefix": "AFO"
29
+ },
30
+ "position": 1,
31
+ "required": false,
32
+ "sub_fields": [],
33
+ "text_sub_fields": []
34
+ },
35
+ {
36
+ "type": "text",
37
+ "field": "data_type",
38
+ "label": "Type of the data ",
39
+ "default": "",
40
+ "readonly": true,
41
+ "position": 2,
42
+ "required": false,
43
+ "sub_fields": [],
44
+ "description": "Ontologies!!!! type of entity",
45
+ "placeholder": "e.g. sample or reaction analysis",
46
+ "text_sub_fields": []
47
+ },
48
+ {
49
+ "type": "text",
50
+ "field": "date",
51
+ "label": "date",
52
+ "default": "",
53
+ "position": 3,
54
+ "required": false,
55
+ "sub_fields": [],
56
+ "text_sub_fields": []
57
+ },
58
+ {
59
+ "type": "text",
60
+ "field": "time",
61
+ "label": "starting time",
62
+ "default": "",
63
+ "position": 4,
64
+ "required": false,
65
+ "sub_fields": [],
66
+ "text_sub_fields": []
67
+ },
68
+ {
69
+ "type": "text",
70
+ "field": "contributor",
71
+ "label": "contributor",
72
+ "default": "",
73
+ "position": 5,
74
+ "required": false,
75
+ "sub_fields": [],
76
+ "description": "",
77
+ "text_sub_fields": []
78
+ },
79
+ {
80
+ "type": "text",
81
+ "field": "creator",
82
+ "label": "creator",
83
+ "default": "",
84
+ "position": 6,
85
+ "required": false,
86
+ "sub_fields": [],
87
+ "description": "the person who upload the analysis",
88
+ "text_sub_fields": []
89
+ },
90
+ {
91
+ "type": "text",
92
+ "field": "operator",
93
+ "label": "operator",
94
+ "default": "",
95
+ "position": 7,
96
+ "required": false,
97
+ "sub_fields": [],
98
+ "text_sub_fields": []
99
+ },
100
+ {
101
+ "type": "text",
102
+ "field": "comments",
103
+ "label": "comments",
104
+ "default": "",
105
+ "position": 8,
106
+ "required": false,
107
+ "sub_fields": [],
108
+ "text_sub_fields": []
109
+ },
110
+ {
111
+ "opid": 8,
112
+ "type": "text",
113
+ "field": "Producer",
114
+ "label": "Producer of the sample",
115
+ "is_voc": true,
116
+ "source": "Segment",
117
+ "default": "",
118
+ "field_id": "Producer",
119
+ "layer_id": "bsi",
120
+ "ontology": {
121
+ "id": "schema:property:https://schema.org/producer",
122
+ "iri": "https://schema.org/producer",
123
+ "type": "property",
124
+ "label": "producer",
125
+ "obo_id": "schema:producer",
126
+ "short_form": "schema_producer",
127
+ "description": [
128
+ "The person or organization who produced the work (e.g. music album, movie, TV/radio series etc.)."
129
+ ],
130
+ "ontology_name": "schema",
131
+ "ontology_prefix": "schema"
132
+ },
133
+ "position": 9,
134
+ "source_id": "dce363d5-56d0-4324-9914-0476be999016",
135
+ "identifier": "357b7cd3-ca47-49e0-be47-154d907c2cf5",
136
+ "sub_fields": [],
137
+ "text_sub_fields": []
138
+ }
139
+ ],
140
+ "position": -2,
141
+ "condition": "",
142
+ "wf_position": 0
143
+ },
144
+ "std_sample_details": {
145
+ "wf": false,
146
+ "key": "std_sample_details",
147
+ "cols": 3,
148
+ "std": true,
149
+ "color": "none",
150
+ "label": "Standard Sample details",
151
+ "style": "panel_generic_heading",
152
+ "fields": [
153
+ {
154
+ "type": "text",
155
+ "field": "label",
156
+ "label": "label",
157
+ "default": "",
158
+ "position": 1,
159
+ "required": false,
160
+ "sub_fields": [],
161
+ "text_sub_fields": []
162
+ },
163
+ {
164
+ "type": "text",
165
+ "field": "id",
166
+ "label": "id",
167
+ "default": "",
168
+ "position": 2,
169
+ "required": false,
170
+ "sub_fields": [],
171
+ "text_sub_fields": []
172
+ },
173
+ {
174
+ "type": "text",
175
+ "field": "amount",
176
+ "label": "amount",
177
+ "default": "",
178
+ "position": 3,
179
+ "required": false,
180
+ "sub_fields": [],
181
+ "text_sub_fields": []
182
+ },
183
+ {
184
+ "type": "text",
185
+ "field": "volume",
186
+ "label": "volume",
187
+ "default": "",
188
+ "position": 4,
189
+ "required": false,
190
+ "sub_fields": [],
191
+ "text_sub_fields": []
192
+ },
193
+ {
194
+ "type": "text",
195
+ "field": "size",
196
+ "label": "size",
197
+ "default": "",
198
+ "position": 5,
199
+ "required": false,
200
+ "sub_fields": [],
201
+ "text_sub_fields": []
202
+ }
203
+ ],
204
+ "position": -1,
205
+ "timeRecord": "",
206
+ "wf_position": 0
207
+ }
208
+ },
209
+ "version": "0.1",
210
+ "select_options": {
211
+ }
212
+ }
@@ -197,7 +197,7 @@
197
197
  "type": "string"
198
198
  },
199
199
  "redox": {
200
- "title": "Redox-Potential",
200
+ "title": "Redox potential",
201
201
  "param": 6,
202
202
  "type": "spc"
203
203
  },
@@ -233,4 +233,4 @@
233
233
  }
234
234
  }
235
235
  }
236
- }
236
+ }
@@ -29,7 +29,8 @@ module Labimotion
29
29
  end
30
30
 
31
31
  def self.find_dataset_klass(ols_term_id)
32
- Labimotion::DatasetKlass.find_by(ols_term_id: ols_term_id)
32
+ result = Labimotion::TemplateMatcher.find_best_match(ols_term_id)
33
+ result[:template]
33
34
  end
34
35
 
35
36
  def self.create_dataset(container, klass)
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require 'ostruct'
2
3
  require 'export_table'
3
4
  require 'labimotion/version'
4
5
  require 'labimotion/utils/units'
@@ -35,6 +36,13 @@ module Labimotion
35
36
  Labimotion.log_exception(e)
36
37
  end
37
38
 
39
+ def sample_url
40
+ protocol = Rails.env.production? ? 'https' : 'http'
41
+ host = ENV['PUBLIC_URL'] || 'localhost:3000'
42
+ api = 'mydb/collection/all/sample'
43
+ "#{protocol}://#{host}/#{api}"
44
+ end
45
+
38
46
  def build_layers
39
47
  objs = []
40
48
  @properties[Labimotion::Prop::LAYERS]&.keys&.sort_by do |key|
@@ -182,7 +190,8 @@ module Labimotion
182
190
  name = val['el_name'].present? ? "Name: [#{val['el_name']}] \n" : ''
183
191
  ext = val['el_external_label'].present? ? "Ext. Label: [#{val['el_external_label']}] \n" : ''
184
192
  mass = val['el_molecular_weight'].present? ? "Mass: [#{val['el_molecular_weight']}] \n" : ''
185
- "#{label}#{name}#{ext}#{mass}"
193
+ url = val['el_id'].present? ? "#{sample_url}/#{val['el_id']}" : ''
194
+ "#{label}#{name}#{ext}#{mass}#{url}"
186
195
  when Labimotion::FieldType::DRAG_MOLECULE
187
196
  val = sub_val[sub_field['id']]['value'] || {}
188
197
  smile = val['el_smiles'].present? ? "SMILES: [#{val['el_smiles']}] \n" : ''
@@ -193,7 +202,7 @@ module Labimotion
193
202
  when Labimotion::FieldType::SELECT
194
203
  sub_val[sub_field['id']]['value']
195
204
  when Labimotion::FieldType::SYSTEM_DEFINED
196
- unit = Labimotion::Units::FIELDS.find { |o| o[:field] == sub_field['option_layers'] }&.fetch(:units, [])&.find { |u| u[:key] == sub_field['value_system'] }&.fetch(:label, '')
205
+ unit = Labimotion::Units::FIELDS.find { |o| o[:field] == sub_field['option_layers'] }&.fetch(:units, [])&.find { |u| u[:key] == sub_val[sub_field['id']]['value_system'] }&.fetch(:label, '')
197
206
  val = sub_val[sub_field['id']]['value'].to_s + ' ' + unit
198
207
  val = Sablon.content(:html, "<div>" + val + "</div>") if val.include? '<'
199
208
  val
@@ -4,6 +4,7 @@ require 'labimotion/libs/dataset_builder'
4
4
  require 'labimotion/version'
5
5
  require 'labimotion/utils/mapper_utils'
6
6
  require 'labimotion/utils/utils'
7
+ require 'labimotion/helpers/converter_helpers'
7
8
 
8
9
  module Labimotion
9
10
  ## NmrMapper
@@ -61,6 +62,7 @@ module Labimotion
61
62
 
62
63
  def update_ds(_cid, obj, current_user, element)
63
64
  dataset = obj[:dataset]
65
+ process_general_description(obj, current_user, element)
64
66
  dataset.properties = process_prop(obj, current_user, element)
65
67
  dataset.save!
66
68
  end
@@ -106,6 +108,17 @@ module Labimotion
106
108
  new_prop
107
109
  end
108
110
 
111
+ def process_general_description(obj, current_user, element)
112
+ container = obj[:dataset]&.element
113
+ return unless container.present?
114
+
115
+ dt = obj[:metadata]&.dig('DATE')
116
+ date = Labimotion::MapperUtils.format_timestamp(dt, 'date') if dt.present?
117
+ time = Labimotion::MapperUtils.format_timestamp(dt, 'time') if dt.present?
118
+
119
+ Labimotion::ConverterHelpers.update_general_description(container, current_user, date: date, time: time)
120
+ end
121
+
109
122
  def process_prop(obj, current_user, element)
110
123
  new_prop = obj[:dataset].properties
111
124
  new_prop
@@ -8,9 +8,9 @@ module Labimotion
8
8
  def check_val(field, type)
9
9
  case type
10
10
  when Labimotion::FieldType::DRAG_SAMPLE, Labimotion::FieldType::DRAG_ELEMENT
11
- return field.dig('value', 'el_id').present?
11
+ return field['value'].is_a?(Hash) && field.dig('value', 'el_id').present?
12
12
  when Labimotion::FieldType::UPLOAD
13
- return field.dig('value', 'files')&.length&.positive?
13
+ return field['value'].is_a?(Hash) && field.dig('value', 'files')&.length&.positive?
14
14
  when Labimotion::FieldType::TABLE
15
15
  return field[Labimotion::Prop::SUBFIELDS]&.length&.positive? && field['sub_values']&.length&.positive?
16
16
  end
@@ -91,5 +91,15 @@ module Labimotion
91
91
  Labimotion.log_exception(e)
92
92
  properties
93
93
  end
94
+
95
+ # Update field with value
96
+ def self.update_field_value!(properties, layer_key, field_index, value)
97
+ properties[Labimotion::Prop::LAYERS][layer_key][Labimotion::Prop::FIELDS][field_index]['value'] = value
98
+ end
99
+
100
+ # Delete field key
101
+ def self.delete_field_key!(properties, layer_key, field_index, key)
102
+ properties[Labimotion::Prop::LAYERS][layer_key][Labimotion::Prop::FIELDS][field_index].delete(key)
103
+ end
94
104
  end
95
105
  end
@@ -79,6 +79,7 @@ module Labimotion
79
79
  field_samples.each do |field|
80
80
  idx = properties[Labimotion::Prop::LAYERS][key][Labimotion::Prop::FIELDS].index(field)
81
81
  return if field.is_a?(String) || properties.is_a?(String)
82
+ next unless field['value'].is_a?(Hash)
82
83
 
83
84
  sid = field.dig('value', 'el_id')
84
85
  next if sid.blank?
@@ -102,7 +103,8 @@ module Labimotion
102
103
  field_elements = layer[Labimotion::Prop::FIELDS].select { |ss| ss['type'] == Labimotion::FieldType::DRAG_ELEMENT }
103
104
  field_elements.each do |field|
104
105
  idx = properties[Labimotion::Prop::LAYERS][key][Labimotion::Prop::FIELDS].index(field)
105
- next if field['value'].is_a?(String)
106
+ return if field.is_a?(String) || properties.is_a?(String)
107
+ next unless field['value'].is_a?(Hash)
106
108
 
107
109
  sid = field.dig('value', 'el_id')
108
110
  next if element.nil? || sid.blank? || sid == element.id
@@ -113,11 +115,12 @@ module Labimotion
113
115
  Labimotion::ElementsElement.find_or_create_by(parent_id: element.id, element_id: el.id)
114
116
  els << el.id
115
117
  end
116
-
117
118
  end
118
119
  if element.present?
119
- es_list = Labimotion::ElementsSample.where(element_id: element.id).where.not(sample_id: sds)
120
- ee_list = Labimotion::ElementsElement.where(parent_id: element.id).where.not(element_id: els&.flatten)
120
+ sds = sds.flatten.uniq
121
+ els = els.flatten.uniq
122
+ es_list = sds.present? ? Labimotion::ElementsSample.where(element_id: element.id).where.not(sample_id: sds) : Labimotion::ElementsSample.where(element_id: element.id)
123
+ ee_list = els.present? ? Labimotion::ElementsElement.where(parent_id: element.id).where.not(element_id: els) : Labimotion::ElementsElement.where(parent_id: element.id)
121
124
  es_list.destroy_all if es_list.present?
122
125
  ee_list.destroy_all if ee_list.present?
123
126
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Labimotion
4
+ class TemplateMatcher
5
+ def self.find_best_match(ols_term_id)
6
+ return { template: nil, match_type: nil, info_messages: [] } if ols_term_id.blank?
7
+
8
+ info_messages = []
9
+
10
+ # Step 1: Find "assigned" mode templates
11
+ assigned = Labimotion::DatasetKlass.find_assigned_templates(ols_term_id)
12
+ if assigned.any?
13
+ handle_multiple_results(assigned, :assigned, info_messages)
14
+ return { template: assigned.first, match_type: 'assigned', info_messages: info_messages }
15
+ end
16
+
17
+ # Step 2: Find exact match template
18
+ exact = Labimotion::DatasetKlass.find_by(ols_term_id: ols_term_id)
19
+ return { template: exact, match_type: 'exact', info_messages: info_messages } if exact
20
+
21
+ # Step 3: Find "inferred" mode templates
22
+ info_messages << 'Using parent template'
23
+ inferred = Labimotion::DatasetKlass.find_inferred_templates(ols_term_id)
24
+ if inferred.any?
25
+ handle_multiple_results(inferred, :inferred, info_messages)
26
+ return { template: inferred.first, match_type: 'inferred', info_messages: info_messages }
27
+ end
28
+
29
+ { template: nil, match_type: nil, info_messages: info_messages }
30
+ end
31
+
32
+ def self.handle_multiple_results(templates, mode, info_messages)
33
+ return unless templates.size > 1
34
+
35
+ ols_ids = templates.map(&:ols_term_id).join(', ')
36
+ info_messages << "Multiple #{mode} templates found, ols_term_id(s) are #{ols_ids}"
37
+ end
38
+ end
39
+ end
@@ -4,8 +4,10 @@ module Labimotion
4
4
  class VocabularyHandler
5
5
  class << self
6
6
  def update_vocabularies(properties, current_user, element)
7
+ return properties if properties.nil? || !properties.is_a?(Hash) || properties[Labimotion::Prop::LAYERS].nil?
8
+
7
9
  properties[Labimotion::Prop::LAYERS].each do |key, layer|
8
- update_layer_vocabularies(layer, key, properties, current_user, element)
10
+ update_layer_vocabularies!(layer, key, properties, current_user, element)
9
11
  end
10
12
  properties
11
13
  rescue StandardError => e
@@ -23,12 +25,12 @@ module Labimotion
23
25
 
24
26
  private
25
27
 
26
- def update_layer_vocabularies(layer, key, properties, current_user, element)
28
+ def update_layer_vocabularies!(layer, key, properties, current_user, element)
27
29
  field_vocabularies = layer[Labimotion::Prop::FIELDS].select { |field| field['is_voc'] }
28
30
  field_vocabularies.each do |field|
29
31
  idx = layer[Labimotion::Prop::FIELDS].index(field)
30
32
  val = get_vocabulary_value(field, current_user, element)
31
- update_field_value(properties, key, idx, val) if val.present?
33
+ update_field_value!(properties, key, idx, val)
32
34
  end
33
35
  end
34
36
 
@@ -44,6 +46,7 @@ module Labimotion
44
46
  get_segment_value(field, element)
45
47
  when 'Dataset'
46
48
  # TODO: Implement Dataset logic here
49
+ # TODO: Update datasetable.save_dataset
47
50
  nil
48
51
  end
49
52
  end
@@ -91,9 +94,8 @@ module Labimotion
91
94
  fields.find { |ss| ss['field'] == field['field_id'] }&.dig('value')
92
95
  end
93
96
 
94
- def update_field_value(properties, key, idx, val)
95
- properties[Labimotion::Prop::LAYERS][key][Labimotion::Prop::FIELDS][idx]['value'] = val
96
- properties
97
+ def update_field_value!(properties, key, idx, val)
98
+ Labimotion::PropertiesHandler.update_field_value!(properties, key, idx, val)
97
99
  end
98
100
 
99
101
  def load_from_files