Merge pull request #7846 from artoscinote/ma_SCI_10925

Implement Storage location activities [SCI-10925]
This commit is contained in:
Martin Artnik 2024-09-12 12:25:13 +02:00 committed by GitHub
commit cf7c87a116
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 335 additions and 78 deletions

View file

@ -17,18 +17,8 @@ class StorageLocationRepositoryRowsController < ApplicationController
meta: (pagination_dict(storage_location_repository_row) unless @storage_location.with_grid?) meta: (pagination_dict(storage_location_repository_row) unless @storage_location.with_grid?)
end end
def update
@storage_location_repository_row.update(storage_location_repository_row_params)
if @storage_location_repository_row.save
render json: @storage_location_repository_row,
serializer: Lists::StorageLocationRepositoryRowSerializer
else
render json: @storage_location_repository_row.errors, status: :unprocessable_entity
end
end
def create def create
ActiveRecord::Base.transaction do
@storage_location_repository_row = StorageLocationRepositoryRow.new( @storage_location_repository_row = StorageLocationRepositoryRow.new(
repository_row: @repository_row, repository_row: @repository_row,
storage_location: @storage_location, storage_location: @storage_location,
@ -37,12 +27,28 @@ class StorageLocationRepositoryRowsController < ApplicationController
) )
if @storage_location_repository_row.save if @storage_location_repository_row.save
log_activity(:storage_location_repository_row_created)
render json: @storage_location_repository_row, render json: @storage_location_repository_row,
serializer: Lists::StorageLocationRepositoryRowSerializer serializer: Lists::StorageLocationRepositoryRowSerializer
else else
render json: @storage_location_repository_row.errors, status: :unprocessable_entity render json: @storage_location_repository_row.errors, status: :unprocessable_entity
end end
end end
end
def update
ActiveRecord::Base.transaction do
@storage_location_repository_row.update(storage_location_repository_row_params)
if @storage_location_repository_row.save
log_activity(:storage_location_repository_row_moved)
render json: @storage_location_repository_row,
serializer: Lists::StorageLocationRepositoryRowSerializer
else
render json: @storage_location_repository_row.errors, status: :unprocessable_entity
end
end
end
def move def move
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
@ -53,7 +59,7 @@ class StorageLocationRepositoryRowsController < ApplicationController
metadata: storage_location_repository_row_params[:metadata] || {}, metadata: storage_location_repository_row_params[:metadata] || {},
created_by: current_user created_by: current_user
) )
log_activity(:storage_location_repository_row_moved)
render json: @storage_location_repository_row, render json: @storage_location_repository_row,
serializer: Lists::StorageLocationRepositoryRowSerializer serializer: Lists::StorageLocationRepositoryRowSerializer
rescue ActiveRecord::RecordInvalid => e rescue ActiveRecord::RecordInvalid => e
@ -63,18 +69,21 @@ class StorageLocationRepositoryRowsController < ApplicationController
end end
def destroy def destroy
ActiveRecord::Base.transaction do
if @storage_location_repository_row.discard if @storage_location_repository_row.discard
log_activity(:storage_location_repository_row_deleted)
render json: {} render json: {}
else else
render json: { errors: @storage_location_repository_row.errors.full_messages }, status: :unprocessable_entity render json: { errors: @storage_location_repository_row.errors.full_messages }, status: :unprocessable_entity
end end
end end
end
def actions_toolbar def actions_toolbar
render json: { render json: {
actions: Toolbars::StorageLocationRepositoryRowsService.new( actions: Toolbars::StorageLocationRepositoryRowsService.new(
current_user, current_user,
items_ids: JSON.parse(params[:items]).map { |i| i['id'] } items_ids: JSON.parse(params[:items]).pluck('id')
).actions ).actions
} }
end end
@ -116,4 +125,18 @@ class StorageLocationRepositoryRowsController < ApplicationController
def check_manage_permissions def check_manage_permissions
render_403 unless can_manage_storage_location?(@storage_location) render_403 unless can_manage_storage_location?(@storage_location)
end end
def log_activity(type_of, message_items = {})
Activities::CreateActivityService
.call(activity_type: type_of,
owner: current_user,
team: @storage_location.team,
subject: @storage_location_repository_row.repository_row,
message_items: {
storage_location: @storage_location_repository_row.storage_location_id,
repository_row: @storage_location_repository_row.repository_row_id,
position: @storage_location_repository_row.human_readable_position,
user: current_user.id
}.merge(message_items))
end
end end

