labimotion 2.3.0.rc5 → 2.4.0.rc2
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/CHANGELOG.md +10 -1
- data/lib/labimotion/apis/generic_element_api.rb +22 -0
- data/lib/labimotion/apis/generic_klass_api.rb +25 -3
- data/lib/labimotion/apis/labimotion_api.rb +1 -0
- data/lib/labimotion/apis/user_klass_settings_api.rb +70 -0
- data/lib/labimotion/entities/generic_klass_entity.rb +1 -0
- data/lib/labimotion/entities/properties_entity.rb +28 -0
- data/lib/labimotion/entities/segment_entity.rb +8 -0
- data/lib/labimotion/entities/user_klass_setting_entity.rb +10 -0
- data/lib/labimotion/helpers/generic_helpers.rb +9 -0
- data/lib/labimotion/helpers/param_helpers.rb +15 -0
- data/lib/labimotion/libs/converter.rb +5 -43
- data/lib/labimotion/libs/linked_element.rb +200 -0
- data/lib/labimotion/libs/sample_association.rb +5 -0
- data/lib/labimotion/models/cellline.rb +47 -0
- data/lib/labimotion/models/concerns/segmentable.rb +2 -0
- data/lib/labimotion/models/user_klass_setting.rb +12 -0
- data/lib/labimotion/version.rb +1 -1
- data/lib/labimotion.rb +5 -0
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4c92bee7516a80229220d269bcc9b53d700df17b0fa78f98857476f1bdd9ab02
|
|
4
|
+
data.tar.gz: 70ebf34d13b72d35eaeb4a05e039977add30227afe2acef0c00f1d434f2ae110
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 15952d33d6cceadfc8a3bb21e020e0d924b27e127e84b4fa5667e98bb34a691969d19b642d8624f233ab9c9f18adb2c36d45920594f73fc56296d2a0590c72f5
|
|
7
|
+
data.tar.gz: 2d47c8be30f7ecbbe68db65e8a6979ed7e0d8a1edff6fdaa6fe0dcf1cafbaf1e914f085fdde09385b6546f4be1ba8e0de0830a94584d9aee75d45738ba5f384a
|
data/CHANGELOG.md
CHANGED
|
@@ -2,13 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
* Features and enhancements:
|
|
5
|
+
* Added per-user, per-klass toolbar configuration (Overview/Arrange visibility) via `POST /user_klass_settings/save` (upsert) and `GET /user_klass_settings/fetch` ([chem-generic-ui#905](https://github.com/LabIMotion/chem-generic-ui/issues/905)). Each user stores their own `settings` (`{ toolbar: { overview, arrange } }`) per element/segment/dataset klass, keyed by the stable klass identifier so it applies to every instance of that klass across template versions. Requires host ELN twin migration `create_user_klass_settings`.
|
|
6
|
+
* Designer can enable/disable common layer functionalities (record time, add reaction, add/remove layer) per template; template-scoped and independent of template versions ([chem-generic-ui#904](https://github.com/LabIMotion/chem-generic-ui/issues/904)). Saved segments expose the effective klass `settings` so records honor the toggles after a reload. Requires host ELN twin migration `add_settings_to_klasses`.
|
|
7
|
+
* Support linking CellLine elements in the generic Link Element via the `Labimotion::CellLine` wrapper ([chem-generic-ui#906](https://github.com/LabIMotion/chem-generic-ui/issues/906)). Requires a host ELN `element_klasses` row for `cell_line` (migration) — see PR notes.
|
|
8
|
+
* `LinkedElement` resolves `cell_line` links to curated `CelllineSample` columns (previously an unknown el_klass misresolved to a generic `Labimotion::Element` sharing the same id).
|
|
9
|
+
* `LinkedElement` exposes two distinct names for `cell_line` links — the shared **Cell line name** (`material_name`, from the linked `CelllineMaterial`) and the per-sample **Name of specific sample** (`name`) — as separately selectable inline attributes.
|
|
5
10
|
* Element variations can be exported to and imported from Excel (`.xlsx`). The exported workbook is itself the import template: `GET /api/v1/element_variations/:element_id/export` and `POST /api/v1/element_variations/:element_id/import`.
|
|
6
11
|
* The exported sheet carries the grid's grouped header -- group, sub-group and field rows, merged as the grid draws them -- above the hidden key and unit rows. Workbooks from the earlier single-row header still import.
|
|
7
12
|
* Import merges rows by uuid, so a sheet only updates the columns it carries; a row with a blank `Row ID` is added as a new variation.
|
|
8
13
|
* `number`, `integer` and `system-defined` columns accept numbers only. A cell holding anything else is cleared with a warning rather than stored as text, which also cleans up text those columns already hold.
|
|
9
14
|
* Import warnings name the cell they came from (`Cell I6: ...`), so the user can go straight to it in the spreadsheet.
|
|
10
15
|
* Bug fixes:
|
|
11
|
-
* `fetch_for_user`: the shared-records branch joined `collection_shares` directly on the element model (an association that only exists on `Collection`), raising `ActiveRecord::ConfigurationError` — rescued into empty search results. Now joins through `collections`.
|
|
16
|
+
* `ElementFetchable#fetch_for_user`: the shared-records branch joined `collection_shares` directly on the element model (an association that only exists on `Collection`), raising `ActiveRecord::ConfigurationError` — rescued into empty search results for every element type. Now joins through `collections`.
|
|
17
|
+
* `Labimotion::Element.fetch_for_user` carried the same broken `collection_shares` join, so the generic-element `search_by_like` endpoint silently returned no shared records. Now joins through `collections` as well.
|
|
18
|
+
* `Labimotion::CellLine` delegation forwards keyword arguments on Ruby >= 3.0 (`ruby2_keywords`); without it `fetch_for_user(id, name:, short_label:, limit:)` raised `ArgumentError`.
|
|
19
|
+
* `SampleAssociation#update_sample_association` no longer creates `Labimotion::ElementsElement` join rows for drag_element values that link standard elements (e.g. `cell_line`, `reaction`), whose `el_id` is not a `Labimotion::Element` id.
|
|
20
|
+
* `Segmentable#save_segments` → `touch_analyses_properties` assumed every segmentable model defines `#analyses`; `CelllineSample` did not, so `POST /api/v1/cell_lines` raised `NameError` on create/update. Guard the analysis-vocabulary touch when `#analyses` is undefined so the save no longer fails.
|
|
12
21
|
* Dependencies:
|
|
13
22
|
* Added `roo` (~> 2.10) for reading the uploaded workbook.
|
|
14
23
|
|
|
@@ -88,6 +88,28 @@ module Labimotion
|
|
|
88
88
|
end
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
+
namespace :linked_attributes do
|
|
92
|
+
desc 'List the selectable inline attributes of an element linked from a drag_element field'
|
|
93
|
+
params do
|
|
94
|
+
optional :el_klass, type: String, desc: 'Generic element klass name or permit-target key (reaction, sample, ...)'
|
|
95
|
+
optional :el_type, type: String, desc: 'Linked element type (element, sample, molecule, ...)'
|
|
96
|
+
requires :el_id, type: Integer, desc: 'Linked element id'
|
|
97
|
+
end
|
|
98
|
+
get do
|
|
99
|
+
linked = Labimotion::LinkedElement.new(params[:el_klass], params[:el_id], params[:el_type])
|
|
100
|
+
record = linked.record
|
|
101
|
+
error!('404 Not Found', 404) if record.nil?
|
|
102
|
+
# Reference data (molecules) is public; everything else is collection-scoped.
|
|
103
|
+
readable = record.is_a?(::Molecule) || ElementPolicy.new(current_user, record).read?
|
|
104
|
+
error!('401 Unauthorized', 401) unless readable
|
|
105
|
+
|
|
106
|
+
{ attributes: linked.available }
|
|
107
|
+
rescue StandardError => e
|
|
108
|
+
Labimotion.log_exception(e, current_user)
|
|
109
|
+
{ attributes: [], error: e.message }
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
91
113
|
namespace :search_basic_by_like do
|
|
92
114
|
desc 'Search basic elements by name and short label (case-insensitive like search)'
|
|
93
115
|
params do
|
|
@@ -14,7 +14,7 @@ module Labimotion
|
|
|
14
14
|
desc 'export klass'
|
|
15
15
|
params do
|
|
16
16
|
requires :id, type: Integer, desc: 'element id'
|
|
17
|
-
requires :klass, type: String, desc: 'Klass', values:
|
|
17
|
+
requires :klass, type: String, desc: 'Klass', values: Labimotion::Constants::Klass::ALL
|
|
18
18
|
end
|
|
19
19
|
get do
|
|
20
20
|
entity = "Labimotion::#{params[:klass]}".constantize.find_by(id: params[:id])
|
|
@@ -35,7 +35,7 @@ module Labimotion
|
|
|
35
35
|
namespace :de_activate do
|
|
36
36
|
desc 'activate or deactivate Generic Klass'
|
|
37
37
|
params do
|
|
38
|
-
requires :klass, type: String, desc: 'Klass', values:
|
|
38
|
+
requires :klass, type: String, desc: 'Klass', values: Labimotion::Constants::Klass::ALL
|
|
39
39
|
requires :id, type: Integer, desc: 'Klass ID'
|
|
40
40
|
requires :is_active, type: Boolean, desc: 'Active or Inactive Klass'
|
|
41
41
|
end
|
|
@@ -54,11 +54,33 @@ module Labimotion
|
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
namespace :update_settings do
|
|
58
|
+
desc 'update Generic Klass settings'
|
|
59
|
+
params do
|
|
60
|
+
requires :klass, type: String, desc: 'Klass', values: Labimotion::Constants::Klass::ALL
|
|
61
|
+
requires :id, type: Integer, desc: 'Klass ID'
|
|
62
|
+
requires :settings, type: Hash, desc: 'Klass settings'
|
|
63
|
+
end
|
|
64
|
+
after_validation do
|
|
65
|
+
authenticate_admin!(params[:klass].gsub(/(Klass)/, 's').downcase)
|
|
66
|
+
fetch_klass(params[:klass], params[:id])
|
|
67
|
+
end
|
|
68
|
+
post do
|
|
69
|
+
updated_klass = update_klass_settings(params)
|
|
70
|
+
entity_class = "Labimotion::#{params[:klass]}Entity".constantize
|
|
71
|
+
serialized_data = entity_class.represent(updated_klass)
|
|
72
|
+
{ mc: 'ss00', data: serialized_data }
|
|
73
|
+
rescue StandardError => e
|
|
74
|
+
Labimotion.log_exception(e, current_user)
|
|
75
|
+
{ mc: 'se00', msg: e.message, data: {} }
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
57
79
|
namespace :fetch do
|
|
58
80
|
desc 'fetch Generic Klass by id'
|
|
59
81
|
params do
|
|
60
82
|
requires :id, type: Integer, desc: 'Klass ID'
|
|
61
|
-
requires :klass, type: String, desc: 'Klass', values:
|
|
83
|
+
requires :klass, type: String, desc: 'Klass', values: Labimotion::Constants::Klass::ALL
|
|
62
84
|
end
|
|
63
85
|
get do
|
|
64
86
|
klass_obj = fetch_klass(params[:klass], params[:id])
|
|
@@ -13,6 +13,7 @@ module Labimotion
|
|
|
13
13
|
mount Labimotion::StandardLayerAPI
|
|
14
14
|
mount Labimotion::VocabularyAPI
|
|
15
15
|
mount Labimotion::UserAPI
|
|
16
|
+
mount Labimotion::UserKlassSettingsAPI
|
|
16
17
|
mount Labimotion::MttAPI
|
|
17
18
|
mount Labimotion::ElementVariationAPI
|
|
18
19
|
mount Labimotion::LabimotionDoiAPI
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'labimotion/version'
|
|
4
|
+
|
|
5
|
+
module Labimotion
|
|
6
|
+
# Per-user, per-klass UI settings API (toolbar Overview/Arrange visibility).
|
|
7
|
+
# Every route is scoped to the authenticated user via created_by, so a user
|
|
8
|
+
# only ever reads/writes their own setting; no klass-edit permission needed.
|
|
9
|
+
class UserKlassSettingsAPI < Grape::API
|
|
10
|
+
helpers Labimotion::ParamHelpers
|
|
11
|
+
|
|
12
|
+
helpers do
|
|
13
|
+
def settings_scope
|
|
14
|
+
Labimotion::UserKlassSetting.where(created_by: current_user.id)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def klass_toolbar(settings)
|
|
18
|
+
(settings || {})['toolbar'] || (settings || {})[:toolbar] || {}
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
resource :user_klass_settings do
|
|
23
|
+
desc 'Fetch the current user UI setting for a klass'
|
|
24
|
+
params do
|
|
25
|
+
requires :klass, type: String, values: %w[ElementKlass SegmentKlass DatasetKlass], desc: 'Klass type'
|
|
26
|
+
requires :identifier, type: String, desc: 'Klass identifier'
|
|
27
|
+
end
|
|
28
|
+
get 'fetch' do
|
|
29
|
+
error!('401 Unauthorized', 401) unless current_user
|
|
30
|
+
|
|
31
|
+
record = settings_scope.find_by(klass: params[:klass], klass_identifier: params[:identifier])
|
|
32
|
+
data = record ? Labimotion::UserKlassSettingEntity.represent(record) : nil
|
|
33
|
+
{ mc: 'ss00', data: data }
|
|
34
|
+
rescue StandardError => e
|
|
35
|
+
Labimotion.log_exception(e, current_user)
|
|
36
|
+
{ mc: 'se00', msg: e.message, data: nil }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
desc 'Upsert the current user UI setting for a klass'
|
|
40
|
+
params do
|
|
41
|
+
use :user_klass_settings_save
|
|
42
|
+
end
|
|
43
|
+
post 'save' do
|
|
44
|
+
error!('401 Unauthorized', 401) unless current_user
|
|
45
|
+
|
|
46
|
+
record = settings_scope.find_or_initialize_by(
|
|
47
|
+
klass: params[:klass], klass_identifier: params[:klass_identifier]
|
|
48
|
+
)
|
|
49
|
+
record.klass_label = params[:klass_label] if params[:klass_label]
|
|
50
|
+
record.created_by ||= current_user.id
|
|
51
|
+
record.settings = (record.settings || {}).merge('toolbar' => klass_toolbar(params[:settings]))
|
|
52
|
+
record.save!
|
|
53
|
+
{ mc: 'ss00', data: Labimotion::UserKlassSettingEntity.represent(record) }
|
|
54
|
+
rescue StandardError => e
|
|
55
|
+
Labimotion.log_exception(e, current_user)
|
|
56
|
+
{ mc: 'se00', msg: e.message, data: {} }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
desc 'List all UI settings of the current user'
|
|
60
|
+
get 'list' do
|
|
61
|
+
error!('401 Unauthorized', 401) unless current_user
|
|
62
|
+
|
|
63
|
+
{ mc: 'ss00', data: Labimotion::UserKlassSettingEntity.represent(settings_scope.to_a) }
|
|
64
|
+
rescue StandardError => e
|
|
65
|
+
Labimotion.log_exception(e, current_user)
|
|
66
|
+
{ mc: 'se00', msg: e.message, data: [] }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -9,6 +9,7 @@ module Labimotion
|
|
|
9
9
|
expose :properties_template, **DISPLAYED_IN_LIST_CONDITION, anonymize_with: {}
|
|
10
10
|
expose :properties_release, **DISPLAYED_IN_LIST_CONDITION, anonymize_with: {}
|
|
11
11
|
expose :metadata, **DISPLAYED_IN_LIST_CONDITION, anonymize_with: {}
|
|
12
|
+
expose :settings, **DISPLAYED_IN_LIST_CONDITION, anonymize_with: {}
|
|
12
13
|
expose_timestamps(timestamp_fields: %i[released_at created_at updated_at sync_time])
|
|
13
14
|
end
|
|
14
15
|
end
|
|
@@ -20,6 +20,7 @@ module Labimotion
|
|
|
20
20
|
|
|
21
21
|
def process_fields(key, layer)
|
|
22
22
|
process_sample_and_molecule_fields(key, layer)
|
|
23
|
+
process_linked_attr_fields(key, layer)
|
|
23
24
|
process_reaction_fields(key, layer)
|
|
24
25
|
process_table_fields(key, layer)
|
|
25
26
|
# process_voc_fields(key, layer)
|
|
@@ -32,6 +33,18 @@ module Labimotion
|
|
|
32
33
|
end
|
|
33
34
|
end
|
|
34
35
|
|
|
36
|
+
# Resolve the linked attributes a user chose to surface inline on a drag field
|
|
37
|
+
# (stored as value['el_attrs']). Only the selection is persisted; the current
|
|
38
|
+
# values are refreshed here from the linked element on every load. Works for
|
|
39
|
+
# drag_element (generic element or any permit-target type) as well as
|
|
40
|
+
# drag_sample / drag_molecule.
|
|
41
|
+
def process_linked_attr_fields(key, layer)
|
|
42
|
+
select_fields(layer, [Labimotion::FieldType::DRAG_ELEMENT, Labimotion::FieldType::DRAG_SAMPLE,
|
|
43
|
+
Labimotion::FieldType::DRAG_MOLECULE]).each do |field, idx|
|
|
44
|
+
update_linked_attr_field(key, field, idx)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
35
48
|
def process_reaction_fields(key, layer)
|
|
36
49
|
select_fields(layer, [Labimotion::FieldType::SYS_REACTION]).each do |field, idx|
|
|
37
50
|
update_reaction_field(key, field, idx)
|
|
@@ -80,6 +93,21 @@ module Labimotion
|
|
|
80
93
|
})
|
|
81
94
|
end
|
|
82
95
|
|
|
96
|
+
def update_linked_attr_field(key, field, idx)
|
|
97
|
+
return unless field['value'].is_a?(Hash)
|
|
98
|
+
|
|
99
|
+
attrs = field.dig('value', 'el_attrs')
|
|
100
|
+
return unless attrs.is_a?(Array) && attrs.present?
|
|
101
|
+
return unless object.properties.dig(Labimotion::Prop::LAYERS, key, Labimotion::Prop::FIELDS, idx,
|
|
102
|
+
'value').present?
|
|
103
|
+
|
|
104
|
+
el_klass = field.dig('value', 'el_klass')
|
|
105
|
+
el_id = field.dig('value', 'el_id')
|
|
106
|
+
el_type = field.dig('value', 'el_type')
|
|
107
|
+
object.properties[Labimotion::Prop::LAYERS][key][Labimotion::Prop::FIELDS][idx]['value']['el_attrs'] =
|
|
108
|
+
Labimotion::LinkedElement.new(el_klass, el_id, el_type).resolve(attrs)
|
|
109
|
+
end
|
|
110
|
+
|
|
83
111
|
def update_reaction_field(key, field, idx)
|
|
84
112
|
return unless field['value'].is_a?(Hash)
|
|
85
113
|
|
|
@@ -9,9 +9,17 @@ module Labimotion
|
|
|
9
9
|
expose :properties, **DISPLAYED_IN_LIST_CONDITION, anonymize_with: {}
|
|
10
10
|
expose :properties_release, **DISPLAYED_IN_LIST_CONDITION, anonymize_with: {}
|
|
11
11
|
expose :metadata, **DISPLAYED_IN_LIST_CONDITION, anonymize_with: {}
|
|
12
|
+
expose :settings, **DISPLAYED_IN_LIST_CONDITION, anonymize_with: {}
|
|
12
13
|
|
|
13
14
|
def klass_label
|
|
14
15
|
object.segment_klass.label
|
|
15
16
|
end
|
|
17
|
+
|
|
18
|
+
# Saved segments are serialized without their klass, so surface the
|
|
19
|
+
# template-scoped settings here; guarded for hosts not yet migrated.
|
|
20
|
+
def settings
|
|
21
|
+
klass = object.segment_klass
|
|
22
|
+
klass.respond_to?(:settings) ? klass.settings || {} : {}
|
|
23
|
+
end
|
|
16
24
|
end
|
|
17
25
|
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'labimotion/entities/application_entity'
|
|
4
|
+
module Labimotion
|
|
5
|
+
# Per-user, per-klass UI setting entity
|
|
6
|
+
class UserKlassSettingEntity < Labimotion::ApplicationEntity
|
|
7
|
+
expose :id, :klass, :klass_identifier, :klass_label, :settings
|
|
8
|
+
expose_timestamps(timestamp_fields: %i[created_at])
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -36,6 +36,15 @@ module Labimotion
|
|
|
36
36
|
raise e
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
def update_klass_settings(params)
|
|
40
|
+
klz = fetch_klass(params[:klass], params[:id])
|
|
41
|
+
klz&.update!(settings: (klz.settings || {}).merge(params[:settings] || {}))
|
|
42
|
+
klz
|
|
43
|
+
rescue StandardError => e
|
|
44
|
+
Labimotion.log_exception(e, current_user)
|
|
45
|
+
raise e
|
|
46
|
+
end
|
|
47
|
+
|
|
39
48
|
def delete_klass(params)
|
|
40
49
|
authenticate_admin!(params[:klass].gsub(/(Klass)/, 's').downcase)
|
|
41
50
|
klz = fetch_klass(params[:klass], params[:id])
|
|
@@ -14,6 +14,7 @@ module Labimotion
|
|
|
14
14
|
optional :klass_prefix, type: String, desc: 'Klass klass_prefix'
|
|
15
15
|
optional :icon_name, type: String, desc: 'Klass icon_name'
|
|
16
16
|
optional :metadata, type: Hash, desc: 'Klass metadata'
|
|
17
|
+
optional :settings, type: Hash, desc: 'Klass settings'
|
|
17
18
|
requires :properties_template, type: Hash, desc: 'Klass template'
|
|
18
19
|
optional :properties_release, type: Hash, desc: 'Klass release'
|
|
19
20
|
optional :released_at, type: DateTime, desc: 'Klass released_at'
|
|
@@ -72,6 +73,7 @@ module Labimotion
|
|
|
72
73
|
requires :label, type: String, desc: 'Klass label'
|
|
73
74
|
optional :desc, type: String, desc: 'Klass desc'
|
|
74
75
|
optional :metadata, type: Hash, desc: 'Klass metadata'
|
|
76
|
+
optional :settings, type: Hash, desc: 'Klass settings'
|
|
75
77
|
requires :properties_template, type: Hash, desc: 'Klass template'
|
|
76
78
|
optional :properties_release, type: Hash, desc: 'Klass release'
|
|
77
79
|
optional :released_at, type: DateTime, desc: 'Klass released_at'
|
|
@@ -91,6 +93,7 @@ module Labimotion
|
|
|
91
93
|
optional :desc, type: String, desc: 'Segment Klass Desc'
|
|
92
94
|
optional :place, type: String, desc: 'Segment Klass Place', default: '100'
|
|
93
95
|
optional :identifier, type: String, desc: 'Segment Identifier'
|
|
96
|
+
optional :metadata, type: Hash, desc: 'Klass metadata'
|
|
94
97
|
end
|
|
95
98
|
|
|
96
99
|
params :create_segment_klass_params do
|
|
@@ -152,5 +155,17 @@ module Labimotion
|
|
|
152
155
|
requires :id, type: Integer, desc: 'Element ID'
|
|
153
156
|
requires :wellplate_ids, type: Array, desc: 'Selected Wellplates'
|
|
154
157
|
end
|
|
158
|
+
|
|
159
|
+
params :user_klass_settings_save do
|
|
160
|
+
requires :klass, type: String, values: %w[ElementKlass SegmentKlass DatasetKlass], desc: 'Klass type'
|
|
161
|
+
requires :klass_identifier, type: String, desc: 'Stable klass identifier'
|
|
162
|
+
optional :klass_label, type: String, desc: 'Klass label (display only)'
|
|
163
|
+
requires :settings, type: Hash do
|
|
164
|
+
optional :toolbar, type: Hash do
|
|
165
|
+
optional :overview, type: Boolean, default: true
|
|
166
|
+
optional :arrange, type: Boolean, default: true
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
155
170
|
end
|
|
156
171
|
end
|
|
@@ -125,6 +125,7 @@ module Labimotion
|
|
|
125
125
|
tmp_file.rewind
|
|
126
126
|
|
|
127
127
|
filename = oat.filename
|
|
128
|
+
# name = "#{File.basename(filename, '.*')}.zip"
|
|
128
129
|
name = "#{File.basename(filename, '.*')}#{File.extname(filename) == '.zip' ? '.bagit.zip' : '.zip'}"
|
|
129
130
|
att = Attachment.new(
|
|
130
131
|
filename: name,
|
|
@@ -246,22 +247,14 @@ module Labimotion
|
|
|
246
247
|
dsr.each do |ds|
|
|
247
248
|
layer = layers[ds[:layer]]
|
|
248
249
|
next if layer.blank? || layer[Labimotion::Prop::FIELDS].blank?
|
|
249
|
-
is_unit = ds[:field].start_with?(Prop::CONVERTER_FIELD_UINT_PREFIX)
|
|
250
|
-
next if is_unit && ds[:value].nil?
|
|
251
250
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
fields = layer[Labimotion::Prop::FIELDS].select{ |f| f['field'] == field_name }
|
|
251
|
+
fields = layer[Labimotion::Prop::FIELDS].select{ |f| f['field'] == ds[:field] }
|
|
255
252
|
fi = fields&.first
|
|
256
253
|
next if fi.blank?
|
|
257
254
|
|
|
258
255
|
idx = layer[Labimotion::Prop::FIELDS].find_index(fi)
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
else
|
|
262
|
-
fi['value'] = ds[:value]
|
|
263
|
-
fi['device'] = ds[:device] || ds[:value]
|
|
264
|
-
end
|
|
256
|
+
fi['value'] = ds[:value]
|
|
257
|
+
fi['device'] = ds[:device] || ds[:value]
|
|
265
258
|
new_prop[Labimotion::Prop::LAYERS][ds[:layer]][Labimotion::Prop::FIELDS][idx] = fi
|
|
266
259
|
end
|
|
267
260
|
element = Container.find(dataset.element_id)&.root_element
|
|
@@ -327,37 +320,6 @@ module Labimotion
|
|
|
327
320
|
res
|
|
328
321
|
end
|
|
329
322
|
|
|
330
|
-
def self.test_conversions(tmpfile, format)
|
|
331
|
-
res = {}
|
|
332
|
-
File.open(tmpfile.path, 'r') do |file|
|
|
333
|
-
body = { file: file, format: format }
|
|
334
|
-
response = HTTParty.post(
|
|
335
|
-
uri('conversions'),
|
|
336
|
-
basic_auth: auth,
|
|
337
|
-
body: body,
|
|
338
|
-
timeout: timeout,
|
|
339
|
-
)
|
|
340
|
-
res = response.parsed_response
|
|
341
|
-
end
|
|
342
|
-
res
|
|
343
|
-
end
|
|
344
|
-
|
|
345
|
-
def self.restore(profile_id, version, hard)
|
|
346
|
-
body = { hard: hard }
|
|
347
|
-
response = HTTParty.post(
|
|
348
|
-
uri("profiles/restore/#{profile_id}/#{version}"),
|
|
349
|
-
headers: {
|
|
350
|
-
"Content-Type" => "application/json"
|
|
351
|
-
},
|
|
352
|
-
basic_auth: auth,
|
|
353
|
-
body: body.to_json,
|
|
354
|
-
timeout: timeout,
|
|
355
|
-
)
|
|
356
|
-
res = response.parsed_response
|
|
357
|
-
|
|
358
|
-
res
|
|
359
|
-
end
|
|
360
|
-
|
|
361
323
|
def self.metadata(id, current_user)
|
|
362
324
|
att = Attachment.find(id)
|
|
363
325
|
return if att.nil? || att.attachable_id.nil? || att.attachable_type != Labimotion::Prop::CONTAINER
|
|
@@ -372,4 +334,4 @@ end
|
|
|
372
334
|
# rubocop: enable Metrics/AbcSize
|
|
373
335
|
# rubocop: enable Metrics/MethodLength
|
|
374
336
|
# rubocop: enable Metrics/ClassLength
|
|
375
|
-
# rubocop: enable Metrics/CyclomaticComplexity
|
|
337
|
+
# rubocop: enable Metrics/CyclomaticComplexity
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Labimotion
|
|
4
|
+
# Resolves the "inline attributes" of an element linked from a generic
|
|
5
|
+
# drag_element field.
|
|
6
|
+
#
|
|
7
|
+
# A drag_element link target is identified by the stored value's +el_klass+:
|
|
8
|
+
# * a generic element klass name -> the target is a Labimotion::Element and
|
|
9
|
+
# its selectable attributes are its own property fields (layer + field);
|
|
10
|
+
# * a permit-target key (reaction, sample, molecule, wellplate, screen,
|
|
11
|
+
# research_plan, device_description, cell_line) -> the target is a standard
|
|
12
|
+
# ELN model and its selectable attributes are a curated set of database columns.
|
|
13
|
+
#
|
|
14
|
+
# Used both to list the attributes a user can pick (#available) and to refresh
|
|
15
|
+
# the current values of a previously stored selection (#resolve).
|
|
16
|
+
class LinkedElement
|
|
17
|
+
SOURCE_PROPERTY = 'property'
|
|
18
|
+
SOURCE_COLUMN = 'column'
|
|
19
|
+
|
|
20
|
+
# el_klass (permit-target key) => model class name
|
|
21
|
+
MODELS = {
|
|
22
|
+
'reaction' => '::Reaction',
|
|
23
|
+
'sample' => '::Sample',
|
|
24
|
+
'molecule' => '::Molecule',
|
|
25
|
+
'wellplate' => '::Wellplate',
|
|
26
|
+
'screen' => '::Screen',
|
|
27
|
+
'research_plan' => '::ResearchPlan',
|
|
28
|
+
'device_description' => '::DeviceDescription',
|
|
29
|
+
'cell_line' => '::CelllineSample'
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
# Curated, scalar columns exposed per type: [column/method, label, unit_column?]
|
|
33
|
+
COLUMNS = {
|
|
34
|
+
'reaction' => [
|
|
35
|
+
%w[name Name], %w[short_label], ['status', 'Status'], ['role', 'Role'],
|
|
36
|
+
['rxno', 'Reaction Type'], ['conditions', 'Conditions'],
|
|
37
|
+
['duration', 'Duration'], %w[solvent Solvent]
|
|
38
|
+
],
|
|
39
|
+
'sample' => [
|
|
40
|
+
%w[name Name], %w[short_label], ['external_label', 'External Label'],
|
|
41
|
+
['sum_formula', 'Sum Formula'], ['molecular_mass', 'Molecular Mass'],
|
|
42
|
+
%w[purity Purity], %w[density Density],
|
|
43
|
+
['real_amount_value', 'Real Amount', 'real_amount_unit'],
|
|
44
|
+
['target_amount_value', 'Target Amount', 'target_amount_unit'],
|
|
45
|
+
%w[location Location]
|
|
46
|
+
],
|
|
47
|
+
'molecule' => [
|
|
48
|
+
['iupac_name', 'IUPAC Name'], ['sum_formular', 'Sum Formula'],
|
|
49
|
+
['molecular_weight', 'Molecular Weight'],
|
|
50
|
+
['exact_molecular_weight', 'Exact Molecular Weight'],
|
|
51
|
+
['cano_smiles', 'Canonical SMILES'], ['melting_point', 'Melting Point'],
|
|
52
|
+
['boiling_point', 'Boiling Point'], %w[density Density]
|
|
53
|
+
],
|
|
54
|
+
'wellplate' => [
|
|
55
|
+
%w[name Name], %w[short_label], ['description', 'Description'],
|
|
56
|
+
%w[width Width], %w[height Height]
|
|
57
|
+
],
|
|
58
|
+
'screen' => [
|
|
59
|
+
%w[name Name], ['description', 'Description'], %w[result Result],
|
|
60
|
+
%w[collaborator Collaborator], ['conditions', 'Conditions'],
|
|
61
|
+
%w[requirements Requirements]
|
|
62
|
+
],
|
|
63
|
+
'research_plan' => [
|
|
64
|
+
%w[name Name], %w[short_label]
|
|
65
|
+
],
|
|
66
|
+
'device_description' => [
|
|
67
|
+
%w[name Name], %w[short_label], ['serial_number', 'Serial Number'],
|
|
68
|
+
['device_class', 'Device Class'], ['operation_mode', 'Operation Mode'],
|
|
69
|
+
['application_name', 'Application Name'], ['vendor_url', 'Vendor URL'],
|
|
70
|
+
%w[institute Institute], %w[building Building], %w[room Room]
|
|
71
|
+
],
|
|
72
|
+
'cell_line' => [
|
|
73
|
+
['material_name', 'Cell line name'], ['name', 'Name of specific sample'],
|
|
74
|
+
%w[short_label], %w[description Description],
|
|
75
|
+
%w[amount Amount unit], %w[passage Passage],
|
|
76
|
+
%w[contamination Contamination]
|
|
77
|
+
]
|
|
78
|
+
}.freeze
|
|
79
|
+
|
|
80
|
+
# Property field types whose value is a scalar worth surfacing inline.
|
|
81
|
+
PROPERTY_TYPES = %w[
|
|
82
|
+
text textarea number integer select select-multi checkbox
|
|
83
|
+
datetime system-defined formula-field text-formula ontology-select
|
|
84
|
+
].freeze
|
|
85
|
+
|
|
86
|
+
def initialize(el_klass, el_id, el_type = nil)
|
|
87
|
+
@el_klass = el_klass.to_s
|
|
88
|
+
@el_type = el_type.to_s
|
|
89
|
+
@el_id = el_id
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# drag_element keeps the linked class in +el_klass+; drag_sample / drag_molecule
|
|
93
|
+
# keep it in +el_type+ (and may omit el_klass). Resolve one effective dispatch key.
|
|
94
|
+
def target_key
|
|
95
|
+
return @target_key if defined?(@target_key)
|
|
96
|
+
|
|
97
|
+
@target_key =
|
|
98
|
+
if MODELS.key?(@el_klass) || generic_klass?(@el_klass)
|
|
99
|
+
@el_klass
|
|
100
|
+
elsif MODELS.key?(@el_type)
|
|
101
|
+
@el_type
|
|
102
|
+
else
|
|
103
|
+
''
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def column_based?
|
|
108
|
+
MODELS.key?(target_key)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def generic?
|
|
112
|
+
generic_klass?(target_key)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def record
|
|
116
|
+
return @record if defined?(@record)
|
|
117
|
+
|
|
118
|
+
@record =
|
|
119
|
+
if column_based?
|
|
120
|
+
MODELS[target_key].safe_constantize&.find_by(id: @el_id)
|
|
121
|
+
elsif generic?
|
|
122
|
+
Labimotion::Element.find_by(id: @el_id)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# All attributes the user can pick, each with its current value.
|
|
127
|
+
def available
|
|
128
|
+
return generic_attributes if generic?
|
|
129
|
+
return column_attributes if column_based?
|
|
130
|
+
|
|
131
|
+
[]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Refresh the values/labels of a previously stored selection. Selections whose
|
|
135
|
+
# source attribute no longer exists are kept but flagged as missing; for an
|
|
136
|
+
# unknown/unsupported el_klass the selection is returned untouched.
|
|
137
|
+
def resolve(selected)
|
|
138
|
+
return selected unless selected.is_a?(Array)
|
|
139
|
+
return selected unless generic? || column_based?
|
|
140
|
+
|
|
141
|
+
index = available.index_by { |a| [a['source'], a['key']] }
|
|
142
|
+
selected.map do |attr|
|
|
143
|
+
next attr unless attr.is_a?(Hash)
|
|
144
|
+
|
|
145
|
+
index[[attr['source'], attr['key']]] || attr.merge('value' => nil, 'missing' => true)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
|
|
151
|
+
def generic_klass?(name)
|
|
152
|
+
name.present? && !MODELS.key?(name) && Labimotion::ElementKlass.exists?(name: name)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def generic_attributes
|
|
156
|
+
el = record
|
|
157
|
+
return [] unless el&.properties.is_a?(Hash)
|
|
158
|
+
|
|
159
|
+
attrs = []
|
|
160
|
+
layers = el.properties[Labimotion::Prop::LAYERS] || {}
|
|
161
|
+
layers.values.sort_by { |layer| layer['position'] || 0 }.each do |layer|
|
|
162
|
+
(layer[Labimotion::Prop::FIELDS] || []).each do |field|
|
|
163
|
+
next unless PROPERTY_TYPES.include?(field['type'])
|
|
164
|
+
|
|
165
|
+
attr = {
|
|
166
|
+
'source' => SOURCE_PROPERTY,
|
|
167
|
+
'key' => "#{layer['key']}::#{field['field']}",
|
|
168
|
+
'label' => field['label'].presence || field['field'],
|
|
169
|
+
'type' => field['type'],
|
|
170
|
+
'value' => field['value'],
|
|
171
|
+
'group' => layer['label'].presence || layer['key']
|
|
172
|
+
}
|
|
173
|
+
attr['value_system'] = field['value_system'] if field['value_system'].present?
|
|
174
|
+
attrs << attr
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
attrs
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def column_attributes
|
|
181
|
+
rec = record
|
|
182
|
+
return [] if rec.blank?
|
|
183
|
+
|
|
184
|
+
(COLUMNS[target_key] || []).filter_map do |col, label, unit_col|
|
|
185
|
+
next unless rec.respond_to?(col)
|
|
186
|
+
|
|
187
|
+
attr = {
|
|
188
|
+
'source' => SOURCE_COLUMN,
|
|
189
|
+
'key' => col,
|
|
190
|
+
'label' => label.presence || col.to_s.humanize,
|
|
191
|
+
'type' => SOURCE_COLUMN,
|
|
192
|
+
'value' => rec.public_send(col),
|
|
193
|
+
'group' => target_key
|
|
194
|
+
}
|
|
195
|
+
attr['value_system'] = rec.public_send(unit_col) if unit_col && rec.respond_to?(unit_col) && rec.public_send(unit_col).present?
|
|
196
|
+
attr
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
@@ -109,6 +109,11 @@ module Labimotion
|
|
|
109
109
|
sid = field.dig('value', 'el_id')
|
|
110
110
|
next if element.nil? || sid.blank? || sid == element.id
|
|
111
111
|
|
|
112
|
+
# drag_element may link a standard element (reaction, cell_line, ...);
|
|
113
|
+
# el_id is then NOT a Labimotion::Element id, so an ElementsElement row
|
|
114
|
+
# would associate an unrelated generic element that shares the id.
|
|
115
|
+
next if Labimotion::LinkedElement::MODELS.key?(field.dig('value', 'el_klass').to_s)
|
|
116
|
+
|
|
112
117
|
el = Labimotion::Element.find_by(id: sid)
|
|
113
118
|
next if el.nil?
|
|
114
119
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This file extends the existing CelllineSample model in the consuming app
|
|
4
|
+
# and defines a Labimotion::CellLine wrapper for convenient access.
|
|
5
|
+
# NB: the ELN host model is ::CelllineSample, but the wrapper is named
|
|
6
|
+
# Labimotion::CellLine so that "cell_line".camelize == "CellLine" resolves
|
|
7
|
+
# to it in the search_basic_by_like endpoint.
|
|
8
|
+
|
|
9
|
+
# rubocop:disable Style/RedundantConstantBase
|
|
10
|
+
|
|
11
|
+
ActiveSupport.on_load(:active_record) do
|
|
12
|
+
if defined?(::CelllineSample)
|
|
13
|
+
::CelllineSample.class_eval do
|
|
14
|
+
include Labimotion::ElementFetchable
|
|
15
|
+
|
|
16
|
+
def self.element_klass_name
|
|
17
|
+
'cell_line'
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
warn '[Labimotion] CelllineSample is not defined when Labimotion extension was loaded.'
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Namespace wrapper to keep your preferred call style
|
|
26
|
+
module Labimotion
|
|
27
|
+
module CellLine
|
|
28
|
+
class << self
|
|
29
|
+
# Delegate class methods to ::CelllineSample.
|
|
30
|
+
# ruby2_keywords is required so keyword arguments survive the *args
|
|
31
|
+
# round-trip on Ruby >= 3.0 (e.g. fetch_for_user(id, name:, limit:)).
|
|
32
|
+
ruby2_keywords def method_missing(method, *args, &block)
|
|
33
|
+
if ::CelllineSample.respond_to?(method)
|
|
34
|
+
::CelllineSample.public_send(method, *args, &block)
|
|
35
|
+
else
|
|
36
|
+
super
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def respond_to_missing?(method, include_private = false)
|
|
41
|
+
::CelllineSample.respond_to?(method, include_private) || super
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# rubocop:enable Style/RedundantConstantBase
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Labimotion
|
|
4
|
+
# Per-user, per-klass UI settings (e.g. toolbar Overview/Arrange visibility).
|
|
5
|
+
# Scoped to the owning user via created_by; at most one row per
|
|
6
|
+
# (created_by, klass, klass_identifier) — see the unique index. Keyed on the
|
|
7
|
+
# stable klass identifier so it applies to every instance of that klass,
|
|
8
|
+
# across template versions.
|
|
9
|
+
class UserKlassSetting < ApplicationRecord
|
|
10
|
+
self.table_name = :user_klass_settings
|
|
11
|
+
end
|
|
12
|
+
end
|
data/lib/labimotion/version.rb
CHANGED
data/lib/labimotion.rb
CHANGED
|
@@ -28,6 +28,7 @@ module Labimotion
|
|
|
28
28
|
autoload :StandardLayerAPI, 'labimotion/apis/standard_layer_api'
|
|
29
29
|
autoload :VocabularyAPI, 'labimotion/apis/vocabulary_api'
|
|
30
30
|
autoload :UserAPI, 'labimotion/apis/user_api'
|
|
31
|
+
autoload :UserKlassSettingsAPI, 'labimotion/apis/user_klass_settings_api'
|
|
31
32
|
autoload :MttAPI, 'labimotion/apis/mtt_api'
|
|
32
33
|
autoload :ElementVariationAPI, 'labimotion/apis/element_variation_api'
|
|
33
34
|
autoload :LabimotionDoiAPI, 'labimotion/apis/labimotion_doi_api'
|
|
@@ -56,6 +57,7 @@ module Labimotion
|
|
|
56
57
|
## autoload :DatasetRevisionEntity, 'labimotion/entities/dataset_revision_entity'
|
|
57
58
|
autoload :VocabularyEntity, 'labimotion/entities/vocabulary_entity'
|
|
58
59
|
autoload :UserEntity, 'labimotion/entities/user_entity'
|
|
60
|
+
autoload :UserKlassSettingEntity, 'labimotion/entities/user_klass_setting_entity'
|
|
59
61
|
autoload :ElementVariationEntity, 'labimotion/entities/element_variation_entity'
|
|
60
62
|
autoload :LabimotionTemplateDoiEntity, 'labimotion/entities/labimotion_template_doi_entity'
|
|
61
63
|
|
|
@@ -89,6 +91,7 @@ module Labimotion
|
|
|
89
91
|
autoload :ExportDataset, 'labimotion/libs/export_dataset'
|
|
90
92
|
autoload :SampleAssociation, 'labimotion/libs/sample_association'
|
|
91
93
|
autoload :PropertiesHandler, 'labimotion/libs/properties_handler'
|
|
94
|
+
autoload :LinkedElement, 'labimotion/libs/linked_element'
|
|
92
95
|
autoload :AttachmentHandler, 'labimotion/libs/attachment_handler'
|
|
93
96
|
autoload :VocabularyHandler, 'labimotion/libs/vocabulary_handler'
|
|
94
97
|
autoload :XlsxExporter, 'labimotion/libs/xlsx_exporter'
|
|
@@ -133,7 +136,9 @@ module Labimotion
|
|
|
133
136
|
|
|
134
137
|
autoload :StdLayer, 'labimotion/models/std_layer'
|
|
135
138
|
autoload :StdLayersRevision, 'labimotion/models/std_layers_revision'
|
|
139
|
+
autoload :UserKlassSetting, 'labimotion/models/user_klass_setting'
|
|
136
140
|
|
|
141
|
+
autoload :CellLine, 'labimotion/models/cellline'
|
|
137
142
|
autoload :DeviceDescription, 'labimotion/models/device_description'
|
|
138
143
|
autoload :DoseRespRequest, 'labimotion/models/dose_resp_request'
|
|
139
144
|
autoload :DoseRespOutput, 'labimotion/models/dose_resp_output'
|
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.
|
|
4
|
+
version: 2.4.0.rc2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chia-Lin Lin
|
|
@@ -86,6 +86,7 @@ files:
|
|
|
86
86
|
- lib/labimotion/apis/standard_api.rb
|
|
87
87
|
- lib/labimotion/apis/standard_layer_api.rb
|
|
88
88
|
- lib/labimotion/apis/user_api.rb
|
|
89
|
+
- lib/labimotion/apis/user_klass_settings_api.rb
|
|
89
90
|
- lib/labimotion/apis/vocabulary_api.rb
|
|
90
91
|
- lib/labimotion/apis/wellplate_api.rb
|
|
91
92
|
- lib/labimotion/collection/export.rb
|
|
@@ -110,6 +111,7 @@ files:
|
|
|
110
111
|
- lib/labimotion/entities/segment_klass_entity.rb
|
|
111
112
|
- lib/labimotion/entities/segment_revision_entity.rb
|
|
112
113
|
- lib/labimotion/entities/user_entity.rb
|
|
114
|
+
- lib/labimotion/entities/user_klass_setting_entity.rb
|
|
113
115
|
- lib/labimotion/entities/vocabulary_entity.rb
|
|
114
116
|
- lib/labimotion/helpers/converter_helpers.rb
|
|
115
117
|
- lib/labimotion/helpers/dataset_helpers.rb
|
|
@@ -136,6 +138,7 @@ files:
|
|
|
136
138
|
- lib/labimotion/libs/export_element.rb
|
|
137
139
|
- lib/labimotion/libs/export_element_variations.rb
|
|
138
140
|
- lib/labimotion/libs/import_element_variations.rb
|
|
141
|
+
- lib/labimotion/libs/linked_element.rb
|
|
139
142
|
- lib/labimotion/libs/nmr_mapper.rb
|
|
140
143
|
- lib/labimotion/libs/properties_handler.rb
|
|
141
144
|
- lib/labimotion/libs/sample_association.rb
|
|
@@ -143,6 +146,7 @@ files:
|
|
|
143
146
|
- lib/labimotion/libs/template_matcher.rb
|
|
144
147
|
- lib/labimotion/libs/vocabulary_handler.rb
|
|
145
148
|
- lib/labimotion/libs/xlsx_exporter.rb
|
|
149
|
+
- lib/labimotion/models/cellline.rb
|
|
146
150
|
- lib/labimotion/models/collections_element.rb
|
|
147
151
|
- lib/labimotion/models/concerns/attachment_converter.rb
|
|
148
152
|
- lib/labimotion/models/concerns/datasetable.rb
|
|
@@ -182,6 +186,7 @@ files:
|
|
|
182
186
|
- lib/labimotion/models/std_layer.rb
|
|
183
187
|
- lib/labimotion/models/std_layers_revision.rb
|
|
184
188
|
- lib/labimotion/models/template_submission.rb
|
|
189
|
+
- lib/labimotion/models/user_klass_setting.rb
|
|
185
190
|
- lib/labimotion/models/vocabulary.rb
|
|
186
191
|
- lib/labimotion/models/wellplate.rb
|
|
187
192
|
- lib/labimotion/usecases/build_template_doi_xml.rb
|