diff --git a/app/jobs/data_import_job.rb b/app/jobs/data_import_job.rb index 6146336fa..c4f70b2d6 100644 --- a/app/jobs/data_import_job.rb +++ b/app/jobs/data_import_job.rb @@ -20,15 +20,18 @@ class DataImportJob < ApplicationJob def process_import_file @data_import.update!(status: :processing) - contacts, rejected_contacts = parse_csv_and_build_contacts + identifiable_contacts, non_identifiable_contacts, rejected_contacts = parse_csv_and_build_contacts - import_contacts(contacts) - update_data_import_status(contacts.length, rejected_contacts.length) + all_valid_contacts = identifiable_contacts + non_identifiable_contacts + import_contacts(all_valid_contacts) + update_data_import_status(identifiable_contacts.length, non_identifiable_contacts.length, rejected_contacts.length) save_failed_records_csv(rejected_contacts) end + # rubocop:disable Metrics/MethodLength def parse_csv_and_build_contacts - contacts = [] + identifiable_contacts = [] + non_identifiable_contacts = [] rejected_contacts = [] # Ensuring that importing non utf-8 characters will not throw error data = @data_import.import_file.download @@ -42,14 +45,21 @@ class DataImportJob < ApplicationJob csv.each do |row| current_contact = @contact_manager.build_contact(row.to_h.with_indifferent_access) if current_contact.valid? - contacts << current_contact + is_identifiable = @contact_manager.contact_identifiable?(current_contact) + + if is_identifiable + identifiable_contacts << current_contact + else + non_identifiable_contacts << current_contact + end else append_rejected_contact(row, current_contact, rejected_contacts) end end - [contacts, rejected_contacts] + [identifiable_contacts, non_identifiable_contacts, rejected_contacts] end + # rubocop:enable Metrics/MethodLength def append_rejected_contact(row, contact, rejected_contacts) row['errors'] = contact.errors.full_messages.join(', ') @@ -61,8 +71,15 @@ class DataImportJob < ApplicationJob Contact.import(contacts, synchronize: contacts, on_duplicate_key_ignore: true, track_validation_failures: true, validate: true, batch_size: 1000) end - def update_data_import_status(processed_records, rejected_records) - @data_import.update!(status: :completed, processed_records: processed_records, total_records: processed_records + rejected_records) + def update_data_import_status(identifiable_records, non_identifiable_records, rejected_records) + total_records = identifiable_records + non_identifiable_records + rejected_records + processed_records = identifiable_records + non_identifiable_records + @data_import.update!( + status: :completed, + processed_records: processed_records, + non_identifiable_records: non_identifiable_records, + total_records: total_records + ) end def save_failed_records_csv(rejected_contacts) diff --git a/app/mailers/administrator_notifications/account_notification_mailer.rb b/app/mailers/administrator_notifications/account_notification_mailer.rb index 5ac753372..67b3b2664 100644 --- a/app/mailers/administrator_notifications/account_notification_mailer.rb +++ b/app/mailers/administrator_notifications/account_notification_mailer.rb @@ -32,9 +32,16 @@ class AdministratorNotifications::AccountNotificationMailer < AdministratorNotif "#{ENV.fetch('FRONTEND_URL', nil)}/app/accounts/#{resource.account.id}/contacts" end + # Calculate metrics with backward compatibility + non_identifiable_count = resource.respond_to?(:non_identifiable_records) ? resource.non_identifiable_records : 0 + identifiable_count = resource.processed_records - non_identifiable_count + failed_count = resource.total_records - resource.processed_records + meta = { - 'failed_contacts' => resource.total_records - resource.processed_records, - 'imported_contacts' => resource.processed_records + 'identifiable_contacts' => identifiable_count, + 'non_identifiable_contacts' => non_identifiable_count, + 'failed_contacts' => failed_count, + 'total_contacts' => resource.total_records } send_notification(subject, action_url: action_url, meta: meta) diff --git a/app/models/data_import.rb b/app/models/data_import.rb index a44650a22..4df150464 100644 --- a/app/models/data_import.rb +++ b/app/models/data_import.rb @@ -2,15 +2,16 @@ # # Table name: data_imports # -# id :bigint not null, primary key -# data_type :string not null -# processed_records :integer -# processing_errors :text -# status :integer default("pending"), not null -# total_records :integer -# created_at :datetime not null -# updated_at :datetime not null -# account_id :bigint not null +# id :bigint not null, primary key +# data_type :string not null +# non_identifiable_records :integer default(0), not null +# processed_records :integer +# processing_errors :text +# status :integer default("pending"), not null +# total_records :integer +# created_at :datetime not null +# updated_at :datetime not null +# account_id :bigint not null # # Indexes # diff --git a/app/services/data_import/contact_manager.rb b/app/services/data_import/contact_manager.rb index 460a83726..a41863c97 100644 --- a/app/services/data_import/contact_manager.rb +++ b/app/services/data_import/contact_manager.rb @@ -9,6 +9,10 @@ class DataImport::ContactManager contact end + def contact_identifiable?(contact) + contact.email.present? || contact.phone_number.present? || contact.identifier.present? + end + def find_or_initialize_contact(params) contact = find_existing_contact(params) contact_params = params.slice(:email, :identifier, :phone_number) diff --git a/app/views/mailers/administrator_notifications/account_notification_mailer/contact_import_complete.liquid b/app/views/mailers/administrator_notifications/account_notification_mailer/contact_import_complete.liquid index 776077214..fa6079c6a 100644 --- a/app/views/mailers/administrator_notifications/account_notification_mailer/contact_import_complete.liquid +++ b/app/views/mailers/administrator_notifications/account_notification_mailer/contact_import_complete.liquid @@ -1,10 +1,21 @@
Hello,
-Your contact import has been completed. Please check the contacts tab to view the imported contacts.
+Your contact import has been completed. Here's a summary of the results:
-Number of records imported: {{meta['imported_contacts']}}
+Contacts imported successfully: {{meta['identifiable_contacts']}}
+These contacts are visible in your contacts list and can be used for conversations.
-Number of records failed: {{meta['failed_contacts']}}
+{% if meta['non_identifiable_contacts'] > 0 %} +Contacts created but not visible: {{meta['non_identifiable_contacts']}}
+These contacts were created successfully but don't have email, phone number, or identifier, so they won't appear in your contacts list until you add this information.
+{% endif %} + +{% if meta['failed_contacts'] > 0 %} +Contacts that failed to import: {{meta['failed_contacts']}}
+These contacts had validation errors and could not be imported.
+{% endif %} + +Total contacts processed: {{meta['total_contacts']}}
{% if meta['failed_contacts'] == 0 %}@@ -12,6 +23,6 @@
{% else %}- Click here to view failed records. + Click here to download the failed records and see specific error messages.
{% endif %} diff --git a/db/migrate/20251024000001_add_non_identifiable_records_to_data_imports.rb b/db/migrate/20251024000001_add_non_identifiable_records_to_data_imports.rb new file mode 100644 index 000000000..8830ef3f4 --- /dev/null +++ b/db/migrate/20251024000001_add_non_identifiable_records_to_data_imports.rb @@ -0,0 +1,5 @@ +class AddNonIdentifiableRecordsToDataImports < ActiveRecord::Migration[7.0] + def change + add_column :data_imports, :non_identifiable_records, :integer, default: 0, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index c0d539f6a..922c8f444 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2025_10_03_091242) do +ActiveRecord::Schema[7.1].define(version: 2025_10_25_033349) do # These extensions should be enabled to support this database enable_extension "pg_stat_statements" enable_extension "pg_trgm" @@ -784,6 +784,7 @@ ActiveRecord::Schema[7.1].define(version: 2025_10_03_091242) do t.integer "processed_records" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "non_identifiable_records", default: 0, null: false t.index ["account_id"], name: "index_data_imports_on_account_id" end