View file

@ -21,19 +21,8 @@ class StorageLocationsController < ApplicationController
def show; end def show; end
def update
@storage_location.image.purge if params[:file_name].blank?
@storage_location.image.attach(params[:signed_blob_id]) if params[:signed_blob_id]
@storage_location.update(storage_location_params)
if @storage_location.save
render json: @storage_location, serializer: Lists::StorageLocationSerializer
else
render json: { error: @storage_location.errors.full_messages }, status: :unprocessable_entity
end
end
def create def create
ActiveRecord::Base.transaction do
@storage_location = StorageLocation.new( @storage_location = StorageLocation.new(
storage_location_params.merge({ created_by: current_user }) storage_location_params.merge({ created_by: current_user })
) )
@ -43,38 +32,67 @@ class StorageLocationsController < ApplicationController
@storage_location.image.attach(params[:signed_blob_id]) if params[:signed_blob_id] @storage_location.image.attach(params[:signed_blob_id]) if params[:signed_blob_id]
if @storage_location.save if @storage_location.save
log_activity('storage_location_created')
render json: @storage_location, serializer: Lists::StorageLocationSerializer render json: @storage_location, serializer: Lists::StorageLocationSerializer
else else
render json: { error: @storage_location.errors.full_messages }, status: :unprocessable_entity render json: { error: @storage_location.errors.full_messages }, status: :unprocessable_entity
end end
end end
end
def update
ActiveRecord::Base.transaction do
@storage_location.image.purge if params[:file_name].blank?
@storage_location.image.attach(params[:signed_blob_id]) if params[:signed_blob_id]
@storage_location.update(storage_location_params)
if @storage_location.save
log_activity('storage_location_edited')
render json: @storage_location, serializer: Lists::StorageLocationSerializer
else
render json: { error: @storage_location.errors.full_messages }, status: :unprocessable_entity
end
end
end
def destroy def destroy
ActiveRecord::Base.transaction do
if @storage_location.discard if @storage_location.discard
log_activity('storage_location_deleted')
render json: {} render json: {}
else else
render json: { error: @storage_location.errors.full_messages }, status: :unprocessable_entity render json: { error: @storage_location.errors.full_messages }, status: :unprocessable_entity
end end
end end
end
def duplicate def duplicate
ActiveRecord::Base.transaction do
new_storage_location = @storage_location.duplicate! new_storage_location = @storage_location.duplicate!
if new_storage_location if new_storage_location
render json: new_storage_location, serializer: Lists::StorageLocationSerializer @storage_location = new_storage_location
log_activity('storage_location_created')
render json: @storage_location, serializer: Lists::StorageLocationSerializer
else else
render json: { errors: :failed }, status: :unprocessable_entity render json: { errors: :failed }, status: :unprocessable_entity
end end
end end
end
def move def move
storage_location_destination = ActiveRecord::Base.transaction do
original_storage_location = @storage_location.parent
destination_storage_location =
if move_params[:destination_storage_location_id] == 'root_storage_location' if move_params[:destination_storage_location_id] == 'root_storage_location'
nil nil
else else
current_team.storage_locations.find(move_params[:destination_storage_location_id]) current_team.storage_locations.find(move_params[:destination_storage_location_id])
end end
@storage_location.update!(parent: storage_location_destination) @storage_location.update!(parent: destination_storage_location)
log_activity('storage_location_moved', { storage_location_original: original_storage_location.id, storage_location_destination: destination_storage_location.id })
end
render json: { message: I18n.t('storage_locations.index.move_modal.success_flash') } render json: { message: I18n.t('storage_locations.index.move_modal.success_flash') }
rescue StandardError => e rescue StandardError => e
@ -93,7 +111,11 @@ class StorageLocationsController < ApplicationController
end end
def unassign_rows def unassign_rows
@storage_location.storage_location_repository_rows.where(id: params[:ids]).discard_all ActiveRecord::Base.transaction do
@storage_location_repository_rows = @storage_location.storage_location_repository_rows.where(id: params[:ids])
@storage_location_repository_rows.each(&:discard)
log_unassign_activities
end
render json: { status: :ok } render json: { status: :ok }
end end
@ -206,4 +228,32 @@ class StorageLocationsController < ApplicationController
} }
end end
end end
def log_activity(type_of, message_items = {})
Activities::CreateActivityService
.call(activity_type: "#{'container_' if @storage_location.container}#{type_of}",
owner: current_user,
team: @storage_location.team,
subject: @storage_location,
message_items: {
storage_location: @storage_location.id,
user: current_user.id
}.merge(message_items))
end
def log_unassign_activities
@storage_location_repository_rows.each do |storage_location_repository_row|
Activities::CreateActivityService
.call(activity_type: :storage_location_repository_row_deleted,
owner: current_user,
team: @storage_location.team,
subject: storage_location_repository_row.repository_row,
message_items: {
storage_location: storage_location_repository_row.storage_location_id,
repository_row: storage_location_repository_row.repository_row_id,
position: storage_location_repository_row.human_readable_position,
user: current_user.id
})
end
end
end end

