labimotion 2.0.0 → 2.1.0.rc11
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/lib/labimotion/apis/exporter_api.rb +271 -0
- data/lib/labimotion/apis/generic_dataset_api.rb +37 -3
- data/lib/labimotion/apis/generic_element_api.rb +106 -4
- data/lib/labimotion/apis/generic_klass_api.rb +42 -2
- data/lib/labimotion/apis/labimotion_api.rb +1 -0
- data/lib/labimotion/apis/segment_api.rb +5 -3
- data/lib/labimotion/entities/application_entity.rb +7 -80
- data/lib/labimotion/entities/dataset_entity.rb +8 -3
- data/lib/labimotion/entities/dataset_klass_entity.rb +3 -2
- data/lib/labimotion/entities/element_entity.rb +1 -1
- data/lib/labimotion/entities/element_klass_entity.rb +2 -2
- data/lib/labimotion/entities/element_revision_entity.rb +3 -1
- data/lib/labimotion/entities/eln_element_entity.rb +4 -2
- data/lib/labimotion/entities/generic_klass_entity.rb +8 -9
- data/lib/labimotion/entities/generic_public_entity.rb +5 -3
- data/lib/labimotion/entities/klass_revision_entity.rb +2 -1
- data/lib/labimotion/entities/properties_entity.rb +5 -0
- data/lib/labimotion/entities/segment_entity.rb +5 -2
- data/lib/labimotion/entities/segment_revision_entity.rb +4 -2
- data/lib/labimotion/entities/vocabulary_entity.rb +2 -2
- data/lib/labimotion/helpers/converter_helpers.rb +16 -3
- data/lib/labimotion/helpers/dataset_helpers.rb +31 -6
- data/lib/labimotion/helpers/element_helpers.rb +7 -4
- data/lib/labimotion/helpers/exporter_helpers.rb +139 -0
- data/lib/labimotion/helpers/param_helpers.rb +6 -0
- data/lib/labimotion/helpers/segment_helpers.rb +2 -2
- data/lib/labimotion/libs/converter.rb +14 -0
- data/lib/labimotion/libs/data/layer/StdDataset.json +212 -0
- data/lib/labimotion/libs/data/mapper/Chemwiki.json +2 -2
- data/lib/labimotion/libs/dataset_builder.rb +2 -1
- data/lib/labimotion/libs/export_element.rb +11 -2
- data/lib/labimotion/libs/nmr_mapper.rb +13 -0
- data/lib/labimotion/libs/properties_handler.rb +12 -2
- data/lib/labimotion/libs/sample_association.rb +7 -4
- data/lib/labimotion/libs/template_matcher.rb +39 -0
- data/lib/labimotion/libs/vocabulary_handler.rb +8 -6
- data/lib/labimotion/libs/xlsx_exporter.rb +285 -0
- data/lib/labimotion/models/concerns/datasetable.rb +3 -3
- data/lib/labimotion/models/concerns/element_fetchable.rb +53 -0
- data/lib/labimotion/models/concerns/generic_klass.rb +16 -0
- data/lib/labimotion/models/concerns/segmentable.rb +44 -7
- data/lib/labimotion/models/dataset_klass.rb +30 -2
- data/lib/labimotion/models/device_description.rb +36 -0
- data/lib/labimotion/models/element.rb +37 -1
- data/lib/labimotion/models/element_klass.rb +12 -1
- data/lib/labimotion/models/reaction.rb +36 -0
- data/lib/labimotion/models/research_plan.rb +40 -0
- data/lib/labimotion/models/sample.rb +36 -0
- data/lib/labimotion/models/screen.rb +36 -0
- data/lib/labimotion/models/segment_klass.rb +12 -4
- data/lib/labimotion/models/wellplate.rb +40 -0
- data/lib/labimotion/utils/serializer.rb +2 -0
- data/lib/labimotion/utils/units.rb +609 -468
- data/lib/labimotion/utils/utils.rb +42 -0
- data/lib/labimotion/version.rb +1 -1
- data/lib/labimotion.rb +12 -0
- metadata +45 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b62609cc21d0cca64e84a149f6d81af0656cc3e95d5086e75f0f3b6baca27472
|
|
4
|
+
data.tar.gz: be1fa743e3c305cc25130deed4fe4f5bbe567fa0b07e93a9429412c981c08c64
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b5dafb066cf6137a934e2dfb67d532c7edbf84c4dd46f076d4f9055d4d23ae61cb0568d65a99c3ee34ded0f658a4daf9ec1cffad8cffa9f25b089774f5a3c424
|
|
7
|
+
data.tar.gz: d085a36adef5d13a3dbfa97c4979c83afea948c17bff059c9050b88043ab0b927b9c9dea693e7057185669e632baba2be392739f535b97459e8d91626d909507
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Labimotion
|
|
4
|
+
# ExporterAPI handles exporting data to various formats (e.g., XLSX)
|
|
5
|
+
# The class length is justified due to the comprehensive helper methods needed for export functionality
|
|
6
|
+
class ExporterAPI < Grape::API
|
|
7
|
+
include Grape::Kaminari
|
|
8
|
+
|
|
9
|
+
helpers Labimotion::ParamHelpers
|
|
10
|
+
helpers Labimotion::ExporterHelpers
|
|
11
|
+
|
|
12
|
+
resource :exporter do
|
|
13
|
+
resource :table_xlsx do
|
|
14
|
+
desc 'Export single table field to XLSX format'
|
|
15
|
+
params do
|
|
16
|
+
use :table_xlsx_params
|
|
17
|
+
end
|
|
18
|
+
route_param :id do
|
|
19
|
+
before do
|
|
20
|
+
validate_and_authorize_request!
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
get do
|
|
24
|
+
layer = fetch_layer
|
|
25
|
+
field = fetch_field(layer)
|
|
26
|
+
validate_table_field!(field)
|
|
27
|
+
|
|
28
|
+
headers, expanded_sub_fields = build_table_headers(field)
|
|
29
|
+
rows = build_table_rows(field, expanded_sub_fields)
|
|
30
|
+
|
|
31
|
+
export_to_xlsx(layer, field, headers, rows)
|
|
32
|
+
rescue StandardError => e
|
|
33
|
+
Labimotion.log_exception(e, current_user)
|
|
34
|
+
error!("500 Internal Server Error: #{e.message}", 500)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# rubocop:disable Metrics/BlockLength
|
|
41
|
+
# The helpers block is necessarily large due to the many specialized helper methods
|
|
42
|
+
# for data fetching, validation, formatting, and export functionality
|
|
43
|
+
helpers do
|
|
44
|
+
# Validate and authorize the request, sets @element instance variable
|
|
45
|
+
def validate_and_authorize_request!
|
|
46
|
+
klass = fetch_klass
|
|
47
|
+
@element = klass.find_by(id: params[:id])
|
|
48
|
+
|
|
49
|
+
error!('404 Not Found', 404) if @element.nil?
|
|
50
|
+
|
|
51
|
+
authorize_element_access!
|
|
52
|
+
rescue ActiveRecord::RecordNotFound
|
|
53
|
+
error!('404 Not Found', 404)
|
|
54
|
+
rescue NameError => e
|
|
55
|
+
Labimotion.log_exception(e, current_user)
|
|
56
|
+
error!('400 Bad Request - Invalid type', 400)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Fetch the element or segment class based on params
|
|
60
|
+
def fetch_klass
|
|
61
|
+
Labimotion::Utils.resolve_class(params[:klass])
|
|
62
|
+
end
|
|
63
|
+
|
|
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
|
+
# Fetch layer from element properties
|
|
81
|
+
def fetch_layer
|
|
82
|
+
properties = @element.properties
|
|
83
|
+
layers = properties[Labimotion::Prop::LAYERS] || {}
|
|
84
|
+
layer_key = params[:layer_id].to_s
|
|
85
|
+
layer = layers[layer_key]
|
|
86
|
+
|
|
87
|
+
error!('404 Layer Not Found', 404) if layer.nil?
|
|
88
|
+
layer
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Fetch field from layer
|
|
92
|
+
def fetch_field(layer)
|
|
93
|
+
fields = layer[Labimotion::Prop::FIELDS] || []
|
|
94
|
+
field = fields.find { |f| f['field'] == params[:field_id].to_s }
|
|
95
|
+
|
|
96
|
+
error!('404 Field Not Found', 404) if field.nil?
|
|
97
|
+
field
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Validate that the field is a table type
|
|
101
|
+
def validate_table_field!(field)
|
|
102
|
+
return if field['type'] == Labimotion::FieldType::TABLE
|
|
103
|
+
|
|
104
|
+
error!('400 Bad Request - Field is not a table type', 400)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Build table headers from field definition, expands DRAG_SAMPLE and DRAG_MOLECULE columns
|
|
108
|
+
def build_table_headers(field)
|
|
109
|
+
sub_fields = field.fetch('sub_fields', [])
|
|
110
|
+
headers = []
|
|
111
|
+
expanded_sub_fields = []
|
|
112
|
+
|
|
113
|
+
sub_fields.each do |sf|
|
|
114
|
+
base_col_name = normalize_column_name(sf['col_name'])
|
|
115
|
+
|
|
116
|
+
if expandable_field?(sf['type'])
|
|
117
|
+
headers, expanded_sub_fields = expand_field_columns(sf, base_col_name, headers, expanded_sub_fields)
|
|
118
|
+
else
|
|
119
|
+
headers << base_col_name
|
|
120
|
+
expanded_sub_fields << sf
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
[headers, expanded_sub_fields]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Expand field columns for DRAG_SAMPLE and DRAG_MOLECULE with sub-headers
|
|
128
|
+
def expand_field_columns(sub_field, base_col_name, headers, expanded_sub_fields)
|
|
129
|
+
sub_headers = sub_field['value'].to_s.split(';').reject(&:empty?)
|
|
130
|
+
|
|
131
|
+
# Always add the base column first (for SVG image link)
|
|
132
|
+
headers << base_col_name
|
|
133
|
+
expanded_sub_fields << sub_field
|
|
134
|
+
|
|
135
|
+
# For DRAG_SAMPLE, add base columns for smiles and short_label
|
|
136
|
+
if sub_field['type'] == Labimotion::FieldType::DRAG_SAMPLE
|
|
137
|
+
%w[smiles short_label].each do |base_header|
|
|
138
|
+
headers << base_header
|
|
139
|
+
expanded_sub_fields << {
|
|
140
|
+
'id' => sub_field['id'],
|
|
141
|
+
'type' => sub_field['type'],
|
|
142
|
+
'sub_header' => base_header,
|
|
143
|
+
'original' => sub_field,
|
|
144
|
+
'is_base_column' => true
|
|
145
|
+
}
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Add extra columns for each sub-header
|
|
150
|
+
sub_headers.each do |sub_header|
|
|
151
|
+
headers << sub_header
|
|
152
|
+
expanded_sub_fields << {
|
|
153
|
+
'id' => sub_field['id'],
|
|
154
|
+
'type' => sub_field['type'],
|
|
155
|
+
'sub_header' => sub_header,
|
|
156
|
+
'original' => sub_field
|
|
157
|
+
}
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
[headers, expanded_sub_fields]
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Build table rows from field data
|
|
164
|
+
def build_table_rows(field, expanded_sub_fields)
|
|
165
|
+
sub_values = field.fetch('sub_values', [])
|
|
166
|
+
|
|
167
|
+
sub_values.map do |sub_val|
|
|
168
|
+
expanded_sub_fields.map do |exp_field|
|
|
169
|
+
if exp_field.is_a?(Hash) && exp_field['sub_header']
|
|
170
|
+
format_expanded_cell(sub_val, exp_field)
|
|
171
|
+
else
|
|
172
|
+
format_table_cell(sub_val, exp_field)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Export data to XLSX format
|
|
179
|
+
def export_to_xlsx(layer, field, headers, rows)
|
|
180
|
+
element_name = @element.is_a?(Labimotion::Segment) ? @element.element.name : @element.name
|
|
181
|
+
segment_name = @element.is_a?(Labimotion::Segment) ? @element.segment_klass.label : ''
|
|
182
|
+
selected_layer = "#{layer['label'] || ExporterHelpers::CONST_UNNAMED} (#{layer['key']})"
|
|
183
|
+
selected_field = field['label'] || ExporterHelpers::CONST_UNNAMED
|
|
184
|
+
|
|
185
|
+
filename = generate_filename(element_name, segment_name, selected_layer, selected_field)
|
|
186
|
+
exporter = Labimotion::XlsxExporter.new(filename)
|
|
187
|
+
|
|
188
|
+
worksheet_params = {
|
|
189
|
+
exporter: exporter,
|
|
190
|
+
element_name: element_name,
|
|
191
|
+
layer_name: selected_layer,
|
|
192
|
+
field_name: selected_field,
|
|
193
|
+
headers: headers,
|
|
194
|
+
rows: rows
|
|
195
|
+
}
|
|
196
|
+
worksheet_params[:segment_name] = segment_name unless segment_name.empty?
|
|
197
|
+
populate_worksheet(worksheet_params)
|
|
198
|
+
|
|
199
|
+
# Set response headers and format
|
|
200
|
+
configure_xlsx_response(exporter)
|
|
201
|
+
|
|
202
|
+
exporter.read
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Build metadata array for worksheet
|
|
206
|
+
def build_metadata(params)
|
|
207
|
+
metadata = [['Element:', params[:element_name]]]
|
|
208
|
+
metadata << ['Segment:', params[:segment_name]] if params[:segment_name].present?
|
|
209
|
+
metadata.push(
|
|
210
|
+
['Layer:', params[:layer_name]],
|
|
211
|
+
['Field:', params[:field_name]],
|
|
212
|
+
['Exported at:', Time.zone.now.strftime('%Y-%m-%d %H:%M:%S')],
|
|
213
|
+
['Exported by:', current_user.name]
|
|
214
|
+
)
|
|
215
|
+
metadata
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Populate worksheet with data
|
|
219
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
220
|
+
def populate_worksheet(params)
|
|
221
|
+
# Store params in local variables for use in the instance_eval block
|
|
222
|
+
table_headers = params[:headers]
|
|
223
|
+
table_rows = params[:rows]
|
|
224
|
+
metadata = build_metadata(params)
|
|
225
|
+
|
|
226
|
+
params[:exporter].add_worksheet('Table Data') do |sheet|
|
|
227
|
+
# Add metadata section
|
|
228
|
+
sheet.add_section('Table Information', metadata)
|
|
229
|
+
|
|
230
|
+
# Add table data with headers
|
|
231
|
+
header_row_index = sheet.add_header(table_headers)
|
|
232
|
+
|
|
233
|
+
# Add data rows with hyperlink support
|
|
234
|
+
table_rows.each do |row_data|
|
|
235
|
+
# Process row data to handle hyperlinks
|
|
236
|
+
processed_row = row_data.map do |cell_value|
|
|
237
|
+
cell_value.is_a?(Hash) && cell_value[:hyperlink] ? cell_value[:text] : cell_value
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Add the row
|
|
241
|
+
row_index = sheet.add_row(processed_row)
|
|
242
|
+
|
|
243
|
+
# Add hyperlinks to cells that need them
|
|
244
|
+
row_data.each_with_index do |cell_value, col_index|
|
|
245
|
+
next unless cell_value.is_a?(Hash) && cell_value[:hyperlink]
|
|
246
|
+
|
|
247
|
+
sheet.add_hyperlink(row_index, col_index, cell_value[:hyperlink])
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Auto-fit columns and freeze panes
|
|
252
|
+
sheet.auto_fit_columns
|
|
253
|
+
sheet.freeze_panes(header_row_index + 1, 0) if table_headers.any?
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
257
|
+
|
|
258
|
+
# Set XLSX response headers with proper encoding
|
|
259
|
+
def configure_xlsx_response(exporter)
|
|
260
|
+
env['api.format'] = :binary
|
|
261
|
+
content_type 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
262
|
+
|
|
263
|
+
# Use both filename and filename* for better browser compatibility
|
|
264
|
+
filename = exporter.filename
|
|
265
|
+
encoded_filename = URI.encode_www_form_component(filename)
|
|
266
|
+
header('Content-Disposition', "attachment; filename=\"#{filename}\"; filename*=UTF-8''#{encoded_filename}")
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
# rubocop:enable Metrics/BlockLength
|
|
270
|
+
end
|
|
271
|
+
end
|
|
@@ -4,6 +4,7 @@ module Labimotion
|
|
|
4
4
|
## Generic Dataset API
|
|
5
5
|
class GenericDatasetAPI < Grape::API
|
|
6
6
|
include Grape::Kaminari
|
|
7
|
+
|
|
7
8
|
helpers Labimotion::GenericHelpers
|
|
8
9
|
helpers Labimotion::DatasetHelpers
|
|
9
10
|
|
|
@@ -11,19 +12,38 @@ module Labimotion
|
|
|
11
12
|
namespace :klasses do
|
|
12
13
|
desc 'get dataset klasses'
|
|
13
14
|
get do
|
|
14
|
-
list = klass_list(true)
|
|
15
|
+
list = klass_list(true, false)
|
|
15
16
|
present list.sort_by(&:place), with: Labimotion::DatasetKlassEntity, root: 'klass'
|
|
16
17
|
end
|
|
17
18
|
end
|
|
18
19
|
|
|
20
|
+
namespace :list_klass do
|
|
21
|
+
desc 'list Generic Dataset Klass'
|
|
22
|
+
params do
|
|
23
|
+
optional :is_active, type: Boolean, desc: 'Active or Inactive Dataset'
|
|
24
|
+
optional :displayed_in_list, type: Boolean, desc: 'Display in list format', default: true
|
|
25
|
+
end
|
|
26
|
+
get do
|
|
27
|
+
list = klass_list(params[:is_active], params[:displayed_in_list])
|
|
28
|
+
serialized_data = Labimotion::DatasetKlassEntity.represent(list,
|
|
29
|
+
displayed_in_list: params[:displayed_in_list])
|
|
30
|
+
{ mc: 'ss00', data: serialized_data }
|
|
31
|
+
rescue StandardError => e
|
|
32
|
+
Labimotion.log_exception(e, current_user)
|
|
33
|
+
{ mc: 'se00', msg: e.message, data: [] }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Deprecated: This namespace is no longer used, but kept for backward compatibility.
|
|
38
|
+
# It is replaced by `list_klass`.
|
|
19
39
|
namespace :list_dataset_klass do
|
|
20
40
|
desc 'list Generic Dataset Klass'
|
|
21
41
|
params do
|
|
22
42
|
optional :is_active, type: Boolean, desc: 'Active or Inactive Dataset'
|
|
23
43
|
end
|
|
24
44
|
get do
|
|
25
|
-
list = klass_list(params[:is_active])
|
|
26
|
-
present list, with: Labimotion::DatasetKlassEntity, root: 'klass'
|
|
45
|
+
list = klass_list(params[:is_active], false)
|
|
46
|
+
present list, with: Labimotion::DatasetKlassEntity, root: 'klass', displayed_in_list: false
|
|
27
47
|
end
|
|
28
48
|
end
|
|
29
49
|
|
|
@@ -48,6 +68,20 @@ module Labimotion
|
|
|
48
68
|
{ error: e.message }
|
|
49
69
|
end
|
|
50
70
|
end
|
|
71
|
+
|
|
72
|
+
namespace :find_template do
|
|
73
|
+
desc 'Find best matching template for given OLS term ID'
|
|
74
|
+
params do
|
|
75
|
+
requires :ols_term_id, type: String, desc: 'OLS Term ID (e.g., CHMO:0000470)'
|
|
76
|
+
end
|
|
77
|
+
get do
|
|
78
|
+
result = find_best_match_template(params[:ols_term_id])
|
|
79
|
+
result
|
|
80
|
+
rescue StandardError => e
|
|
81
|
+
Labimotion.log_exception(e, current_user)
|
|
82
|
+
raise e
|
|
83
|
+
end
|
|
84
|
+
end
|
|
51
85
|
end
|
|
52
86
|
end
|
|
53
87
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'cgi'
|
|
3
4
|
require 'labimotion/conf'
|
|
4
5
|
require 'labimotion/libs/export_element'
|
|
5
6
|
|
|
@@ -59,6 +60,65 @@ module Labimotion
|
|
|
59
60
|
end
|
|
60
61
|
end
|
|
61
62
|
|
|
63
|
+
namespace :search_by_like do
|
|
64
|
+
desc 'Search elements by name (case-insensitive like search)'
|
|
65
|
+
params do
|
|
66
|
+
requires :name, type: String, desc: 'Search query for element name'
|
|
67
|
+
requires :short_label, type: String, desc: 'Search query for element short label'
|
|
68
|
+
requires :klass_id, type: Integer, desc: 'Filter by element klass id'
|
|
69
|
+
optional :limit, type: Integer, desc: 'Maximum number of results', default: 20
|
|
70
|
+
end
|
|
71
|
+
get do
|
|
72
|
+
scope = Labimotion::Element.fetch_for_user(
|
|
73
|
+
current_user.id,
|
|
74
|
+
name: params[:name],
|
|
75
|
+
short_label: params[:short_label],
|
|
76
|
+
klass_id: params[:klass_id],
|
|
77
|
+
limit: params[:limit]
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
results = scope.map do |element|
|
|
81
|
+
Labimotion::ElementLookupEntity.represent(element)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
{ elements: results, total_count: results.count }
|
|
85
|
+
rescue StandardError => e
|
|
86
|
+
Labimotion.log_exception(e, current_user)
|
|
87
|
+
{ elements: [], total_count: 0, error: e.message }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
namespace :search_basic_by_like do
|
|
92
|
+
desc 'Search basic elements by name and short label (case-insensitive like search)'
|
|
93
|
+
params do
|
|
94
|
+
requires :klass_name, type: String, desc: 'Class name (device_description or wellplate or ...etc.)', default: 'device_description'
|
|
95
|
+
requires :name, type: String, desc: 'Search query for basic element name'
|
|
96
|
+
requires :short_label, type: String, desc: 'Search query for basic element short label'
|
|
97
|
+
optional :limit, type: Integer, desc: 'Maximum number of results', default: 20
|
|
98
|
+
end
|
|
99
|
+
get do
|
|
100
|
+
# Convert snake_case to PascalCase (e.g. device_description -> DeviceDescription)
|
|
101
|
+
klass_name = params[:klass_name].camelize
|
|
102
|
+
klass = "Labimotion::#{klass_name}".constantize
|
|
103
|
+
|
|
104
|
+
scope = klass.fetch_for_user(
|
|
105
|
+
current_user.id,
|
|
106
|
+
name: params[:name],
|
|
107
|
+
short_label: params[:short_label],
|
|
108
|
+
limit: params[:limit]
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
results = scope.map do |record|
|
|
112
|
+
Labimotion::ElementLookupEntity.represent(record)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
{ elements: results, total_count: results.count }
|
|
116
|
+
rescue StandardError => e
|
|
117
|
+
Labimotion.log_exception(e, current_user)
|
|
118
|
+
{ elements: [], total_count: 0, error: e.message }
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
62
122
|
namespace :export do
|
|
63
123
|
desc 'export element'
|
|
64
124
|
params do
|
|
@@ -79,7 +139,7 @@ module Labimotion
|
|
|
79
139
|
env['api.format'] = :binary
|
|
80
140
|
content_type 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
|
81
141
|
el_filename = export.res_name
|
|
82
|
-
filename =
|
|
142
|
+
filename = CGI.escape(el_filename)
|
|
83
143
|
# header['Content-Disposition'] = "attachment; filename=abc.docx"
|
|
84
144
|
header('Content-Disposition', "attachment; filename=\"#{filename}\"")
|
|
85
145
|
|
|
@@ -188,7 +248,7 @@ module Labimotion
|
|
|
188
248
|
get do
|
|
189
249
|
klass = Labimotion::Segment.find(params[:id])
|
|
190
250
|
list = klass.segments_revisions unless klass.nil?
|
|
191
|
-
present list&.
|
|
251
|
+
present list&.order(created_at: :desc)&.limit(10), with: Labimotion::SegmentRevisionEntity, root: 'revisions'
|
|
192
252
|
rescue StandardError => e
|
|
193
253
|
Labimotion.log_exception(e, current_user)
|
|
194
254
|
[]
|
|
@@ -221,6 +281,27 @@ module Labimotion
|
|
|
221
281
|
end
|
|
222
282
|
end
|
|
223
283
|
|
|
284
|
+
namespace :list_element_klass do
|
|
285
|
+
desc 'list Generic Element Klass'
|
|
286
|
+
params do
|
|
287
|
+
optional :is_generic, type: Boolean, desc: 'Is Generic or Non-Generic Element'
|
|
288
|
+
optional :is_active, type: Boolean, desc: 'Active or Inactive'
|
|
289
|
+
optional :displayed_in_list, type: Boolean, desc: 'Display in list format', default: true
|
|
290
|
+
end
|
|
291
|
+
get do
|
|
292
|
+
scope = params[:displayed_in_list] ? Labimotion::ElementKlass.for_list_display : Labimotion::ElementKlass.all
|
|
293
|
+
scope = scope.where(is_generic: params[:is_generic]) if params.key?(:is_generic)
|
|
294
|
+
scope = scope.where(is_active: params[:is_active]) if params.key?(:is_active)
|
|
295
|
+
|
|
296
|
+
list = scope.sort_by(&:place)
|
|
297
|
+
present list, with: Labimotion::ElementKlassEntity, root: 'klass', displayed_in_list: params[:displayed_in_list]
|
|
298
|
+
rescue StandardError => e
|
|
299
|
+
Labimotion.log_exception(e, current_user)
|
|
300
|
+
raise e
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Deprecated: This namespace is no longer used, but kept for backward compatibility.
|
|
224
305
|
namespace :klasses_all do
|
|
225
306
|
desc 'get all klasses for admin function'
|
|
226
307
|
get do
|
|
@@ -266,7 +347,7 @@ module Labimotion
|
|
|
266
347
|
end
|
|
267
348
|
after_validation do
|
|
268
349
|
authenticate_admin!(params[:klass].gsub(/(Klass)/, 's').downcase)
|
|
269
|
-
|
|
350
|
+
fetch_klass(params[:klass], params[:id])
|
|
270
351
|
end
|
|
271
352
|
post do
|
|
272
353
|
deactivate_klass(params)
|
|
@@ -301,7 +382,7 @@ module Labimotion
|
|
|
301
382
|
end
|
|
302
383
|
after_validation do
|
|
303
384
|
authenticate_admin!(params[:klass].gsub(/(Klass)/, 's').downcase)
|
|
304
|
-
|
|
385
|
+
fetch_klass(params[:klass], params[:id])
|
|
305
386
|
end
|
|
306
387
|
post do
|
|
307
388
|
update_template(params, current_user)
|
|
@@ -445,4 +526,25 @@ module Labimotion
|
|
|
445
526
|
end
|
|
446
527
|
end
|
|
447
528
|
end
|
|
529
|
+
|
|
530
|
+
# Entity for element lookup by name response
|
|
531
|
+
class ElementLookupEntity < Grape::Entity
|
|
532
|
+
expose :id
|
|
533
|
+
expose :name
|
|
534
|
+
expose :short_label do |element|
|
|
535
|
+
element.respond_to?(:short_label) ? element.short_label : nil
|
|
536
|
+
end
|
|
537
|
+
expose :element_klass_id, as: :element_klass_id do |element|
|
|
538
|
+
element.element_klass&.id
|
|
539
|
+
end
|
|
540
|
+
expose :klass_label, as: :klass_label do |element|
|
|
541
|
+
element.element_klass&.label
|
|
542
|
+
end
|
|
543
|
+
expose :klass_name, as: :klass_name do |element|
|
|
544
|
+
element.element_klass&.name
|
|
545
|
+
end
|
|
546
|
+
expose :klass_icon, as: :klass_icon do |element|
|
|
547
|
+
element.element_klass&.icon_name
|
|
548
|
+
end
|
|
549
|
+
end
|
|
448
550
|
end
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'cgi'
|
|
3
4
|
require 'labimotion/version'
|
|
4
5
|
require 'labimotion/libs/export_element'
|
|
5
6
|
|
|
6
7
|
module Labimotion
|
|
7
8
|
# Generic Element API
|
|
8
9
|
class GenericKlassAPI < Grape::API
|
|
10
|
+
helpers Labimotion::GenericHelpers
|
|
9
11
|
|
|
10
12
|
resource :generic_klass do
|
|
11
13
|
namespace :download_klass do
|
|
@@ -19,7 +21,7 @@ module Labimotion
|
|
|
19
21
|
entity.update_columns(identifier: SecureRandom.uuid) if entity&.identifier.nil?
|
|
20
22
|
env['api.format'] = :binary
|
|
21
23
|
content_type('application/json')
|
|
22
|
-
filename =
|
|
24
|
+
filename = CGI.escape("LabIMotion_#{params[:klass]}_#{entity.label}-#{Time.new.strftime("%Y%m%d%H%M%S")}.json")
|
|
23
25
|
# header['Content-Disposition'] = "attachment; filename=abc.docx"
|
|
24
26
|
header('Content-Disposition', "attachment; filename=\"#{filename}\"")
|
|
25
27
|
"Labimotion::#{params[:klass]}Entity".constantize.represent(entity)
|
|
@@ -30,6 +32,44 @@ module Labimotion
|
|
|
30
32
|
end
|
|
31
33
|
end
|
|
32
34
|
|
|
35
|
+
namespace :de_activate do
|
|
36
|
+
desc 'activate or deactivate Generic Klass'
|
|
37
|
+
params do
|
|
38
|
+
requires :klass, type: String, desc: 'Klass', values: %w[ElementKlass SegmentKlass DatasetKlass]
|
|
39
|
+
requires :id, type: Integer, desc: 'Klass ID'
|
|
40
|
+
requires :is_active, type: Boolean, desc: 'Active or Inactive Klass'
|
|
41
|
+
end
|
|
42
|
+
after_validation do
|
|
43
|
+
authenticate_admin!(params[:klass].gsub(/(Klass)/, 's').downcase)
|
|
44
|
+
fetch_klass(params[:klass], params[:id])
|
|
45
|
+
end
|
|
46
|
+
post do
|
|
47
|
+
updated_klass = deactivate_klass(params)
|
|
48
|
+
entity_class = "Labimotion::#{params[:klass]}Entity".constantize
|
|
49
|
+
serialized_data = entity_class.represent(updated_klass)
|
|
50
|
+
{ mc: 'ss00', data: serialized_data }
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
Labimotion.log_exception(e, current_user)
|
|
53
|
+
{ mc: 'se00', msg: e.message, data: {} }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
namespace :fetch do
|
|
58
|
+
desc 'fetch Generic Klass by id'
|
|
59
|
+
params do
|
|
60
|
+
requires :id, type: Integer, desc: 'Klass ID'
|
|
61
|
+
requires :klass, type: String, desc: 'Klass', values: %w[ElementKlass SegmentKlass DatasetKlass]
|
|
62
|
+
end
|
|
63
|
+
get do
|
|
64
|
+
klass_obj = fetch_klass(params[:klass], params[:id])
|
|
65
|
+
entity_class = "Labimotion::#{params[:klass]}Entity".constantize
|
|
66
|
+
serialized_data = entity_class.represent(klass_obj)
|
|
67
|
+
{ mc: 'ss00', data: serialized_data }
|
|
68
|
+
rescue StandardError => e
|
|
69
|
+
Labimotion.log_exception(e, current_user)
|
|
70
|
+
{ mc: 'se00', msg: e.message, data: {} }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
33
73
|
end
|
|
34
74
|
end
|
|
35
|
-
end
|
|
75
|
+
end
|
|
@@ -12,19 +12,21 @@ module Labimotion
|
|
|
12
12
|
optional :element, type: String, desc: "Klass Element, e.g. Sample, Reaction, Mof,..."
|
|
13
13
|
end
|
|
14
14
|
get do
|
|
15
|
-
list = klass_list(params[:element], true)
|
|
15
|
+
list = klass_list(params[:element], true, false)
|
|
16
16
|
present list, with: Labimotion::SegmentKlassEntity, root: 'klass'
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
# TODO: params[:displayed_in_list] will be used in the future to control the display format, set 'false' for now.
|
|
20
21
|
namespace :list_segment_klass do
|
|
21
22
|
desc 'list Generic Segment Klass'
|
|
22
23
|
params do
|
|
23
24
|
optional :is_active, type: Boolean, desc: 'Active or Inactive Segment'
|
|
25
|
+
optional :displayed_in_list, type: Boolean, desc: 'Display in list format', default: false
|
|
24
26
|
end
|
|
25
27
|
get do
|
|
26
|
-
list = klass_list(nil, params[:is_active])
|
|
27
|
-
present list, with: Labimotion::SegmentKlassEntity, root: 'klass'
|
|
28
|
+
list = klass_list(nil, params[:is_active], false)
|
|
29
|
+
present list, with: Labimotion::SegmentKlassEntity, root: 'klass', displayed_in_list: false
|
|
28
30
|
end
|
|
29
31
|
end
|
|
30
32
|
|