labimotion 2.0.0.rc8 → 2.1.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/labimotion/apis/generic_dataset_api.rb +20 -3
  3. data/lib/labimotion/apis/generic_element_api.rb +22 -2
  4. data/lib/labimotion/apis/generic_klass_api.rb +40 -1
  5. data/lib/labimotion/apis/segment_api.rb +5 -3
  6. data/lib/labimotion/entities/application_entity.rb +7 -80
  7. data/lib/labimotion/entities/dataset_entity.rb +8 -3
  8. data/lib/labimotion/entities/dataset_klass_entity.rb +3 -2
  9. data/lib/labimotion/entities/element_entity.rb +1 -1
  10. data/lib/labimotion/entities/element_klass_entity.rb +2 -2
  11. data/lib/labimotion/entities/element_revision_entity.rb +1 -1
  12. data/lib/labimotion/entities/eln_element_entity.rb +2 -2
  13. data/lib/labimotion/entities/generic_klass_entity.rb +8 -9
  14. data/lib/labimotion/entities/generic_public_entity.rb +5 -3
  15. data/lib/labimotion/entities/klass_revision_entity.rb +2 -1
  16. data/lib/labimotion/entities/properties_entity.rb +1 -0
  17. data/lib/labimotion/entities/segment_entity.rb +5 -2
  18. data/lib/labimotion/entities/segment_revision_entity.rb +2 -2
  19. data/lib/labimotion/entities/vocabulary_entity.rb +2 -2
  20. data/lib/labimotion/helpers/dataset_helpers.rb +6 -4
  21. data/lib/labimotion/helpers/element_helpers.rb +6 -4
  22. data/lib/labimotion/helpers/segment_helpers.rb +2 -2
  23. data/lib/labimotion/libs/data/mapper/Chemwiki.json +2 -2
  24. data/lib/labimotion/libs/properties_handler.rb +10 -0
  25. data/lib/labimotion/libs/vocabulary_handler.rb +8 -6
  26. data/lib/labimotion/models/concerns/datasetable.rb +3 -3
  27. data/lib/labimotion/models/concerns/generic_klass.rb +16 -0
  28. data/lib/labimotion/models/concerns/segmentable.rb +44 -7
  29. data/lib/labimotion/models/dataset_klass.rb +15 -2
  30. data/lib/labimotion/models/element.rb +7 -1
  31. data/lib/labimotion/models/element_klass.rb +10 -1
  32. data/lib/labimotion/models/segment_klass.rb +10 -4
  33. data/lib/labimotion/version.rb +2 -2
  34. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e53e20059dd2a04452a513beb5024c762f8596d5694874c67b58d64baaf0ce47
4
- data.tar.gz: d6b10330fe1dbce975b54a799e5a25d4cb13202fcf7b0a909ae9204fa1442146
3
+ metadata.gz: dc6bf6015a4c970c7f2bd3e3d6e1a2dfb334e6acc4c53f255c5ef7cb912b6b1e
4
+ data.tar.gz: c8d7fda79cb1e3967a884b7ebd53db0f08eb90777ec0f78e58cabada98038f82
5
5
  SHA512:
6
- metadata.gz: 118380f7c4b0483f3ceb7b80d5aa324db7d3fcde3e0b7a5ee66a92a02528fd8e8b352072c79fce563c9955eedc7efcf199e1f1645d391736c54715993f8d4c1d
7
- data.tar.gz: c23b0428e3854382643548c08abdd0b875efbcdc052b7b6dd7a612996781056679dc23ce09d46b99e844c1e69016bd17dc54d9dd6d99bbfe1d22a6ce9bd06c44
6
+ metadata.gz: 0dc6b6c5242a5480c52535ab0dba78593ed66258ba9d509c8d10674dcddd50a683a319331753b098112889faa6c5199df2dab26a17e16990944227ff914a85ff
7
+ data.tar.gz: e78481dec58ed4a2a3e50d968fd39561ce0a2e25aba773db3f1f97b79cdc58c2737c3ec354456ceb1eb3fcae8a953ad51228f631720f9dc31dbabd4b794856d5
@@ -11,19 +11,36 @@ module Labimotion
11
11
  namespace :klasses do
