labimotion 2.1.0.rc12 → 2.1.0.rc14

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e3778897d34066e72b55508a3f5788f26f85daa5b30223a81d60736c55150ff3
4
- data.tar.gz: dddc91940a4e1f56869ed6e1ff27c155197836f3061eb091258b597884f0cb46
3
+ metadata.gz: 3be4d822cf9ab027e3d64f1b6356f02a6d696a6125c0db56bf8ca61e342eaabe
4
+ data.tar.gz: 8038b4423f9c7a26fa01c07defd715809d4698a51b6362948de4560870ab4c61
5
5
  SHA512:
6
- metadata.gz: c842df479d71468944e769f4f5978ccce3cd4597f6368439b0e2da189f1740434755827787e7201cd3d654268aa0fac00407d4e230b23f767c0f2009f14a08af
7
- data.tar.gz: a8a16ba782bb66fdd878aeb8d27b57f7a8c42b669854e274a5081f45c99f772338f1a43bd2728e7ca5b83558c7e5c757877a1696c48bb8148a91667be14deb55
6
+ metadata.gz: 3c2fe973a0fd5593246206ac697d84ae77bea51f32d1501d3fbd4dc79273d67d9ac04c54866c1cf2a952424043a1edca73591756cd5644c8156d07943bc51208
7
+ data.tar.gz: 70b348d0130ee3fe9cc72e829013b06357172a16f5cda79e4799dcd019d317f2561ce2910e457382904cda7fe02d36465ef58fbde0de24803990fd0b428ea5c6
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # app/api/labimotion/central_api.rb
2
4
  module Labimotion
3
5
  class LabimotionAPI < Grape::API
@@ -11,4 +13,4 @@ module Labimotion
11
13
  mount Labimotion::StandardLayerAPI
12
14
  mount Labimotion::VocabularyAPI
13
15
  end
14
- end
16
+ end
@@ -11,24 +11,24 @@ module Labimotion
11
11
 
12
12
  namespace :labimotion_hub do
13
13
  namespace :list do
14
- desc "get active generic templates"
14
+ desc 'get active generic templates'
15
15
  params do
16
- requires :klass, type: String, desc: 'Klass', values: %w[ElementKlass SegmentKlass DatasetKlass]
16
+ requires :klass, type: String, desc: 'Klass', values: Labimotion::Constants::Klass::ALL
17
17
  optional :with_props, type: Boolean, desc: 'With Properties', default: false
18
18
  end
19
19
  get do
20
20
  list = "Labimotion::#{params[:klass]}".constantize.where(is_active: true).where.not(released_at: nil)
21
- list = list.where(is_generic: true) if params[:klass] == 'ElementKlass'
22
- entities = Labimotion::GenericPublicEntity.represent(list, displayed: params[:with_props], root: 'list')
21
+ list = list.where(is_generic: true) if params[:klass] == Labimotion::Constants::Klass::ELEMENT
22
+ Labimotion::GenericPublicEntity.represent(list, displayed: params[:with_props], root: 'list')
23
23
  rescue StandardError => e
24
24
  Labimotion.log_exception(e, current_user)
25
25
  []
26
26
  end
27
27
  end
28
28
  namespace :fetch do
29
- desc "get active generic templates"
29
+ desc 'get active generic templates'
30
30
  params do
31
- requires :klass, type: String, desc: 'Klass', values: %w[ElementKlass SegmentKlass DatasetKlass]
31
+ requires :klass, type: String, desc: 'Klass', values: Labimotion::Constants::Klass::ALL
32
32
  requires :origin, type: String, desc: 'origin'
33
33
  requires :identifier, type: String, desc: 'Identifier'
34
34
  end
@@ -43,13 +43,16 @@ module Labimotion
43
43
  end
44
44
 
45
45
  namespace :element_klasses_name do
46
- desc "get klasses"
46
+ desc 'get klasses'
47
47
  params do
48
- optional :generic_only, type: Boolean, desc: "list generic element only"
48
+ optional :generic_only, type: Boolean, desc: 'list generic element only'
49
49
  end
50
50
  get do
51
- list = Labimotion::ElementKlass.where(is_active: true) if params[:generic_only].present? && params[:generic_only] == true
52
- list = Labimotion::ElementKlass.where(is_active: true) unless params[:generic_only].present? && params[:generic_only] == true
51
+ if params[:generic_only].present? && params[:generic_only] == true
52
+ list = Labimotion::ElementKlass.where(is_active: true, is_generic: true)
53
+ else
54
+ list = Labimotion::ElementKlass.where(is_active: true)
55
+ end
53
56
  list.pluck(:name)
54
57
  rescue StandardError => e
