From 7584fef1a53d6a18983bbe41e2c3007918e47ccd Mon Sep 17 00:00:00 2001 From: Mehreen Mansur Date: Tue, 26 Mar 2024 11:23:38 +0100 Subject: [PATCH] feat: Copy research plan (#1667) * add copy research plan feature * create new derivatives for copy research plan * update copied annonated file * update identifier in copied research plan body * update copy attachment method --------- Co-authored-by: Mehreen --- app/api/chemotion/research_plan_api.rb | 11 ++- app/api/entities/application_entity.rb | 4 + app/api/entities/reaction_entity.rb | 4 - app/api/entities/research_plan_entity.rb | 1 + app/api/entities/sample_entity.rb | 4 - app/models/attachment.rb | 8 -- app/models/research_plan.rb | 17 ++++- .../researchPlans/ResearchPlanDetails.js | 16 +++- .../ResearchPlanDetailsName.js | 1 + .../src/components/common/CopyElementModal.js | 2 + app/packs/src/models/Attachment.js | 6 ++ app/packs/src/models/ResearchPlan.js | 34 +++++++++ .../src/stores/alt/actions/ElementActions.js | 11 +++ .../src/stores/alt/stores/ElementStore.js | 7 ++ app/packs/src/utilities/routesUtils.js | 2 + .../annotation/annotation_loader.rb | 2 +- .../annotation/annotation_updater.rb | 8 ++ app/usecases/attachments/copy.rb | 41 ++++++++++ lib/import/import_collections.rb | 8 +- spec/features/copy_research_plan_spec.rb | 74 +++++++++++++++++++ 20 files changed, 229 insertions(+), 32 deletions(-) create mode 100644 app/usecases/attachments/copy.rb create mode 100644 spec/features/copy_research_plan_spec.rb diff --git a/app/api/chemotion/research_plan_api.rb b/app/api/chemotion/research_plan_api.rb index 740482bb3f..e04cdaa512 100644 --- a/app/api/chemotion/research_plan_api.rb +++ b/app/api/chemotion/research_plan_api.rb @@ -68,6 +68,7 @@ class ResearchPlanAPI < Grape::API optional :collection_id, type: Integer, desc: 'Collection ID' requires :container, type: Hash, desc: 'Containers' optional :segments, type: Array, desc: 'Segments' + optional :attachments, type: Array, desc: 'Attachments' end post do attributes = { @@ -75,12 +76,14 @@ class ResearchPlanAPI < Grape::API body: params[:body] } + attributes.delete(:can_copy) research_plan = ResearchPlan.new attributes research_plan.creator = current_user research_plan.container = update_datamodel(params[:container]) research_plan.save! research_plan.save_segments(segments: params[:segments], current_user_id: current_user.id) - + clone_attachs = params[:attachments]&.reject { |a| a[:is_new] } + Usecases::Attachments::Copy.execute!(clone_attachs, research_plan, current_user.id) if clone_attachs if params[:collection_id] collection = current_user.collections.where(id: params[:collection_id]).take @@ -157,7 +160,8 @@ class ResearchPlanAPI < Grape::API end route_param :id do before do - error!('401 Unauthorized', 401) unless ElementPolicy.new(current_user, ResearchPlan.find(params[:id])).read? + @element_policy = ElementPolicy.new(current_user, ResearchPlan.find(params[:id])) + error!('401 Unauthorized', 401) unless @element_policy.read? end get do research_plan = ResearchPlan.find(params[:id]) @@ -170,7 +174,8 @@ class ResearchPlanAPI < Grape::API { research_plan: Entities::ResearchPlanEntity.represent( research_plan, - detail_levels: ElementDetailLevelCalculator.new(user: current_user, element: research_plan).detail_levels + detail_levels: ElementDetailLevelCalculator.new(user: current_user, element: research_plan).detail_levels, + policy: @element_policy, ), attachments: Entities::AttachmentEntity.represent(research_plan.attachments), } diff --git a/app/api/entities/application_entity.rb b/app/api/entities/application_entity.rb index 05e788ee0c..e8519a0692 100644 --- a/app/api/entities/application_entity.rb +++ b/app/api/entities/application_entity.rb @@ -82,6 +82,10 @@ def detail_levels minimal_default_levels.merge(options[:detail_levels]) end + def can_copy + options[:policy].try(:copy?) || false + end + class MissingCurrentUserError < StandardError end end diff --git a/app/api/entities/reaction_entity.rb b/app/api/entities/reaction_entity.rb index 6fe11e51fb..cbcbb1036c 100644 --- a/app/api/entities/reaction_entity.rb +++ b/app/api/entities/reaction_entity.rb @@ -56,10 +56,6 @@ def can_update options[:policy].try(:update?) || false end - def can_copy - options[:policy].try(:copy?) || false - end - def code_log displayed_in_list? ? nil : object.code_log end diff --git a/app/api/entities/research_plan_entity.rb b/app/api/entities/research_plan_entity.rb index a5694a5762..6934c90788 100644 --- a/app/api/entities/research_plan_entity.rb +++ b/app/api/entities/research_plan_entity.rb @@ -4,6 +4,7 @@ module Entities class ResearchPlanEntity < ApplicationEntity # rubocop:disable Layout/ExtraSpacing with_options(anonymize_below: 0) do + expose! :can_copy, unless: :displayed_in_list expose! :body expose! :container, using: 'Entities::ContainerEntity' expose! :id diff --git a/app/api/entities/sample_entity.rb b/app/api/entities/sample_entity.rb index ac01d08f3a..1e07bb0b5d 100644 --- a/app/api/entities/sample_entity.rb +++ b/app/api/entities/sample_entity.rb @@ -86,10 +86,6 @@ def can_update options[:policy].try(:update?) || false end - def can_copy - options[:policy].try(:copy?) || false - end - def can_publish options[:policy].try(:destroy?) || false end diff --git a/app/models/attachment.rb b/app/models/attachment.rb index 81d695fc84..c81b6f5660 100644 --- a/app/models/attachment.rb +++ b/app/models/attachment.rb @@ -75,14 +75,6 @@ class Attachment < ApplicationRecord where(attachable_type: 'Template') } - def copy(**args) - d = dup - d.identifier = nil - d.duplicated = true - d.update(args) - d - end - def extname File.extname(filename.to_s) end diff --git a/app/models/research_plan.rb b/app/models/research_plan.rb index 19abd4839e..27c5b47a04 100644 --- a/app/models/research_plan.rb +++ b/app/models/research_plan.rb @@ -66,6 +66,7 @@ class ResearchPlan < ApplicationRecord before_destroy :delete_attachment accepts_nested_attributes_for :collections_research_plans + attr_accessor :can_copy unless Dir.exists?(path = Rails.root.to_s + '/public/images/research_plans') Dir.mkdir path @@ -98,15 +99,25 @@ def svg_files svg_files end + def update_body_attachments(original_identifier, copy_identifier) + attach = body&.detect { |x| x['value']['public_name'] == original_identifier } + if attach.present? + attach['id'] = SecureRandom.uuid + attach['value']['public_name'] = copy_identifier + end + + save! + end + private + def delete_attachment if Rails.env.production? - attachments.each { |attachment| + attachments.each do |attachment| attachment.delay(run_at: 96.hours.from_now, queue: 'attachment_deletion').destroy! - } + end else attachments.each(&:destroy!) end end - end diff --git a/app/packs/src/apps/mydb/elements/details/researchPlans/ResearchPlanDetails.js b/app/packs/src/apps/mydb/elements/details/researchPlans/ResearchPlanDetails.js index f49cdb0ff3..81fc28481c 100644 --- a/app/packs/src/apps/mydb/elements/details/researchPlans/ResearchPlanDetails.js +++ b/app/packs/src/apps/mydb/elements/details/researchPlans/ResearchPlanDetails.js @@ -9,6 +9,7 @@ import { import { unionBy, findIndex } from 'lodash'; import Immutable from 'immutable'; import ElementCollectionLabels from 'src/apps/mydb/elements/labels/ElementCollectionLabels'; +import UIStore from 'src/stores/alt/stores/UIStore'; import UIActions from 'src/stores/alt/actions/UIActions'; import ElementActions from 'src/stores/alt/actions/ElementActions'; import DetailActions from 'src/stores/alt/actions/DetailActions'; @@ -36,6 +37,7 @@ import HeaderCommentSection from 'src/components/comments/HeaderCommentSection'; import CommentSection from 'src/components/comments/CommentSection'; import CommentActions from 'src/stores/alt/actions/CommentActions'; import CommentModal from 'src/components/common/CommentModal'; +import CopyElementModal from 'src/components/common/CopyElementModal'; import { formatTimeStampsOfElement } from 'src/utilities/timezoneHelper'; import UserStore from 'src/stores/alt/stores/UserStore'; import MatrixCheck from 'src/components/common/MatrixCheck'; @@ -487,7 +489,16 @@ export default class ResearchPlanDetails extends Component { } /* eslint-enable */ renderPanelHeading(researchPlan) { + const { currentCollection } = UIStore.getState(); + const rootCol = currentCollection && currentCollection.is_shared === false && + currentCollection.is_locked === false && currentCollection.label !== 'All' ? currentCollection.id : null; const titleTooltip = formatTimeStampsOfElement(researchPlan || {}); + const copyBtn = ( + + ); return ( @@ -513,7 +524,7 @@ export default class ResearchPlanDetails extends Component {