Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: element vessel api and UI #2235

Draft
wants to merge 24 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e11e794
feat: add vessel api endpoints and entities
ashwiniHerle Oct 21, 2024
5a2366e
feat: add update endpoint for vessels
ashwiniHerle Oct 23, 2024
48989ab
feat: ui changes for adding vessel element
ashwiniHerle Oct 25, 2024
e4d9d63
add vesselsFetcher and Vessel model
ashwiniHerle Oct 25, 2024
83e2748
add ui components for vessels
ashwiniHerle Oct 25, 2024
e91777b
added vessel fontawesome icon and code cleanup
ashwiniHerle Nov 6, 2024
33066e4
feat: add updateVessel call to fetcher
ashwiniHerle Nov 6, 2024
4947e83
feat: added vessel element to elementsStore
ashwiniHerle Nov 8, 2024
51fce91
feat: add second level filter to fetch vessel layout
ashwiniHerle Nov 8, 2024
f140e89
revert bootstrap changes that caused createButton dependency with gen…
ashwiniHerle Nov 14, 2024
3cac5c0
feat: add new condition to vessel api
ashwiniHerle Nov 19, 2024
fc372d8
fix: fixing inconsistencies
ashwiniHerle Nov 20, 2024
c414dcb
feat: add vesselUtilities to extract params
ashwiniHerle Dec 2, 2024
6f7237f
refactor update api and add vesselTemplateId
ashwiniHerle Dec 2, 2024
92f4728
feat: add functionalcomponent to handle attachment
ashwiniHerle Dec 4, 2024
6a980f4
replace attachmentsContainer with VesselAttachmentsContainer
ashwiniHerle Jan 14, 2025
97f1b2b
feat: upload and persist attachments
ashwiniHerle Jan 21, 2025
9c762d6
test: add tests and code cleanup
ashwiniHerle Jan 21, 2025
dbc847d
api: added distinct response to vessel names
ashwiniHerle Jan 22, 2025
8c8d268
feat: display vessels in groups
ashwiniHerle Jan 22, 2025
47656a0
fix: autocomplete vessel properties
ashwiniHerle Jan 22, 2025
d004359
feat: display images along with vesselEntry
ashwiniHerle Jan 22, 2025
b8de641
chore: code cleanup
ashwiniHerle Jan 23, 2025
feafc7c
feat: create multiple vessel instances at once
ashwiniHerle Jan 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion app/api/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def to_json_camel_case(val)

TARGET = Rails.env.production? ? 'https://www.chemotion-repository.net/' : 'http://localhost:3000/'

ELEMENTS = %w[research_plan screen wellplate reaction sample cell_line].freeze
ELEMENTS = %w[research_plan screen wellplate reaction sample cell_line vessel].freeze

ELEMENT_CLASS = {
'research_plan' => ResearchPlan,
Expand All @@ -147,6 +147,7 @@ def to_json_camel_case(val)
'reaction' => Reaction,
'sample' => Sample,
'cell_line' => CelllineSample,
'vessel' => Vessel,
}.freeze

mount Chemotion::LiteratureAPI
Expand Down Expand Up @@ -203,6 +204,7 @@ def to_json_camel_case(val)
mount Chemotion::AdminDeviceAPI
mount Chemotion::AdminDeviceMetadataAPI
mount Chemotion::ChemicalAPI
mount Chemotion::VesselAPI