12
12
  desc 'get dataset klasses'
13
13
  get do
14
- list = klass_list(true)
14
+ list = klass_list(true, false)
15
15
  present list.sort_by(&:place), with: Labimotion::DatasetKlassEntity, root: 'klass'
16
16
  end
17
17
  end
18
18
 
19
+ namespace :list_klass do
20
+ desc 'list Generic Dataset Klass'
21
+ params do
22
+ optional :is_active, type: Boolean, desc: 'Active or Inactive Dataset'
23
+ optional :displayed_in_list, type: Boolean, desc: 'Display in list format', default: true
24
+ end
25
+ get do
26
+ list = klass_list(params[:is_active], params[:displayed_in_list])
27
+ serialized_data = Labimotion::DatasetKlassEntity.represent(list, displayed_in_list: params[:displayed_in_list])
28
+ { mc: 'ss00', data: serialized_data }
29
+ rescue StandardError => e
30
+ Labimotion.log_exception(e, current_user)
31
+ { mc: 'se00', msg: e.message, data: [] }
32
+ end
33
+ end
34
+
35
+ # Deprecated: This namespace is no longer used, but kept for backward compatibility. It is replaced by `list_klass`.
19
36
  namespace :list_dataset_klass do
20
37
  desc 'list Generic Dataset Klass'
21
38
  params do
22
39
  optional :is_active, type: Boolean, desc: 'Active or Inactive Dataset'
23
40
  end
24
41
  get do
25
- list = klass_list(params[:is_active])
26
- present list, with: Labimotion::DatasetKlassEntity, root: 'klass'
42
+ list = klass_list(params[:is_active], false)
43
+ present list, with: Labimotion::DatasetKlassEntity, root: 'klass', displayed_in_list: false
27
44
  end
28
45
  end
29
46
 
@@ -221,6 +221,26 @@ module Labimotion
221
221
  end
222
222
  end
223
223
 
224
+ namespace :list_element_klass do
225
+ desc 'list Generic Element Klass'
226
+ params do
227
+ optional :is_generic, type: Boolean, desc: 'Is Generic or Non-Generic Element'
228
+ optional :is_active, type: Boolean, desc: 'Active or Inactive Dataset'
229
+ optional :displayed_in_list, type: Boolean, desc: 'Display in list format', default: true
230
+ end
231
+ get do
232
+ scope = params[:displayed_in_list] ? Labimotion::ElementKlass.for_list_display : Labimotion::ElementKlass.all
233
+ scope = scope.where(is_generic: params[:is_generic]) if params.key?(:is_generic)
234
+
235
+ list = scope.sort_by(&:place)
236
+ present list, with: Labimotion::ElementKlassEntity, root: 'klass', displayed_in_list: params[:displayed_in_list]
237
+ rescue StandardError => e
238
+ Labimotion.log_exception(e, current_user)
239
+ raise e
240
+ end
241
+ end
242
+
243
+ # Deprecated: This namespace is no longer used, but kept for backward compatibility.
224
244
  namespace :klasses_all do
225
245
  desc 'get all klasses for admin function'
226
246
  get do
@@ -266,7 +286,7 @@ module Labimotion
266
286
  end
267
287
  after_validation do
268
288
  authenticate_admin!(params[:klass].gsub(/(Klass)/, 's').downcase)
269
- @klz = fetch_klass(params[:klass], params[:id])
289
+ fetch_klass(params[:klass], params[:id])
270
290
  end
271
291
  post do
272
292
  deactivate_klass(params)
@@ -301,7 +321,7 @@ module Labimotion
301
321
  end
302
322
  after_validation do
303
323
  authenticate_admin!(params[:klass].gsub(/(Klass)/, 's').downcase)
