From 7725661a9623c73f837927ef5600e9cdc3165d42 Mon Sep 17 00:00:00 2001 From: Number Eight <55629655+CylonN8@users.noreply.github.com> Date: Thu, 19 Feb 2026 21:54:44 +0100 Subject: [PATCH] fix: Enable Banking DNS issues and provide better UI sync feedback (#1021) * fix(docker): add explicit DNS config to fix enable banking sync * fix(enable-banking): surface sync errors in the UI * fix: add spaces inside array brackets for RuboCop * fix(enable-banking): surface sync errors and partial failures in UI --- app/controllers/accounts_controller.rb | 2 ++ .../enable_banking_items/maps_helper.rb | 9 ++---- app/models/enable_banking_item/importer.rb | 31 +++++++++++++++++-- .../sync_complete_event.rb | 20 ++++++++++-- app/models/enable_banking_item/syncer.rb | 11 +++++++ .../_enable_banking_item.html.erb | 19 +++++++++--- .../providers/_enable_banking_panel.html.erb | 14 ++++++++- compose.example.ai.yml | 6 ++++ compose.example.yml | 6 ++++ config/locales/views/settings/en.yml | 2 ++ 10 files changed, 102 insertions(+), 18 deletions(-) diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index d0b69547b..4258e86ad 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -237,9 +237,11 @@ class AccountsController < ApplicationController # Enable Banking sync stats @enable_banking_sync_stats_map = {} + @enable_banking_latest_sync_error_map = {} @enable_banking_items.each do |item| latest_sync = item.syncs.ordered.first @enable_banking_sync_stats_map[item.id] = latest_sync&.sync_stats || {} + @enable_banking_latest_sync_error_map[item.id] = latest_sync&.error end # CoinStats sync stats diff --git a/app/controllers/concerns/enable_banking_items/maps_helper.rb b/app/controllers/concerns/enable_banking_items/maps_helper.rb index 3293cc25d..7c3489f29 100644 --- a/app/controllers/concerns/enable_banking_items/maps_helper.rb +++ b/app/controllers/concerns/enable_banking_items/maps_helper.rb @@ -15,6 +15,7 @@ module EnableBankingItems @enable_banking_unlinked_count_map ||= {} @enable_banking_duplicate_only_map ||= {} @enable_banking_show_relink_map ||= {} + @enable_banking_latest_sync_error_map ||= {} # Batch-check if ANY family has manual accounts (same result for all items from same family) family_ids = items.map { |i| i.family_id }.uniq @@ -42,6 +43,7 @@ module EnableBankingItems end stats = (latest_sync&.sync_stats || {}) @enable_banking_sync_stats_map[item.id] = stats + @enable_banking_latest_sync_error_map[item.id] = latest_sync&.error # Whether the family has any manual accounts available to link (from batch query) @enable_banking_has_unlinked_map[item.id] = families_with_manuals.include?(item.family_id) @@ -68,13 +70,6 @@ module EnableBankingItems @enable_banking_show_relink_map[item.id] = false end end - - # Ensure maps are hashes even when items empty - @enable_banking_sync_stats_map ||= {} - @enable_banking_has_unlinked_map ||= {} - @enable_banking_unlinked_count_map ||= {} - @enable_banking_duplicate_only_map ||= {} - @enable_banking_show_relink_map ||= {} end private diff --git a/app/models/enable_banking_item/importer.rb b/app/models/enable_banking_item/importer.rb index 178d146f2..2e335c15c 100644 --- a/app/models/enable_banking_item/importer.rb +++ b/app/models/enable_banking_item/importer.rb @@ -18,7 +18,8 @@ class EnableBankingItem::Importer session_data = fetch_session_data unless session_data - return { success: false, error: "Failed to fetch session data", accounts_updated: 0, transactions_imported: 0 } + error_msg = @session_error || "Failed to fetch session data" + return { success: false, error: error_msg, accounts_updated: 0, transactions_imported: 0 } end # Store raw payload @@ -92,17 +93,41 @@ class EnableBankingItem::Importer end end - { + result = { success: accounts_failed == 0 && transactions_failed == 0, accounts_updated: accounts_updated, accounts_failed: accounts_failed, transactions_imported: transactions_imported, transactions_failed: transactions_failed } + if !result[:success] && (accounts_failed > 0 || transactions_failed > 0) + parts = [] + parts << "#{accounts_failed} #{'account'.pluralize(accounts_failed)} failed" if accounts_failed > 0 + parts << "#{transactions_failed} #{'transaction'.pluralize(transactions_failed)} failed" if transactions_failed > 0 + result[:error] = parts.join(", ") + end + result end private + def extract_friendly_error_message(exception) + [ exception, exception.cause ].compact.each do |ex| + case ex + when SocketError then return "DNS resolution failed: check your network/DNS configuration" + when Net::OpenTimeout, Net::ReadTimeout then return "Connection timed out: the Enable Banking API may be unreachable" + when Errno::ECONNREFUSED then return "Connection refused: the Enable Banking API is unreachable" + end + end + + msg = exception.message.to_s + return "DNS resolution failed: check your network/DNS configuration" if msg.include?("getaddrinfo") || msg.match?(/name or service not known/i) + return "Connection timed out: the Enable Banking API may be unreachable" if msg.include?("execution expired") || msg.include?("timeout") || msg.match?(/timed out/i) + return "Connection refused: the Enable Banking API is unreachable" if msg.include?("ECONNREFUSED") || msg.match?(/connection refused/i) + + msg + end + def fetch_session_data enable_banking_provider.get_session(session_id: enable_banking_item.session_id) rescue Provider::EnableBanking::EnableBankingError => e @@ -110,9 +135,11 @@ class EnableBankingItem::Importer enable_banking_item.update!(status: :requires_update) end Rails.logger.error "EnableBankingItem::Importer - Enable Banking API error: #{e.message}" + @session_error = extract_friendly_error_message(e) nil rescue => e Rails.logger.error "EnableBankingItem::Importer - Unexpected error fetching session: #{e.class} - #{e.message}" + @session_error = extract_friendly_error_message(e) nil end diff --git a/app/models/enable_banking_item/sync_complete_event.rb b/app/models/enable_banking_item/sync_complete_event.rb index 7900226c4..455ebccff 100644 --- a/app/models/enable_banking_item/sync_complete_event.rb +++ b/app/models/enable_banking_item/sync_complete_event.rb @@ -6,20 +6,34 @@ class EnableBankingItem::SyncCompleteEvent end def broadcast + enable_banking_item.reload + # Update UI with latest account data enable_banking_item.accounts.each do |account| account.broadcast_sync_complete end - # Update the Enable Banking item view + family = enable_banking_item.family + return unless family + + # Update the Enable Banking item view on the Accounts page enable_banking_item.broadcast_replace_to( - enable_banking_item.family, + family, target: "enable_banking_item_#{enable_banking_item.id}", partial: "enable_banking_items/enable_banking_item", locals: { enable_banking_item: enable_banking_item } ) + # Update the Settings > Providers panel + enable_banking_items = family.enable_banking_items.ordered.includes(:syncs) + enable_banking_item.broadcast_replace_to( + family, + target: "enable_banking-providers-panel", + partial: "settings/providers/enable_banking_panel", + locals: { enable_banking_items: enable_banking_items } + ) + # Let family handle sync notifications - enable_banking_item.family.broadcast_sync_complete + family.broadcast_sync_complete end end diff --git a/app/models/enable_banking_item/syncer.rb b/app/models/enable_banking_item/syncer.rb index a3cc283a0..627d03de6 100644 --- a/app/models/enable_banking_item/syncer.rb +++ b/app/models/enable_banking_item/syncer.rb @@ -17,6 +17,17 @@ class EnableBankingItem::Syncer sync.update!(status_text: "Importing accounts from Enable Banking...") if sync.respond_to?(:status_text) import_result = enable_banking_item.import_latest_enable_banking_data + unless import_result[:success] + error_msg = import_result[:error] + if error_msg.blank? && (import_result[:accounts_failed].to_i > 0 || import_result[:transactions_failed].to_i > 0) + parts = [] + parts << "#{import_result[:accounts_failed]} #{'account'.pluralize(import_result[:accounts_failed])} failed" if import_result[:accounts_failed].to_i > 0 + parts << "#{import_result[:transactions_failed]} #{'transaction'.pluralize(import_result[:transactions_failed])} failed" if import_result[:transactions_failed].to_i > 0 + error_msg = parts.join(", ") + end + raise StandardError.new(error_msg.presence || "Import failed") + end + # Phase 2: Check account setup status and collect sync statistics sync.update!(status_text: "Checking account configuration...") if sync.respond_to?(:status_text) total_accounts = enable_banking_item.enable_banking_accounts.count diff --git a/app/views/enable_banking_items/_enable_banking_item.html.erb b/app/views/enable_banking_items/_enable_banking_item.html.erb index f82ea7b27..3497cc3f5 100644 --- a/app/views/enable_banking_items/_enable_banking_item.html.erb +++ b/app/views/enable_banking_items/_enable_banking_item.html.erb @@ -86,11 +86,20 @@ <% end %> <%# Sync summary (collapsible) - using shared ProviderSyncSummary component %> - <% stats = if defined?(@enable_banking_sync_stats_map) && @enable_banking_sync_stats_map - @enable_banking_sync_stats_map[enable_banking_item.id] || {} - else - enable_banking_item.syncs.ordered.first&.sync_stats || {} - end %> + <% if defined?(@enable_banking_sync_stats_map) && @enable_banking_sync_stats_map %> + <% stats = @enable_banking_sync_stats_map[enable_banking_item.id] || {} %> + <% latest_sync_error = defined?(@enable_banking_latest_sync_error_map) && @enable_banking_latest_sync_error_map ? @enable_banking_latest_sync_error_map[enable_banking_item.id] : nil %> + <% else %> + <% latest_sync = enable_banking_item.syncs.ordered.first %> + <% stats = latest_sync&.sync_stats || {} %> + <% latest_sync_error = latest_sync&.error %> + <% end %> + <% if latest_sync_error.present? && stats.is_a?(Hash) %> + <% stats = stats.merge( + "total_errors" => 1, + "errors" => [{ "message" => latest_sync_error }] + ) %> + <% end %> <%= render ProviderSyncSummary.new( stats: stats, provider_item: enable_banking_item diff --git a/app/views/settings/providers/_enable_banking_panel.html.erb b/app/views/settings/providers/_enable_banking_panel.html.erb index 92eb93709..e1568a8f4 100644 --- a/app/views/settings/providers/_enable_banking_panel.html.erb +++ b/app/views/settings/providers/_enable_banking_panel.html.erb @@ -112,7 +112,19 @@ <% items.each do |item| %>
- <% if item.session_valid? %> + <% if item.syncing? %> +
+
+

<%= item.aspsp_name || t("settings.providers.enable_banking_panel.syncing", default: "Syncing") %>

+

<%= t("settings.providers.enable_banking_panel.syncing", default: "Syncing") %>

+
+ <% elsif item.sync_error.present? %> +
+
+

<%= item.aspsp_name || t("settings.providers.enable_banking_panel.connection_error") %>

+

<%= item.sync_error.truncate(50) %>

+
+ <% elsif item.session_valid? %>

<%= item.aspsp_name || "Connected Bank" %>

diff --git a/compose.example.ai.yml b/compose.example.ai.yml index 6a2e9f78d..e711fc8f4 100644 --- a/compose.example.ai.yml +++ b/compose.example.ai.yml @@ -115,6 +115,9 @@ services: condition: service_healthy redis: condition: service_healthy + dns: + - 8.8.8.8 + - 1.1.1.1 networks: - sure_net @@ -129,6 +132,9 @@ services: condition: service_healthy redis: condition: service_healthy + dns: + - 8.8.8.8 + - 1.1.1.1 environment: <<: *rails_env networks: diff --git a/compose.example.yml b/compose.example.yml index a01daca2e..b52c047e1 100644 --- a/compose.example.yml +++ b/compose.example.yml @@ -59,6 +59,9 @@ services: condition: service_healthy redis: condition: service_healthy + dns: + - 8.8.8.8 + - 1.1.1.1 networks: - sure_net @@ -73,6 +76,9 @@ services: condition: service_healthy redis: condition: service_healthy + dns: + - 8.8.8.8 + - 1.1.1.1 environment: <<: *rails_env networks: diff --git a/config/locales/views/settings/en.yml b/config/locales/views/settings/en.yml index 55730d2b6..e0030605a 100644 --- a/config/locales/views/settings/en.yml +++ b/config/locales/views/settings/en.yml @@ -172,3 +172,5 @@ en: disconnect_confirm: Are you sure you want to disconnect this Coinbase connection? Your synced accounts will become manual accounts. status_connected: Coinbase is connected and syncing your crypto holdings. status_not_connected: Not connected. Enter your API credentials above to get started. + enable_banking_panel: + connection_error: Connection Error