if Rails.env.development?
add_swagger_documentation(info: {
Expand Down
10 changes: 10 additions & 0 deletions app/api/chemotion/collection_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ class CollectionAPI < Grape::API
optional :cell_line, type: Hash do
use :ui_state_params
end
optional :vessel, type: Hash do
use :ui_state_params
end
end
requires :collection_attributes, type: Hash do
requires :permission_level, type: Integer
Expand Down Expand Up @@ -221,6 +224,9 @@ class CollectionAPI < Grape::API
cell_lines = CelllineSample.by_collection_id(@cid)
.by_ui_state(params[:elements_filter][:cell_line])
.for_user_n_groups(user_ids)
vessels = Vessel.by_collection_id(@cid)
.by_ui_state(params[:elements_filter][:vessel])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/MultilineMethodCallIndentation: Align .by_ui_state with .by_collection_id on line 227.

.for_user_n_groups(user_ids)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/MultilineMethodCallIndentation: Align .for_user_n_groups with .by_collection_id on line 227.

elements = {}
Labimotion::ElementKlass.find_each do |klass|
elements[klass.name] = Labimotion::Element.by_collection_id(@cid).by_ui_state(params[:elements_filter][klass.name]).for_user_n_groups(user_ids)
Expand All @@ -237,6 +243,7 @@ class CollectionAPI < Grape::API
share_screens = ElementsPolicy.new(current_user, screens).share?
share_research_plans = ElementsPolicy.new(current_user, research_plans).share?
share_cell_lines = ElementsPolicy.new(current_user, cell_lines).share?
share_vessels = ElementsPolicy.new(current_user, vessels).share?
share_elements = !(elements&.length > 0)
elements.each do |k, v|
share_elements = ElementsPolicy.new(current_user, v).share?
Expand All @@ -249,6 +256,7 @@ class CollectionAPI < Grape::API
share_screens &&
share_research_plans &&
share_cell_lines &&
share_vessels &&
share_elements
error!('401 Unauthorized', 401) if (!sharing_allowed || is_top_secret)

Expand All @@ -258,6 +266,7 @@ class CollectionAPI < Grape::API
@screen_ids = screens.pluck(:id)
@research_plan_ids = research_plans.pluck(:id)
@cell_line_ids = cell_lines.pluck(:id)
@vessel_ids = vessels.pluck(:id)
@element_ids = elements&.transform_values { |v| v && v.pluck(:id) }
end

Expand All @@ -280,6 +289,7 @@ class CollectionAPI < Grape::API
screen_ids: @screen_ids,
research_plan_ids: @research_plan_ids,
cell_line_ids: @cell_line_ids,
vessel_ids: @vessel_ids,
element_ids: @element_ids,
collection_attributes: params[:collection_attributes].merge(shared_by_id: current_user.id)
).execute!
Expand Down
5 changes: 4 additions & 1 deletion app/api/chemotion/element_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ class ElementAPI < Grape::API
optional :cell_line, type: Hash do
use :ui_state_params
end
optional :vessel, type: Hash do
use :ui_state_params
end
optional :selecteds, desc: 'Elements currently opened in detail tabs', type: Array do
optional :type, type: String
optional :id, type: Integer
Expand Down Expand Up @@ -77,7 +80,7 @@ class ElementAPI < Grape::API
desc "delete element from ui state selection."
delete do
deleted = { 'sample' => [] }
%w[sample reaction wellplate screen research_plan cell_line].each do |element|
%w[sample reaction wellplate screen research_plan cell_line vessel].each do |element|
next unless params[element]
next unless params[element][:checkedAll] || params[element][:checkedIds].present?

Expand Down
7 changes: 7 additions & 0 deletions app/api/chemotion/profile_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ class ProfileAPI < Grape::API
data[ll.to_s] = layout[ll] if layout[ll].present? && data[ll.to_s].nil?
end

if (element_list = layout.dig(:layout))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/ClassLength: Class has too many lines. [218/200]

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/SingleArgumentDig: Use layout[:layout] instead of layout.dig(:layout).

element_list.each do |element, sorting|
data['layout'][element.to_s] = sorting if data['layout'][element.to_s].nil?
end
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/EndAlignment: end at 37, 7 is not aligned with if at 33, 8.


if current_user.matrix_check_by_name('genericElement')
available_elements = Labimotion::ElementKlass.where(is_active: true).pluck(:name)
new_layout = data['layout'] || {}
Expand Down Expand Up @@ -134,6 +140,7 @@ class ProfileAPI < Grape::API
'screen' => 4,
'research_plan' => 5,
'cell_line' => -1000,
'vessel' => -1100,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/ClassLength: Class has too many lines. [213/200]

} if data['layout'].nil?

layout = data['layout'].select { |e| available_ements.include?(e) }
Expand Down
281 changes: 281 additions & 0 deletions app/api/chemotion/vessel_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
# frozen_string_literal: true

module Chemotion
class VesselAPI < Grape::API
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/ClassLength: Class has too many lines. [221/200]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/ClassLength: Class has too many lines. [238/200]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/ClassLength: Class has too many lines. [248/200]

include Grape::Kaminari
helpers ParamsHelpers
helpers ContainerHelpers

rescue_from ActiveRecord::RecordNotFound do
error!('Ressource not found', 401)
end
resource :vessels do
desc 'return vessels of a collection'
params do
optional :collection_id, type: Integer, desc: 'Collection id'
optional :sync_collection_id, type: Integer, desc: 'SyncCollectionsUser id'
optional :filter_created_at, type: Boolean, desc: 'filter by created at or updated at'
optional :from_date, type: Integer, desc: 'created_date from in ms'
optional :to_date, type: Integer, desc: 'created_date to in ms'
end
paginate per_page: 5, offset: 0
before do
params[:per_page].to_i > 50 && (params[:per_page] = 50)
end
get do
scope = if params[:collection_id]
begin
Collection.belongs_to_or_shared_by(current_user.id, current_user.group_ids)
.find(params[:collection_id]).vessels
rescue ActiveRecord::RecordNotFound
Vessel.none
end
elsif params[:sync_collection_id]
begin
current_user.all_sync_in_collections_users
.find(params[:sync_collection_id])
.collection
.vessels
rescue ActiveRecord::RecordNotFound
Vessel.none
end
else
# All collection of current_user
Vessel.none.joins(:collections).where(collections: { user_id: current_user.id }).distinct
end.order('created_at DESC')