304
- @klz = fetch_klass(params[:klass], params[:id])
324
+ fetch_klass(params[:klass], params[:id])
305
325
  end
306
326
  post do
307
327
  update_template(params, current_user)
@@ -6,6 +6,7 @@ require 'labimotion/libs/export_element'
6
6
  module Labimotion
7
7
  # Generic Element API
8
8
  class GenericKlassAPI < Grape::API
9
+ helpers Labimotion::GenericHelpers
9
10
 
10
11
  resource :generic_klass do
11
12
  namespace :download_klass do
@@ -30,6 +31,44 @@ module Labimotion
30
31
  end
31
32
  end
32
33
 
34
+ namespace :de_activate do
35
+ desc 'activate or deactivate Generic Klass'
36
+ params do
37
+ requires :klass, type: String, desc: 'Klass', values: %w[ElementKlass SegmentKlass DatasetKlass]
38
+ requires :id, type: Integer, desc: 'Klass ID'
39
+ requires :is_active, type: Boolean, desc: 'Active or Inactive Klass'
40
+ end
41
+ after_validation do
42
+ authenticate_admin!(params[:klass].gsub(/(Klass)/, 's').downcase)
43
+ fetch_klass(params[:klass], params[:id])
44
+ end
45
+ post do
46
+ updated_klass = deactivate_klass(params)
47
+ entity_class = "Labimotion::#{params[:klass]}Entity".constantize
48
+ serialized_data = entity_class.represent(updated_klass)
49
+ { mc: 'ss00', data: serialized_data }
50
+ rescue StandardError => e
51
+ Labimotion.log_exception(e, current_user)
52
+ { mc: 'se00', msg: e.message, data: {} }
53
+ end
54
+ end
55
+
56
+ namespace :fetch do
57
+ desc 'fetch Generic Klass by id'
58
+ params do
59
+ requires :id, type: Integer, desc: 'Klass ID'
60
+ requires :klass, type: String, desc: 'Klass', values: %w[ElementKlass SegmentKlass DatasetKlass]
61
+ end
62
+ get do
63
+ klass_obj = fetch_klass(params[:klass], params[:id])
64
+ entity_class = "Labimotion::#{params[:klass]}Entity".constantize
65
+ serialized_data = entity_class.represent(klass_obj)
66
+ { mc: 'ss00', data: serialized_data }
67
+ rescue StandardError => e
68
+ Labimotion.log_exception(e, current_user)
69
+ { mc: 'se00', msg: e.message, data: {} }
70
+ end
71
+ end
33
72
  end
34
73
  end
35
- end
74
+ end
@@ -12,19 +12,21 @@ module Labimotion
12
12
  optional :element, type: String, desc: "Klass Element, e.g. Sample, Reaction, Mof,..."
13
13
  end
14
14
  get do
15
- list = klass_list(params[:element], true)
15
+ list = klass_list(params[:element], true, false)
16
16
  present list, with: Labimotion::SegmentKlassEntity, root: 'klass'
17
17
  end
18
18
  end
19
19
 
20
+ # TODO: params[:displayed_in_list] will be used in the future to control the display format, set 'false' for now.
20
21
  namespace :list_segment_klass do
21
22
  desc 'list Generic Segment Klass'
22
23
  params do
23
24
  optional :is_active, type: Boolean, desc: 'Active or Inactive Segment'
25
+ optional :displayed_in_list, type: Boolean, desc: 'Display in list format', default: false
24
26
  end
25
27
  get do
26
- list = klass_list(nil, params[:is_active])
27
- present list, with: Labimotion::SegmentKlassEntity, root: 'klass'
28
+ list = klass_list(nil, params[:is_active], false)
29
+ present list, with: Labimotion::SegmentKlassEntity, root: 'klass', displayed_in_list: false
28
30
  end
29
31
  end
30
32
 
@@ -1,88 +1,15 @@
1
1
  # frozen_string_literal: true
2
- #
2
+
3
3
  module Labimotion