55
58
  Labimotion.log_exception(e, current_user)
@@ -57,6 +60,76 @@ module Labimotion
57
60
  end
58
61
  end
59
62
 
63
+ namespace :submit do
64
+ desc 'submit a template'
65
+ params do
66
+ requires :klass, type: String, desc: 'klass of the template',
67
+ values: Labimotion::Constants::Klass::ALL
68
+ requires :id, type: Integer, desc: 'template revision id'
69
+ requires :contact_email, type: String, desc: 'email of the submitter', regexp: URI::MailTo::EMAIL_REGEXP
70
+ requires :application, type: String, desc: 'application for the template'
71
+ requires :message, type: String, desc: 'message of the submission'
72
+ end
73
+ post do
74
+ klass = params[:klass]
75
+ template = "Labimotion::#{klass}esRevision".constantize.find(params[:id])
76
+ klass_data = "Labimotion::#{klass}Entity".constantize.represent(template.klass, displayed_in_list: true)
77
+ metadata = {
78
+ klass: {
79
+ klass: klass,
80
+ data: klass_data
81
+ },
82
+ submission: {
83
+ last_name: current_user.last_name,
84
+ first_name: current_user.first_name,
85
+ email: current_user.email,
86
+ id: current_user.id,
87
+ contact_email: params[:contact_email],
88
+ application: params[:application],
89
+ message: params[:message]
90
+ }
91
+ }
92
+
93
+ # Submit the main template
94
+ result = Labimotion::TemplateHub.send_to_central_hub(klass, template.properties_release, metadata,
95
+ request.headers['Origin'])
96
+
97
+ # Increment submitted counter if submission was successful
98
+ template.increment_submitted! if result[:mc] == 'ss00'
99
+
100
+ # For SegmentKlass, also submit the associated ElementKlass
101
+ if klass == Labimotion::Constants::Klass::SEGMENT && result[:mc] == 'ss00'
102
+ element_klass = template.klass.element_klass
103
+ element_klass_data = Labimotion::ElementKlassEntity.represent(element_klass, displayed_in_list: true)
104
+ element_metadata = {
105
+ klass: {
106
+ klass: Labimotion::Constants::Klass::ELEMENT,
107
+ data: element_klass_data
108
+ },
109
+ submission: metadata[:submission].merge(
110
+ associated_submission_id: result[:data][:id]
111
+ )
112
+ }
113
+
114
+ element_result = Labimotion::TemplateHub.send_to_central_hub(Labimotion::Constants::Klass::ELEMENT,
115
+ element_klass.properties_release,
116
+ element_metadata, request.headers['Origin'])
117
+
118
+ # Increment the ElementKlass revision counter if submission was successful
119
+ if element_result[:mc] == 'ss00'
120
+ # Find the revision that matches the ElementKlass UUID
121
+ element_revision = Labimotion::ElementKlassesRevision.find_by(uuid: element_klass.uuid)
122
+ element_revision&.increment_submitted!
123
+ end
124
+ end
125
+
126
+ # Only return the main result; TODO: handle element_result as well
127
+ result
128
+ rescue StandardError => e
129
+ Labimotion.log_exception(e, current_user)
130
+ { mc: 'se00', msg: e.message, data: [] }
131
+ end
132
+ end
60
133
  end
61
134
  end
62
135
  end
@@ -21,5 +21,12 @@ module Labimotion
21
21
  NMR_CONFIG = ::File.join(__dir__, 'libs', 'data', 'mapper', 'Source.json').freeze
22
22
  WIKI_CONFIG = ::File.join(__dir__, 'libs', 'data', 'mapper', 'Chemwiki.json').freeze
23
23
  end
24
+
25
+ module Klass
26
+ ELEMENT = 'ElementKlass'
27
+ SEGMENT = 'SegmentKlass'
28
+ DATASET = 'DatasetKlass'
29
+ ALL = [ELEMENT, SEGMENT, DATASET].freeze
30
+ end
24
31
  end
25
32
  end
@@ -21,6 +21,7 @@ module Labimotion
21
21
  expose! :type
22
22
  expose! :uuid
23
23
  expose! :user_labels
24
+ expose! :preview_attachment # align with eln change
24
25
  end
25
26
 
26
27
  with_options(anonymize_below: 10) do
@@ -2,15 +2,12 @@
2
2
 
3
3
  require 'labimotion/entities/application_entity'
4
4
  module Labimotion
5
- # KlassRevisionEntity
6
5
  class KlassRevisionEntity < Labimotion::ApplicationEntity
7
- expose :id, :uuid, :properties_release, :version, :released_at
6
+ expose :id, :uuid, :version, :released_at, :klass_id, :submitted
7
+ expose :properties_release, **DISPLAYED_IN_LIST_CONDITION, anonymize_with: {}
8
8
 
