mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 06:21:23 +00:00
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
This commit is contained in:
@@ -237,9 +237,11 @@ class AccountsController < ApplicationController
|
|||||||
|
|
||||||
# Enable Banking sync stats
|
# Enable Banking sync stats
|
||||||
@enable_banking_sync_stats_map = {}
|
@enable_banking_sync_stats_map = {}
|
||||||
|
@enable_banking_latest_sync_error_map = {}
|
||||||
@enable_banking_items.each do |item|
|
@enable_banking_items.each do |item|
|
||||||
latest_sync = item.syncs.ordered.first
|
latest_sync = item.syncs.ordered.first
|
||||||
@enable_banking_sync_stats_map[item.id] = latest_sync&.sync_stats || {}
|
@enable_banking_sync_stats_map[item.id] = latest_sync&.sync_stats || {}
|
||||||
|
@enable_banking_latest_sync_error_map[item.id] = latest_sync&.error
|
||||||
end
|
end
|
||||||
|
|
||||||
# CoinStats sync stats
|
# CoinStats sync stats
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ module EnableBankingItems
|
|||||||
@enable_banking_unlinked_count_map ||= {}
|
@enable_banking_unlinked_count_map ||= {}
|
||||||
@enable_banking_duplicate_only_map ||= {}
|
@enable_banking_duplicate_only_map ||= {}
|
||||||
@enable_banking_show_relink_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)
|
# 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
|
family_ids = items.map { |i| i.family_id }.uniq
|
||||||
@@ -42,6 +43,7 @@ module EnableBankingItems
|
|||||||
end
|
end
|
||||||
stats = (latest_sync&.sync_stats || {})
|
stats = (latest_sync&.sync_stats || {})
|
||||||
@enable_banking_sync_stats_map[item.id] = 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)
|
# 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)
|
@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
|
@enable_banking_show_relink_map[item.id] = false
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ class EnableBankingItem::Importer
|
|||||||
|
|
||||||
session_data = fetch_session_data
|
session_data = fetch_session_data
|
||||||
unless 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
|
end
|
||||||
|
|
||||||
# Store raw payload
|
# Store raw payload
|
||||||
@@ -92,17 +93,41 @@ class EnableBankingItem::Importer
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
{
|
result = {
|
||||||
success: accounts_failed == 0 && transactions_failed == 0,
|
success: accounts_failed == 0 && transactions_failed == 0,
|
||||||
accounts_updated: accounts_updated,
|
accounts_updated: accounts_updated,
|
||||||
accounts_failed: accounts_failed,
|
accounts_failed: accounts_failed,
|
||||||
transactions_imported: transactions_imported,
|
transactions_imported: transactions_imported,
|
||||||
transactions_failed: transactions_failed
|
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
|
end
|
||||||
|
|
||||||
private
|
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
|
def fetch_session_data
|
||||||
enable_banking_provider.get_session(session_id: enable_banking_item.session_id)
|
enable_banking_provider.get_session(session_id: enable_banking_item.session_id)
|
||||||
rescue Provider::EnableBanking::EnableBankingError => e
|
rescue Provider::EnableBanking::EnableBankingError => e
|
||||||
@@ -110,9 +135,11 @@ class EnableBankingItem::Importer
|
|||||||
enable_banking_item.update!(status: :requires_update)
|
enable_banking_item.update!(status: :requires_update)
|
||||||
end
|
end
|
||||||
Rails.logger.error "EnableBankingItem::Importer - Enable Banking API error: #{e.message}"
|
Rails.logger.error "EnableBankingItem::Importer - Enable Banking API error: #{e.message}"
|
||||||
|
@session_error = extract_friendly_error_message(e)
|
||||||
nil
|
nil
|
||||||
rescue => e
|
rescue => e
|
||||||
Rails.logger.error "EnableBankingItem::Importer - Unexpected error fetching session: #{e.class} - #{e.message}"
|
Rails.logger.error "EnableBankingItem::Importer - Unexpected error fetching session: #{e.class} - #{e.message}"
|
||||||
|
@session_error = extract_friendly_error_message(e)
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -6,20 +6,34 @@ class EnableBankingItem::SyncCompleteEvent
|
|||||||
end
|
end
|
||||||
|
|
||||||
def broadcast
|
def broadcast
|
||||||
|
enable_banking_item.reload
|
||||||
|
|
||||||
# Update UI with latest account data
|
# Update UI with latest account data
|
||||||
enable_banking_item.accounts.each do |account|
|
enable_banking_item.accounts.each do |account|
|
||||||
account.broadcast_sync_complete
|
account.broadcast_sync_complete
|
||||||
end
|
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.broadcast_replace_to(
|
||||||
enable_banking_item.family,
|
family,
|
||||||
target: "enable_banking_item_#{enable_banking_item.id}",
|
target: "enable_banking_item_#{enable_banking_item.id}",
|
||||||
partial: "enable_banking_items/enable_banking_item",
|
partial: "enable_banking_items/enable_banking_item",
|
||||||
locals: { enable_banking_item: 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
|
# Let family handle sync notifications
|
||||||
enable_banking_item.family.broadcast_sync_complete
|
family.broadcast_sync_complete
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,6 +17,17 @@ class EnableBankingItem::Syncer
|
|||||||
sync.update!(status_text: "Importing accounts from Enable Banking...") if sync.respond_to?(:status_text)
|
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
|
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
|
# Phase 2: Check account setup status and collect sync statistics
|
||||||
sync.update!(status_text: "Checking account configuration...") if sync.respond_to?(:status_text)
|
sync.update!(status_text: "Checking account configuration...") if sync.respond_to?(:status_text)
|
||||||
total_accounts = enable_banking_item.enable_banking_accounts.count
|
total_accounts = enable_banking_item.enable_banking_accounts.count
|
||||||
|
|||||||
@@ -86,11 +86,20 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%# Sync summary (collapsible) - using shared ProviderSyncSummary component %>
|
<%# Sync summary (collapsible) - using shared ProviderSyncSummary component %>
|
||||||
<% stats = if defined?(@enable_banking_sync_stats_map) && @enable_banking_sync_stats_map
|
<% if defined?(@enable_banking_sync_stats_map) && @enable_banking_sync_stats_map %>
|
||||||
@enable_banking_sync_stats_map[enable_banking_item.id] || {}
|
<% stats = @enable_banking_sync_stats_map[enable_banking_item.id] || {} %>
|
||||||
else
|
<% 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 %>
|
||||||
enable_banking_item.syncs.ordered.first&.sync_stats || {}
|
<% else %>
|
||||||
end %>
|
<% 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(
|
<%= render ProviderSyncSummary.new(
|
||||||
stats: stats,
|
stats: stats,
|
||||||
provider_item: enable_banking_item
|
provider_item: enable_banking_item
|
||||||
|
|||||||
@@ -112,7 +112,19 @@
|
|||||||
<% items.each do |item| %>
|
<% items.each do |item| %>
|
||||||
<div class="flex items-center justify-between p-3 rounded-lg bg-container border border-primary">
|
<div class="flex items-center justify-between p-3 rounded-lg bg-container border border-primary">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<% if item.session_valid? %>
|
<% if item.syncing? %>
|
||||||
|
<div class="w-2 h-2 bg-primary rounded-full animate-pulse"></div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-primary"><%= item.aspsp_name || t("settings.providers.enable_banking_panel.syncing", default: "Syncing") %></p>
|
||||||
|
<p class="text-xs text-secondary"><%= t("settings.providers.enable_banking_panel.syncing", default: "Syncing") %></p>
|
||||||
|
</div>
|
||||||
|
<% elsif item.sync_error.present? %>
|
||||||
|
<div class="w-2 h-2 bg-destructive rounded-full"></div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-primary"><%= item.aspsp_name || t("settings.providers.enable_banking_panel.connection_error") %></p>
|
||||||
|
<p class="text-xs text-destructive" title="<%= item.sync_error %>"><%= item.sync_error.truncate(50) %></p>
|
||||||
|
</div>
|
||||||
|
<% elsif item.session_valid? %>
|
||||||
<div class="w-2 h-2 bg-success rounded-full"></div>
|
<div class="w-2 h-2 bg-success rounded-full"></div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-medium text-primary"><%= item.aspsp_name || "Connected Bank" %></p>
|
<p class="text-sm font-medium text-primary"><%= item.aspsp_name || "Connected Bank" %></p>
|
||||||
|
|||||||
@@ -115,6 +115,9 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
dns:
|
||||||
|
- 8.8.8.8
|
||||||
|
- 1.1.1.1
|
||||||
networks:
|
networks:
|
||||||
- sure_net
|
- sure_net
|
||||||
|
|
||||||
@@ -129,6 +132,9 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
dns:
|
||||||
|
- 8.8.8.8
|
||||||
|
- 1.1.1.1
|
||||||
environment:
|
environment:
|
||||||
<<: *rails_env
|
<<: *rails_env
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
dns:
|
||||||
|
- 8.8.8.8
|
||||||
|
- 1.1.1.1
|
||||||
networks:
|
networks:
|
||||||
- sure_net
|
- sure_net
|
||||||
|
|
||||||
@@ -73,6 +76,9 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
dns:
|
||||||
|
- 8.8.8.8
|
||||||
|
- 1.1.1.1
|
||||||
environment:
|
environment:
|
||||||
<<: *rails_env
|
<<: *rails_env
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -172,3 +172,5 @@ en:
|
|||||||
disconnect_confirm: Are you sure you want to disconnect this Coinbase connection? Your synced accounts will become manual accounts.
|
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_connected: Coinbase is connected and syncing your crypto holdings.
|
||||||
status_not_connected: Not connected. Enter your API credentials above to get started.
|
status_not_connected: Not connected. Enter your API credentials above to get started.
|
||||||
|
enable_banking_panel:
|
||||||
|
connection_error: Connection Error
|
||||||
|
|||||||
Reference in New Issue
Block a user