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.
- checksums.yaml +4 -4
- data/lib/labimotion/apis/exporter_api.rb +271 -0
- data/lib/labimotion/apis/generic_dataset_api.rb +37 -3
- data/lib/labimotion/apis/generic_element_api.rb +106 -4
- data/lib/labimotion/apis/generic_klass_api.rb +42 -2
- data/lib/labimotion/apis/labimotion_api.rb +1 -0
- data/lib/labimotion/apis/segment_api.rb +5 -3
- data/lib/labimotion/entities/application_entity.rb +7 -80
- data/lib/labimotion/entities/dataset_entity.rb +8 -3
- data/lib/labimotion/entities/dataset_klass_entity.rb +3 -2
- data/lib/labimotion/entities/element_entity.rb +1 -1
- data/lib/labimotion/entities/element_klass_entity.rb +2 -2
- data/lib/labimotion/entities/element_revision_entity.rb +3 -1
- data/lib/labimotion/entities/eln_element_entity.rb +4 -2
- data/lib/labimotion/entities/generic_klass_entity.rb +8 -9
- data/lib/labimotion/entities/generic_public_entity.rb +5 -3
- data/lib/labimotion/entities/klass_revision_entity.rb +2 -1
- data/lib/labimotion/entities/properties_entity.rb +5 -0
- data/lib/labimotion/entities/segment_entity.rb +5 -2
- data/lib/labimotion/entities/segment_revision_entity.rb +4 -2
- data/lib/labimotion/entities/vocabulary_entity.rb +2 -2
- data/lib/labimotion/helpers/converter_helpers.rb +16 -3
- data/lib/labimotion/helpers/dataset_helpers.rb +31 -6
- data/lib/labimotion/helpers/element_helpers.rb +7 -4
- data/lib/labimotion/helpers/exporter_helpers.rb +139 -0
- data/lib/labimotion/helpers/param_helpers.rb +6 -0
- data/lib/labimotion/helpers/segment_helpers.rb +2 -2
- data/lib/labimotion/libs/converter.rb +14 -0
- data/lib/labimotion/libs/data/layer/StdDataset.json +212 -0
- data/lib/labimotion/libs/data/mapper/Chemwiki.json +2 -2
- data/lib/labimotion/libs/dataset_builder.rb +2 -1
- data/lib/labimotion/libs/export_element.rb +11 -2
- data/lib/labimotion/libs/nmr_mapper.rb +13 -0
- data/lib/labimotion/libs/properties_handler.rb +12 -2
- data/lib/labimotion/libs/sample_association.rb +7 -4
- data/lib/labimotion/libs/template_matcher.rb +39 -0
- data/lib/labimotion/libs/vocabulary_handler.rb +8 -6
- data/lib/labimotion/libs/xlsx_exporter.rb +285 -0
- data/lib/labimotion/models/concerns/datasetable.rb +3 -3
- data/lib/labimotion/models/concerns/element_fetchable.rb +53 -0
- data/lib/labimotion/models/concerns/generic_klass.rb +16 -0
- data/lib/labimotion/models/concerns/segmentable.rb +44 -7
- data/lib/labimotion/models/dataset_klass.rb +30 -2
- data/lib/labimotion/models/device_description.rb +36 -0
- data/lib/labimotion/models/element.rb +37 -1
- data/lib/labimotion/models/element_klass.rb +12 -1
- data/lib/labimotion/models/reaction.rb +36 -0
- data/lib/labimotion/models/research_plan.rb +40 -0
- data/lib/labimotion/models/sample.rb +36 -0
- data/lib/labimotion/models/screen.rb +36 -0
- data/lib/labimotion/models/segment_klass.rb +12 -4
- data/lib/labimotion/models/wellplate.rb +40 -0
- data/lib/labimotion/utils/serializer.rb +2 -0
- data/lib/labimotion/utils/units.rb +609 -468
- data/lib/labimotion/utils/utils.rb +42 -0
- data/lib/labimotion/version.rb +1 -1
- data/lib/labimotion.rb +12 -0
- metadata +45 -8
|
@@ -1,88 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
module Labimotion
|
|
4
4
|
## ApplicationEntity
|
|
5
|
-
class
|
|
6
|
-
|
|
5
|
+
# This class serves as the base entity for the Labimotion module,
|
|
6
|
+
# inheriting from the core ApplicationEntity and providing custom formatting.
|
|
7
|
+
class ApplicationEntity < ::Entities::ApplicationEntity
|
|
8
|
+
# Common condition for fields that should not be displayed in list views
|
|
9
|
+
DISPLAYED_IN_LIST_CONDITION = { unless: :displayed_in_list }.freeze
|
|
7
10
|
|
|
8
11
|
format_with(:eln_timestamp) do |datetime|
|
|
9
|
-
datetime.present? ? datetime
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def self.expose!(*args)
|
|
13
|
-
fields = args.first
|
|
14
|
-
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
15
|
-
options = merge_options(options) # merges additional params set in #with_options
|
|
16
|
-
expose_fields_with_anonymization!(fields, options)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# rubocop:disable Metrics/MethodLength
|
|
20
|
-
def self.expose_fields_with_anonymization!(fields, options)
|
|
21
|
-
anonymize_below = options[:anonymize_below] || 0
|
|
22
|
-
anonymize_with = options.key?(:anonymize_with) ? options[:anonymize_with] : '***'
|
|
23
|
-
|
|
24
|
-
Array(fields).each do |field|
|
|
25
|
-
expose(field, options) do |represented_object, _options|
|
|
26
|
-
if detail_levels[represented_object.class] < anonymize_below
|
|
27
|
-
anonymize_with
|
|
28
|
-
elsif respond_to?(field, true) # Entity has a method with the same name
|
|
29
|
-
send(field)
|
|
30
|
-
elsif represented_object.respond_to?(field)
|
|
31
|
-
represented_object.public_send(field)
|
|
32
|
-
else
|
|
33
|
-
represented_object[field] # works both for AR and Hash objects
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
private_class_method :expose_fields_with_anonymization!
|
|
39
|
-
# rubocop:enable Metrics/MethodLength
|
|
40
|
-
|
|
41
|
-
def self.expose_timestamps(timestamp_fields: %i[created_at updated_at], **additional_args)
|
|
42
|
-
timestamp_fields.each do |field|
|
|
43
|
-
expose field, format_with: :eln_timestamp, **additional_args
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# overridden method from Grape::Entity to support our custom anonymization options
|
|
48
|
-
# https://github.com/ruby-grape/grape-entity/blob/v0.7.1/lib/grape_entity/entity.rb#L565
|
|
49
|
-
def self.valid_options(options)
|
|
50
|
-
options.each_key do |key|
|
|
51
|
-
next if OPTIONS.include?(key) || CUSTOM_ENTITY_OPTIONS.include?(key)
|
|
52
|
-
|
|
53
|
-
raise ArgumentError, "#{key.inspect} is not a valid option."
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
options[:using] = options.delete(:with) if options.key?(:with)
|
|
57
|
-
options
|
|
58
|
-
end
|
|
59
|
-
private_class_method :valid_options
|
|
60
|
-
|
|
61
|
-
private
|
|
62
|
-
|
|
63
|
-
def displayed_in_list?
|
|
64
|
-
options[:displayed_in_list] == true
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def current_user
|
|
68
|
-
unless options[:current_user]
|
|
69
|
-
raise MissingCurrentUserError, "#{self.class} requires a current user to work properly"
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
options[:current_user]
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def detail_levels
|
|
76
|
-
maximal_default_levels = Hash.new(10) # every requested detail level will be returned as 10
|
|
77
|
-
minimal_default_levels = Hash.new(0) # every requested detail level will be returned as 0
|
|
78
|
-
return maximal_default_levels if !options.key?(:detail_levels) || options[:detail_levels].empty?
|
|
79
|
-
|
|
80
|
-
# When explicitly configured detail levels are available, we want to return only those and all other
|
|
81
|
-
# requests (by using `detail_levels[SomeUnconfiguredModel]`) should return the minimum detail level
|
|
82
|
-
minimal_default_levels.merge(options[:detail_levels])
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
class MissingCurrentUserError < StandardError
|
|
12
|
+
datetime.present? ? datetime.strftime('%Y-%m-%d %H:%M:%S %Z') : nil
|
|
86
13
|
end
|
|
87
14
|
end
|
|
88
15
|
end
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
require 'labimotion/entities/application_entity'
|
|
4
|
+
require 'labimotion/entities/properties_entity'
|
|
4
5
|
module Labimotion
|
|
5
6
|
# Dataset entity
|
|
6
|
-
class DatasetEntity < PropertiesEntity
|
|
7
|
-
expose :id, :dataset_klass_id, :
|
|
7
|
+
class DatasetEntity < Labimotion::PropertiesEntity
|
|
8
|
+
expose :id, :dataset_klass_id, :element_id, :element_type
|
|
9
|
+
expose :klass_ols, :klass_label, :klass_uuid
|
|
10
|
+
expose :properties, **DISPLAYED_IN_LIST_CONDITION, anonymize_with: {}
|
|
11
|
+
expose :properties_release, **DISPLAYED_IN_LIST_CONDITION, anonymize_with: {}
|
|
12
|
+
|
|
8
13
|
def klass_ols
|
|
9
14
|
object&.dataset_klass&.ols_term_id
|
|
10
15
|
end
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require 'labimotion/entities/generic_klass_entity'
|
|
3
4
|
module Labimotion
|
|
4
|
-
class DatasetKlassEntity < GenericKlassEntity
|
|
5
|
+
class DatasetKlassEntity < Labimotion::GenericKlassEntity
|
|
5
6
|
expose(
|
|
6
|
-
:ols_term_id
|
|
7
|
+
:ols_term_id
|
|
7
8
|
)
|
|
8
9
|
end
|
|
9
10
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require 'labimotion/entities/properties_entity'
|
|
4
4
|
## TODO: Refactor labimotion to use the same entities as chemotion
|
|
5
5
|
module Labimotion
|
|
6
|
-
class ElementEntity < PropertiesEntity
|
|
6
|
+
class ElementEntity < Labimotion::PropertiesEntity
|
|
7
7
|
with_options(anonymize_below: 0) do
|
|
8
8
|
expose! :can_copy, unless: :displayed_in_list
|
|
9
9
|
expose! :can_publish, unless: :displayed_in_list
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
require 'labimotion/entities/generic_klass_entity'
|
|
4
4
|
|
|
5
5
|
module Labimotion
|
|
6
6
|
# ElementKlassEntity
|
|
7
|
-
class ElementKlassEntity < GenericKlassEntity
|
|
7
|
+
class ElementKlassEntity < Labimotion::GenericKlassEntity
|
|
8
8
|
expose :name, :icon_name, :klass_prefix, :is_generic
|
|
9
9
|
end
|
|
10
10
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require 'labimotion/entities/application_entity'
|
|
4
4
|
module Labimotion
|
|
5
5
|
# ElementRevisionEntity
|
|
6
|
-
class ElementRevisionEntity < ApplicationEntity
|
|
6
|
+
class ElementRevisionEntity < Labimotion::ApplicationEntity
|
|
7
7
|
expose :id, :element_id, :uuid, :name, :klass_uuid, :properties, :created_at
|
|
8
8
|
def created_at
|
|
9
9
|
object.created_at.strftime('%d.%m.%Y, %H:%M')
|
|
@@ -13,6 +13,8 @@ module Labimotion
|
|
|
13
13
|
object.properties[Labimotion::Prop::LAYERS]&.keys.each do |key|
|
|
14
14
|
field_sample_molecules = object.properties[Labimotion::Prop::LAYERS][key][Labimotion::Prop::FIELDS].select { |ss| ss['type'] == Labimotion::FieldType::DRAG_SAMPLE || ss['type'] == Labimotion::FieldType::DRAG_MOLECULE }
|
|
15
15
|
field_sample_molecules.each do |field|
|
|
16
|
+
next unless field['value'].is_a?(Hash)
|
|
17
|
+
|
|
16
18
|
idx = object.properties[Labimotion::Prop::LAYERS][key][Labimotion::Prop::FIELDS].index(field)
|
|
17
19
|
sid = field.dig('value', 'el_id')
|
|
18
20
|
next unless sid.present?
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
require 'labimotion/entities/application_entity'
|
|
4
4
|
module Labimotion
|
|
5
5
|
## ElementEntity
|
|
6
|
-
class ElnElementEntity < ApplicationEntity
|
|
6
|
+
class ElnElementEntity < Labimotion::ApplicationEntity
|
|
7
7
|
with_options(anonymize_below: 0) do
|
|
8
8
|
expose! :created_by
|
|
9
9
|
expose! :id
|
|
@@ -30,6 +30,8 @@ module Labimotion
|
|
|
30
30
|
ss['type'] == Labimotion::FieldType::DRAG_SAMPLE || ss['type'] == Labimotion::FieldType::DRAG_MOLECULE
|
|
31
31
|
end
|
|
32
32
|
field_sample_molecules.each do |field|
|
|
33
|
+
next unless field['value'].is_a?(Hash)
|
|
34
|
+
|
|
33
35
|
idx = object.properties[Labimotion::Prop::LAYERS][key][Labimotion::Prop::FIELDS].index(field)
|
|
34
36
|
sid = field.dig('value', 'el_id')
|
|
35
37
|
next unless sid.present?
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
require 'labimotion/entities/application_entity'
|
|
4
4
|
module Labimotion
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
expose :
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
expose_timestamps(timestamp_fields: [
|
|
12
|
-
expose_timestamps(timestamp_fields: [:sync_time])
|
|
5
|
+
class GenericKlassEntity < Labimotion::ApplicationEntity
|
|
6
|
+
expose :id, :uuid, :label, :desc, :is_active, :version, :place
|
|
7
|
+
expose :released_at, :identifier, :sync_time
|
|
8
|
+
|
|
9
|
+
expose :properties_template, **DISPLAYED_IN_LIST_CONDITION, anonymize_with: {}
|
|
10
|
+
expose :properties_release, **DISPLAYED_IN_LIST_CONDITION, anonymize_with: {}
|
|
11
|
+
expose_timestamps(timestamp_fields: %i[released_at created_at updated_at sync_time])
|
|
13
12
|
end
|
|
14
13
|
end
|
|
@@ -10,15 +10,17 @@ module Labimotion
|
|
|
10
10
|
expose! :desc
|
|
11
11
|
expose! :icon_name
|
|
12
12
|
expose! :klass_prefix
|
|
13
|
-
expose
|
|
13
|
+
expose :klass_name do |obj|
|
|
14
|
+
obj[:name] || ''
|
|
15
|
+
end
|
|
14
16
|
expose! :label
|
|
15
17
|
expose! :identifier
|
|
16
18
|
expose! :version
|
|
17
19
|
expose! :released_at
|
|
18
|
-
expose
|
|
20
|
+
expose :properties_release, **DISPLAYED_IN_LIST_CONDITION, anonymize_with: {}
|
|
19
21
|
expose :element_klass do |obj|
|
|
20
22
|
if obj[:element_klass_id]
|
|
21
|
-
{ :
|
|
23
|
+
{ label: obj.element_klass.label, icon_name: obj.element_klass.icon_name, id: obj.element_klass_id }
|
|
22
24
|
else
|
|
23
25
|
{}
|
|
24
26
|
end
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'labimotion/entities/application_entity'
|
|
3
4
|
module Labimotion
|
|
4
5
|
# KlassRevisionEntity
|
|
5
|
-
class KlassRevisionEntity < ApplicationEntity
|
|
6
|
+
class KlassRevisionEntity < Labimotion::ApplicationEntity
|
|
6
7
|
expose :id, :uuid, :properties_release, :version, :released_at
|
|
7
8
|
|
|
8
9
|
expose :klass_id do |object|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'labimotion/entities/application_entity'
|
|
2
3
|
|
|
3
4
|
module Labimotion
|
|
@@ -57,6 +58,8 @@ module Labimotion
|
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
def update_sample_or_molecule_field(key, field, idx)
|
|
61
|
+
return unless field['value'].is_a?(Hash)
|
|
62
|
+
|
|
60
63
|
sid = field.dig('value', 'el_id')
|
|
61
64
|
return unless sid.present?
|
|
62
65
|
|
|
@@ -78,6 +81,8 @@ module Labimotion
|
|
|
78
81
|
end
|
|
79
82
|
|
|
80
83
|
def update_reaction_field(key, field, idx)
|
|
84
|
+
return unless field['value'].is_a?(Hash)
|
|
85
|
+
|
|
81
86
|
sid = field.dig('value', 'el_id')
|
|
82
87
|
return unless sid.present?
|
|
83
88
|
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'labimotion/entities/application_entity'
|
|
4
|
+
require 'labimotion/entities/properties_entity'
|
|
4
5
|
module Labimotion
|
|
5
6
|
## Segment entity
|
|
6
|
-
class SegmentEntity < PropertiesEntity
|
|
7
|
-
expose :id, :segment_klass_id, :element_type, :element_id, :
|
|
7
|
+
class SegmentEntity < Labimotion::PropertiesEntity
|
|
8
|
+
expose :id, :segment_klass_id, :element_type, :element_id, :uuid, :klass_uuid, :klass_label
|
|
9
|
+
expose :properties, **DISPLAYED_IN_LIST_CONDITION, anonymize_with: {}
|
|
10
|
+
expose :properties_release, **DISPLAYED_IN_LIST_CONDITION, anonymize_with: {}
|
|
8
11
|
|
|
9
12
|
def klass_label
|
|
10
13
|
object.segment_klass.label
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
require 'labimotion/entities/application_entity'
|
|
4
4
|
module Labimotion
|
|
5
|
-
class SegmentRevisionEntity < ApplicationEntity
|
|
5
|
+
class SegmentRevisionEntity < Labimotion::ApplicationEntity
|
|
6
6
|
expose :id, :segment_id, :uuid, :klass_uuid, :properties, :created_at
|
|
7
7
|
def created_at
|
|
8
8
|
object.created_at.strftime('%d.%m.%Y, %H:%M')
|
|
@@ -12,6 +12,8 @@ module Labimotion
|
|
|
12
12
|
object.properties[Labimotion::Prop::LAYERS]&.keys.each do |key|
|
|
13
13
|
field_sample_molecules = object.properties[Labimotion::Prop::LAYERS][key][Labimotion::Prop::FIELDS].select { |ss| ss['type'] == Labimotion::FieldType::DRAG_SAMPLE || ss['type'] == Labimotion::FieldType::DRAG_MOLECULE }
|
|
14
14
|
field_sample_molecules.each do |field|
|
|
15
|
+
next unless field['value'].is_a?(Hash)
|
|
16
|
+
|
|
15
17
|
idx = object.properties[Labimotion::Prop::LAYERS][key][Labimotion::Prop::FIELDS].index(field)
|
|
16
18
|
sid = field.dig('value', 'el_id')
|
|
17
19
|
next unless sid.present?
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
require 'labimotion/entities/application_entity'
|
|
4
4
|
module Labimotion
|
|
5
5
|
# Dataset entity
|
|
6
|
-
class VocabularyEntity < ApplicationEntity
|
|
6
|
+
class VocabularyEntity < Labimotion::ApplicationEntity
|
|
7
7
|
expose :id, :identifier, :name, :label, :field_type, :opid, :term_id,
|
|
8
8
|
:field_id, :properties, :source, :source_id, :layer_id
|
|
9
9
|
expose :voc do |obj|
|
|
@@ -2,12 +2,25 @@
|
|
|
2
2
|
require 'grape'
|
|
3
3
|
|
|
4
4
|
module Labimotion
|
|
5
|
-
##
|
|
5
|
+
## ConverterHelpers
|
|
6
6
|
module ConverterHelpers
|
|
7
7
|
extend Grape::API::Helpers
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
# Update the general_description field in the container's extended_metadata
|
|
10
|
+
def self.update_general_description(container, current_user, date: nil, time: nil)
|
|
11
|
+
return unless container.present?
|
|
12
|
+
|
|
13
|
+
desc = container.extended_metadata['general_description']
|
|
14
|
+
|
|
15
|
+
general_desc = desc if desc.present? && desc.is_a?(Hash)
|
|
16
|
+
general_desc = JSON.parse(container.extended_metadata['general_description']) if desc.present? && desc.is_a?(String)
|
|
17
|
+
general_desc = {} unless general_desc.is_a?(Hash)
|
|
18
|
+
general_desc['creator'] = current_user.name if current_user.present?
|
|
19
|
+
general_desc['date'] = date if date.present?
|
|
20
|
+
general_desc['time'] = time if time.present?
|
|
21
|
+
|
|
22
|
+
container.extended_metadata['general_description'] = general_desc.to_json
|
|
23
|
+
container.save!
|
|
11
24
|
end
|
|
12
25
|
end
|
|
13
26
|
end
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require 'grape'
|
|
3
4
|
module Labimotion
|
|
4
|
-
|
|
5
|
+
## DatasetHelpers
|
|
5
6
|
module DatasetHelpers
|
|
6
7
|
extend Grape::API::Helpers
|
|
7
8
|
|
|
8
|
-
def klass_list(is_active)
|
|
9
|
+
def klass_list(is_active, displayed_in_list = false)
|
|
10
|
+
scope = displayed_in_list ? Labimotion::DatasetKlass.for_list_display : Labimotion::DatasetKlass.all
|
|
9
11
|
if is_active == true
|
|
10
|
-
|
|
12
|
+
scope.where(is_active: true).order('place') || []
|
|
11
13
|
else
|
|
12
|
-
|
|
14
|
+
scope.order('place') || []
|
|
13
15
|
end
|
|
14
16
|
end
|
|
15
17
|
|
|
@@ -31,18 +33,41 @@ module Labimotion
|
|
|
31
33
|
ds = Labimotion::DatasetKlass.find_by(ols_term_id: attributes['ols_term_id'])
|
|
32
34
|
ds.update!(attributes)
|
|
33
35
|
ds.create_klasses_revision(current_user)
|
|
34
|
-
{ status: 'success',
|
|
36
|
+
{ status: 'success',
|
|
37
|
+
message: "This dataset: [#{attributes['label']}] has been upgraded to the version: #{attributes['version']}!" }
|
|
35
38
|
end
|
|
36
39
|
else
|
|
37
40
|
attributes['created_by'] = current_user.id
|
|
38
41
|
ds = Labimotion::DatasetKlass.create!(attributes)
|
|
39
42
|
ds.create_klasses_revision(current_user)
|
|
40
|
-
{ status: 'success',
|
|
43
|
+
{ status: 'success',
|
|
44
|
+
message: "The dataset: #{attributes['label']} has been created using version: #{attributes['version']}!" }
|
|
41
45
|
end
|
|
42
46
|
rescue StandardError => e
|
|
43
47
|
Labimotion.log_exception(e, current_user)
|
|
44
48
|
# { error: e.message }
|
|
45
49
|
raise e
|
|
46
50
|
end
|
|
51
|
+
|
|
52
|
+
def find_best_match_template(ols_term_id)
|
|
53
|
+
result = Labimotion::TemplateMatcher.find_best_match(ols_term_id)
|
|
54
|
+
if result[:template]
|
|
55
|
+
build_template_response(result[:template], result[:match_type], result[:info_messages])
|
|
56
|
+
else
|
|
57
|
+
{ error: '', data: {}, info: "No matching template found for term id [#{ols_term_id}]" }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def build_template_response(template, match_type, info_messages)
|
|
64
|
+
response = {
|
|
65
|
+
error: '',
|
|
66
|
+
data: Labimotion::DatasetKlassEntity.represent(template, displayed_in_list: false),
|
|
67
|
+
match_type: match_type
|
|
68
|
+
}
|
|
69
|
+
response[:info] = info_messages.join('; ') if info_messages.any?
|
|
70
|
+
response
|
|
71
|
+
end
|
|
47
72
|
end
|
|
48
73
|
end
|
|
@@ -93,12 +93,13 @@ module Labimotion
|
|
|
93
93
|
all_coll = Collection.get_all_collection_for_user(current_user.id)
|
|
94
94
|
element.collections << all_coll
|
|
95
95
|
element.save!
|
|
96
|
-
|
|
97
|
-
element.properties = update_vocabularies(_properties, current_user, element)
|
|
96
|
+
element.properties = update_sample_association(params[:properties], current_user, element)
|
|
97
|
+
# element.properties = update_vocabularies(_properties, current_user, element)
|
|
98
98
|
element.container = update_datamodel(params[:container], current_user)
|
|
99
99
|
element.save!
|
|
100
100
|
update_element_labels(element, params[:user_labels], current_user.id)
|
|
101
101
|
element.save_segments(segments: params[:segments], current_user_id: current_user.id)
|
|
102
|
+
element.save!
|
|
102
103
|
element
|
|
103
104
|
rescue StandardError => e
|
|
104
105
|
Labimotion.log_exception(e, current_user)
|
|
@@ -129,9 +130,11 @@ module Labimotion
|
|
|
129
130
|
attributes['klass_uuid'] = properties['klass_uuid']
|
|
130
131
|
element.update_columns(attributes)
|
|
131
132
|
end
|
|
133
|
+
# element.save_segments(segments: params[:segments], current_user_id: current_user.id)
|
|
134
|
+
element.reload
|
|
132
135
|
element.save_segments(segments: params[:segments], current_user_id: current_user.id)
|
|
133
136
|
element.reload
|
|
134
|
-
element.properties = update_vocabularies(element.properties, current_user, element)
|
|
137
|
+
# element.properties = update_vocabularies(element.properties, current_user, element)
|
|
135
138
|
## element.user_for_revision = current_user
|
|
136
139
|
element.save!
|
|
137
140
|
element
|
|
@@ -192,7 +195,7 @@ module Labimotion
|
|
|
192
195
|
def element_revisions(params)
|
|
193
196
|
klass = Labimotion::Element.find(params[:id])
|
|
194
197
|
list = klass.elements_revisions unless klass.nil?
|
|
195
|
-
list&.
|
|
198
|
+
list&.order(created_at: :desc)&.limit(10)
|
|
196
199
|
rescue StandardError => e
|
|
197
200
|
Labimotion.log_exception(e, current_user)
|
|
198
201
|
raise e
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Labimotion
|
|
4
|
+
# ExporterHelpers provides utility methods for formatting and processing export data
|
|
5
|
+
module ExporterHelpers
|
|
6
|
+
# Constants
|
|
7
|
+
CONST_UNNAMED = '(Unnamed)'
|
|
8
|
+
|
|
9
|
+
# Normalize column name, returns CONST_UNNAMED for blank values
|
|
10
|
+
def normalize_column_name(col_name)
|
|
11
|
+
return CONST_UNNAMED if col_name.nil? || col_name.to_s.strip.empty?
|
|
12
|
+
|
|
13
|
+
col_name
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Check if field type should be expanded (DRAG_SAMPLE or DRAG_MOLECULE)
|
|
17
|
+
def expandable_field?(field_type)
|
|
18
|
+
[Labimotion::FieldType::DRAG_SAMPLE, Labimotion::FieldType::DRAG_MOLECULE].include?(field_type)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Generate sanitized filename for export
|
|
22
|
+
def generate_filename(element_name, segment_name, layer_name, field_name)
|
|
23
|
+
timestamp = Time.zone.now.strftime('%Y%m%d_%H%M%S')
|
|
24
|
+
parts = [element_name]
|
|
25
|
+
parts << segment_name unless segment_name.empty?
|
|
26
|
+
parts.push(layer_name, field_name, timestamp)
|
|
27
|
+
parts.join('_').gsub(/\s+/, '_').gsub(/[()]/, '(' => '[', ')' => ']')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Format table cell value based on field type
|
|
31
|
+
def format_table_cell(sub_val, sub_field)
|
|
32
|
+
return '' if sub_field.fetch('id', nil).nil? || sub_val[sub_field['id']].nil?
|
|
33
|
+
|
|
34
|
+
case sub_field['type']
|
|
35
|
+
when Labimotion::FieldType::DRAG_SAMPLE, Labimotion::FieldType::DRAG_MOLECULE
|
|
36
|
+
format_drag_field_cell(sub_val, sub_field)
|
|
37
|
+
when Labimotion::FieldType::SELECT
|
|
38
|
+
format_select_cell(sub_val, sub_field)
|
|
39
|
+
when Labimotion::FieldType::SYSTEM_DEFINED
|
|
40
|
+
format_system_defined_cell(sub_val, sub_field)
|
|
41
|
+
else
|
|
42
|
+
format_default_cell(sub_val, sub_field)
|
|
43
|
+
end
|
|
44
|
+
rescue StandardError => e
|
|
45
|
+
Labimotion.log_exception(e)
|
|
46
|
+
''
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Format expanded cell for DRAG_SAMPLE or DRAG_MOLECULE with sub-headers
|
|
50
|
+
def format_expanded_cell(sub_val, exp_field)
|
|
51
|
+
field_id = exp_field['id']
|
|
52
|
+
sub_header = exp_field['sub_header']
|
|
53
|
+
|
|
54
|
+
return '' if field_id.nil? || sub_val[field_id].nil?
|
|
55
|
+
|
|
56
|
+
val = sub_val[field_id]['value'] || {}
|
|
57
|
+
return '' if val.blank? || !val.is_a?(Hash)
|
|
58
|
+
|
|
59
|
+
property_key = map_sub_header_to_property(sub_header)
|
|
60
|
+
val[property_key].to_s
|
|
61
|
+
rescue StandardError => e
|
|
62
|
+
Labimotion.log_exception(e)
|
|
63
|
+
''
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Map sub-header to property key
|
|
67
|
+
def map_sub_header_to_property(sub_header)
|
|
68
|
+
property_map = {
|
|
69
|
+
'name' => 'el_name',
|
|
70
|
+
'label' => 'el_label',
|
|
71
|
+
'short_label' => 'el_short_label',
|
|
72
|
+
'external_label' => 'el_external_label',
|
|
73
|
+
'molecular_weight' => 'el_molecular_weight',
|
|
74
|
+
'smiles' => 'el_smiles',
|
|
75
|
+
'inchikey' => 'el_inchikey',
|
|
76
|
+
'iupac' => 'el_iupac',
|
|
77
|
+
'sum_formula' => 'el_sum_formula',
|
|
78
|
+
'decoupled' => 'el_decoupled'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
property_map[sub_header] || "el_#{sub_header}"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Format drag field cell (DRAG_SAMPLE or DRAG_MOLECULE), returns hyperlink or label
|
|
85
|
+
def format_drag_field_cell(sub_val, sub_field)
|
|
86
|
+
val = sub_val[sub_field['id']]['value'] || {}
|
|
87
|
+
return '' if val.blank?
|
|
88
|
+
|
|
89
|
+
svg_url = val['el_svg'].to_s
|
|
90
|
+
if svg_url.present?
|
|
91
|
+
full_url = URI.join(Rails.application.config.root_url, svg_url).to_s
|
|
92
|
+
{ hyperlink: full_url, text: '[image link]' }
|
|
93
|
+
else
|
|
94
|
+
val['el_label'].to_s
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Format select cell
|
|
99
|
+
def format_select_cell(sub_val, sub_field)
|
|
100
|
+
sub_val[sub_field['id']]['value'].to_s
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Format system-defined cell with unit
|
|
104
|
+
def format_system_defined_cell(sub_val, sub_field)
|
|
105
|
+
value = sub_val[sub_field['id']]['value'].to_s
|
|
106
|
+
unit = find_unit_label(sub_val, sub_field)
|
|
107
|
+
|
|
108
|
+
unit.present? ? "#{value} #{unit}" : value
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Find unit label for system-defined field
|
|
112
|
+
def find_unit_label(sub_val, sub_field)
|
|
113
|
+
value_system = extract_value_system(sub_val, sub_field)
|
|
114
|
+
find_unit_by_key(sub_field['option_layers'], value_system)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Extract value_system from sub_val or sub_field
|
|
118
|
+
def extract_value_system(sub_val, sub_field)
|
|
119
|
+
field_data = sub_val[sub_field['id']]
|
|
120
|
+
field_data.is_a?(Hash) ? field_data['value_system'] : sub_field['value_system']
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Find unit label by field and key
|
|
124
|
+
def find_unit_by_key(option_layers, value_system)
|
|
125
|
+
field_config = Labimotion::Units::FIELDS.find { |o| o[:field] == option_layers }
|
|
126
|
+
return '' unless field_config
|
|
127
|
+
|
|
128
|
+
units = field_config.fetch(:units, [])
|
|
129
|
+
unit = units.find { |u| u[:key] == value_system }
|
|
130
|
+
unit ? unit.fetch(:label, '') : ''
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Format default cell
|
|
134
|
+
def format_default_cell(sub_val, sub_field)
|
|
135
|
+
cell_data = sub_val[sub_field['id']]
|
|
136
|
+
cell_data.is_a?(Hash) ? cell_data['value'].to_s : cell_data.to_s
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -134,5 +134,11 @@ module Labimotion
|
|
|
134
134
|
optional :select_options, type: Hash, desc: 'selections'
|
|
135
135
|
optional :option_layers, type: String, desc: 'option'
|
|
136
136
|
end
|
|
137
|
+
|
|
138
|
+
params :table_xlsx_params do
|
|
139
|
+
requires :klass, type: String, desc: 'Generic Type', values: %w[Element Segment]
|
|
140
|
+
requires :layer_id, type: String, desc: 'layer identifier'
|
|
141
|
+
requires :field_id, type: String, desc: 'field identifier'
|
|
142
|
+
end
|
|
137
143
|
end
|
|
138
144
|
end
|
|
@@ -8,8 +8,8 @@ module Labimotion
|
|
|
8
8
|
module SegmentHelpers
|
|
9
9
|
extend Grape::API::Helpers
|
|
10
10
|
|
|
11
|
-
def klass_list(el_klass, is_active=false)
|
|
12
|
-
scope = Labimotion::SegmentKlass.all
|
|
11
|
+
def klass_list(el_klass, is_active = false, displayed_in_list = false)
|
|
12
|
+
scope = displayed_in_list ? Labimotion::SegmentKlass.for_list_display : Labimotion::SegmentKlass.all
|
|
13
13
|
scope = scope.where(is_active: is_active) if is_active.present? && is_active == true
|
|
14
14
|
scope = scope.joins(:element_klass).where(klass_element: params[:element], is_active: true).preload(:element_klass) if el_klass.present?
|
|
15
15
|
scope.order('place') || []
|
|
@@ -6,6 +6,7 @@ require 'json'
|
|
|
6
6
|
require 'date'
|
|
7
7
|
require 'labimotion/version'
|
|
8
8
|
require 'labimotion/utils/utils'
|
|
9
|
+
require 'labimotion/helpers/converter_helpers'
|
|
9
10
|
|
|
10
11
|
# rubocop: disable Metrics/AbcSize
|
|
11
12
|
# rubocop: disable Metrics/MethodLength
|
|
@@ -228,6 +229,19 @@ module Labimotion
|
|
|
228
229
|
def self.update_ds(dataset, dsr, current_user = nil) # rubocop: disable Metrics/PerceivedComplexity
|
|
229
230
|
layers = dataset.properties[Labimotion::Prop::LAYERS] || {}
|
|
230
231
|
new_prop = dataset.properties
|
|
232
|
+
|
|
233
|
+
container = dataset.element
|
|
234
|
+
|
|
235
|
+
# Extract and update general description fields
|
|
236
|
+
general_dsr = dsr.select { |ds| ds[:layer] == 'general' }
|
|
237
|
+
if general_dsr.present?
|
|
238
|
+
date = general_dsr.find { |ds| ds[:field] == 'date' }&.dig(:value)
|
|
239
|
+
time = general_dsr.find { |ds| ds[:field] == 'time' }&.dig(:value)
|
|
240
|
+
Labimotion::ConverterHelpers.update_general_description(container, current_user, date: date, time: time)
|
|
241
|
+
else
|
|
242
|
+
Labimotion::ConverterHelpers.update_general_description(container, current_user)
|
|
243
|
+
end
|
|
244
|
+
|
|
231
245
|
dsr.each do |ds|
|
|
232
246
|
layer = layers[ds[:layer]]
|
|
233
247
|
next if layer.blank? || layer[Labimotion::Prop::FIELDS].blank?
|