mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-10 15:14:33 +08:00
Merge pull request #1086 from ZmagoD/zd_SCI_2207
enables copying of repository items [fixes SCI-2207]
This commit is contained in:
commit
1290435372
10 changed files with 313 additions and 7 deletions
|
@ -570,6 +570,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
|
// Edit record
|
||||||
global.onClickEdit = function() {
|
global.onClickEdit = function() {
|
||||||
if (rowsSelected.length !== 1) {
|
if (rowsSelected.length !== 1) {
|
||||||
|
@ -775,6 +798,8 @@ var RepositoryDatatable = (function(global) {
|
||||||
$('.repository-row-selector').removeClass('disabled');
|
$('.repository-row-selector').removeClass('disabled');
|
||||||
$('.repository-row-selector').prop('disabled', false);
|
$('.repository-row-selector').prop('disabled', false);
|
||||||
if (rowsSelected.length === 0) {
|
if (rowsSelected.length === 0) {
|
||||||
|
$('#copyRepositoryRecords').prop('disabled', true);
|
||||||
|
$('#copyRepositoryRecords').addClass('disabled');
|
||||||
$('#editRepositoryRecord').prop('disabled', true);
|
$('#editRepositoryRecord').prop('disabled', true);
|
||||||
$('#editRepositoryRecord').addClass('disabled');
|
$('#editRepositoryRecord').addClass('disabled');
|
||||||
$('#deleteRepositoryRecordsButton').prop('disabled', true);
|
$('#deleteRepositoryRecordsButton').prop('disabled', true);
|
||||||
|
@ -812,6 +837,8 @@ var RepositoryDatatable = (function(global) {
|
||||||
}
|
}
|
||||||
$('#deleteRepositoryRecordsButton').prop('disabled', false);
|
$('#deleteRepositoryRecordsButton').prop('disabled', false);
|
||||||
$('#deleteRepositoryRecordsButton').removeClass('disabled');
|
$('#deleteRepositoryRecordsButton').removeClass('disabled');
|
||||||
|
$('#copyRepositoryRecords').prop('disabled', false);
|
||||||
|
$('#copyRepositoryRecords').removeClass('disabled');
|
||||||
$('#assignRepositoryRecords').removeClass('disabled');
|
$('#assignRepositoryRecords').removeClass('disabled');
|
||||||
$('#assignRepositoryRecords').prop('disabled', false);
|
$('#assignRepositoryRecords').prop('disabled', false);
|
||||||
$('#unassignRepositoryRecords').removeClass('disabled');
|
$('#unassignRepositoryRecords').removeClass('disabled');
|
||||||
|
|
|
@ -5,9 +5,11 @@ class RepositoryRowsController < ApplicationController
|
||||||
|
|
||||||
before_action :load_info_modal_vars, only: :show
|
before_action :load_info_modal_vars, only: :show
|
||||||
before_action :load_vars, only: %i(edit update)
|
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_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
|
def index
|
||||||
@draw = params[:draw].to_i
|
@draw = params[:draw].to_i
|
||||||
|
@ -283,6 +285,17 @@ class RepositoryRowsController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def copy_records
|
||||||
|
duplicate_service = RepositoryActions::DuplicateRows.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
|
private
|
||||||
|
|
||||||
def load_info_modal_vars
|
def load_info_modal_vars
|
||||||
|
|
110
app/services/repository_actions/duplicate_cell.rb
Normal file
110
app/services/repository_actions/duplicate_cell.rb
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RepositoryActions
|
||||||
|
class DuplicateCell
|
||||||
|
def initialize(cell, new_row, user, team)
|
||||||
|
@cell = cell
|
||||||
|
@new_row = new_row
|
||||||
|
@user = user
|
||||||
|
@team = team
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
self.send("duplicate_#{@cell.value_type.underscore}")
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def duplicate_repository_list_value
|
||||||
|
old_value = @cell.value
|
||||||
|
RepositoryListValue.create(
|
||||||
|
old_value.attributes.merge(
|
||||||
|
id: nil, created_by: @user, last_modified_by: @user,
|
||||||
|
repository_cell_attributes: {
|
||||||
|
repository_row: @new_row,
|
||||||
|
repository_column: @cell.repository_column
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def duplicate_repository_text_value
|
||||||
|
old_value = @cell.value
|
||||||
|
RepositoryTextValue.create(
|
||||||
|
old_value.attributes.merge(
|
||||||
|
id: nil, created_by: @user, last_modified_by: @user,
|
||||||
|
repository_cell_attributes: {
|
||||||
|
repository_row: @new_row,
|
||||||
|
repository_column: @cell.repository_column
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def duplicate_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, created_by: @user, last_modified_by: @user,
|
||||||
|
repository_cell_attributes: {
|
||||||
|
repository_row: @new_row,
|
||||||
|
repository_column: @cell.repository_column
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def duplicate_repository_date_value
|
||||||
|
old_value = @cell.value
|
||||||
|
RepositoryDateValue.create(
|
||||||
|
old_value.attributes.merge(
|
||||||
|
id: nil, created_by: @user, last_modified_by: @user,
|
||||||
|
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 = @team
|
||||||
|
new_asset.last_modified_by = @user
|
||||||
|
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
|
56
app/services/repository_actions/duplicate_rows.rb
Normal file
56
app/services/repository_actions/duplicate_rows.rb
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'repository_actions/duplicate_cell'
|
||||||
|
|
||||||
|
module RepositoryActions
|
||||||
|
class DuplicateRows
|
||||||
|
attr_reader :number_of_duplicated_items
|
||||||
|
def initialize(user, repository, rows_ids = [])
|
||||||
|
@user = user
|
||||||
|
@repository = repository
|
||||||
|
@rows_to_duplicate = sanitize_rows_to_duplicate(rows_ids)
|
||||||
|
@number_of_duplicated_items = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
@rows_to_duplicate.each do |row_id|
|
||||||
|
duplicate_row(row_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def sanitize_rows_to_duplicate(rows_ids)
|
||||||
|
process_ids = rows_ids.map(&:to_i).uniq
|
||||||
|
@repository.repository_rows.where(id: process_ids).pluck(:id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def duplicate_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|
|
||||||
|
duplicate_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 duplicate_repository_cell(cell, new_row)
|
||||||
|
RepositoryActions::DuplicateCell.new(
|
||||||
|
cell, new_row, @user, @repository.team
|
||||||
|
).call
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,6 +6,7 @@
|
||||||
data-num-columns="<%= 6 + repository.repository_columns.count %>"
|
data-num-columns="<%= 6 + repository.repository_columns.count %>"
|
||||||
data-create-record="<%= repository_repository_rows_path(repository) %>"
|
data-create-record="<%= repository_repository_rows_path(repository) %>"
|
||||||
data-delete-record="<%= repository_delete_records_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-max-dropdown-length="<%= Constants::NAME_TRUNCATION_LENGTH_DROPDOWN %>"
|
||||||
data-save-text="<%= I18n.t('general.save') %>"
|
data-save-text="<%= I18n.t('general.save') %>"
|
||||||
data-edit-text="<%= I18n.t('general.edit') %>"
|
data-edit-text="<%= I18n.t('general.edit') %>"
|
||||||
|
|
|
@ -106,12 +106,11 @@
|
||||||
|
|
||||||
<!-- These buttons are appended to table in javascript, after table initialization -->
|
<!-- These buttons are appended to table in javascript, after table initialization -->
|
||||||
<div class="toolbarButtons" style="display:none">
|
<div class="toolbarButtons" style="display:none">
|
||||||
<button type="button" class="btn btn-default editAdd" id="editRepositoryRecord" onclick="onClickEdit()" disabled>
|
|
||||||
<span class="glyphicon glyphicon-pencil"></span>
|
|
||||||
<span class="hidden-xs-custom"><%= t("repositories.edit_record") %></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<% if can_manage_repository_rows?(@repository.team) %>
|
<% if can_manage_repository_rows?(@repository.team) %>
|
||||||
|
<button type="button" class="btn btn-default editAdd" id="editRepositoryRecord" onclick="onClickEdit()" disabled>
|
||||||
|
<span class="glyphicon glyphicon-pencil"></span>
|
||||||
|
<span class="hidden-xs-custom"><%= t("repositories.edit_record") %></span>
|
||||||
|
</button>
|
||||||
<button type="button" class="btn btn-default"
|
<button type="button" class="btn btn-default"
|
||||||
id="deleteRepositoryRecordsButton" data-target="#deleteRepositoryRecord" data-toggle="modal" disabled>
|
id="deleteRepositoryRecordsButton" data-target="#deleteRepositoryRecord" data-toggle="modal" disabled>
|
||||||
<span class="glyphicon glyphicon-trash"></span>
|
<span class="glyphicon glyphicon-trash"></span>
|
||||||
|
@ -119,6 +118,10 @@
|
||||||
<%= submit_tag I18n.t('repositories.delete_record'), :class => "hidden
|
<%= submit_tag I18n.t('repositories.delete_record'), :class => "hidden
|
||||||
delete_repository_records_submit" %>
|
delete_repository_records_submit" %>
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" class="btn btn-default copyRow" id="copyRepositoryRecords" onclick="onClickCopyRepositoryRecords()" disabled>
|
||||||
|
<span class="glyphicon glyphicon-duplicate"></span>
|
||||||
|
<span class="hidden-xs-custom"><%= t("repositories.copy_record") %></span>
|
||||||
|
</button>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -938,6 +938,7 @@ en:
|
||||||
errors_list_title: "Items were not imported because one or more errors were found:"
|
errors_list_title: "Items were not imported because one or more errors were found:"
|
||||||
no_repository_name: "Item name is required!"
|
no_repository_name: "Item name is required!"
|
||||||
edit_record: "Edit"
|
edit_record: "Edit"
|
||||||
|
copy_record: "Copy"
|
||||||
delete_record: "Delete"
|
delete_record: "Delete"
|
||||||
save_record: "Save"
|
save_record: "Save"
|
||||||
cancel_save: "Cancel"
|
cancel_save: "Cancel"
|
||||||
|
@ -989,6 +990,7 @@ en:
|
||||||
no_records_assigned_flash: "No items were assigned to task"
|
no_records_assigned_flash: "No items were assigned to task"
|
||||||
no_records_unassigned_flash: "No items were unassigned from task"
|
no_records_unassigned_flash: "No items were unassigned from task"
|
||||||
default_column: 'Name'
|
default_column: 'Name'
|
||||||
|
copy_records_report: "%{number} item(s) successfully copied."
|
||||||
|
|
||||||
libraries:
|
libraries:
|
||||||
manange_modal_column:
|
manange_modal_column:
|
||||||
|
|
|
@ -461,6 +461,9 @@ Rails.application.routes.draw do
|
||||||
to: 'repository_rows#delete_records',
|
to: 'repository_rows#delete_records',
|
||||||
as: 'delete_records',
|
as: 'delete_records',
|
||||||
defaults: { format: 'json' }
|
defaults: { format: 'json' }
|
||||||
|
post 'copy_records',
|
||||||
|
to: 'repository_rows#copy_records',
|
||||||
|
defaults: { format: 'json' }
|
||||||
get 'repository_columns/:id/destroy_html',
|
get 'repository_columns/:id/destroy_html',
|
||||||
to: 'repository_columns#destroy_html',
|
to: 'repository_columns#destroy_html',
|
||||||
as: 'columns_destroy_html'
|
as: 'columns_destroy_html'
|
||||||
|
|
|
@ -115,4 +115,12 @@ describe RepositoryRowsController, type: :controller do
|
||||||
end
|
end
|
||||||
end
|
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
|
end
|
||||||
|
|
83
spec/services/repository_actions/duplicate_rows_spec.rb
Normal file
83
spec/services/repository_actions/duplicate_rows_spec.rb
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe RepositoryActions::DuplicateRows do
|
||||||
|
let!(:user) { create :user }
|
||||||
|
let!(:repository) { create :repository }
|
||||||
|
let!(:list_column) do
|
||||||
|
create(:repository_column, name: 'list',
|
||||||
|
repository: repository,
|
||||||
|
created_by: user,
|
||||||
|
data_type: 'RepositoryListValue')
|
||||||
|
end
|
||||||
|
let!(:text_column) do
|
||||||
|
create(:repository_column, name: 'text',
|
||||||
|
repository: repository,
|
||||||
|
created_by: user,
|
||||||
|
data_type: 'RepositoryTextValue')
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#call' do
|
||||||
|
before do
|
||||||
|
@rows_ids = []
|
||||||
|
|
||||||
|
3.times do |index|
|
||||||
|
row = create :repository_row, name: "row (#{index})",
|
||||||
|
repository: repository
|
||||||
|
create :repository_text_value, data: "text (#{index})",
|
||||||
|
repository_cell_attributes: {
|
||||||
|
repository_row: row,
|
||||||
|
repository_column: text_column
|
||||||
|
}
|
||||||
|
create :repository_list_value,
|
||||||
|
repository_list_item: create(:repository_list_item,
|
||||||
|
repository: repository,
|
||||||
|
repository_column: list_column,
|
||||||
|
data: "list item (#{index})"),
|
||||||
|
repository_cell_attributes: {
|
||||||
|
repository_row: row,
|
||||||
|
repository_column: list_column
|
||||||
|
}
|
||||||
|
@rows_ids << row.id.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'generates a duplicate of selected items' do
|
||||||
|
expect(repository.repository_rows.reload.size).to eq 3
|
||||||
|
described_class.new(user, repository, @rows_ids).call
|
||||||
|
expect(repository.repository_rows.reload.size).to eq 6
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'generates an exact duplicate of the row with custom column values' do
|
||||||
|
described_class.new(user, repository, [@rows_ids.first]).call
|
||||||
|
duplicated_row = repository.repository_rows.order('created_at ASC').last
|
||||||
|
expect(duplicated_row.name).to eq 'row (0) (1)'
|
||||||
|
duplicated_row.repository_cells.each do |cell|
|
||||||
|
if cell.value_type == 'RepositoryListValue'
|
||||||
|
expect(cell.value.data).to eq 'list item (0)'
|
||||||
|
else
|
||||||
|
expect(cell.value.data).to eq 'text (0)'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'prevents to duplicate items that do not already belong to repository' do
|
||||||
|
new_repository = create :repository, name: 'new repo'
|
||||||
|
new_row = create :repository_row, name: 'other row',
|
||||||
|
repository: new_repository
|
||||||
|
described_class.new(user, repository, [new_row.id]).call
|
||||||
|
expect(repository.repository_rows.reload.size).to eq 3
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the number of duplicated items' do
|
||||||
|
service_obj = described_class.new(user, repository, @rows_ids)
|
||||||
|
service_obj.call
|
||||||
|
expect(service_obj.number_of_duplicated_items).to eq 3
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the number of duplicated items' do
|
||||||
|
service_obj = described_class.new(user, repository, [])
|
||||||
|
service_obj.call
|
||||||
|
expect(service_obj.number_of_duplicated_items).to eq 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Add table
Reference in a new issue