diff --git a/app/assets/javascripts/repositories/repository_datatable.js.erb b/app/assets/javascripts/repositories/repository_datatable.js.erb index 78a571825..727dd12eb 100644 --- a/app/assets/javascripts/repositories/repository_datatable.js.erb +++ b/app/assets/javascripts/repositories/repository_datatable.js.erb @@ -565,6 +565,29 @@ var RepositoryDatatable = (function(global) { }); } + + global.onClickCopyRepositoryRecords = function() { + animateSpinner(); + $.ajax({ + url: $('table' + TABLE_ID).data('copy-records'), + type: 'POST', + dataType: 'json', + data: { selected_rows: rowsSelected }, + success: function(data) { + HelperModule.flashAlertMsg(data.flash, 'success'); + rowsSelected = []; + onClickCancel(); + }, + error: function(e) { + if (e.status === 403) { + HelperModule.flashAlertMsg( + I18n.t('repositories.js.permission_error'), e.responseJSON.style + ); + } + } + }); + } + // Edit record global.onClickEdit = function() { if (rowsSelected.length !== 1) { @@ -768,6 +791,8 @@ var RepositoryDatatable = (function(global) { $('.repository-row-selector').removeClass('disabled'); $('.repository-row-selector').prop('disabled', false); if (rowsSelected.length === 0) { + $('#copyRepositoryRecords').prop('disabled', true); + $('#copyRepositoryRecords').addClass('disabled'); $('#editRepositoryRecord').prop('disabled', true); $('#editRepositoryRecord').addClass('disabled'); $('#deleteRepositoryRecordsButton').prop('disabled', true); @@ -805,6 +830,8 @@ var RepositoryDatatable = (function(global) { } $('#deleteRepositoryRecordsButton').prop('disabled', false); $('#deleteRepositoryRecordsButton').removeClass('disabled'); + $('#copyRepositoryRecords').prop('disabled', false); + $('#copyRepositoryRecords').removeClass('disabled'); $('#assignRepositoryRecords').removeClass('disabled'); $('#assignRepositoryRecords').prop('disabled', false); $('#unassignRepositoryRecords').removeClass('disabled'); diff --git a/app/controllers/repository_rows_controller.rb b/app/controllers/repository_rows_controller.rb index e8c191253..b02d96545 100644 --- a/app/controllers/repository_rows_controller.rb +++ b/app/controllers/repository_rows_controller.rb @@ -5,9 +5,11 @@ class RepositoryRowsController < ApplicationController before_action :load_info_modal_vars, only: :show before_action :load_vars, only: %i(edit update) - before_action :load_repository, only: %i(create delete_records index) + before_action :load_repository, + only: %i(create delete_records index copy_records) before_action :check_create_permissions, only: :create - before_action :check_manage_permissions, only: %i(edit update delete_records) + before_action :check_manage_permissions, + only: %i(edit update delete_records copy_records) def index @draw = params[:draw].to_i @@ -276,6 +278,17 @@ class RepositoryRowsController < ApplicationController end end + def copy_records + duplicate_service = RepositoryActions::Duplicate.new(current_user, + @repository, + params[:selected_rows]) + duplicate_service.call + render json: { + flash: t('repositories.copy_records_report', + number: duplicate_service.number_of_duplicated_items) + }, status: :ok + end + private def load_info_modal_vars diff --git a/app/services/repository_actions/duplicate.rb b/app/services/repository_actions/duplicate.rb new file mode 100644 index 000000000..c4f2ac102 --- /dev/null +++ b/app/services/repository_actions/duplicate.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'repository_actions/repository_cell_resolver' + +module RepositoryActions + class Duplicate + attr_reader :number_of_duplicated_items + def initialize(user, repository, rows_to_copy = []) + @user = user + @repository = repository + @rows_to_copy = rows_to_copy.map(&:to_i).uniq + @number_of_duplicated_items = 0 + end + + def call + sanitize_rows_to_copy + @rows_to_copy.each do |row_id| + copy_row(row_id) + end + end + + private + + def sanitize_rows_to_copy + ids = @repository.repository_rows.pluck(:id) + @rows_to_copy.map! { |el| ids.include?(el) ? el : nil }.compact! + end + + def copy_row(id) + row = RepositoryRow.find_by_id(id) + new_row = RepositoryRow.new( + row.attributes.merge(new_row_attributes(row.name)) + ) + + if new_row.save + @number_of_duplicated_items += 1 + row.repository_cells.each do |cell| + copy_repository_cell(cell, new_row) + end + end + end + + def new_row_attributes(name) + timestamp = DateTime.now + { id: nil, + name: "#{name} (1)", + created_at: timestamp, + updated_at: timestamp } + end + + def copy_repository_cell(cell, new_row) + RepositoryActions::RepositoryCellResolver.new(cell, + new_row, + @user.current_team).call + end + end +end diff --git a/app/services/repository_actions/repository_cell_resolver.rb b/app/services/repository_actions/repository_cell_resolver.rb new file mode 100644 index 000000000..334648b1a --- /dev/null +++ b/app/services/repository_actions/repository_cell_resolver.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +module RepositoryActions + class RepositoryCellResolver + def initialize(cell, new_row, team) + @cell = cell + @new_row = new_row + @team = team + end + + def call + case @cell.value_type + when 'RepositoryListValue' + clone_repository_list_value + when 'RepositoryTextValue' + clone_repository_text_value + when 'RepositoryAssetValue' + clone_repository_asset_value + when 'RepositoryDateValue' + clone_repository_date_value + end + end + + private + + def clone_repository_list_value + old_value = @cell.value + RepositoryListValue.create( + old_value.attributes.merge(id: nil, + repository_cell_attributes: { + repository_row: @new_row, + repository_column: @cell.repository_column + }) + ) + end + + def clone_repository_text_value + old_value = @cell.value + RepositoryTextValue.create( + old_value.attributes.merge(id: nil, + repository_cell_attributes: { + repository_row: @new_row, + repository_column: @cell.repository_column + }) + ) + end + + def clone_repository_asset_value + old_value = @cell.value + new_asset = create_new_asset(old_value.asset) + RepositoryAssetValue.create( + old_value.attributes.merge( + id: nil, + asset: new_asset, + repository_cell_attributes: { + repository_row: @new_row, + repository_column: @cell.repository_column + } + ) + ) + end + + def clone_repository_date_value + old_value = @cell.value + RepositoryDateValue.create( + old_value.attributes.merge(id: nil, + repository_cell_attributes: { + repository_row: @new_row, + repository_column: @cell.repository_column + }) + ) + end + + # reuses the same code we have in copy protocols action + def create_new_asset(old_asset) + new_asset = Asset.new_empty( + old_asset.file_file_name, + old_asset.file_file_size + ) + new_asset.created_by = old_asset.created_by + new_asset.team = old_asset.team + new_asset.last_modified_by = old_asset.last_modified_by + new_asset.file_processing = true if old_asset.is_image? + new_asset.file = old_asset.file + new_asset.save + + return unless new_asset.valid? + + if new_asset.is_image? + new_asset.file.reprocess!(:large) + new_asset.file.reprocess!(:medium) + end + + # Clone extracted text data if it exists + if old_asset.asset_text_datum + AssetTextDatum.create(data: new_asset.data, asset: new_asset) + end + + # Update estimated size of cloned asset + # (& file_present flag) + new_asset.update( + estimated_size: old_asset.estimated_size, + file_present: true + ) + + # Update team's space taken + @team.reload + @team.take_space(new_asset.estimated_size) + @team.save! + new_asset + end + end +end diff --git a/app/views/repositories/_repository_table.html.erb b/app/views/repositories/_repository_table.html.erb index aad7bcc3c..3505fdde6 100644 --- a/app/views/repositories/_repository_table.html.erb +++ b/app/views/repositories/_repository_table.html.erb @@ -6,6 +6,7 @@ data-num-columns="<%= 6 + repository.repository_columns.count %>" data-create-record="<%= repository_repository_rows_path(repository) %>" data-delete-record="<%= repository_delete_records_path(repository) %>" + data-copy-records="<%= repository_copy_records_path(repository) %>" data-max-dropdown-length="<%= Constants::NAME_TRUNCATION_LENGTH_DROPDOWN %>" data-save-text="<%= I18n.t('general.save') %>" data-edit-text="<%= I18n.t('general.edit') %>" diff --git a/app/views/repositories/show.html.erb b/app/views/repositories/show.html.erb index 944be2e31..1a8589c07 100644 --- a/app/views/repositories/show.html.erb +++ b/app/views/repositories/show.html.erb @@ -105,12 +105,11 @@ diff --git a/config/locales/en.yml b/config/locales/en.yml index 1210b2f88..71dc89901 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -941,6 +941,7 @@ en: errors_list_title: "Items were not imported because one or more errors were found:" no_repository_name: "Item name is required!" edit_record: "Edit" + copy_record: "Copy" delete_record: "Delete" save_record: "Save" cancel_save: "Cancel" @@ -993,6 +994,7 @@ en: no_records_assigned_flash: "No items were assigned to task" no_records_unassigned_flash: "No items were unassigned from task" default_column: 'Name' + copy_records_report: "%{number} item(s) successfully copied." libraries: manange_modal_column: diff --git a/config/routes.rb b/config/routes.rb index 89089aed8..65334a66c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -467,6 +467,9 @@ Rails.application.routes.draw do to: 'repository_rows#delete_records', as: 'delete_records', defaults: { format: 'json' } + post 'copy_records', + to: 'repository_rows#copy_records', + defaults: { format: 'json' } get 'repository_columns/:id/destroy_html', to: 'repository_columns#destroy_html', as: 'columns_destroy_html' diff --git a/spec/controllers/repository_rows_controller_spec.rb b/spec/controllers/repository_rows_controller_spec.rb index feb487cb0..72dc1d42d 100644 --- a/spec/controllers/repository_rows_controller_spec.rb +++ b/spec/controllers/repository_rows_controller_spec.rb @@ -115,4 +115,12 @@ describe RepositoryRowsController, type: :controller do end end end + + describe 'POST #copy_records' do + it 'returns a success response' do + post :copy_records, params: { repository_id: repository.id, + selected_rows: [repository_row.id] } + expect(response).to have_http_status(:success) + end + end end