4
4
  ## ApplicationEntity
5
- class ApplicationEntity < Grape::Entity
6
- CUSTOM_ENTITY_OPTIONS = %i[anonymize_below anonymize_with].freeze
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&.strftime('%Y-%m-%d %H:%M:%S %Z') : nil
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, :properties, :properties_release, :element_id, :element_type, :klass_ols, :klass_label, :klass_uuid
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')
@@ -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
@@ -1,14 +1,13 @@
1
1
  # frozen_string_literal: true
2
- #
2
+
3
3
  require 'labimotion/entities/application_entity'
4
4
  module Labimotion
5
- # GenericKlassEntity
6
- class GenericKlassEntity < ApplicationEntity
7
- expose :id, :uuid, :label, :desc, :properties_template, :properties_release, :is_active, :version,
8
- :place, :released_at, :identifier, :sync_time, :created_by, :updated_by, :created_at, :updated_at
9
- expose_timestamps(timestamp_fields: [:released_at])
10
- expose_timestamps(timestamp_fields: [:created_at])
11
- expose_timestamps(timestamp_fields: [:updated_at])
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! :klass_name
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! :properties_release, if: :displayed
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
- { :label => obj.element_klass.label, :icon_name => obj.element_klass.icon_name }
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
@@ -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, :properties, :properties_release, :uuid, :klass_uuid, :klass_label
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')
@@ -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|
@@ -1,15 +1,17 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'grape'
3
4
  module Labimotion
4
- ## DatasetHelpers
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
- Labimotion::DatasetKlass.where(is_active: true).order('place') || []
12
+ scope.where(is_active: true).order('place') || []
11
13
  else
12
- Labimotion::DatasetKlass.all.order('place') || []
14
+ scope.order('place') || []
13
15
  end
14
16
  end
15
17
 
@@ -93,8 +93,8 @@ 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
- _properties = update_sample_association(params[:properties], current_user, element)
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)
@@ -129,11 +129,13 @@ module Labimotion
129
129
  attributes['klass_uuid'] = properties['klass_uuid']
130
130
  element.update_columns(attributes)
131
131
  end
132
+ # element.save_segments(segments: params[:segments], current_user_id: current_user.id)
133
+ element.reload
132
134
  element.save_segments(segments: params[:segments], current_user_id: current_user.id)
133
135
  element.reload
134
- element.properties = update_vocabularies(element.properties, current_user, element)
136
+ # element.properties = update_vocabularies(element.properties, current_user, element)
135
137
  ## element.user_for_revision = current_user
136
- element.save!
138
+ # element.save!
137
139
  element
138
140
  rescue StandardError => e
139
141
  Labimotion.log_exception(e, current_user)
@@ -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') || []
@@ -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
+ }
@@ -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
@@ -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
@@ -42,15 +42,15 @@ module Labimotion
42
42
  props['identifier'] = klass.identifier if klass.identifier.present?
43
43
  props['uuid'] = uuid
44
44
  props['klass'] = 'Dataset'
45
+ props['klass_uuid'] = klass.uuid
45
46
  props = Labimotion::VocabularyHandler.update_vocabularies(props, args[:current_user], args[:element])
46
47
 
47
48
  ds = Labimotion::Dataset.find_by(element_type: self.class.name, element_id: id)
48
- if ds.present? && (ds.klass_uuid != props['klass_uuid'] || ds.properties != props)
49
- ds.update!(properties_release: klass.properties_release, uuid: uuid, dataset_klass_id: args[:dataset_klass_id], properties: props, klass_uuid: props['klass_uuid'])
49
+ if ds.present? && (ds.klass_uuid != klass.uuid || ds.properties != props)
50
+ ds.update!(properties_release: klass.properties_release, uuid: uuid, dataset_klass_id: args[:dataset_klass_id], properties: props, klass_uuid: klass.uuid)
50
51
  end
51
52
  return if ds.present?
52
53
 