9
- expose :klass_id do |object|
10
- klass_id = object.element_klass_id if object.respond_to? :element_klass_id
11
- klass_id = object.segment_klass_id if object.respond_to? :segment_klass_id
12
- klass_id = object.dataset_klass_id if object.respond_to? :dataset_klass_id
13
- klass_id
9
+ def klass_id
10
+ object.klass&.id
14
11
  end
15
12
 
16
13
  def released_at
@@ -18,4 +15,3 @@ module Labimotion
18
15
  end
19
16
  end
20
17
  end
21
-
@@ -5,9 +5,6 @@ require 'uri'
5
5
  require 'json'
6
6
  require 'date'
7
7
 
8
- # rubocop: disable Metrics/AbcSize
9
- # rubocop: disable Metrics/MethodLength
10
-
11
8
  module Labimotion
12
9
  ## TemplateHub
13
10
  class TemplateHub
@@ -18,12 +15,11 @@ module Labimotion
18
15
  "#{url}api/v1/labimotion_hub/#{api_name}"
19
16
  end
20
17
 
21
-
22
18
  def self.header(opt = {})
23
19
  opt || { timeout: 10, headers: { 'Content-Type' => 'text/json' } }
24
20
  end
25
21
 
26
- def self.handle_response(oat, response) # rubocop: disable Metrics/PerceivedComplexity
22
+ def self.handle_response(oat, response)
27
23
  begin
28
24
  response&.success? ? 'OK' : 'ERROR'
29
25
  rescue StandardError => e
@@ -56,10 +52,32 @@ module Labimotion
56
52
  Labimotion.log_exception(e)
57
53
  error!('Cannot connect to Chemotion Repository', 401)
58
54
  end
55
+
56
+ def self.send_to_central_hub(klass, template, metadata, origin)
57
+ body = {
58
+ template_klass: klass,
59
+ template: template,
60
+ metadata: metadata,
61
+ origin: origin
62
+ }
63
+ response = HTTParty.post(
64
+ Labimotion::TemplateHub.uri('template_submissions'),
65
+ headers: {
66
+ 'Content-Type' => 'application/json',
67
+ 'X-Origin-URL' => origin
68
+ },
69
+ body: body.to_json,
70
+ timeout: 10
71
+ )
72
+
73
+ if [200, 201].include?(response.code)
74
+ parsed_response = JSON.parse(response.body)
75
+ return { mc: 'ss00', data: { id: parsed_response['id'] } }
76
+ end
77
+ { mc: 'se00', msg: "HTTP #{response.code}: #{response.message}", data: {} }
78
+ rescue StandardError => e
79
+ Labimotion.log_exception(e)
80
+ { mc: 'se00', msg: "Connection failure: #{e.message}", data: {} }
81
+ end
59
82
  end
60
83
  end
61
-
62
- # rubocop: enable Metrics/AbcSize
63
- # rubocop: enable Metrics/MethodLength
64
- # rubocop: enable Metrics/ClassLength
65
- # rubocop: enable Metrics/CyclomaticComplexity
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Labimotion
4
+ # Shared concern for all Klass Revision models
5
+ # Provides a unified interface to access the parent klass object
6
+ module KlassRevision
7
+ extend ActiveSupport::Concern
8
+
9
+ # Returns the associated klass object (ElementKlass, SegmentKlass, or DatasetKlass)
10
+ def klass
11
+ return element_klass if respond_to?(:element_klass)
12
+ return segment_klass if respond_to?(:segment_klass)
13
+ return dataset_klass if respond_to?(:dataset_klass)
14
+
15
+ nil
16
+ end
17
+
18
+ # Increments the submitted counter and saves the record
19
+ def increment_submitted!
20
+ increment!(:submitted)
21
+ end
22
+ end
23
+ end
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'labimotion/models/concerns/klass_revision'
4
+
3
5
  module Labimotion
4
6
  class DatasetKlassesRevision < ApplicationRecord
5
7
  self.table_name = :dataset_klasses_revisions
6
8
  acts_as_paranoid
7
- has_one :dataset_klass, class_name: 'Labimotion::DatasetKlass'
9
+ include KlassRevision
10
+ belongs_to :dataset_klass, class_name: 'Labimotion::DatasetKlass'
8
11
  end
9
12
  end
@@ -60,6 +60,12 @@ module Labimotion
60
60
  after_create :update_counter
61
61
  before_destroy :delete_attachment
62
62
 
63
+ # align with eln change on preview attachment
64
+ def preview_attachment
65
+ image_atts = attachments.select(&:type_image?)
66
+ image_atts[0] || attachments[0]
67
+ end
68
+
63
69
  def user_labels