View file

@ -6,25 +6,54 @@ class TeamSharedObjectsController < ApplicationController
def update def update
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
@activities_to_log = []
# Global share # Global share
if @model.globally_shareable?
permission_level =
if params[:select_all_teams] if params[:select_all_teams]
@model.update!(permission_level: params[:select_all_write_permission] ? :shared_write : :shared_read) params[:select_all_write_permission] ? :shared_write : :shared_read
@model.team_shared_objects.each(&:destroy!) else
next :not_shared
end
@model.permission_level = permission_level
if @model.permission_level_changed?
@model.save!
@model.team_shared_objects.each(&:destroy!) unless permission_level == :not_shared
case @model
when Repository
setup_repository_global_share_activity
end
log_activities and next
end
end end
# Share to specific teams # Share to specific teams
params[:team_share_params].each do |t| params[:team_share_params].each do |t|
@model.update!(permission_level: :not_shared) if @model.globally_shareable? @model.update!(permission_level: :not_shared) if @model.globally_shareable?
@model.team_shared_objects.find_or_initialize_by(team_id: t['id']).update!(
team_shared_object = @model.team_shared_objects.find_or_initialize_by(team_id: t['id'])
new_record = team_shared_object.new_record?
team_shared_object.update!(
permission_level: t['private_shared_with_write'] ? :shared_write : :shared_read permission_level: t['private_shared_with_write'] ? :shared_write : :shared_read
) )
setup_team_share_activity(team_shared_object, new_record) if team_shared_object.saved_changes?
end end
# Unshare # Unshare
@model.team_shared_objects.where.not( @model.team_shared_objects.where.not(
team_id: params[:team_share_params].filter { |t| t['private_shared_with'] }.pluck('id') team_id: params[:team_share_params].filter { |t| t['private_shared_with'] }.pluck('id')
).each(&:destroy!) ).each do |team_shared_object|
team_shared_object.destroy!
setup_team_share_activity(team_shared_object, false)
end
log_activities
end end
end end
@ -71,7 +100,66 @@ class TeamSharedObjectsController < ApplicationController
} }
end end
def log_activity(type_of, team_shared_object) def setup_team_share_activity(team_shared_object, new_record)
# log activity logic type =
case @model
when Repository
if team_shared_object.destroyed?
:unshare_inventory
elsif new_record
:share_inventory
else
:update_share_inventory
end
when StorageLocation
if team_shared_object.destroyed?
"#{'container_' if @model.container?}storage_location_unshared"
elsif new_record
"#{'container_' if @model.container?}storage_location_shared"
else
"#{'container_' if @model.container?}storage_location_sharing_updated"
end
end
@activities_to_log << {
type: type,
message_items: {
@model.model_name.param_key.to_sym => team_shared_object.shared_object.id,
team: team_shared_object.team.id,
permission_level: Extends::SHARED_INVENTORIES_PL_MAPPINGS[team_shared_object.permission_level.to_sym]
}
}
end
def setup_repository_global_share_activity
message_items = {
repository: @model.id,
team: @model.team.id,
permission_level: Extends::SHARED_INVENTORIES_PL_MAPPINGS[@model.permission_level.to_sym]
}
activity_params =
if @model.saved_changes['permission_level'][0] == 'not_shared'
{ type: :share_inventory_with_all, message_items: message_items }
elsif @model.saved_changes['permission_level'][1] == 'not_shared'
{ type: :unshare_inventory_with_all, message_items: message_items }
else
{ type: :update_share_with_all_permission_level, message_items: message_items }
end
@activities_to_log << activity_params
end
def log_activities
@activities_to_log.each do |activity_params|
Activities::CreateActivityService
.call(activity_type: activity_params[:type],
owner: current_user,
team: @model.team,
subject: @model,
message_items: {
user: current_user.id
}.merge(activity_params[:message_items]))
end
end end
end end

