labimotion 2.3.3 → 2.3.4

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: 3b0302788cd4be249920160317e9a4bc999fe5446a61cadb5d356620da18290b
4
- data.tar.gz: 8abc1cf487e158576fe998ae09f75cff30f44ac653525a27393e60cb46de1849
3
+ metadata.gz: 7f2a9943b6c1fd9c1d0e09f9b2917985db173634db909a8a3eb3b80ad99d61d4
4
+ data.tar.gz: 828d2be1b26596f926f2e7f4ff06a05925ef7cae8dad9db06f5318b5c5ce527e
5
5
  SHA512:
6
- metadata.gz: 6b03a637a3cc878f8560b806da47301d9edfee557b59d83cb4944cee48df1aca4c35d13a74604f45ab5459a522d443095ecd01a02f35ae61b3ad577015e891b6
7
- data.tar.gz: 1c7f622321fc6f0d4bac046a9f0b14c94d56dffe3b1cda9e0ee64ca6b0cfd63a09bcf12f41680843e1b4a0313265558658edd0ff64dd62dfd2a04afff27321ca
6
+ metadata.gz: 2566c6dc15a6acc91cb3cbc9264946b34c7a99be61c02a5916aea8de366bbc7e0b928319ff63b994b0b5fe48d82b6a5ee1a9c8a7e19b4e2a158471a9b9436a2f
7
+ data.tar.gz: ee020ae64b55ff37621cecc819a4675808b815530b3aa8bcea2c49a3b5aa2107779a1297424f986648ecdd2c477adbf5dcdc72b7327430bbe2091ef63414611c
data/CHANGELOG.md CHANGED
@@ -1,6 +1,24 @@
1
1
  # LabIMotion Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [2.3.4]
