Implemented issue #2064 - Add delete existing records to CSV import feature.

This commit is contained in:
Martin Edenhofer 2018-06-12 22:58:59 +02:00
parent 4b8e615640
commit 8af6e2b9eb
15 changed files with 198 additions and 24 deletions

View file

@ -663,6 +663,7 @@ class App.ControllerModal extends App.Controller
large: false large: false
small: false small: false
head: '?' head: '?'
autoFocusOnFirstInput: true
container: null container: null
buttonClass: 'btn--success' buttonClass: 'btn--success'
centerButtons: [] centerButtons: []
@ -812,7 +813,8 @@ class App.ControllerModal extends App.Controller
@onShown(e) @onShown(e)
onShown: (e) => onShown: (e) =>
@$('input:not([disabled]):not([type="hidden"]):not(".btn"), textarea').first().focus() if @autoFocusOnFirstInput
@$('input:not([disabled]):not([type="hidden"]):not(".btn"), textarea').first().focus()
@initalFormParams = @formParams() @initalFormParams = @formParams()
localOnClose: (e) => localOnClose: (e) =>

View file

@ -1285,6 +1285,7 @@ class App.Import extends App.ControllerModal
buttonClose: true buttonClose: true
buttonCancel: true buttonCancel: true
buttonSubmit: 'Import' buttonSubmit: 'Import'
autoFocusOnFirstInput: false
head: 'Import' head: 'Import'
large: true large: true
templateDirectory: 'generic/object_import' templateDirectory: 'generic/object_import'
@ -1296,6 +1297,7 @@ class App.Import extends App.ControllerModal
content = $(App.view("#{@templateDirectory}/index")( content = $(App.view("#{@templateDirectory}/index")(
head: 'Import' head: 'Import'
import_example_url: "#{@baseUrl}/import_example" import_example_url: "#{@baseUrl}/import_example"
deleteOption: @deleteOption
)) ))
# check if data is processing... # check if data is processing...
@ -1342,6 +1344,7 @@ class App.ImportTryResult extends App.ControllerModal
buttonClose: true buttonClose: true
buttonCancel: true buttonCancel: true
buttonSubmit: 'Yes, start real import.' buttonSubmit: 'Yes, start real import.'
autoFocusOnFirstInput: false
head: 'Import' head: 'Import'
large: true large: true
templateDirectory: 'generic/object_import/' templateDirectory: 'generic/object_import/'
@ -1386,6 +1389,7 @@ class App.ImportResult extends App.ControllerModal
buttonClose: true buttonClose: true
buttonCancel: true buttonCancel: true
buttonSubmit: 'Close' buttonSubmit: 'Close'
autoFocusOnFirstInput: false
head: 'Import' head: 'Import'
large: true large: true
templateDirectory: 'generic/object_import/' templateDirectory: 'generic/object_import/'

View file

@ -12,6 +12,7 @@ class Index extends App.ControllerSubContent
new App.Import( new App.Import(
baseUrl: '/api/v1/text_modules' baseUrl: '/api/v1/text_modules'
container: @el.closest('.content') container: @el.closest('.content')
deleteOption: true
) )
pageData: pageData:
home: 'text_modules' home: 'text_modules'

View file

@ -14,10 +14,10 @@
<h2><input checked="checked" disabled="disabled" type="checkbox" name="update" value="true"> <%- @T('Update existing records') %></h2> <h2><input checked="checked" disabled="disabled" type="checkbox" name="update" value="true"> <%- @T('Update existing records') %></h2>
<%- @T('Update existing records with the attributes specified in the import data.') %> <%- @T('Update existing records with the attributes specified in the import data.') %>
<!-- <% if @deleteOption is true: %>
<h2><input checked="" type="checkbox" name="delete" value="true"> <%- @T('Delete records') %></h2> <h2><input type="checkbox" name="delete" value="true"> <%- @T('Delete records') %></h2>
<%- @T('Delete all existigs records first.') %> <%- @T('Delete all existing records first.') %>
--> <% end %>
<h2><%- @T('Select CSV file') %></h2> <h2><%- @T('Select CSV file') %></h2>
<input name="file" type="file"> <input name="file" type="file">

