Merge pull request #1086 from ZmagoD/zd_SCI_2207

enables copying of repository items [fixes SCI-2207]
This commit is contained in:
Zmago Devetak 2018-04-19 17:13:43 +02:00 committed by GitHub
commit 1290435372
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 313 additions and 7 deletions

View file

@ -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
global.onClickEdit = function() {
if (rowsSelected.length !== 1) {
@ -775,6 +798,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);
@ -812,6 +837,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');

View file

@ -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
@ -283,6 +285,17 @@ class RepositoryRowsController < ApplicationController
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
def load_info_modal_vars

View 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

View 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

View file

@ -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') %>"

View file

@ -106,12 +106,11 @@
<!-- These buttons are appended to table in javascript, after table initialization -->
<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) %>
<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"
id="deleteRepositoryRecordsButton" data-target="#deleteRepositoryRecord" data-toggle="modal" disabled>
<span class="glyphicon glyphicon-trash"></span>
@ -119,6 +118,10 @@
<%= submit_tag I18n.t('repositories.delete_record'), :class => "hidden
delete_repository_records_submit" %>
</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 %>
</div>

View file

@ -938,6 +938,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"
@ -989,6 +990,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:

View file

@ -461,6 +461,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'

View file

@ -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

View 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