chore: Contact import improvements (CW-1362) (#6747)

Fixes: https://linear.app/chatwoot/issue/CW-1362/csv-imports-are-not-working-properly
Fixes: #3462

---------

Co-authored-by: Sojan <sojan@pepalo.com>
This commit is contained in:
Tejaswini Chile
2023-04-18 00:40:55 +05:30
committed by GitHub
parent 4505c5dda3
commit 905e77048f
9 changed files with 256 additions and 50 deletions

View File

@@ -1,5 +1,5 @@
id,first_name,last_name,email,gender,ip_address,identifier,phone_number
1,Clarice,Uzzell,cuzzell0@mozilla.org,Genderfluid,70.61.11.201,bb4e11cd-0f23-49da-a123-dcc1fec6852c,+918080808080
id,first_name,last_name,email,gender,ip_address,identifier,phone_number,company
1,Clarice,Uzzell,cuzzell0@mozilla.org,Genderfluid,70.61.11.201,bb4e11cd-0f23-49da-a123-dcc1fec6852c,+918080808080,My Company Name
2,Marieann,Creegan,mcreegan1@cornell.edu,Genderfluid,168.186.4.241,e60bab4c-9fbb-47eb-8f75-42025b789c47,+918080808081
3,Nancey,Windibank,nwindibank2@bluehost.com,Agender,73.44.41.59,f793e813-4210-4bf3-a812-711418de25d2,+918080808082
4,Sibel,Stennine,sstennine3@yellowbook.com,Genderqueer,115.249.27.155,d6e35a2d-d093-4437-a577-7df76316b937,+918080808083
1 id id,first_name,last_name,email,gender,ip_address,identifier,phone_number,company first_name last_name email gender ip_address identifier phone_number
2 1 1,Clarice,Uzzell,cuzzell0@mozilla.org,Genderfluid,70.61.11.201,bb4e11cd-0f23-49da-a123-dcc1fec6852c,+918080808080,My Company Name Clarice Uzzell cuzzell0@mozilla.org Genderfluid 70.61.11.201 bb4e11cd-0f23-49da-a123-dcc1fec6852c +918080808080
3 2 2,Marieann,Creegan,mcreegan1@cornell.edu,Genderfluid,168.186.4.241,e60bab4c-9fbb-47eb-8f75-42025b789c47,+918080808081 Marieann Creegan mcreegan1@cornell.edu Genderfluid 168.186.4.241 e60bab4c-9fbb-47eb-8f75-42025b789c47 +918080808081
4 3 3,Nancey,Windibank,nwindibank2@bluehost.com,Agender,73.44.41.59,f793e813-4210-4bf3-a812-711418de25d2,+918080808082 Nancey Windibank nwindibank2@bluehost.com Agender 73.44.41.59 f793e813-4210-4bf3-a812-711418de25d2 +918080808082
5 4 4,Sibel,Stennine,sstennine3@yellowbook.com,Genderqueer,115.249.27.155,d6e35a2d-d093-4437-a577-7df76316b937,+918080808083 Sibel Stennine sstennine3@yellowbook.com Genderqueer 115.249.27.155 d6e35a2d-d093-4437-a577-7df76316b937 +918080808083

View File

@@ -5,18 +5,104 @@ RSpec.describe DataImportJob, type: :job do
let!(:data_import) { create(:data_import) }
it 'queues the job' do
expect { job }.to have_enqueued_job(described_class)
.with(data_import)
.on_queue('low')
describe 'enqueueing the job' do
it 'queues the job on the low priority queue' do
expect { job }.to have_enqueued_job(described_class)
.with(data_import)
.on_queue('low')
end
end
it 'imports data into the account' do
csv_length = CSV.parse(data_import.import_file.download, headers: true).length
described_class.perform_now(data_import)
expect(data_import.account.contacts.count).to eq(csv_length)
expect(data_import.reload.total_records).to eq(csv_length)
expect(data_import.reload.processed_records).to eq(csv_length)
expect(Contact.find_by(phone_number: '+918080808080')).to be_truthy
describe 'importing data' do
context 'when the data is valid' do
it 'imports data into the account' do
csv_length = CSV.parse(data_import.import_file.download, headers: true).length
described_class.perform_now(data_import)
expect(data_import.account.contacts.count).to eq(csv_length)
expect(data_import.reload.total_records).to eq(csv_length)
expect(data_import.reload.processed_records).to eq(csv_length)
contact = Contact.find_by(phone_number: '+918080808080')
expect(contact).to be_truthy
expect(contact['additional_attributes']['company']).to eq('My Company Name')
end
end
context 'when the data contains errors' do
it 'imports erroneous data into the account, skipping invalid records' do
# Last record is invalid because of duplicate email
invalid_data = [
%w[id first_name last_name email phone_number],
['1', 'Clarice', 'Uzzell', 'cuzzell0@mozilla.org', '+918484848484'],
['2', 'Marieann', 'Creegan', 'mcreegan1@cornell.edu', '+918484848485'],
['3', 'Nancey', 'Windibank', 'cuzzell0@mozilla.org', '+91848484848']
]
invalid_data_import = create(:data_import, import_file: generate_csv_file(invalid_data))
csv_data = CSV.parse(invalid_data_import.import_file.download, headers: true)
csv_length = csv_data.length
described_class.perform_now(invalid_data_import)
expect(invalid_data_import.account.contacts.count).to eq(csv_length - 1)
expect(invalid_data_import.reload.total_records).to eq(csv_length)
expect(invalid_data_import.reload.processed_records).to eq(csv_length)
end
end
context 'when the data contains existing records' do
let(:existing_data) do
[
%w[id first_name last_name email phone_number],
['1', 'Clarice', 'Uzzell', 'cuzzell0@mozilla.org', '+918080808080'],
['2', 'Marieann', 'Creegan', 'mcreegan1@cornell.edu', '+918080808081'],
['3', 'Nancey', 'Windibank', 'nwindibank2@bluehost.com', '+918080808082']
]
end
let(:existing_data_import) { create(:data_import, import_file: generate_csv_file(existing_data)) }
let(:csv_data) { CSV.parse(existing_data_import.import_file.download, headers: true) }
context 'when the existing record has an email in import data' do
it 'updates the existing record with new data' do
contact = Contact.create!(email: csv_data[0]['email'], account_id: existing_data_import.account_id)
expect(contact.reload.phone_number).to be_nil
csv_length = csv_data.length
described_class.perform_now(existing_data_import)
expect(existing_data_import.account.contacts.count).to eq(csv_length)
expect(Contact.find_by(email: csv_data[0]['email']).phone_number).to eq(csv_data[0]['phone_number'])
expect(Contact.where(email: csv_data[0]['email']).count).to eq(1)
end
end
context 'when the existing record has a phone_number in import data' do
it 'updates the existing record with new data' do
contact = Contact.create!(account_id: existing_data_import.account_id, phone_number: csv_data[1]['phone_number'])
expect(contact.reload.email).to be_nil
csv_length = csv_data.length
described_class.perform_now(existing_data_import)
expect(existing_data_import.account.contacts.count).to eq(csv_length)
expect(Contact.find_by(phone_number: csv_data[1]['phone_number']).email).to eq(csv_data[1]['email'])
expect(Contact.where(phone_number: csv_data[1]['phone_number']).count).to eq(1)
end
end
context 'when the existing record has both email and phone_number in import data' do
it 'skips importing the records' do
phone_contact = Contact.create!(account_id: existing_data_import.account_id, phone_number: csv_data[1]['phone_number'])
email_contact = Contact.create!(account_id: existing_data_import.account_id, email: csv_data[1]['email'])
csv_length = csv_data.length
described_class.perform_now(existing_data_import)
expect(phone_contact.reload.email).to be_nil
expect(email_contact.reload.phone_number).to be_nil
expect(existing_data_import.total_records).to eq(csv_length)
expect(existing_data_import.processed_records).to eq(csv_length - 1)
end
end
end
end
end

View File

@@ -69,6 +69,7 @@ RSpec.configure do |config|
# config.filter_gems_from_backtrace("gem name")
config.include SlackStubs
config.include FileUploadHelpers
config.include CsvSpecHelpers
config.include Devise::Test::IntegrationHelpers, type: :request
config.include ActiveSupport::Testing::TimeHelpers
config.include ActionCable::TestHelper

View File

@@ -0,0 +1,18 @@
module CsvSpecHelpers
# Generates a Rack::Test::UploadedFile object from an array of arrays
# data: Accepts an array of arrays as the only argument
def generate_csv_file(data)
# Create a temporary file
temp_file = Tempfile.new(['data', '.csv'])
# Write the array of arrays to the temporary file as CSV
CSV.open(temp_file.path, 'wb') do |csv|
data.each do |row|
csv << row
end
end
# Create and return a Rack::Test::UploadedFile object
Rack::Test::UploadedFile.new(temp_file.path, 'text/csv')
end
end