View file

@ -354,6 +354,7 @@ curl http://localhost/api/v1/organization/{id} -v -u #{login}:#{password} -H "Co
col_sep: params[:col_sep] || ',', col_sep: params[:col_sep] || ',',
}, },
try: params[:try], try: params[:try],
delete: params[:delete],
) )
render json: result, status: :ok render json: result, status: :ok
end end

View file

@ -193,6 +193,7 @@ curl http://localhost/api/v1/text_modules.json -v -u #{login}:#{password} -H "Co
col_sep: params[:col_sep] || ',', col_sep: params[:col_sep] || ',',
}, },
try: params[:try], try: params[:try],
delete: params[:delete],
) )
render json: result, status: :ok render json: result, status: :ok
end end

View file

@ -1107,6 +1107,7 @@ curl http://localhost/api/v1/users/avatar -v -u #{login}:#{password} -H "Content
col_sep: params[:col_sep] || ',', col_sep: params[:col_sep] || ',',
}, },
try: params[:try], try: params[:try],
delete: params[:delete],
) )
render json: result, status: :ok render json: result, status: :ok
end end

View file

@ -16,6 +16,7 @@ module CanCsvImport
col_sep: ',', col_sep: ',',
}, },
try: true, try: true,
delete: false,
) )
result = Model.csv_import( result = Model.csv_import(
@ -24,6 +25,7 @@ module CanCsvImport
col_sep: ',', col_sep: ',',
}, },
try: true, try: true,
delete: false,
) )
result = TextModule.csv_import( result = TextModule.csv_import(
@ -32,6 +34,7 @@ module CanCsvImport
col_sep: ',', col_sep: ',',
}, },
try: false, try: false,
delete: false,
) )
returns returns
@ -45,7 +48,25 @@ returns
=end =end
def csv_import(data) def csv_import(data)
try = true
if data[:try] != 'true' && data[:try] != true
try = false
end
delete = false
if data[:delete] == true || data[:delete] == 'true'
delete = true
end
errors = [] errors = []
if delete == true && @csv_delete_possible != true
errors.push "Delete is not possible for #{new.class}."
result = {
errors: errors,
try: try,
result: 'failed',
}
return result
end
if data[:file].present? if data[:file].present?
raise Exceptions::UnprocessableEntity, "No such file '#{data[:file]}'" if !File.exist?(data[:file]) raise Exceptions::UnprocessableEntity, "No such file '#{data[:file]}'" if !File.exist?(data[:file])
@ -60,7 +81,7 @@ returns
errors.push "Unable to parse empty file/string for #{new.class}." errors.push "Unable to parse empty file/string for #{new.class}."
result = { result = {
errors: errors, errors: errors,
try: data[:try], try: try,
result: 'failed', result: 'failed',
} }
return result return result
@ -72,7 +93,7 @@ returns
errors.push "Unable to parse file/string without header for #{new.class}." errors.push "Unable to parse file/string without header for #{new.class}."
result = { result = {
errors: errors, errors: errors,
try: data[:try], try: try,
result: 'failed', result: 'failed',
} }
return result return result
@ -89,7 +110,7 @@ returns
errors.push "No records found in file/string for #{new.class}." errors.push "No records found in file/string for #{new.class}."
result = { result = {
errors: errors, errors: errors,
try: data[:try], try: try,
result: 'failed', result: 'failed',
} }
return result return result
@ -127,13 +148,22 @@ returns
payload.push attributes payload.push attributes
end end
# create or update records
csv_object_ids_ignored = @csv_object_ids_ignored || []
records = []
stats = { stats = {
created: 0, created: 0,
updated: 0, updated: 0,
} }
# delete
if delete == true
stats[:deleted] = self.count
if try == false
destroy_all
end
end
# create or update records
csv_object_ids_ignored = @csv_object_ids_ignored || []
records = []
line_count = 0 line_count = 0
payload.each do |attributes| payload.each do |attributes|
line_count += 1 line_count += 1
@ -166,7 +196,7 @@ returns
# create object # create object
Transaction.execute(disable_notification: true, reset_user_id: true) do Transaction.execute(disable_notification: true, reset_user_id: true) do
UserInfo.current_user_id = clean_params[:updated_by_id] || clean_params[:created_by_id] UserInfo.current_user_id = clean_params[:updated_by_id] || clean_params[:created_by_id]
if !record if !record || delete == true
stats[:created] += 1 stats[:created] += 1
begin begin
csv_verify_attributes(clean_params) csv_verify_attributes(clean_params)
@ -177,7 +207,7 @@ returns
clean_params[:updated_by_id] = 1 clean_params[:updated_by_id] = 1
end end
record = new(clean_params) record = new(clean_params)
next if data[:try] == 'true' || data[:try] == true next if try == true
record.associations_from_param(attributes) record.associations_from_param(attributes)
record.save! record.save!
rescue => e rescue => e
@ -186,7 +216,7 @@ returns
end end
else else
stats[:updated] += 1 stats[:updated] += 1
next if data[:try] == 'true' || data[:try] == true next if try == true
begin begin
csv_verify_attributes(clean_params) csv_verify_attributes(clean_params)
clean_params = param_cleanup(clean_params) clean_params = param_cleanup(clean_params)
@ -218,7 +248,7 @@ returns
stats: stats, stats: stats,
records: records, records: records,
errors: errors, errors: errors,
try: data[:try], try: try,
result: result, result: result,
} }
@ -371,5 +401,20 @@ end
@csv_attributes_ignored = attributes @csv_attributes_ignored = attributes
end end
=begin
serve methode to define if delete option is possible or not
class Model < ApplicationModel
include CanCsvImport
csv_delete_possible true
end
=end
def csv_delete_possible(value)
@csv_delete_possible = value
end
end end
end end