View file

@ -108,6 +108,8 @@ module GlobalActivitiesHelper
else else
project_folder_path(obj, team: obj.team.id) project_folder_path(obj, team: obj.team.id)
end end
when StorageLocation
path = storage_location_path(obj)
else else
return current_value return current_value
end end

View file

@ -187,6 +187,9 @@ class Activity < ApplicationRecord
when Asset when Asset
breadcrumbs[:asset] = subject.blob.filename.to_s breadcrumbs[:asset] = subject.blob.filename.to_s
generate_breadcrumb(subject.result || subject.step || subject.repository_cell.repository_row.repository) generate_breadcrumb(subject.result || subject.step || subject.repository_cell.repository_row.repository)
when StorageLocation
breadcrumbs[:storage_location] = subject.name
generate_breadcrumb(subject.team)
end end
end end

View file

@ -14,10 +14,17 @@ class StorageLocationRepositoryRow < ApplicationRecord
validate :ensure_uniq_position validate :ensure_uniq_position
end end
def position_must_be_present def human_readable_position
if metadata['position'].blank? return unless metadata['position']
errors.add(:base, I18n.t('activerecord.errors.models.storage_location.missing_position'))
column_letter = ('A'..'Z').to_a[metadata['position'][0] - 1]
row_number = metadata['position'][1]
"#{column_letter}#{row_number}"
end end
def position_must_be_present
errors.add(:base, I18n.t('activerecord.errors.models.storage_location.missing_position')) if metadata['position'].blank?
end end
def ensure_uniq_position def ensure_uniq_position

View file

@ -61,7 +61,11 @@ module Activities
end end
if id if id
obj = const.find id obj = if const.respond_to?(:with_discarded)
const.with_discarded.find id
else
const.find id
end
@activity.message_items[k] = { type: const.to_s, value: obj.public_send(getter_method).to_s, id: id } @activity.message_items[k] = { type: const.to_s, value: obj.public_send(getter_method).to_s, id: id }
@activity.message_items[k][:value_for] = getter_method @activity.message_items[k][:value_for] = getter_method
@activity.message_items[k][:value_type] = value_type unless value_type.nil? @activity.message_items[k][:value_type] = value_type unless value_type.nil?

View file

@ -0,0 +1,15 @@
<%= render partial: "global_activities/references/team",
locals: { team: team, subject: team, breadcrumbs: breadcrumbs, values: values, type_of: type_of } %>
<div class="ga-breadcrumb">
<span class="sn-icon sn-icon-storage"></span>
<% if subject %>
<%= route_to_other_team(storage_location_path(subject.id, team: subject.team.id),
team,
subject.name&.truncate(Constants::NAME_TRUNCATION_LENGTH),
title: subject.name) %>
<% else %>
<span title="<%= breadcrumbs['storage_location'] %>">
<%= breadcrumbs['storage_location']&.truncate(Constants::NAME_TRUNCATION_LENGTH) %>
</span>
<% end %>
</div>

