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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/lib/labimotion/apis/exporter_api.rb +271 -0
  3. data/lib/labimotion/apis/generic_dataset_api.rb +37 -3
  4. data/lib/labimotion/apis/generic_element_api.rb +106 -4
  5. data/lib/labimotion/apis/generic_klass_api.rb +42 -2
  6. data/lib/labimotion/apis/labimotion_api.rb +1 -0
  7. data/lib/labimotion/apis/segment_api.rb +5 -3
  8. data/lib/labimotion/entities/application_entity.rb +7 -80
  9. data/lib/labimotion/entities/dataset_entity.rb +8 -3
  10. data/lib/labimotion/entities/dataset_klass_entity.rb +3 -2
  11. data/lib/labimotion/entities/element_entity.rb +1 -1
  12. data/lib/labimotion/entities/element_klass_entity.rb +2 -2
  13. data/lib/labimotion/entities/element_revision_entity.rb +3 -1
  14. data/lib/labimotion/entities/eln_element_entity.rb +4 -2
  15. data/lib/labimotion/entities/generic_klass_entity.rb +8 -9
  16. data/lib/labimotion/entities/generic_public_entity.rb +5 -3
  17. data/lib/labimotion/entities/klass_revision_entity.rb +2 -1
  18. data/lib/labimotion/entities/properties_entity.rb +5 -0
  19. data/lib/labimotion/entities/segment_entity.rb +5 -2
  20. data/lib/labimotion/entities/segment_revision_entity.rb +4 -2
  21. data/lib/labimotion/entities/vocabulary_entity.rb +2 -2
  22. data/lib/labimotion/helpers/converter_helpers.rb +16 -3
  23. data/lib/labimotion/helpers/dataset_helpers.rb +31 -6
  24. data/lib/labimotion/helpers/element_helpers.rb +7 -4
  25. data/lib/labimotion/helpers/exporter_helpers.rb +139 -0
  26. data/lib/labimotion/helpers/param_helpers.rb +6 -0
  27. data/lib/labimotion/helpers/segment_helpers.rb +2 -2
  28. data/lib/labimotion/libs/converter.rb +14 -0
  29. data/lib/labimotion/libs/data/layer/StdDataset.json +212 -0
  30. data/lib/labimotion/libs/data/mapper/Chemwiki.json +2 -2
  31. data/lib/labimotion/libs/dataset_builder.rb +2 -1
  32. data/lib/labimotion/libs/export_element.rb +11 -2
  33. data/lib/labimotion/libs/nmr_mapper.rb +13 -0
  34. data/lib/labimotion/libs/properties_handler.rb +12 -2
  35. data/lib/labimotion/libs/sample_association.rb +7 -4
  36. data/lib/labimotion/libs/template_matcher.rb +39 -0
  37. data/lib/labimotion/libs/vocabulary_handler.rb +8 -6
  38. data/lib/labimotion/libs/xlsx_exporter.rb +285 -0
  39. data/lib/labimotion/models/concerns/datasetable.rb +3 -3
  40. data/lib/labimotion/models/concerns/element_fetchable.rb +53 -0
  41. data/lib/labimotion/models/concerns/generic_klass.rb +16 -0
  42. data/lib/labimotion/models/concerns/segmentable.rb +44 -7
  43. data/lib/labimotion/models/dataset_klass.rb +30 -2
  44. data/lib/labimotion/models/device_description.rb +36 -0
  45. data/lib/labimotion/models/element.rb +37 -1
  46. data/lib/labimotion/models/element_klass.rb +12 -1
  47. data/lib/labimotion/models/reaction.rb +36 -0
  48. data/lib/labimotion/models/research_plan.rb +40 -0
  49. data/lib/labimotion/models/sample.rb +36 -0
  50. data/lib/labimotion/models/screen.rb +36 -0
  51. data/lib/labimotion/models/segment_klass.rb +12 -4
  52. data/lib/labimotion/models/wellplate.rb +40 -0
  53. data/lib/labimotion/utils/serializer.rb +2 -0
  54. data/lib/labimotion/utils/units.rb +609 -468
  55. data/lib/labimotion/utils/utils.rb +42 -0
  56. data/lib/labimotion/version.rb +1 -1
  57. data/lib/labimotion.rb +12 -0
  58. metadata +45 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c1fa1527f921643836476d930d66fedc973e06e2a6300d9e100e2852fc96168
4
- data.tar.gz: 4ccc2fe165c170aa98fba5db9d63c576ed997e9650e54da439b1b295da65c816
3
+ metadata.gz: b62609cc21d0cca64e84a149f6d81af0656cc3e95d5086e75f0f3b6baca27472
4
+ data.tar.gz: be1fa743e3c305cc25130deed4fe4f5bbe567fa0b07e93a9429412c981c08c64
5
5
  SHA512:
6
- metadata.gz: 39915628df304676e86ad4e601d6776e994eb5007cc2f857c48b64498c24dba596d3ae1a46136b1d9c909dc3620724c10f0605682ba34224430c5405324d9076
7
- data.tar.gz: fae1a472384cd8ba2c7e93c647399add301ed35e4404038382f451c6b2f2c7644331004c67cba91e76872e844b334fb8041e4a1dbf682dc6644010d9fd7630fe
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 = URI.escape(el_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&.sort_by(&:created_at).reverse, with: Labimotion::SegmentRevisionEntity, root: 'revisions'
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
- @klz = fetch_klass(params[:klass], params[:id])
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
- @klz = fetch_klass(params[:klass], params[:id])
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 = URI.escape("LabIMotion_#{params[:klass]}_#{entity.label}-#{Time.new.strftime("%Y%m%d%H%M%S")}.json")
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
@@ -2,6 +2,7 @@
2
2
  module Labimotion
3
3
  class LabimotionAPI < Grape::API
4
4
  mount Labimotion::ConverterAPI
5
+ mount Labimotion::ExporterAPI
5
6
  mount Labimotion::GenericKlassAPI
6
7
  mount Labimotion::GenericElementAPI
7
8
  mount Labimotion::GenericDatasetAPI
@@ -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