View file

@ -13,6 +13,8 @@ class TextModule < ApplicationModel
sanitized_html :content sanitized_html :content
csv_delete_possible true
=begin =begin
load text modules from online load text modules from online

View file

@ -538,7 +538,7 @@ class OrganizationControllerTest < ActionDispatch::IntegrationTest
result = JSON.parse(@response.body) result = JSON.parse(@response.body)
assert_equal(Hash, result.class) assert_equal(Hash, result.class)
assert_equal('true', result['try']) assert_equal(true, result['try'])
assert_equal(2, result['records'].count) assert_equal(2, result['records'].count)
assert_equal('failed', result['result']) assert_equal('failed', result['result'])
assert_equal(2, result['errors'].count) assert_equal(2, result['errors'].count)
@ -553,7 +553,7 @@ class OrganizationControllerTest < ActionDispatch::IntegrationTest
result = JSON.parse(@response.body) result = JSON.parse(@response.body)
assert_equal(Hash, result.class) assert_equal(Hash, result.class)
assert_equal('true', result['try']) assert_equal(true, result['try'])
assert_equal(2, result['records'].count) assert_equal(2, result['records'].count)
assert_equal('success', result['result']) assert_equal('success', result['result'])
@ -568,7 +568,7 @@ class OrganizationControllerTest < ActionDispatch::IntegrationTest
result = JSON.parse(@response.body) result = JSON.parse(@response.body)
assert_equal(Hash, result.class) assert_equal(Hash, result.class)
assert_nil(result['try']) assert_equal(false, result['try'])
assert_equal(2, result['records'].count) assert_equal(2, result['records'].count)
assert_equal('success', result['result']) assert_equal('success', result['result'])