View file

@ -188,7 +188,7 @@ class Extends
ACTIVITY_SUBJECT_TYPES = %w( ACTIVITY_SUBJECT_TYPES = %w(
Team RepositoryBase Project Experiment MyModule Result Protocol Report RepositoryRow Team RepositoryBase Project Experiment MyModule Result Protocol Report RepositoryRow
ProjectFolder Asset Step LabelTemplate ProjectFolder Asset Step LabelTemplate StorageLocation StorageLocationRepositoryRow
).freeze ).freeze
SEARCHABLE_ACTIVITY_SUBJECT_TYPES = %w( SEARCHABLE_ACTIVITY_SUBJECT_TYPES = %w(
@ -205,7 +205,8 @@ class Extends
my_module: %i(results protocols), my_module: %i(results protocols),
result: [:assets], result: [:assets],
protocol: [:steps], protocol: [:steps],
step: [:assets] step: [:assets],
storage_location: [:storage_location_repository_rows]
} }
ACTIVITY_MESSAGE_ITEMS_TYPES = ACTIVITY_MESSAGE_ITEMS_TYPES =
@ -495,7 +496,24 @@ class Extends
task_step_asset_renamed: 305, task_step_asset_renamed: 305,
result_asset_renamed: 306, result_asset_renamed: 306,
protocol_step_asset_renamed: 307, protocol_step_asset_renamed: 307,
inventory_items_added_or_updated_with_import: 308 inventory_items_added_or_updated_with_import: 308,
storage_location_created: 309,
storage_location_deleted: 310,
storage_location_edited: 311,
storage_location_moved: 312,
storage_location_shared: 313,
storage_location_unshared: 314,
storage_location_sharing_updated: 315,
container_storage_location_created: 316,
container_storage_location_deleted: 317,
container_storage_location_edited: 318,
container_storage_location_moved: 319,
container_storage_location_shared: 320,
container_storage_location_unshared: 321,
container_storage_location_sharing_updated: 322,
storage_location_repository_row_created: 323,
storage_location_repository_row_deleted: 324,
storage_location_repository_row_moved: 325
} }
ACTIVITY_GROUPS = { ACTIVITY_GROUPS = {
@ -515,7 +533,10 @@ class Extends
190, 191, *204..215, 220, 223, 227, 228, 229, *230..235, 190, 191, *204..215, 220, 223, 227, 228, 229, *230..235,
*237..240, *253..256, *279..283, 300, 304, 307], *237..240, *253..256, *279..283, 300, 304, 307],
team: [92, 94, 93, 97, 104, 244, 245], team: [92, 94, 93, 97, 104, 244, 245],
label_templates: [*216..219] label_templates: [*216..219],
storage_locations: [*309..315],
container_storage_location: [*316..322],
storage_location_repository_rows: [*323..325]
} }
TOP_LEVEL_ASSIGNABLES = %w(Project Team Protocol Repository).freeze TOP_LEVEL_ASSIGNABLES = %w(Project Team Protocol Repository).freeze

View file