53
- props['klass_uuid'] = klass.uuid
54
54
  Labimotion::Dataset.create!(properties_release: klass.properties_release, uuid: uuid, dataset_klass_id: args[:dataset_klass_id], element_type: self.class.name, element_id: id, properties: props, klass_uuid: klass.uuid)
55
55
  end
56
56
 
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Labimotion
4
+ ## Generic Klass Helpers
5
+ module GenericKlass
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ # Scope for active and released templates
10
+ scope :active_and_released, -> { for_list_display.where(is_active: true).where.not(released_at: nil) }
11
+
12
+ # Scope for active, released, and generic templates (primarily for ElementKlass)
13
+ scope :active_released_generic, -> { active_and_released.where(is_generic: true) }
14
+ end
15
+ end
16
+ end
@@ -46,11 +46,17 @@ module Labimotion
46
46
  end
47
47
  end
48
48
 
49
+ def touch_vocabulary(current_user)
50
+ touch_element_properties(current_user) if instance_of?(::Labimotion::Element)
51
+ touch_segments_properties(current_user)
52
+ touch_analyses_properties(current_user)
53
+ end
54
+
49
55
  def save_segments(**args) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
50
- return if args[:segments].nil?
56
+ args_segments = args[:segments] || []
51
57
 
52
- segments = []
53
- args[:segments]&.each do |seg|
58
+ current_user = User.find_by(id: args[:current_user_id])
59
+ args_segments.each do |seg|
54
60
  klass = Labimotion::SegmentKlass.find_by(id: seg['segment_klass_id'])
55
61
  uuid = SecureRandom.uuid
56
62
  props = seg['properties']
@@ -59,21 +65,52 @@ module Labimotion
59
65
  props['uuid'] = uuid
60
66
  props['klass'] = 'Segment'
61
67
  props = Labimotion::SampleAssociation.update_sample_association(props, args[:current_user_id])
62
- current_user = User.find_by(id: args[:current_user_id])
63
- props = Labimotion::VocabularyHandler.update_vocabularies(props, current_user, self)
68
+ # props = Labimotion::VocabularyHandler.update_vocabularies(props, current_user, self)
64
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
65
70
  if segment.present? && (segment.klass_uuid != props['klass_uuid'] || segment.properties != props)
66
71
  segment.update!(properties_release: klass.properties_release, properties: props, uuid: uuid, klass_uuid: props['klass_uuid'])
67
- segments.push(segment)
72
+ # segments.push(segment)
68
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
69
74
  end
70
75
  next if segment.present?
71
76
 
72
77
  props['klass_uuid'] = klass.uuid
73
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)
74
- segments.push(segment)
79
+ # segments.push(segment)
75
80
  end
81
+
82
+ self.reload
83
+ touch_vocabulary(current_user)
84
+ self.reload
76
85
  segments
77
86
  end
87
+
88
+ def touch_element_properties(current_user)
89
+ touch_properties_for_object(self, current_user)
90
+ end
91
+
92
+ def touch_segments_properties(current_user)
93
+ segments.each do |segment|
94
+ touch_properties_for_object(segment, current_user)
95
+ end
96
+ end
97
+
98
+ def touch_analyses_properties(current_user)
99
+ analyses.each do |analysis|
100
+ analysis.children.each do |child|
101
+ dataset = child.dataset
102
+ next if dataset.nil?
103
+
104
+ touch_properties_for_object(dataset, current_user)
105
+ end
106
+ end
107
+ end
108
+
109
+ # NOTE: Update bypassing validations, callbacks, and timestamp updates
110
+ def touch_properties_for_object(object, current_user)
111
+ props_dup = object.properties.deep_dup
112
+ Labimotion::VocabularyHandler.update_vocabularies(props_dup, current_user, self)
113
+ object.update_column(:properties, props_dup) if props_dup != object.properties
114
+ end
78
115
  end
79
116
  end
@@ -1,22 +1,35 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'labimotion/models/concerns/generic_klass_revisions'
4
+ require 'labimotion/models/concerns/generic_klass'
3
5
 