from = params[:from_date]
to = params[:to_date]
by_created_at = params[:filter_created_at] || false

scope = scope.created_time_from(Time.zone.at(from)) if from && by_created_at
scope = scope.created_time_to(Time.zone.at(to) + 1.day) if to && by_created_at
scope = scope.updated_time_from(Time.zone.at(from)) if from && !by_created_at
scope = scope.updated_time_to(Time.zone.at(to) + 1.day) if to && !by_created_at

reset_pagination_page(scope)

vessels = paginate(scope).map do |vessel|
Entities::VesselInstanceEntity.represent(
vessel,
displayed_in_list: true,
# detail_levels: ElementDetailLevelCalculator.new(user: current_user, element: cell_line_sample).detail_levels,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/LineLength: Line is too long. [124/120]

)
end
{ vessels: vessels }
end

desc 'Get a vessel by id'
params do
requires :id, type: String, desc: 'id of vessel instance to load'
end
get ':id' do
if params[:id] == 'new'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/IfUnlessModifier: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.

return present Vessel.new, with: Entities::VesselInstanceEntity
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/EmptyLineAfterGuardClause: Add empty line after guard clause.

begin
vessel = Vessel.find(params[:id])
rescue ActiveRecord::RecordNotFound
error!('404 Not Found', 404)
rescue StandardError => e
error!(e, 400)
end
return present vessel, with: Entities::VesselInstanceEntity
end

desc 'Create a new Vessel sample'
params do
requires :template_name, type: String, desc: 'name of a vessel template'
optional :vessel_name, type: String, desc: 'name of a vessel sample (for single instance)'
optional :material_details, type: String, desc: 'details of the vessel template'
optional :details, type: String, desc: 'additional details'
optional :material_type, type: String, desc: 'vessel material type of the vessel'
optional :vessel_type, type: String, desc: 'type of the vessel'
optional :volume_amount, type: Float, desc: 'volume amount'
optional :volume_unit, type: String, desc: 'volume unit'
optional :weight_amount, type: Float, desc: 'weight of the vessel'
optional :weight_unit, type: String, desc: 'weight unit of the vessel'
optional :bar_code, type: String, desc: 'bar code of the vessel (for single instance)'
optional :qr_code, type: String, desc: 'qr code of the vessel (for single instance)'
optional :description, type: String, desc: 'description of a vessel sample (for single instance)'
optional :short_label, type: String, desc: 'short label of a vessel sample'
requires :collection_id, type: Integer, desc: 'collection of the vessel sample'
optional :instances, type: Array, desc: 'array of vessel instances', documentation: { is_array: true } do
requires :vessel_name, type: String, desc: 'name of the vessel instance'
optional :description, type: String, desc: 'description of the vessel instance'
optional :bar_code, type: String, desc: 'bar code of the vessel instance'
optional :qr_code, type: String, desc: 'qr code of the vessel instance'
end
requires :container, type: Hash, desc: 'root container of element'
end
post do
error!('401 Unauthorized', 401) unless current_user.collections.find(params[:collection_id])
vessel_template = VesselTemplate.find_or_create_by!(
name: params[:template_name],
details: params[:details],
material_details: params[:material_details],
material_type: params[:material_type],
vessel_type: params[:vessel_type],
volume_amount: params[:volume_amount],
volume_unit: params[:volume_unit],
weight_amount: params[:weight_amount],
weight_unit: params[:weight_unit]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/TrailingCommaInArguments: Put a comma after the last parameter of a multiline method call.

)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/TrailingWhitespace: Trailing whitespace detected.

created_vessels = []

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/TrailingWhitespace: Trailing whitespace detected.

ActiveRecord::Base.transaction do
(params[:instances] || []).each do |instance_params|
vessel = Vessel.create!(
vessel_template: vessel_template,
name: instance_params[:vessel_name],
user_id: current_user.id,
description: instance_params[:description],
bar_code: instance_params[:bar_code],
qr_code: instance_params[:qr_code],
short_label: params[:short_label]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/TrailingCommaInArguments: Put a comma after the last parameter of a multiline method call.

)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/TrailingWhitespace: Trailing whitespace detected.

if params[:collection_id]
collection = current_user.collections.find_by(id: params[:collection_id])
vessel.collections << collection if collection.present?
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/TrailingWhitespace: Trailing whitespace detected.

if params[:container]
begin
vessel.container = update_datamodel(params[:container].deep_dup)
rescue StandardError => e
Rails.logger.error "Error updating container: #{e.message}"
error!("Container update failed: #{e.message}", 400)
end
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/TrailingWhitespace: Trailing whitespace detected.