64
70
  tag&.taggable_data&.fetch('user_labels', nil)
65
71
  end
@@ -1,14 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'labimotion/models/concerns/workflow'
4
+ require 'labimotion/models/concerns/klass_revision'
4
5
 
5
6
  module Labimotion
6
7
  class ElementKlassesRevision < ApplicationRecord
7
8
  acts_as_paranoid
8
9
  self.table_name = :element_klasses_revisions
9
10
  include Workflow
10
- has_one :element_klass, class_name: 'Labimotion::ElementKlass'
11
-
11
+ include KlassRevision
12
+ belongs_to :element_klass, class_name: 'Labimotion::ElementKlass'
12
13
 
13
14
  def migrate_workflow
14
15
  return if properties_release.nil? || properties_release['flow'].nil?
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'labimotion/models/concerns/klass_revision'
4
+
3
5
  module Labimotion
4
6
  class SegmentKlassesRevision < ApplicationRecord
5
7
  acts_as_paranoid
6
8
  self.table_name = :segment_klasses_revisions
7
- has_one :segment_klass, class_name: 'Labimotion::SegmentKlass'
9
+ include KlassRevision
10
+ belongs_to :segment_klass, class_name: 'Labimotion::SegmentKlass'
8
11
  end
9
12
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == Schema Information
4
+ #
5
+ # Table name: template_submissions
6
+ #
7
+ # id :bigint not null, primary key
8
+ # template_klass :string not null
9
+ # template :jsonb not null, default: {}
10
+ # metadata :jsonb not null, default: {}
11
+ # origin :string not null
12
+ # state :integer not null, default: 0
13
+ # created_at :datetime not null
14
+ # updated_at :datetime
15
+ # deleted_at :datetime
16
+ #
17
+ # Indexes
18
+ #
19
+ # idx_template_submissions_template (template) USING gin
20
+ # idx_template_submissions_metadata (metadata) USING gin
21
+ #
22
+
23
+ module Labimotion
24
+ class TemplateSubmission < ApplicationRecord
25
+ acts_as_paranoid
26
+
27
+ # State enum
28
+ enum state: {
29
+ pending: 0,
30
+ approved: 1,
31
+ rejected: 2,
32
+ released: 3
33
+ }
34
+
35
+ # Validations
36
+ validates :template_klass, presence: true
37
+ validates :template, presence: true
38
+ validates :metadata, presence: true
39
+ validates :origin, presence: true
40
+
41
+ # Scopes
42
+ scope :by_template_klass, ->(klass) { where(template_klass: klass) }
43
+ scope :by_state, ->(state) { where(state: state) }
44
+ scope :by_origin, ->(origin) { where(origin: origin) }
45
+ scope :by_email, ->(email) { where("metadata->'submission'->>'email' = ?", email) }
46
+ scope :by_contact_email, ->(email) { where("metadata->'submission'->>'contact_email' = ?", email) }
47
+ scope :by_any_email, lambda { |email|
48
+ where("metadata->'submission'->>'email' = ? OR metadata->'submission'->>'contact_email' = ?", email, email)
49
+ }
50
+ scope :recent, -> { order(created_at: :desc) }
51
+ end
52
+ end
@@ -2,5 +2,5 @@
2
2
 
3
3
  ## Labimotion Version
4
4
  module Labimotion
5
- VERSION = '2.1.0.rc12'
5
+ VERSION = '2.1.0.rc14'
6
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.1.0.rc12
4
+ version: 2.1.0.rc14
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-11-19 00:00:00.000000000 Z
12
+ date: 2025-12-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: caxlsx
@@ -120,6 +120,7 @@ files:
120
120
  - lib/labimotion/models/concerns/generic_klass.rb
121
121
  - lib/labimotion/models/concerns/generic_klass_revisions.rb
122
122
  - lib/labimotion/models/concerns/generic_revisions.rb
123
+ - lib/labimotion/models/concerns/klass_revision.rb
123
124
  - lib/labimotion/models/concerns/linked_properties.rb
124
125
  - lib/labimotion/models/concerns/segmentable.rb
125
126
  - lib/labimotion/models/concerns/workflow.rb
@@ -145,6 +146,7 @@ files:
145
146
  - lib/labimotion/models/segments_revision.rb
146
147
  - lib/labimotion/models/std_layer.rb
147
148
  - lib/labimotion/models/std_layers_revision.rb
149
+ - lib/labimotion/models/template_submission.rb
148
150
  - lib/labimotion/models/vocabulary.rb
149
151
  - lib/labimotion/models/wellplate.rb
150
152
  - lib/labimotion/utils/con_state.rb