4
6
  module Labimotion
5
7
  class DatasetKlass < ApplicationRecord
6
8
  acts_as_paranoid
7
9
  self.table_name = :dataset_klasses
8
10
  include GenericKlassRevisions
11
+ include GenericKlass
9
12
  has_many :datasets, dependent: :destroy, class_name: 'Labimotion::Dataset'
10
13
  has_many :dataset_klasses_revisions, dependent: :destroy, class_name: 'Labimotion::DatasetKlassesRevision'
11
14
 
15
+ # Scope for displayed_in_list - select only necessary columns for list view
16
+ scope :for_list_display, lambda {
17
+ select(:id, :uuid, :label, :desc, :is_active, :version, :place, :released_at,
18
+ :identifier, :sync_time, :created_at, :updated_at, :ols_term_id)
19
+ }
20
+
12
21
  def self.init_seeds
13
- seeds_path = File.join(Rails.root, 'db', 'seeds', 'json', 'dataset_klasses.json')
22
+ gem_spec = Gem.loaded_specs['labimotion']
23
+ gem_root = gem_spec.gem_dir if gem_spec
24
+ seeds_path = File.join(gem_root, 'db', 'seeds', 'dataset_klasses.json')
14
25
  seeds = JSON.parse(File.read(seeds_path))
15
26
 
16
27
  seeds['chmo'].each do |term|
17
28
  next if Labimotion::DatasetKlass.where(ols_term_id: term['id']).count.positive?
18
29
 
19
- attributes = { ols_term_id: term['id'], label: "#{term['label']} (#{term['synonym']})", desc: "#{term['label']} (#{term['synonym']})", place: term['position'], created_by: Admin.first&.id || 0 }
30
+ attributes = { ols_term_id: term['id'], label: "#{term['label']} (#{term['synonym']})",
31
+ desc: "#{term['label']} (#{term['synonym']})", place: term['position'],
32
+ created_by: Admin.first&.id || 0 }
20
33
  Labimotion::DatasetKlass.create!(attributes)
21
34
  end
22
35
  true
@@ -31,6 +31,7 @@ module Labimotion
31
31
 
32
32
  belongs_to :element_klass, class_name: 'Labimotion::ElementKlass'
33
33
 
34
+ # has_ancestry ancestry_format: :materialized_path2
34
35
  has_ancestry orphan_strategy: :adopt
35
36
 
36
37
  has_many :collections_elements, inverse_of: :element, dependent: :destroy, class_name: 'Labimotion::CollectionsElement'
@@ -49,6 +50,7 @@ module Labimotion
49
50
  scope :elements_updated_time_to, ->(time) { where('elements.updated_at <= ?', time) }
50
51
 
51
52
  belongs_to :creator, foreign_key: :created_by, class_name: 'User'
53
+ before_validation :set_root_ancestry_if_nil
52
54
  validates :creator, presence: true
53
55
 
54
56
  has_many :elements_elements, foreign_key: :parent_id, class_name: 'Labimotion::ElementsElement'
@@ -71,7 +73,7 @@ module Labimotion
71
73
  end
72
74
 
73
75
  def analyses
74
- container ? container.analyses : []
76
+ container ? container.analyses : Container.none
75
77
  end
76
78
 
77
79
  def auto_set_short_label
@@ -148,5 +150,9 @@ module Labimotion
148
150
  attachments.each(&:destroy!)
149
151
  end
150
152
  end
153
+
154
+ def set_root_ancestry_if_nil
155
+ self.ancestry ||= '/'
156
+ end
151
157
  end
152
158
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'labimotion/conf'
3
4
  require 'labimotion/models/concerns/generic_klass_revisions'
5
+ require 'labimotion/models/concerns/generic_klass'
4
6
  require 'labimotion/models/concerns/workflow'
5
7
 
6
8
  module Labimotion