4
+ > Unreleased
5
+ * Bug fixes:
6
+ * `upload_generics_files` stays on the element it was authorized for ([chemotion#684](https://github.com/ComPlat/chemotion/issues/684)). `delfiles` detached any generic-element attachment by id; it now only detaches the updated element's own. Uploads take `ElementProps`/`SegmentProps` from the server instead of the `type` the client sends in `elInfo`/`seInfo`, and the segment lookup also matches the element type, so an upload authorized for sample 5 no longer reaches the segment of reaction 5 or generic element 5. `att_type` is limited to `Element`, `Sample`, `Reaction`, `ResearchPlan` and `Screen` (400 otherwise, instead of `constantize` on the request value; the client's `CellLineSample`, which already failed in `constantize`, now gets the 400). `elfiles`, `attfiles` and `delfiles` are only handled for `Element`: an `elfiles` upload authorized for a sample used to write into the generic element with the same id. No client change.
7
+ * `GET /generic_elements/export` checks read permission before building the docx (labimotion#358, port of labimotion#336). It exported any Element, Segment or Dataset by id to any signed-in user. It now answers 404 for a missing record and 401 unless the user passes the matrix check (`genericElement` for an element, `segment` for a segment, `genericDataset` for a dataset) and `ElementPolicy#read?` on the element that holds the record: the element itself, a segment's element, or the root element of a dataset's container. A dataset also needs `ElementPolicy#read_dataset?`, so a share below the dataset detail level cannot export one. The table xlsx export (`GET /exporter/table_xlsx/:id`), which already ran these checks for elements and segments, now shares them through the new `Labimotion::ExportAuthorizationHelpers`.
8
+ * A dataset the gem builds itself — by the converter, or through `Labimotion::DatasetBuilder` — stores the metadata of its klass's active release (labimotion#343, port of labimotion#342). Both builders stored the klass's own `metadata`, which follows every save, drafts included, so a dataset created after a draft save got the released layers with the draft's layer groups and group restrictions, and the form then used them. A klass with no revision matching its uuid still uses its own `metadata`. `Labimotion::ReleaseMetadata.for_klass` returns that value for one klass.
9
+
10
+ ## [2.3.3]
11
+ > 2026-10-19
12
+ * Bug fixes:
13
+ * New records and element Reload build from the release's metadata (labimotion#326, port of labimotion#321 and labimotion#324, [chem-generic-ui#1438](https://github.com/cllde8/chem-generic-ui/issues/1438), port of [chem-generic-ui#1430](https://github.com/cllde8/chem-generic-ui/issues/1430)). A klass's `metadata` follows every save, drafts included, so a new element, segment or dataset, and an element brought up to the release by Reload, combined the released layers with the draft's layer groups and restrictions. Klass payloads now carry `metadata_release`: the metadata of the klass's active release, read from its revision row, or `null` when no revision matches the klass's uuid. Sent by `generic_elements/klass`, `segments/list_segment_klass` (nested element klasses included), `generic_dataset/klasses`, and on the nested `element_klass` of the element `GET`, create and update responses, with one revision query per klass family; every other payload is unchanged. A client reading `metadata_release ?? metadata` builds from one version. Read-only: no migration.
14
+
15
+ ## [2.3.2]
16
+ > 2026-10-02
17
+ * Bug fixes:
18
+ * A copied element, segment or dataset keeps the template release its properties were built on (labimotion#301, labimotion#303). Saves used to stamp every new segment and every dataset save with the klass's current `uuid` and `properties_release`, and an element with whatever the client sent, so a copy of a v1.0.0 record was labelled v2.0.0 and the client never offered Reload. `Labimotion::KlassVersion` now resolves the submitted `properties['klass_uuid']` against the klass's current release and its `*_klasses_revisions`, deleted revisions included, and stores that uuid together with the matching `properties_release`. A new record whose uuid is missing or unknown follows the current release; an existing record keeps its stored uuid when none is sent, then the uuid its stored `properties_release` names, and follows the current release only when it has neither, and a uuid this instance never released (a record imported from an instance with its own release history) keeps the release stored with the record. A new element whose properties carry no uuid follows the template the client built it on; a new segment does the same, and a copied one falls back to the `klass_uuid` of the row it was copied from. The collection import stamps the release the archive names instead of the importing instance's current one: it tries the uuid inside the archived `properties_release`, then `properties['klass_uuid']`, then the `klass_uuid` column, and stores the first one this instance knows with the local release; when none resolves, the archived release keeps the uuid it names (or, naming none, the first uuid the archive carries), and the imported properties carry the same `klass_uuid` as the column. A `klass_uuid` that is not a string is ignored, and a dataset whose klass changed follows the new klass and ignores the client's release. Element create returns 404 when the template is missing or deleted, element update resolves a soft-deleted template (`with_deleted`) before writing anything, and segments and datasets of a deleted template are skipped on save. A new record whose `klass_uuid` this instance cannot resolve keeps it with the `properties_release` the client sent, only when that release names the same uuid. No migration and no client change.
19
+
20
+ ## [2.3.0]
21
+ > 2026-08-26
4
22
  * Features and enhancements:
5
23
  * 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
24
  * 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.
@@ -8,8 +26,6 @@
8
26
  * `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
27
  * Import warnings name the cell they came from (`Cell I6: ...`), so the user can go straight to it in the spreadsheet.
10
28
  * Bug fixes:
11
- * New records and element Reload build from the release's metadata (labimotion#326, port of labimotion#321 and labimotion#324, [chem-generic-ui#1438](https://github.com/cllde8/chem-generic-ui/issues/1438), port of [chem-generic-ui#1430](https://github.com/cllde8/chem-generic-ui/issues/1430)). A klass's `metadata` follows every save, drafts included, so a new element, segment or dataset, and an element brought up to the release by Reload, combined the released layers with the draft's layer groups and restrictions. Klass payloads now carry `metadata_release`: the metadata of the klass's active release, read from its revision row, or `null` when no revision matches the klass's uuid. Sent by `generic_elements/klass`, `segments/list_segment_klass` (nested element klasses included), `generic_dataset/klasses`, and on the nested `element_klass` of the element `GET`, create and update responses, with one revision query per klass family; every other payload is unchanged. A client reading `metadata_release ?? metadata` builds from one version. Read-only: no migration.
12
- * A copied element, segment or dataset keeps the template release its properties were built on (labimotion#301, labimotion#303). Saves used to stamp every new segment and every dataset save with the klass's current `uuid` and `properties_release`, and an element with whatever the client sent, so a copy of a v1.0.0 record was labelled v2.0.0 and the client never offered Reload. `Labimotion::KlassVersion` now resolves the submitted `properties['klass_uuid']` against the klass's current release and its `*_klasses_revisions`, deleted revisions included, and stores that uuid together with the matching `properties_release`. A new record whose uuid is missing or unknown follows the current release; an existing record keeps its stored uuid when none is sent, then the uuid its stored `properties_release` names, and follows the current release only when it has neither, and a uuid this instance never released (a record imported from an instance with its own release history) keeps the release stored with the record. A new element whose properties carry no uuid follows the template the client built it on; a new segment does the same, and a copied one falls back to the `klass_uuid` of the row it was copied from. The collection import stamps the release the archive names instead of the importing instance's current one: it tries the uuid inside the archived `properties_release`, then `properties['klass_uuid']`, then the `klass_uuid` column, and stores the first one this instance knows with the local release; when none resolves, the archived release keeps the uuid it names (or, naming none, the first uuid the archive carries), and the imported properties carry the same `klass_uuid` as the column. A `klass_uuid` that is not a string is ignored, and a dataset whose klass changed follows the new klass and ignores the client's release. Element create returns 404 when the template is missing or deleted, element update resolves a soft-deleted template (`with_deleted`) before writing anything, and segments and datasets of a deleted template are skipped on save. A new record whose `klass_uuid` this instance cannot resolve keeps it with the `properties_release` the client sent, only when that release names the same uuid. No migration and no client change.
13
29
  * `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`. Fixed for both the basic elements (`ElementFetchable`) and the generic element (`Labimotion::Element`), so `search_by_like` and `search_basic_by_like` return shared records again.
14
30
  * Dependencies:
15
31
  * Added `roo` (~> 2.10) for reading the uploaded workbook.
@@ -8,6 +8,7 @@ module Labimotion
8
8
 
9
9
  helpers Labimotion::ParamHelpers
10
10
  helpers Labimotion::ExporterHelpers
11
+ helpers Labimotion::ExportAuthorizationHelpers
11
12
 
12
13
  resource :exporter do
13
14
  resource :table_xlsx do
@@ -46,9 +47,7 @@ module Labimotion
46
47
  klass = fetch_klass
47
48
  @element = klass.find_by(id: params[:id])
48
49
 
49
- error!('404 Not Found', 404) if @element.nil?
50
-
51
- authorize_element_access!
50
+ authorize_export!(params[:klass], @element)
52
51
  rescue ActiveRecord::RecordNotFound
53
52
  error!('404 Not Found', 404)
54
53
  rescue NameError => e
@@ -61,22 +60,6 @@ module Labimotion
61
60
  Labimotion::Utils.resolve_class(params[:klass])
62
61
  end
63
62
 
64
- # Authorize element access based on user permissions
65
- def authorize_element_access!
66
- element_policy = if params[:klass] == 'Element'
67
- ElementPolicy.new(current_user,
68
- @element)
69
- else
70
- ElementPolicy.new(current_user,
71
- @element.element)
72
- end
73
- matrix_name = params[:klass] == 'Element' ? 'genericElement' : 'segment'
74
-
75
- return if current_user.matrix_check_by_name(matrix_name) && element_policy.read?
76
-
77
- error!('401 Unauthorized', 401)
78
- end
79
-
80
63
  # Fetch layer from element properties
81
64
  def fetch_layer
82
65
  properties = @element.properties
@@ -17,6 +17,7 @@ module Labimotion
17
17
  helpers Labimotion::GenericHelpers
18
18
  helpers Labimotion::ElementHelpers
19
19
  helpers Labimotion::ParamHelpers
20
+ helpers Labimotion::ExportAuthorizationHelpers
20
21
 
21
22
  resource :generic_elements do
22
23
  # might be removed because the file is moved to public folder
@@ -129,14 +130,8 @@ module Labimotion
129
130
  optional :export_format, type: String, desc: 'export format'
130
131
  end
131
132
  get do
132
- case params[:klass]
133
- when 'Element'
134
- element = Labimotion::Element.find(params[:id])
135
- when 'Segment'
136
- element = Labimotion::Segment.find(params[:id])
137
- when 'Dataset'
138
- element = Labimotion::Dataset.find(params[:id])
139
- end
133
+ element = "Labimotion::#{params[:klass]}".constantize.find_by(id: params[:id])
134
+ authorize_export!(params[:klass], element)
140
135
  export = Labimotion::ExportElement.new current_user, element, params[:export_format]
141
136
  env['api.format'] = :binary
142
137
  content_type 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
@@ -264,14 +259,10 @@ module Labimotion
264
259
  desc 'upload generic files'
265
260
  params do
266
261
  requires :att_id, type: Integer, desc: 'Element Id'
267
- requires :att_type, type: String, desc: 'Element Type'
262
+ requires :att_type, type: String, values: Labimotion::Prop::UPLOAD_ELEMENT_TYPES.keys, desc: 'Element Type'
268
263
  end
269
264
  after_validation do
270
- if params[:att_type] == 'Sample' || params[:att_type] == 'Reaction' || params[:att_type] == 'ResearchPlan'
271
- el = "#{params[:att_type]}".constantize.find_by(id: params[:att_id])
272
- else
273
- el = "Labimotion::#{params[:att_type]}".constantize.find_by(id: params[:att_id])
274
- end
265
+ el = Labimotion::Prop::UPLOAD_ELEMENT_TYPES[params[:att_type]].constantize.find_by(id: params[:att_id])
275
266
  error!('401 Unauthorized', 401) if el.nil?
276
267
 
277
268
  policy_updatable = ElementPolicy.new(current_user, el).update?
@@ -182,13 +182,18 @@ module Labimotion
182
182
 
183
183
  def upload_generics_files(current_user, params)
184
184
  attach_ary = []
185
+ element_type = Labimotion::Prop::UPLOAD_ELEMENT_TYPES.fetch(params[:att_type])
186
+ generic_element = element_type == Labimotion::Prop::L_ELEMENT
187
+ # Element properties and direct attachments only exist on generic elements; for any
188
+ # other att_type, att_id names a different table, so they are not touched.
185
189
  att_ary = create_uploads(
186
190
  Labimotion::Prop::ELEMENT,
187
191
  params[:att_id],
188
192
  params[:elfiles],
189
193
  params[:elInfo],
190
194
  current_user.id,
191
- ) if params[:elfiles].present? && params[:elInfo].present?
195
+ element_type
196
+ ) if generic_element && params[:elfiles].present? && params[:elInfo].present?
192
197
 
193
198
  (attach_ary << att_ary).flatten! unless att_ary&.empty?
194
199
 
@@ -197,16 +202,17 @@ module Labimotion
197
202
  params[:att_id],
198
203
  params[:sefiles],
199
204
  params[:seInfo],
200
- current_user.id
205
+ current_user.id,
206
+ element_type
201
207
  ) if params[:sefiles].present? && params[:seInfo].present?
202
208
 
203
209
  (attach_ary << att_ary).flatten! unless att_ary&.empty?
204
210
 
205
- if params[:attfiles].present? || params[:delfiles].present? then
211
+ if generic_element && (params[:attfiles].present? || params[:delfiles].present?)
206
212
  att_ary = create_attachments(
207
213
  params[:attfiles],
208
214
  params[:delfiles],
209
- "Labimotion::#{params[:att_type]}",
215
+ element_type,
210
216
  params[:att_id],
211
217
  params[:attfilesIdentifier],
212
218
  current_user.id
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Labimotion
4
+ # Gate shared by the routes that export an Element, Segment or Dataset to a file.
5
+ # A segment or dataset is readable when the element that holds it is; a dataset
6
+ # sits on an analysis container, so its element is that container's root element.
7
+ module ExportAuthorizationHelpers
8
+ # Answers 404 for a missing record and 401 unless the user passes the matrix
9
+ # check for the record's family and may read the element that holds it.
10
+ def authorize_export!(klass_name, record)
11
+ error!('404 Not Found', 404) if record.nil?
12
+ return if export_readable?(klass_name, record)
13
+
14
+ error!('401 Unauthorized', 401)
15
+ end
16
+
17
+ # The matrix right that shows each family in the UI.
18
+ EXPORT_MATRIX = { 'Element' => 'genericElement', 'Segment' => 'segment', 'Dataset' => 'genericDataset' }.freeze
19
+
20
+ def export_readable?(klass_name, record)
21
+ return false unless current_user.matrix_check_by_name(EXPORT_MATRIX.fetch(klass_name))
22
+
23
+ policy = ElementPolicy.new(current_user, export_parent_element(klass_name, record))
24
+ return policy.read? unless klass_name == 'Dataset'
25
+
26
+ # A dataset holds analysis data, which a share below the dataset detail
27
+ # level hides.
28
+ policy.read? && policy.read_dataset?
29
+ end
30
+
31
+ def export_parent_element(klass_name, record)
32
+ case klass_name
33
+ when 'Element' then record
34
+ when 'Segment' then record.element
35
+ when 'Dataset' then record.element&.root_element
36
+ end
37
+ end
38
+ end
39
+ end
@@ -142,16 +142,20 @@ module Labimotion
142
142
  raise e
143
143
  end
144
144
 
145
- def create_uploads(type, id, files, param_info, user_id)
145
+ # element_type is the class name of the element that id names, so a segment
146
+ # lookup cannot reach another element type's segment with the same element_id.
147
+ def create_uploads(type, id, files, param_info, user_id, element_type)
146
148
  return if files.nil? || param_info.nil? || files.empty? || param_info.empty?
147
149
 
148
150
  attach_ary = []
149
151
  map_info = JSON.parse(param_info)
152
+ # Derived here, not taken from param_info, so the request cannot pick the attachable_type
153
+ attachable_type = type == Labimotion::Prop::SEGMENT ? Labimotion::Prop::SEGMENTPROPS : Labimotion::Prop::ELEMENTPROPS
150
154
  map_info&.keys&.each do |key|
151
155
  next if map_info[key]['files'].empty?
152
156
 
153
157
  if type == Labimotion::Prop::SEGMENT
154
- element = Labimotion::Segment.find_by(element_id: id, segment_klass_id: key)
158
+ element = Labimotion::Segment.find_by(element_type: element_type, element_id: id, segment_klass_id: key)
155
159
  elsif type == Labimotion::Prop::ELEMENT
156
160
  element = Labimotion::Element.find_by(id: id)
157
161
  end
@@ -172,7 +176,7 @@ module Labimotion
172
176
  created_by: user_id,
173
177
  created_for: user_id,
174
178
  content_type: file[:type],
175
- attachable_type: map_info[key]['type'],
179
+ attachable_type: attachable_type,
176
180
  attachable_id: element.id,
177
181
  )
178
182
  begin
@@ -221,7 +225,8 @@ module Labimotion
221
225
  end
222
226
  end
223
227
  unless (del_files || []).empty?
224
- Attachment.where('id IN (?) AND attachable_type = (?)', del_files.map!(&:to_i), type).update_all(attachable_id: nil)
228
+ # Scoped to the element, so ids of another element's attachments are left alone
229
+ Attachment.where(id: del_files.map(&:to_i), attachable_type: type, attachable_id: id).update_all(attachable_id: nil)
225
230
  end
226
231
  attach_ary
227
232
  rescue StandardError => e
@@ -262,7 +262,7 @@ module Labimotion
262
262
  properties: props,
263
263
  properties_release: klass.properties_release,
264
264
  klass_uuid: klass.uuid,
265
- metadata: klass.metadata
265
+ metadata: Labimotion::ReleaseMetadata.for_klass(klass)
266
266
  )
267
267
  end
268
268
 
@@ -45,7 +45,7 @@ module Labimotion
45
45
  properties: props,
46
46
  properties_release: klass.properties_release,
47
47
  klass_uuid: klass.uuid,
48
- metadata: klass.metadata
48
+ metadata: Labimotion::ReleaseMetadata.for_klass(klass)
49
49
  )
50
50
  end
51
51
 
@@ -8,9 +8,10 @@ require 'active_support/core_ext/string/inflections'
8
8
 
9
9
  module Labimotion
10
10
  ## ReleaseMetadata
11
- # Builds the `metadata_releases:` option the klass entities read: each klass's metadata as
12
- # of its active release. The klass row's own `metadata` follows every save, drafts included,
13
- # so a client building a record from `properties_release` needs the copy kept on the revision.
11
+ # Each klass's metadata as of its active release, for the `metadata_releases:` option the
12
+ # klass entities read and for records the gem builds itself. The klass row's own `metadata`
13
+ # follows every save, drafts included, so a record built from `properties_release` needs the
14
+ # copy kept on the revision.
14
15
  # The active revision is the one whose uuid equals the klass's, which changes only on release.
15
16
  class ReleaseMetadata
16
17
  # A klass with no revision matching its uuid (never released, or imported without one)
@@ -21,6 +22,13 @@ module Labimotion
21
22
  end
22
23
  end
23
24
 
25
+ # The metadata a record built from the klass's `properties_release` stores: the active
26
+ # release's, or the klass's own when no revision matches its uuid. A failed lookup also
27
+ # falls back to the klass's own, so building the record never fails on it.
28
+ def self.for_klass(klass)
29
+ map_for([klass])[key_of(klass)] || klass.metadata || {}
30
+ end
31
+
24
32
  # Family as well as id: a segment klass payload nests its element klass, and ids repeat
25
33
  # across the klass tables.
26
34
  def self.key_of(klass)
@@ -39,7 +47,7 @@ module Labimotion
39
47
  end
40
48
  rescue StandardError => e
41
49
  # The release metadata is an addition to a list that must still render, and a failing
42
- # family must not blank the families that resolved.
50
+ # family must not blank the families that resolved. A record builder falls back too.
43
51
  Labimotion.log_exception(e)
44
52
  {}
45
53
  end
@@ -19,10 +19,17 @@ module Labimotion
19
19
  SAMPLE = 'Sample'.freeze
20
20
  MOLECULE = 'Molecule'.freeze
21
21
  REACTION = 'Reaction'.freeze
22
+ RESEARCH_PLAN = 'ResearchPlan'.freeze
23
+ SCREEN = 'Screen'.freeze
22
24
  CONTAINER = 'Container'.freeze
23
25
  ELEMENTPROPS = 'ElementProps'.freeze
24
26
  SEGMENTPROPS = 'SegmentProps'.freeze
25
27
  DATASETPROPS = 'DatasetProps'.freeze
26
28
  UPLOADPROPS = [ELEMENTPROPS, SEGMENTPROPS, DATASETPROPS].freeze
29
+ # att_type accepted by upload_generics_files => class name of the element it names
30
+ UPLOAD_ELEMENT_TYPES = {
31
+ ELEMENT => L_ELEMENT, SAMPLE => SAMPLE, REACTION => REACTION, RESEARCH_PLAN => RESEARCH_PLAN,
32
+ SCREEN => SCREEN
33
+ }.freeze
27
34
  end
28
35
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  ## Labimotion Version
4
4
  module Labimotion
5
- VERSION = '2.3.3'
5
+ VERSION = '2.3.4'
6
6
  end
data/lib/labimotion.rb CHANGED
@@ -73,6 +73,7 @@ module Labimotion
73
73
  autoload :ParamHelpers, 'labimotion/helpers/param_helpers'
74
74
  autoload :ConverterHelpers, 'labimotion/helpers/converter_helpers'
75
75
  autoload :ExporterHelpers, 'labimotion/helpers/exporter_helpers'
76
+ autoload :ExportAuthorizationHelpers, 'labimotion/helpers/export_authorization_helpers'
76
77
  autoload :SampleAssociationHelpers, 'labimotion/helpers/sample_association_helpers'
77
78
  autoload :RepositoryHelpers, 'labimotion/helpers/repository_helpers'
78
79
  autoload :VocabularyHelpers, 'labimotion/helpers/vocabulary_helpers'
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.3.3
4
+ version: 2.3.4
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: 2026-10-19 00:00:00.000000000 Z
12
+ date: 2026-11-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: caxlsx
@@ -114,6 +114,7 @@ files:
114
114
  - lib/labimotion/helpers/converter_helpers.rb
115
115
  - lib/labimotion/helpers/dataset_helpers.rb
116
116
  - lib/labimotion/helpers/element_helpers.rb
117
+ - lib/labimotion/helpers/export_authorization_helpers.rb
117
118
  - lib/labimotion/helpers/exporter_helpers.rb
118
119
  - lib/labimotion/helpers/generic_helpers.rb
119
120
  - lib/labimotion/helpers/mtt_helpers.rb