View file

@ -108,7 +108,7 @@ class TextModuleControllerTest < ActionDispatch::IntegrationTest
result = JSON.parse(@response.body) result = JSON.parse(@response.body)
assert_equal(Hash, result.class) assert_equal(Hash, result.class)
assert_equal('true', result['try']) assert_equal(true, result['try'])
assert_equal(2, result['records'].count) assert_equal(2, result['records'].count)
assert_equal('failed', result['result']) assert_equal('failed', result['result'])
assert_equal(2, result['errors'].count) assert_equal(2, result['errors'].count)
@ -123,7 +123,7 @@ class TextModuleControllerTest < ActionDispatch::IntegrationTest
result = JSON.parse(@response.body) result = JSON.parse(@response.body)
assert_equal(Hash, result.class) assert_equal(Hash, result.class)
assert_equal('true', result['try']) assert_equal(true, result['try'])
assert_equal(2, result['records'].count) assert_equal(2, result['records'].count)
assert_equal('success', result['result']) assert_equal('success', result['result'])
@ -138,7 +138,7 @@ class TextModuleControllerTest < ActionDispatch::IntegrationTest
result = JSON.parse(@response.body) result = JSON.parse(@response.body)
assert_equal(Hash, result.class) assert_equal(Hash, result.class)
assert_nil(result['try']) assert_equal(false, result['try'])
assert_equal(2, result['records'].count) assert_equal(2, result['records'].count)
assert_equal('success', result['result']) assert_equal('success', result['result'])

View file

@ -985,7 +985,7 @@ class UserControllerTest < ActionDispatch::IntegrationTest
result = JSON.parse(@response.body) result = JSON.parse(@response.body)
assert_equal(Hash, result.class) assert_equal(Hash, result.class)
assert_equal('true', result['try']) assert_equal(true, result['try'])
assert_equal(2, result['records'].count) assert_equal(2, result['records'].count)
assert_equal('failed', result['result']) assert_equal('failed', result['result'])
assert_equal(2, result['errors'].count) assert_equal(2, result['errors'].count)
@ -1000,7 +1000,7 @@ class UserControllerTest < ActionDispatch::IntegrationTest
result = JSON.parse(@response.body) result = JSON.parse(@response.body)
assert_equal(Hash, result.class) assert_equal(Hash, result.class)
assert_equal('true', result['try']) assert_equal(true, result['try'])
assert_equal(2, result['records'].count) assert_equal(2, result['records'].count)
assert_equal('success', result['result']) assert_equal('success', result['result'])
@ -1015,7 +1015,7 @@ class UserControllerTest < ActionDispatch::IntegrationTest
result = JSON.parse(@response.body) result = JSON.parse(@response.body)
assert_equal(Hash, result.class) assert_equal(Hash, result.class)
assert_nil(result['try']) assert_equal(false, result['try'])
assert_equal(2, result['records'].count) assert_equal(2, result['records'].count)
assert_equal('success', result['result']) assert_equal('success', result['result'])

View file

@ -234,4 +234,20 @@ class OrganizationCsvImportTest < ActiveSupport::TestCase
assert_nil(Organization.find_by(name: 'organization-invalid-import2')) assert_nil(Organization.find_by(name: 'organization-invalid-import2'))
end end
test 'simple import with delete' do
csv_string = "id;name;shared;domain;domain_assignment;active;note\n;org-simple-import1;true;org-simple-import1.example.com;false;true;some note1\n;org-simple-import2;true;org-simple-import2.example.com;false;false;some note2\n"
result = Organization.csv_import(
string: csv_string,
parse_params: {
col_sep: ';',
},
try: true,
delete: true,
)
assert_equal(true, result[:try])
assert_equal('failed', result[:result])
assert_equal('Delete is not possible for Organization.', result[:errors][0])
end
end end

View file

