mirror of
https://github.com/we-promise/sure.git
synced 2026-04-19 12:04:08 +00:00
Add global sync summary component for all providers (#588)
* Add shared sync statistics collection and provider sync summary UI - Introduced `SyncStats::Collector` concern to centralize sync statistics logic, including account, transaction, holdings, and health stats collection. - Added collapsible `ProviderSyncSummary` component for displaying sync summaries across providers. - Updated syncers (e.g., `LunchflowItem::Syncer`) to use the shared collector methods for consistent stats calculation. - Added rake tasks under `dev:sync_stats` for testing and development purposes, including fake stats generation with optional issues. - Enhanced provider-specific views to include sync summaries using the new shared component. * Refactor `ProviderSyncSummary` to improve maintainability - Extracted `severity_color_class` to simplify severity-to-CSS mapping. - Replaced `holdings_label` with `holdings_label_key` for streamlined localization. - Updated locale file to separate `found` and `processed` translations for clarity. --------- Signed-off-by: Juan José Mata <juanjo.mata@gmail.com> Co-authored-by: Josh Waldrep <joshua.waldrep5+github@gmail.com> Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
This commit is contained in:
@@ -80,7 +80,20 @@
|
||||
<div class="space-y-4 mt-4">
|
||||
<% if coinstats_item.accounts.any? %>
|
||||
<%= render "accounts/index/account_groups", accounts: coinstats_item.accounts %>
|
||||
<% else %>
|
||||
<% end %>
|
||||
|
||||
<%# Sync summary (collapsible) - using shared ProviderSyncSummary component %>
|
||||
<% stats = if defined?(@coinstats_sync_stats_map) && @coinstats_sync_stats_map
|
||||
@coinstats_sync_stats_map[coinstats_item.id] || {}
|
||||
else
|
||||
coinstats_item.syncs.ordered.first&.sync_stats || {}
|
||||
end %>
|
||||
<%= render ProviderSyncSummary.new(
|
||||
stats: stats,
|
||||
provider_item: coinstats_item
|
||||
) %>
|
||||
|
||||
<% if coinstats_item.accounts.empty? %>
|
||||
<div class="p-4 flex flex-col gap-3 items-center justify-center">
|
||||
<p class="text-primary font-medium text-sm"><%= t(".no_wallets_title") %></p>
|
||||
<p class="text-secondary text-sm"><%= t(".no_wallets_message") %></p>
|
||||
|
||||
@@ -85,6 +85,17 @@
|
||||
<%= render "accounts/index/account_groups", accounts: enable_banking_item.accounts %>
|
||||
<% 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 %>
|
||||
<%= render ProviderSyncSummary.new(
|
||||
stats: stats,
|
||||
provider_item: enable_banking_item
|
||||
) %>
|
||||
|
||||
<% if enable_banking_item.unlinked_accounts_count > 0 %>
|
||||
<div class="p-4 flex flex-col gap-3 items-center justify-center">
|
||||
<p class="text-primary font-medium text-sm">Setup needed</p>
|
||||
|
||||
@@ -82,6 +82,18 @@
|
||||
<%= render "accounts/index/account_groups", accounts: lunchflow_item.accounts %>
|
||||
<% end %>
|
||||
|
||||
<%# Sync summary (collapsible) - using shared ProviderSyncSummary component %>
|
||||
<% stats = if defined?(@lunchflow_sync_stats_map) && @lunchflow_sync_stats_map
|
||||
@lunchflow_sync_stats_map[lunchflow_item.id] || {}
|
||||
else
|
||||
lunchflow_item.syncs.ordered.first&.sync_stats || {}
|
||||
end %>
|
||||
<%= render ProviderSyncSummary.new(
|
||||
stats: stats,
|
||||
provider_item: lunchflow_item,
|
||||
institutions_count: lunchflow_item.connected_institutions.size
|
||||
) %>
|
||||
|
||||
<%# Use model methods for consistent counts %>
|
||||
<% unlinked_count = lunchflow_item.unlinked_accounts_count %>
|
||||
<% linked_count = lunchflow_item.linked_accounts_count %>
|
||||
|
||||
@@ -80,7 +80,20 @@
|
||||
<div class="space-y-4 mt-4">
|
||||
<% if plaid_item.accounts.any? %>
|
||||
<%= render "accounts/index/account_groups", accounts: plaid_item.accounts %>
|
||||
<% else %>
|
||||
<% end %>
|
||||
|
||||
<%# Sync summary (collapsible) - using shared ProviderSyncSummary component %>
|
||||
<% stats = if defined?(@plaid_sync_stats_map) && @plaid_sync_stats_map
|
||||
@plaid_sync_stats_map[plaid_item.id] || {}
|
||||
else
|
||||
plaid_item.syncs.ordered.first&.sync_stats || {}
|
||||
end %>
|
||||
<%= render ProviderSyncSummary.new(
|
||||
stats: stats,
|
||||
provider_item: plaid_item
|
||||
) %>
|
||||
|
||||
<% if plaid_item.accounts.empty? %>
|
||||
<div class="p-4 flex flex-col gap-3 items-center justify-center">
|
||||
<p class="text-primary font-medium text-sm"><%= t(".no_accounts_title") %></p>
|
||||
<p class="text-secondary text-sm"><%= t(".no_accounts_description") %></p>
|
||||
|
||||
@@ -171,7 +171,7 @@
|
||||
<%= render "accounts/index/account_groups", accounts: simplefin_item.accounts %>
|
||||
<% end %>
|
||||
|
||||
<%# Sync summary (collapsible)
|
||||
<%# Sync summary (collapsible) - using shared ProviderSyncSummary component
|
||||
Prefer controller-provided map; fallback to latest sync stats so Turbo broadcasts
|
||||
can render the summary without requiring a full page refresh. %>
|
||||
<% stats = if defined?(@simplefin_sync_stats_map) && @simplefin_sync_stats_map
|
||||
@@ -180,69 +180,11 @@
|
||||
# `latest_sync` is private on Syncable; access via association for broadcast renders.
|
||||
simplefin_item.syncs.ordered.first&.sync_stats || {}
|
||||
end %>
|
||||
<% if stats.present? %>
|
||||
<details class="group bg-surface rounded-lg border border-surface-inset/50">
|
||||
<summary class="flex items-center justify-between gap-2 p-3 cursor-pointer">
|
||||
<div class="flex items-center gap-2">
|
||||
<%= icon "chevron-right", class: "group-open:transform group-open:rotate-90" %>
|
||||
<span class="text-sm text-primary font-medium">Sync summary</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-xs text-secondary">
|
||||
<% if simplefin_item.last_synced_at %>
|
||||
<span>Last sync: <%= time_ago_in_words(simplefin_item.last_synced_at) %> ago</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</summary>
|
||||
<div class="p-3 text-sm text-secondary grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<div>
|
||||
<h4 class="text-primary font-medium mb-1">Accounts</h4>
|
||||
<div class="flex items-center gap-3">
|
||||
<span>Total: <%= stats["total_accounts"].to_i %></span>
|
||||
<span>Linked: <%= stats["linked_accounts"].to_i %></span>
|
||||
<span>Unlinked: <%= stats["unlinked_accounts"].to_i %></span>
|
||||
<% institutions = simplefin_item.connected_institutions %>
|
||||
<span>Institutions: <%= institutions.size %></span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-primary font-medium mb-1">Transactions</h4>
|
||||
<div class="flex items-center gap-3">
|
||||
<span>Seen: <%= stats["tx_seen"].to_i %></span>
|
||||
<span>Imported: <%= stats["tx_imported"].to_i %></span>
|
||||
<span>Updated: <%= stats["tx_updated"].to_i %></span>
|
||||
<span>Skipped: <%= stats["tx_skipped"].to_i %></span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-primary font-medium mb-1">Holdings</h4>
|
||||
<div class="flex items-center gap-3">
|
||||
<span>Found: <%= stats["holdings_found"].to_i %></span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-primary font-medium mb-1">Health</h4>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center gap-3">
|
||||
<% if stats["rate_limited"].present? || stats["rate_limited_at"].present? %>
|
||||
<% ts = stats["rate_limited_at"] %>
|
||||
<% ago = (ts.present? ? (begin; time_ago_in_words(Time.parse(ts)); rescue StandardError; nil; end) : nil) %>
|
||||
<span class="text-warning">Rate limited <%= ago ? "(#{ago} ago)" : "recently" %></span>
|
||||
<% end %>
|
||||
<% total_errors = stats["total_errors"].to_i %>
|
||||
<% import_started = stats["import_started"].present? %>
|
||||
<% if total_errors > 0 %>
|
||||
<span class="text-destructive">Errors: <%= total_errors %></span>
|
||||
<% elsif import_started %>
|
||||
<span class="text-success">Errors: 0</span>
|
||||
<% else %>
|
||||
<span>Errors: 0</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<% end %>
|
||||
<%= render ProviderSyncSummary.new(
|
||||
stats: stats,
|
||||
provider_item: simplefin_item,
|
||||
institutions_count: simplefin_item.connected_institutions.size
|
||||
) %>
|
||||
|
||||
<%# Compute unlinked SimpleFin accounts (no legacy account and no AccountProvider link)
|
||||
# Prefer controller-provided map; fallback to a local query so the card stays accurate after Turbo broadcasts %>
|
||||
|
||||
Reference in New Issue
Block a user