@@ -8,11 +10,19 @@ module Labimotion
8
10
  self.table_name = :element_klasses
9
11
  acts_as_paranoid
10
12
  include GenericKlassRevisions
13
+ include GenericKlass
11
14
  include Workflow
12
15
  has_many :elements, dependent: :destroy, class_name: 'Labimotion::Element'
13
16
  has_many :segment_klasses, dependent: :destroy, class_name: 'Labimotion::SegmentKlass'
14
17
  has_many :element_klasses_revisions, dependent: :destroy, class_name: 'Labimotion::ElementKlassesRevision'
15
18
 
19
+ # Scope for displayed_in_list - select only necessary columns for list view
20
+ scope :for_list_display, lambda {
21
+ select(:id, :uuid, :label, :desc, :is_active, :version, :place, :released_at,
22
+ :identifier, :sync_time, :created_at, :updated_at, :name, :icon_name,
23
+ :klass_prefix, :is_generic)
24
+ }
25
+
16
26
  def self.gen_klasses_json
17
27
  klasses = where(is_active: true, is_generic: true).order('place')&.pluck(:name) || []
18
28
  rescue ActiveRecord::StatementInvalid, PG::ConnectionBad, PG::UndefinedTable
@@ -24,6 +34,5 @@ module Labimotion
24
34
  klasses&.to_json || []
25
35
  )
26
36
  end
27
-
28
37
  end
29
38
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'labimotion/models/concerns/generic_klass_revisions'
4
+ require 'labimotion/models/concerns/generic_klass'
3
5
  require 'labimotion/models/concerns/workflow'
4
6
 
5
7
  module Labimotion
@@ -7,20 +9,24 @@ module Labimotion
7
9
  self.table_name = :segment_klasses
8
10
  acts_as_paranoid
9
11
  include GenericKlassRevisions
12
+ include GenericKlass
10
13
  include Workflow
11
14
  belongs_to :element_klass, class_name: 'Labimotion::ElementKlass'
12
15
  has_many :segments, dependent: :destroy, class_name: 'Labimotion::Segment'
13
16
  has_many :segment_klasses_revisions, dependent: :destroy, class_name: 'Labimotion::SegmentKlassesRevision'
14
17
 
18
+ # Scope for displayed_in_list - select only necessary columns for list view
19
+ scope :for_list_display, lambda {
20
+ select(:id, :uuid, :label, :desc, :is_active, :version, :place, :released_at,
21
+ :identifier, :sync_time, :created_at, :updated_at, :element_klass_id)
22
+ }
23
+
15
24
  def self.gen_klasses_json
16
25
  klasses = where(is_active: true)&.pluck(:name) || []
17
26
  rescue ActiveRecord::StatementInvalid, PG::ConnectionBad, PG::UndefinedTable
18
27
  klasses = []
19
28
  ensure
20
- File.write(
21
- Rails.root.join('config', 'segment_klass.json'),
22
- klasses&.to_json || []
23
- )
29
+ Rails.root.join('config', 'segment_klass.json').write(klasses&.to_json || [])
24
30
  end
25
31
  end
26
32
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  ## Labimotion Version
4
4
  module Labimotion
5
- VERSION = '2.0.0.rc8'
6
- end
5
+ VERSION = '2.1.0.rc1'
6
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: labimotion
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.rc8
4
+ version: 2.1.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chia-Lin Lin
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2025-02-28 00:00:00.000000000 Z
12
+ date: 2025-08-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -91,6 +91,7 @@ files:
91
91
  - lib/labimotion/models/collections_element.rb
92
92
  - lib/labimotion/models/concerns/attachment_converter.rb
93
93
  - lib/labimotion/models/concerns/datasetable.rb
94
+ - lib/labimotion/models/concerns/generic_klass.rb
94
95
  - lib/labimotion/models/concerns/generic_klass_revisions.rb
95
96
  - lib/labimotion/models/concerns/generic_revisions.rb
96
97
  - lib/labimotion/models/concerns/linked_properties.rb