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,30 +17,36 @@ 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 def create
@storage_location_repository_row.update(storage_location_repository_row_params) ActiveRecord::Base.transaction do
@storage_location_repository_row = StorageLocationRepositoryRow.new(
repository_row: @repository_row,
storage_location: @storage_location,
metadata: storage_location_repository_row_params[:metadata] || {},
created_by: current_user
)
if @storage_location_repository_row.save if @storage_location_repository_row.save
render json: @storage_location_repository_row, log_activity(:storage_location_repository_row_created)
serializer: Lists::StorageLocationRepositoryRowSerializer render json: @storage_location_repository_row,
else serializer: Lists::StorageLocationRepositoryRowSerializer
render json: @storage_location_repository_row.errors, status: :unprocessable_entity else
render json: @storage_location_repository_row.errors, status: :unprocessable_entity
end
end end
end end
def create def update
@storage_location_repository_row = StorageLocationRepositoryRow.new( ActiveRecord::Base.transaction do
repository_row: @repository_row, @storage_location_repository_row.update(storage_location_repository_row_params)
storage_location: @storage_location,
metadata: storage_location_repository_row_params[:metadata] || {},
created_by: current_user
)
if @storage_location_repository_row.save if @storage_location_repository_row.save
render json: @storage_location_repository_row, log_activity(:storage_location_repository_row_moved)
serializer: Lists::StorageLocationRepositoryRowSerializer render json: @storage_location_repository_row,
else serializer: Lists::StorageLocationRepositoryRowSerializer
render json: @storage_location_repository_row.errors, status: :unprocessable_entity else
render json: @storage_location_repository_row.errors, status: :unprocessable_entity
end
end end
end end
@ -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,10 +69,13 @@ class StorageLocationRepositoryRowsController < ApplicationController
end end
def destroy def destroy
if @storage_location_repository_row.discard ActiveRecord::Base.transaction do
render json: {} if @storage_location_repository_row.discard
else log_activity(:storage_location_repository_row_deleted)
render json: { errors: @storage_location_repository_row.errors.full_messages }, status: :unprocessable_entity render json: {}
else
render json: { errors: @storage_location_repository_row.errors.full_messages }, status: :unprocessable_entity
end
end end
end end
@ -74,7 +83,7 @@ class StorageLocationRepositoryRowsController < ApplicationController
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,60 +21,78 @@ class StorageLocationsController < ApplicationController
def show; end def show; end
def update def create
@storage_location.image.purge if params[:file_name].blank? ActiveRecord::Base.transaction do
@storage_location.image.attach(params[:signed_blob_id]) if params[:signed_blob_id] @storage_location = StorageLocation.new(
@storage_location.update(storage_location_params) storage_location_params.merge({ created_by: current_user })
)
if @storage_location.save @storage_location.team = @storage_location.root_storage_location.team || current_team
render json: @storage_location, serializer: Lists::StorageLocationSerializer
else @storage_location.image.attach(params[:signed_blob_id]) if params[:signed_blob_id]
render json: { error: @storage_location.errors.full_messages }, status: :unprocessable_entity
if @storage_location.save
log_activity('storage_location_created')
render json: @storage_location, serializer: Lists::StorageLocationSerializer
else
render json: { error: @storage_location.errors.full_messages }, status: :unprocessable_entity
end
end end
end end
def create def update
@storage_location = StorageLocation.new( ActiveRecord::Base.transaction do
storage_location_params.merge({ created_by: current_user }) @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)
@storage_location.team = @storage_location.root_storage_location.team || current_team if @storage_location.save
log_activity('storage_location_edited')
@storage_location.image.attach(params[:signed_blob_id]) if params[:signed_blob_id] render json: @storage_location, serializer: Lists::StorageLocationSerializer
else
if @storage_location.save render json: { error: @storage_location.errors.full_messages }, status: :unprocessable_entity
render json: @storage_location, serializer: Lists::StorageLocationSerializer end
else
render json: { error: @storage_location.errors.full_messages }, status: :unprocessable_entity
end end
end end
def destroy def destroy
if @storage_location.discard ActiveRecord::Base.transaction do
render json: {} if @storage_location.discard
else log_activity('storage_location_deleted')
render json: { error: @storage_location.errors.full_messages }, status: :unprocessable_entity render json: {}
else
render json: { error: @storage_location.errors.full_messages }, status: :unprocessable_entity
end
end end
end end
def duplicate def duplicate
new_storage_location = @storage_location.duplicate! ActiveRecord::Base.transaction do
if new_storage_location new_storage_location = @storage_location.duplicate!
render json: new_storage_location, serializer: Lists::StorageLocationSerializer if new_storage_location
else @storage_location = new_storage_location
render json: { errors: :failed }, status: :unprocessable_entity log_activity('storage_location_created')
render json: @storage_location, serializer: Lists::StorageLocationSerializer
else
render json: { errors: :failed }, status: :unprocessable_entity
end
end end
end end
def move def move
storage_location_destination = ActiveRecord::Base.transaction do
if move_params[:destination_storage_location_id] == 'root_storage_location' original_storage_location = @storage_location.parent
nil destination_storage_location =
else if move_params[:destination_storage_location_id] == 'root_storage_location'
current_team.storage_locations.find(move_params[:destination_storage_location_id]) nil
end else
current_team.storage_locations.find(move_params[:destination_storage_location_id])
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 params[:select_all_teams] if @model.globally_shareable?
@model.update!(permission_level: params[:select_all_write_permission] ? :shared_write : :shared_read) permission_level =
@model.team_shared_objects.each(&:destroy!) if params[:select_all_teams]
next params[:select_all_write_permission] ? :shared_write : :shared_read
else
: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 human_readable_position
return unless metadata['position']
column_letter = ('A'..'Z').to_a[metadata['position'][0] - 1]
row_number = metadata['position'][1]
"#{column_letter}#{row_number}"
end
def position_must_be_present def position_must_be_present
if metadata['position'].blank? errors.add(:base, I18n.t('activerecord.errors.models.storage_location.missing_position')) if metadata['position'].blank?
errors.add(:base, I18n.t('activerecord.errors.models.storage_location.missing_position'))
end
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