@ -51,6 +51,13 @@ class TextModuleCsvImportTest < ActiveSupport::TestCase
end end
test 'simple import' do test 'simple import' do
TextModule.create!(
name: 'nsome name1',
content: 'nsome name1',
active: true,
updated_by_id: 1,
created_by_id: 1,
)
csv_string = "name;keywords;content;note;active;\nsome name1;keyword1;\"some\ncontent1\";-;\nsome name2;keyword2;some content<br>test123\n" csv_string = "name;keywords;content;note;active;\nsome name1;keyword1;\"some\ncontent1\";-;\nsome name2;keyword2;some content<br>test123\n"
result = TextModule.csv_import( result = TextModule.csv_import(
@ -97,4 +104,82 @@ class TextModuleCsvImportTest < ActiveSupport::TestCase
text_module2.destroy! text_module2.destroy!
end end
test 'simple import with delete' do
assert_equal(0, TextModule.count)
TextModule.create!(
name: 'some name1',
content: 'some name1',
active: true,
updated_by_id: 1,
created_by_id: 1,
)
TextModule.create!(
name: 'name should be deleted 2',
content: 'name should be deleted 1',
active: true,
updated_by_id: 1,
created_by_id: 1,
)
csv_string = "name;keywords;content;note;active;\nsome name1;keyword1;\"some\ncontent1\";-;\nsome name2;keyword2;some content<br>test123\n"
result = TextModule.csv_import(
string: csv_string,
parse_params: {
col_sep: ';',
},
try: true,
delete: true,
)
assert_equal(true, result[:try])
assert(result[:stats])
assert_equal(2, result[:stats][:created])
assert_equal(0, result[:stats][:updated])
assert_equal(2, result[:stats][:deleted])
assert_equal(2, result[:records].count)
assert_equal('success', result[:result])
assert(TextModule.find_by(name: 'some name1'))
assert_nil(TextModule.find_by(name: 'some name2'))
result = TextModule.csv_import(
string: csv_string,
parse_params: {
col_sep: ';',
},
try: false,
delete: true,
)
assert_equal(false, result[:try])
assert(result[:stats])
assert_equal(2, result[:stats][:created])
assert_equal(0, result[:stats][:updated])
assert_equal(2, result[:stats][:deleted])
assert_equal(2, result[:records].count)
assert_equal('success', result[:result])
assert_equal(2, TextModule.count)
text_module1 = TextModule.find_by(name: 'some name1')
assert(text_module1)
assert_equal(text_module1.name, 'some name1')
assert_equal(text_module1.keywords, 'keyword1')
assert_equal(text_module1.content, 'some<br>content1')
assert_equal(text_module1.active, true)
text_module2 = TextModule.find_by(name: 'some name2')
assert(text_module2)
assert_equal(text_module2.name, 'some name2')
assert_equal(text_module2.keywords, 'keyword2')
assert_equal(text_module2.content, 'some content<br>test123')
assert_equal(text_module2.active, true)
assert_nil(TextModule.find_by(name: 'name should be deleted 2'))
text_module1.destroy!
text_module2.destroy!
end
end end

View file

@ -441,4 +441,20 @@ class UserCsvImportTest < ActiveSupport::TestCase
orgaization2.destroy! orgaization2.destroy!
end end
test 'simple import with delete' do
csv_string = "login;firstname;lastname;email\nuser-simple-import-fixed1;firstname-simple-import-fixed1;lastname-simple-import-fixed1;user-simple-import-fixed1@example.com\nuser-simple-import-fixed2;firstname-simple-import-fixed2;lastname-simple-import-fixed2;user-simple-import-fixed2@example.com\n"
result = User.csv_import(
string: csv_string,
parse_params: {
col_sep: ';',
},
try: true,
delete: true,
)
assert_equal(true, result[:try])
assert_equal('failed', result[:result])
assert_equal('Delete is not possible for User.', result[:errors][0])
end
end end