@ -322,6 +322,23 @@ en:
protocol_step_asset_renamed_html: "%{user} renamed file %{old_name} to %{new_name} on protocols step <strong>%{step}</strong> in Protocol repository." protocol_step_asset_renamed_html: "%{user} renamed file %{old_name} to %{new_name} on protocols step <strong>%{step}</strong> in Protocol repository."
result_asset_renamed_html: "%{user} renamed file %{old_name} to %{new_name} on result <strong>%{result}</strong> on task <strong>%{my_module}</strong>." result_asset_renamed_html: "%{user} renamed file %{old_name} to %{new_name} on result <strong>%{result}</strong> on task <strong>%{my_module}</strong>."
item_added_with_import_html: "%{user} edited %{num_of_items} inventory item(s) in %{repository}." item_added_with_import_html: "%{user} edited %{num_of_items} inventory item(s) in %{repository}."
storage_location_created_html: "%{user} created location %{storage_location}."
storage_location_deleted_html: "%{user} deleted location %{storage_location}."
storage_location_edited_html: "%{user} edited location %{storage_location}."
storage_location_moved_html: "%{user} moved location %{storage_location} from %{storage_location_original} to %{storage_location_destination}."
storage_location_shared_html: "%{user} shared location %{storage_location} with team %{team} with %{permission_level} permission."
storage_location_unshared_html: "%{user} unshared location %{storage_location} with team %{team}."
storage_location_sharing_updated_html: "%{user} changed permission of shared location %{storage_location} with team %{team} to %{permission_level}."
container_storage_location_created_html: "%{user} created box %{storage_location}."
container_storage_location_deleted_html: "%{user} deleted box %{storage_location}."
container_storage_location_edited_html: "%{user} edited box %{storage_location}."
container_storage_location_moved_html: "%{user} moved box %{storage_location} from %{storage_location_original} to %{storage_location_destination}."
container_storage_location_shared_html: "%{user} shared box %{storage_location} with team %{team} with %{permission_level} permission."
container_storage_location_unshared_html: "%{user} unshared box %{storage_location} with team %{team}."
container_storage_location_sharing_updated_html: "%{user} changed permission of shared box %{storage_location} with team %{team} to %{permission_level}."
storage_location_repository_row_created_html: "%{user} assigned %{repository_row} to box %{storage_location} %{position}."
storage_location_repository_row_deleted_html: "%{user} unassigned %{repository_row} from box %{storage_location} %{position}."
storage_location_repository_row_moved_html: "%{user} moved item %{repository_row} from box %{storage_location_original} %{positions} to box %{storage_location_destination} %{positions}."
activity_name: activity_name:
create_project: "Project created" create_project: "Project created"
rename_project: "Project renamed" rename_project: "Project renamed"
@ -601,6 +618,23 @@ en:
task_step_file_duplicated: "File attachment on Task step duplicated" task_step_file_duplicated: "File attachment on Task step duplicated"
result_file_duplicated: "File attachment on Task result duplicated" result_file_duplicated: "File attachment on Task result duplicated"
protocol_step_file_duplicated: "File attachment on Protocol step duplicated" protocol_step_file_duplicated: "File attachment on Protocol step duplicated"
storage_location_created: "Location created"
storage_location_deleted: "Location deleted"
storage_location_edited: "Location edited"
storage_location_moved: "Location moved"
storage_location_shared: "Location shared"
storage_location_unshared: "Location unshared"
storage_location_sharing_updated: "Location sharing permission updated"
container_storage_location_created: "Box created"
container_storage_location_deleted: "Box deleted"
container_storage_location_edited: "Box edited"
container_storage_location_moved: "Box moved"
container_storage_location_shared: "Box shared"
container_storage_location_unshared: "Box unshared"
container_storage_location_sharing_updated: "Box sharing permission updated"
storage_location_repository_row_created: "Inventory item location assigned"
storage_location_repository_row_deleted: "Inventory item location unassigned"
storage_location_repository_row_moved: "Inventory item location moved"
activity_group: activity_group:
projects: "Projects" projects: "Projects"
task_results: "Task results" task_results: "Task results"
@ -614,6 +648,8 @@ en:
team: "Team" team: "Team"
exports: "Exports" exports: "Exports"
label_templates: "Label templates" label_templates: "Label templates"
storage_locations: "Locations"
container_storage_locations: "Boxes"
subject_name: subject_name:
repository: "Inventory" repository: "Inventory"
project: "Project" project: "Project"
@ -623,3 +659,4 @@ en:
protocol: "Protocol" protocol: "Protocol"
step: "Step" step: "Step"
report: "Report" report: "Report"
storage_location: "Location"

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class RemoveForeignKeyConstraintFromTeamSharedObjects < ActiveRecord::Migration[7.0]
def change
remove_foreign_key :team_shared_objects, :repositories, column: :shared_object_id
end
end