created_vessels << vessel
end
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/TrailingWhitespace: Trailing whitespace detected.

present created_vessels, with: Entities::VesselInstanceEntity
end

desc 'Update a vessel instance or associated vessel template'
params do
requires :vessel_id, type: String, desc: 'id of the vessel to update'
optional :vessel_template_id, type: String, desc: 'ID of the vessel template to update'
optional :template_name, type: String, desc: 'name of a vessel template'
optional :vessel_name, type: String, desc: 'name of a vessel sample'
optional :material_details, type: String, desc: 'details of the vessel template'
optional :details, type: String, desc: 'additional details'
optional :material_type, type: String, desc: 'vessel material type of the vessel'
optional :vessel_type, type: String, desc: 'type of the vessel'
optional :volume_amount, type: Float, desc: 'volume amount'
optional :volume_unit, type: String, desc: 'volume unit'
optional :weight_amount, type: Float, desc: 'weight of the vessel'
optional :weight_unit, type: String, desc: 'weight unit of the vessel'
optional :bar_code, type: String, desc: 'bar code of the vessel'
optional :qr_code, type: String, desc: 'qr code of the vessel'
optional :collection_id, type: Integer, desc: 'collection of the vessel sample'
optional :description, type: String, desc: 'description of a vessel sample'
optional :short_label, type: String, desc: 'short label of a vessel sample'
requires :container, type: Hash, desc: 'root Container of element'
end
put do
ActiveRecord::Base.transaction do
vessel = Vessel.find_by(id: params[:vessel_id])
error!('Vessel not found', 404) unless vessel

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/TrailingWhitespace: Trailing whitespace detected.

vessel_params = {
name: params[:vessel_name],
description: params[:description],
bar_code: params[:bar_code],
qr_code: params[:qr_code],
short_label: params[:short_label],
}.compact
vessel.update!(vessel_params) if vessel_params.present?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/TrailingWhitespace: Trailing whitespace detected.

if params[:vessel_template_id]
vessel_template = VesselTemplate.find_by(id: params[:vessel_template_id])
error!('Vessel template not found', 404) unless vessel_template

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/TrailingWhitespace: Trailing whitespace detected.

unless vessel.vessel_template_id == vessel_template.id
error!("Vessel does not belong to the specified template", 400)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.

end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/TrailingWhitespace: Trailing whitespace detected.

template_params = {
name: params[:template_name],
details: params[:details],
material_details: params[:material_details],
material_type: params[:material_type],
vessel_type: params[:vessel_type],
volume_amount: params[:volume_amount],
volume_unit: params[:volume_unit],
weight_amount: params[:weight_amount],
weight_unit: params[:weight_unit],
}.compact
vessel_template.update!(template_params) if template_params.present?
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/TrailingWhitespace: Trailing whitespace detected.

if params[:container]
begin
vessel.container = update_datamodel(params[:container])
rescue StandardError => e
Rails.logger.error "Error updating container: #{e.message}"
error!("Container update failed: #{e.message}", 400)
end
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/TrailingWhitespace: Trailing whitespace detected.

present vessel, with: Entities::VesselInstanceEntity
end
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/TrailingWhitespace: Trailing whitespace detected.


Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/TrailingWhitespace: Trailing whitespace detected.


resource :names do
desc 'Returns all accessible vessel templates material names and their id'
get 'all' do
vessel_templates = VesselTemplate.select(:id, :name, :vessel_type, :material_type, :volume_amount, :volume_unit).distinct

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/LineLength: Line is too long. [131/120]

present vessel_templates, with: Entities::VesselTemplateEntity
end
end
resource :material do
params do
requires :id, type: String, desc: 'id of vessel template to load'
end
get ':id' do
return VesselTemplate.find(params[:id])
end
end

desc 'Delete a Vessel'
params do
requires :id, type: String, desc: 'ID of the vessel instance to delete'
end
delete ':id' do
vessel = Vessel.find_by(id: params[:id])
if vessel.nil?
error!('404 Vessel Not Found', 404)
else
vessel.destroy!
status 200
{ message: 'Vessel successfully deleted', vessel_id: params[:id] }
end
end

desc 'Delete a Vessel Template'
params do
requires :id, type: String, desc: 'ID of the vessel template to delete'
end
delete 'vessel_template/:id' do
vessel_template = VesselTemplate.find_by(id: params[:id])
if vessel_template.nil?
error!('404 VesselTemplate Not Found', 404)
elsif vessel_template.vessels.exists?
error!('400 Cannot delete VesselTemplate. It is associated with one or more vessels.', 400)
else
vessel_template.destroy!
status 200
{ message: 'VesselTemplate successfully deleted', vessel_template_id: params[:id] }
end
end
end
end
end
Loading
Loading