labimotion 2.2.0.rc7 → 2.2.0.rc9
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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fde410e148abdf50036fd9146e2faea1ccd5078f7c93ec99dbd1c3fa6680ee96
|
|
4
|
+
data.tar.gz: 3a63d545e4b1c9da579e582f956ead5e2d204c4f9078f47803322a4831ce8b50
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1c2cd08a3c54a452994ab180b014e53545ade8ff6ebf65d19a06a23fe47770b7e27bb2c4e04e8c7c05d4a3ab45875240f5cdac29e3ae1e64f49281ea3d9a6bb3
|
|
7
|
+
data.tar.gz: 318fa9de8609f13f1074620e09aa24d62ad135d956e5415dfa582d262787897dbb70d119144a80227c4b567138fd69e871196dba072b6fb3e9eb5276ae0239f9
|
|
@@ -179,10 +179,11 @@ module Labimotion
|
|
|
179
179
|
end
|
|
180
180
|
|
|
181
181
|
namespace :klass_revisions do
|
|
182
|
-
desc 'list Generic
|
|
182
|
+
desc 'list Generic Klass Revisions'
|
|
183
183
|
params do
|
|
184
184
|
requires :id, type: Integer, desc: 'Generic Element Klass Id'
|
|
185
185
|
requires :klass, type: String, desc: 'Klass', values: %w[ElementKlass SegmentKlass DatasetKlass]
|
|
186
|
+
optional :limit, type: Integer, default: 10, desc: 'Max revisions returned'
|
|
186
187
|
end
|
|
187
188
|
get do
|
|
188
189
|
list = list_klass_revisions(params)
|
|
@@ -197,6 +198,7 @@ module Labimotion
|
|
|
197
198
|
desc 'list Generic Element Revisions'
|
|
198
199
|
params do
|
|
199
200
|
requires :id, type: Integer, desc: 'Generic Element Id'
|
|
201
|
+
optional :limit, type: Integer, default: 10, desc: 'Max revisions returned'
|
|
200
202
|
end
|
|
201
203
|
get do
|
|
202
204
|
list = element_revisions(params)
|
|
@@ -241,14 +243,15 @@ module Labimotion
|
|
|
241
243
|
end
|
|
242
244
|
|
|
243
245
|
namespace :segment_revisions do
|
|
244
|
-
desc 'list Generic
|
|
246
|
+
desc 'list Generic Segment Revisions'
|
|
245
247
|
params do
|
|
246
248
|
optional :id, type: Integer, desc: 'Generic Element Id'
|
|
249
|
+
optional :limit, type: Integer, default: 10, desc: 'Max revisions returned'
|
|
247
250
|
end
|
|
248
251
|
get do
|
|
249
252
|
klass = Labimotion::Segment.find(params[:id])
|
|
250
253
|
list = klass.segments_revisions unless klass.nil?
|
|
251
|
-
present list&.order(created_at: :desc)&.limit(
|
|
254
|
+
present list&.order(created_at: :desc)&.limit(params[:limit]), with: Labimotion::SegmentRevisionEntity, root: 'revisions'
|
|
252
255
|
rescue StandardError => e
|
|
253
256
|
Labimotion.log_exception(e, current_user)
|
|
254
257
|
[]
|
|
@@ -200,7 +200,7 @@ module Labimotion
|
|
|
200
200
|
def element_revisions(params)
|
|
201
201
|
klass = Labimotion::Element.find(params[:id])
|
|
202
202
|
list = klass.elements_revisions unless klass.nil?
|
|
203
|
-
list&.order(created_at: :desc)&.limit(
|
|
203
|
+
list&.order(created_at: :desc)&.limit(params[:limit])
|
|
204
204
|
rescue StandardError => e
|
|
205
205
|
Labimotion.log_exception(e, current_user)
|
|
206
206
|
raise e
|
|
@@ -95,7 +95,7 @@ module Labimotion
|
|
|
95
95
|
def list_klass_revisions(params)
|
|
96
96
|
klass = "Labimotion::#{params[:klass]}".constantize.find_by(id: params[:id])
|
|
97
97
|
list = klass.send("#{params[:klass].underscore}es_revisions") unless klass.nil?
|
|
98
|
-
list&.order(released_at: :desc)&.limit(
|
|
98
|
+
list&.order(released_at: :desc)&.limit(params[:limit])
|
|
99
99
|
rescue StandardError => e
|
|
100
100
|
Labimotion.log_exception(e, current_user)
|
|
101
101
|
raise e
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require 'date'
|
|
2
3
|
require 'ostruct'
|
|
3
4
|
require 'export_table'
|
|
4
5
|
require 'labimotion/version'
|
|
@@ -125,25 +126,13 @@ module Labimotion
|
|
|
125
126
|
val = files&.map { |file| "#{file['filename']} #{file['label']}" }&.join('\n')
|
|
126
127
|
field_obj[:value] = val
|
|
127
128
|
when Labimotion::FieldType::TABLE
|
|
128
|
-
field_obj[:is_table] =
|
|
129
|
-
field_obj[:not_table] =
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
header["col#{idx}"] = sub_field['col_name']
|
|
136
|
-
end
|
|
137
|
-
tbl.push(header)
|
|
138
|
-
field.fetch('sub_values', []).each do |sub_val|
|
|
139
|
-
data = {}
|
|
140
|
-
sub_fields.each_with_index do |sub_field, idx|
|
|
141
|
-
data["col#{idx}"] = build_table_field(sub_val, sub_field)
|
|
142
|
-
end
|
|
143
|
-
tbl.push(data)
|
|
144
|
-
end
|
|
145
|
-
field_obj[:data] = tbl
|
|
146
|
-
# field_obj[:value] = 'this is a table'
|
|
129
|
+
field_obj[:is_table] = false
|
|
130
|
+
field_obj[:not_table] = true
|
|
131
|
+
field_obj[:value] = build_table_wordml(field)
|
|
132
|
+
when Labimotion::FieldType::DATETIME_RANGE
|
|
133
|
+
field_obj[:is_table] = false
|
|
134
|
+
field_obj[:not_table] = true
|
|
135
|
+
field_obj[:value] = build_datetime_range_wordml(field)
|
|
147
136
|
when Labimotion::FieldType::INPUT_GROUP
|
|
148
137
|
val = []
|
|
149
138
|
field.fetch('sub_fields', [])&.each do |sub_field|
|
|
@@ -210,6 +199,243 @@ module Labimotion
|
|
|
210
199
|
end
|
|
211
200
|
end
|
|
212
201
|
|
|
202
|
+
TABLE_BLANK_WORDML = '<w:p/>'
|
|
203
|
+
TABLE_BORDER = '<w:tblBorders>' \
|
|
204
|
+
'<w:top w:val="single" w:sz="4" w:color="auto"/>' \
|
|
205
|
+
'<w:left w:val="single" w:sz="4" w:color="auto"/>' \
|
|
206
|
+
'<w:bottom w:val="single" w:sz="4" w:color="auto"/>' \
|
|
207
|
+
'<w:right w:val="single" w:sz="4" w:color="auto"/>' \
|
|
208
|
+
'<w:insideH w:val="single" w:sz="4" w:color="auto"/>' \
|
|
209
|
+
'<w:insideV w:val="single" w:sz="4" w:color="auto"/>' \
|
|
210
|
+
'</w:tblBorders>'
|
|
211
|
+
|
|
212
|
+
def build_table_wordml(field)
|
|
213
|
+
sub_fields = field.fetch('sub_fields', [])
|
|
214
|
+
return Sablon.content(:word_ml, TABLE_BLANK_WORDML) if sub_fields.empty?
|
|
215
|
+
|
|
216
|
+
width = (9000.0 / sub_fields.length).round
|
|
217
|
+
font_size = sub_fields.length > 6 ? 16 : 20
|
|
218
|
+
grid = sub_fields.map { %(<w:gridCol w:w="#{width}"/>) }.join
|
|
219
|
+
header = build_table_header_row(sub_fields, width, font_size)
|
|
220
|
+
rows = build_table_body_rows(field.fetch('sub_values', []), sub_fields, width, font_size)
|
|
221
|
+
tbl = '<w:tbl><w:tblPr><w:tblW w:w="5000" w:type="pct"/>' \
|
|
222
|
+
"#{TABLE_BORDER}</w:tblPr><w:tblGrid>#{grid}</w:tblGrid>" \
|
|
223
|
+
"#{header}#{rows}</w:tbl>"
|
|
224
|
+
Sablon.content(:word_ml, tbl)
|
|
225
|
+
rescue StandardError => e
|
|
226
|
+
Labimotion.log_exception(e)
|
|
227
|
+
Sablon.content(:word_ml, TABLE_BLANK_WORDML)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
DATETIME_RANGE_HEADERS = ['Start', 'Stop', 'Duration (calc)', 'Duration'].freeze
|
|
231
|
+
DATETIME_RANGE_PRECISE_LABELS = %w[year month day hour minute second].freeze
|
|
232
|
+
DURATION_UNIT_LABELS = {
|
|
233
|
+
'd' => 'day',
|
|
234
|
+
'h' => 'hour',
|
|
235
|
+
'min' => 'minute',
|
|
236
|
+
's' => 'second'
|
|
237
|
+
}.freeze
|
|
238
|
+
|
|
239
|
+
def build_datetime_range_wordml(field)
|
|
240
|
+
sub_fields = field.fetch('sub_fields', []) || []
|
|
241
|
+
by_col = sub_fields.each_with_object({}) { |sf, h| h[sf['col_name']] = sf if sf.is_a?(Hash) }
|
|
242
|
+
values = datetime_range_values(by_col)
|
|
243
|
+
datetime_range_wordml_table(values)
|
|
244
|
+
rescue StandardError => e
|
|
245
|
+
Labimotion.log_exception(e)
|
|
246
|
+
Sablon.content(:word_ml, TABLE_BLANK_WORDML)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def datetime_range_values(by_col)
|
|
250
|
+
time_start = by_col['timeStart']&.dig('value').to_s
|
|
251
|
+
time_stop = by_col['timeStop']&.dig('value').to_s
|
|
252
|
+
[
|
|
253
|
+
time_start,
|
|
254
|
+
time_stop,
|
|
255
|
+
format_duration_calc(by_col['durationCalc'], time_start, time_stop),
|
|
256
|
+
format_duration_value(by_col['duration'])
|
|
257
|
+
]
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def datetime_range_wordml_table(values)
|
|
261
|
+
width = (9000.0 / DATETIME_RANGE_HEADERS.length).round
|
|
262
|
+
font_size = 20
|
|
263
|
+
grid = DATETIME_RANGE_HEADERS.map { %(<w:gridCol w:w="#{width}"/>) }.join
|
|
264
|
+
header_cells = DATETIME_RANGE_HEADERS.map { |h| build_wordml_cell(h, width, font_size, true) }.join
|
|
265
|
+
body_cells = values.map { |v| build_wordml_cell(v, width, font_size, false) }.join
|
|
266
|
+
tbl = '<w:tbl><w:tblPr><w:tblW w:w="5000" w:type="pct"/>' \
|
|
267
|
+
"#{TABLE_BORDER}</w:tblPr><w:tblGrid>#{grid}</w:tblGrid>" \
|
|
268
|
+
"<w:tr>#{header_cells}</w:tr><w:tr>#{body_cells}</w:tr></w:tbl>"
|
|
269
|
+
Sablon.content(:word_ml, tbl)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def format_duration_value(duration_sf)
|
|
273
|
+
val = duration_sf&.dig('value')
|
|
274
|
+
return '' if val.nil? || val.to_s.empty?
|
|
275
|
+
|
|
276
|
+
"#{val} #{duration_unit_label(duration_sf['value_system'], val)}".strip
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def duration_unit_label(value_system, value)
|
|
280
|
+
base = DURATION_UNIT_LABELS[value_system.to_s]
|
|
281
|
+
return value_system.to_s if base.nil?
|
|
282
|
+
|
|
283
|
+
pluralize_unit?(value) ? "#{base}s" : base
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def pluralize_unit?(value)
|
|
287
|
+
numeric = Float(value.to_s)
|
|
288
|
+
(numeric - 1.0).abs > Float::EPSILON
|
|
289
|
+
rescue ArgumentError, TypeError
|
|
290
|
+
true
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def format_duration_calc(_duration_calc_sf, time_start, time_stop)
|
|
294
|
+
compute_duration_calc(time_start, time_stop)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def compute_duration_calc(time_start, time_stop)
|
|
298
|
+
start_t = parse_datetime_range_value(time_start)
|
|
299
|
+
stop_t = parse_datetime_range_value(time_stop)
|
|
300
|
+
return '' unless start_t && stop_t && stop_t > start_t
|
|
301
|
+
|
|
302
|
+
precise_diff_humanize(start_t, stop_t)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def parse_datetime_range_value(str)
|
|
306
|
+
cleaned = str.to_s.strip
|
|
307
|
+
return nil if cleaned.empty?
|
|
308
|
+
|
|
309
|
+
parts = Date._parse(cleaned)
|
|
310
|
+
return nil unless datetime_range_parts_complete?(parts)
|
|
311
|
+
|
|
312
|
+
build_utc_from_parts(parts)
|
|
313
|
+
rescue ArgumentError
|
|
314
|
+
nil
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def datetime_range_parts_complete?(parts)
|
|
318
|
+
%i[year mon mday].all? { |k| parts[k] }
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def build_utc_from_parts(parts)
|
|
322
|
+
Time.utc(parts[:year], parts[:mon], parts[:mday],
|
|
323
|
+
parts[:hour] || 0, parts[:min] || 0, parts[:sec] || 0)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def precise_diff_humanize(start_t, stop_t)
|
|
327
|
+
components = precise_diff_components(start_t, stop_t)
|
|
328
|
+
parts = components.zip(DATETIME_RANGE_PRECISE_LABELS).reject { |n, _| n <= 0 }
|
|
329
|
+
return '0 seconds' if parts.empty?
|
|
330
|
+
|
|
331
|
+
parts.map { |n, label| format_precise_part(n, label) }.join(' ')
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def format_precise_part(count, label)
|
|
335
|
+
suffix = count == 1 ? '' : 's'
|
|
336
|
+
"#{count} #{label}#{suffix}"
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def precise_diff_components(start_t, stop_t)
|
|
340
|
+
y, m, d, h, mi, s = precise_diff_raw(start_t, stop_t)
|
|
341
|
+
mi, s = borrow_unit(mi, s, 60)
|
|
342
|
+
h, mi = borrow_unit(h, mi, 60)
|
|
343
|
+
d, h = borrow_unit(d, h, 24)
|
|
344
|
+
if d.negative?
|
|
345
|
+
m -= 1
|
|
346
|
+
d += (Date.new(stop_t.year, stop_t.month, 1) - 1).day
|
|
347
|
+
end
|
|
348
|
+
y, m = borrow_unit(y, m, 12)
|
|
349
|
+
[y, m, d, h, mi, s]
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def borrow_unit(higher, lower, base)
|
|
353
|
+
return [higher, lower] unless lower.negative?
|
|
354
|
+
|
|
355
|
+
[higher - 1, lower + base]
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def precise_diff_raw(start_t, stop_t)
|
|
359
|
+
[
|
|
360
|
+
stop_t.year - start_t.year,
|
|
361
|
+
stop_t.month - start_t.month,
|
|
362
|
+
stop_t.day - start_t.day,
|
|
363
|
+
stop_t.hour - start_t.hour,
|
|
364
|
+
stop_t.min - start_t.min,
|
|
365
|
+
stop_t.sec - start_t.sec
|
|
366
|
+
]
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def build_table_header_row(sub_fields, width, font_size)
|
|
370
|
+
cells = sub_fields.map { |sf| build_wordml_cell(sf['col_name'].to_s, width, font_size, true) }.join
|
|
371
|
+
"<w:tr>#{cells}</w:tr>"
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def build_table_body_rows(sub_values, sub_fields, width, font_size)
|
|
375
|
+
sub_values.map do |sv|
|
|
376
|
+
cells = sub_fields.map { |sf| build_wordml_cell(table_cell_text(sv, sf), width, font_size, false) }.join
|
|
377
|
+
"<w:tr>#{cells}</w:tr>"
|
|
378
|
+
end.join
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def build_wordml_cell(text, width, font_size, bold)
|
|
382
|
+
bold_xml = bold ? '<w:b/>' : ''
|
|
383
|
+
rpr = "<w:rPr>#{bold_xml}<w:sz w:val=\"#{font_size}\"/></w:rPr>"
|
|
384
|
+
lines = text.to_s.split(/\r?\n/)
|
|
385
|
+
lines = [''] if lines.empty?
|
|
386
|
+
paragraphs = lines.map do |line|
|
|
387
|
+
"<w:p><w:r>#{rpr}<w:t xml:space=\"preserve\">#{xml_escape(line)}</w:t></w:r></w:p>"
|
|
388
|
+
end.join
|
|
389
|
+
"<w:tc><w:tcPr><w:tcW w:w=\"#{width}\" w:type=\"dxa\"/></w:tcPr>#{paragraphs}</w:tc>"
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def xml_escape(str)
|
|
393
|
+
str.to_s.gsub('&', '&').gsub('<', '<').gsub('>', '>')
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
TABLE_CELL_TEXT_RENDERERS = {
|
|
397
|
+
Labimotion::FieldType::DRAG_SAMPLE => ->(_sf, c, ctx) { ctx.table_cell_sample_text(c['value'] || {}) },
|
|
398
|
+
Labimotion::FieldType::DRAG_MOLECULE => ->(_sf, c, ctx) { ctx.table_cell_molecule_text(c['value'] || {}) },
|
|
399
|
+
Labimotion::FieldType::SELECT => ->(_sf, c, _ctx) { c['value'].to_s },
|
|
400
|
+
Labimotion::FieldType::SYSTEM_DEFINED => ->(sf, c, ctx) { ctx.table_cell_sysdef_text(sf, c) }
|
|
401
|
+
}.freeze
|
|
402
|
+
|
|
403
|
+
def table_cell_text(sub_val, sub_field)
|
|
404
|
+
return '' if sub_field.fetch('id', nil).nil? || sub_val[sub_field['id']].nil?
|
|
405
|
+
|
|
406
|
+
cell = sub_val[sub_field['id']]
|
|
407
|
+
renderer = TABLE_CELL_TEXT_RENDERERS[sub_field['type']]
|
|
408
|
+
return renderer.call(sub_field, cell, self) if renderer
|
|
409
|
+
|
|
410
|
+
raw = cell.is_a?(Hash) ? cell['value'] : cell
|
|
411
|
+
raw.to_s
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def table_cell_sample_text(val)
|
|
415
|
+
parts = []
|
|
416
|
+
parts << "Short Label: [#{val['el_label']}]" if val['el_label'].present?
|
|
417
|
+
parts << "Name: [#{val['el_name']}]" if val['el_name'].present?
|
|
418
|
+
parts << "Ext. Label: [#{val['el_external_label']}]" if val['el_external_label'].present?
|
|
419
|
+
parts << "Mass: [#{val['el_molecular_weight']}]" if val['el_molecular_weight'].present?
|
|
420
|
+
parts << "#{sample_url}/#{val['el_id']}" if val['el_id'].present?
|
|
421
|
+
parts.join("\n")
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def table_cell_molecule_text(val)
|
|
425
|
+
parts = []
|
|
426
|
+
parts << "SMILES: [#{val['el_smiles']}]" if val['el_smiles'].present?
|
|
427
|
+
parts << "InChiKey: [#{val['el_inchikey']}]" if val['el_inchikey'].present?
|
|
428
|
+
parts << "IUPAC: [#{val['el_iupac']}]" if val['el_iupac'].present?
|
|
429
|
+
parts << "MASS: [#{val['el_molecular_weight']}]" if val['el_molecular_weight'].present?
|
|
430
|
+
parts.join("\n")
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def table_cell_sysdef_text(sub_field, cell)
|
|
434
|
+
fdef = Labimotion::Units::FIELDS.find { |o| o[:field] == sub_field['option_layers'] }
|
|
435
|
+
unit = fdef&.fetch(:units, [])&.find { |u| u[:key] == cell['value_system'] }&.fetch(:label, '')
|
|
436
|
+
"#{cell['value']} #{unit}".strip
|
|
437
|
+
end
|
|
438
|
+
|
|
213
439
|
def build_fields(layer)
|
|
214
440
|
fields = layer[Labimotion::Prop::FIELDS] || []
|
|
215
441
|
field_objs = []
|
data/lib/labimotion/version.rb
CHANGED
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.2.0.
|
|
4
|
+
version: 2.2.0.rc9
|
|
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-
|
|
12
|
+
date: 2026-06-19 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: caxlsx
|