1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-07 22:45:20 +02:00

Improve account sync performance, handle concurrent market data syncing (#2236)

* PlaidConnectable concern

* Remove bad abstraction

* Put sync implementations in own concerns

* Sync strategies

* Move sync orchestration to Sync class

* Clean up sync class, add state machine

* Basic market data sync cron

* Fix price sync

* Improve sync window column names, add timestamps

* 30 day syncs by default

* Clean up market data methods

* Report high duplicate sync counts to Sentry

* Add sync states throughout app

* account tab session

* Persistent account tab selections

* Remove manual sleep

* Add migration to clear stale syncs on self hosted apps

* Tweak sync states

* Sync completion event broadcasts

* Fix timezones in tests

* Cleanup

* More cleanup

* Plaid item UI broadcasts for sync

* Fix account ID namespace conflict

* Sync broadcasters

* Smoother account sync refreshes

* Remove test sync delay
This commit is contained in:
Zach Gollwitzer 2025-05-15 10:19:56 -04:00 committed by GitHub
parent 9793cc74f9
commit 10dd9e061a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
97 changed files with 1837 additions and 949 deletions

View file

@ -25,11 +25,14 @@
<div class="w-full space-y-6 pb-24">
<% if Current.family.accounts.any? %>
<section class="bg-container py-4 rounded-xl shadow-border-xs px-0.5">
<%= render partial: "pages/dashboard/net_worth_chart", locals: { series: @balance_sheet.net_worth_series(period: @period), period: @period } %>
<section class="bg-container py-4 rounded-xl shadow-border-xs">
<%= render partial: "pages/dashboard/net_worth_chart", locals: {
balance_sheet: @balance_sheet,
period: @period
} %>
</section>
<% else %>
<section class="p-0.5">
<section>
<%= render "pages/dashboard/no_accounts_graph_placeholder" %>
</section>
<% end %>

View file

@ -1,6 +1,6 @@
<%# locals: (balance_sheet:) %>
<%# locals: (balance_sheet:, **args) %>
<div class="space-y-4 overflow-x-auto p-0.5">
<div class="space-y-4" id="balance-sheet">
<% balance_sheet.classification_groups.each do |classification_group| %>
<div class="bg-container shadow-border-xs rounded-xl space-y-4 p-4">
<h2 class="text-lg font-medium inline-flex items-center gap-1.5">
@ -11,26 +11,38 @@
<% if classification_group.account_groups.any? %>
<span class="text-secondary">&middot;</span>
<span class="text-secondary font-medium text-lg"><%= classification_group.total_money.format(precision: 0) %></span>
<% if classification_group.syncing? %>
<div class="flex items-center w-8 h-4 ml-auto">
<div class="bg-loader w-full h-full rounded-md"></div>
</div>
<% else %>
<span class="text-secondary font-medium text-lg"><%= classification_group.total_money.format(precision: 0) %></span>
<% end %>
<% end %>
</h2>
<% if classification_group.account_groups.any? %>
<div class="space-y-4">
<div class="flex gap-1">
<% classification_group.account_groups.each do |account_group| %>
<div class="h-1.5 rounded-sm" style="width: <%= account_group.weight %>%; background-color: <%= account_group.color %>;"></div>
<% end %>
</div>
<div class="flex flex-wrap gap-4">
<% classification_group.account_groups.each do |account_group| %>
<div class="flex items-center gap-2 text-sm">
<div class="h-2.5 w-2.5 rounded-full" style="background-color: <%= account_group.color %>;"></div>
<p class="text-secondary"><%= account_group.name %></p>
<p class="text-primary font-mono"><%= number_to_percentage(account_group.weight, precision: 0) %></p>
</div>
<% end %>
</div>
<% if classification_group.syncing? %>
<p class="text-xs text-subdued animate-pulse">Calculating latest balance data...</p>
<% else %>
<div class="flex flex-wrap gap-4">
<% classification_group.account_groups.each do |account_group| %>
<div class="flex items-center gap-2 text-sm">
<div class="h-2.5 w-2.5 rounded-full" style="background-color: <%= account_group.color %>;"></div>
<p class="text-secondary"><%= account_group.name %></p>
<p class="text-primary font-mono"><%= number_to_percentage(account_group.weight, precision: 0) %></p>
</div>
<% end %>
</div>
<% end %>
</div>
<div class="bg-surface rounded-xl p-1 space-y-1 overflow-x-auto">
@ -56,15 +68,27 @@
<p><%= account_group.name %></p>
</div>
<div class="flex items-center justify-between text-right gap-6">
<div class="w-28 shrink-0 flex items-center justify-end gap-2">
<%= render "pages/dashboard/group_weight", weight: account_group.weight, color: account_group.color %>
</div>
<% if account_group.syncing? %>
<div class="flex items-center justify-between text-right gap-6">
<div class="w-28 shrink-0 flex items-center justify-end gap-2">
<div class="bg-loader rounded-md h-4 w-12"></div>
</div>
<div class="w-40 shrink-0">
<p><%= format_money(account_group.total_money) %></p>
<div class="w-40 shrink-0 flex justify-end">
<div class="bg-loader rounded-md h-4 w-12"></div>
</div>
</div>
</div>
<% else %>
<div class="flex items-center justify-between text-right gap-6">
<div class="w-28 shrink-0 flex items-center justify-end gap-2">
<%= render "pages/dashboard/group_weight", weight: account_group.weight, color: account_group.color %>
</div>
<div class="w-40 shrink-0">
<p><%= format_money(account_group.total_money) %></p>
</div>
</div>
<% end %>
</summary>
<div>
@ -76,15 +100,27 @@
<%= link_to account.name, account_path(account) %>
</div>
<div class="ml-auto flex items-center text-right gap-6">
<div class="w-28 shrink-0 flex items-center justify-end gap-2">
<%= render "pages/dashboard/group_weight", weight: account.weight, color: account_group.color %>
</div>
<% if account.syncing? %>
<div class="ml-auto flex items-center text-right gap-6">
<div class="w-28 shrink-0 flex items-center justify-end gap-2">
<div class="bg-loader rounded-md h-4 w-12"></div>
</div>
<div class="w-40 shrink-0">
<p><%= format_money(account.balance_money) %></p>
<div class="w-40 shrink-0 flex justify-end">
<div class="bg-loader rounded-md h-4 w-12"></div>
</div>
</div>
</div>
<% else %>
<div class="ml-auto flex items-center text-right gap-6">
<div class="w-28 shrink-0 flex items-center justify-end gap-2">
<%= render "pages/dashboard/group_weight", weight: account.weight, color: account_group.color %>
</div>
<div class="w-40 shrink-0">
<p><%= format_money(account.balance_money) %></p>
</div>
</div>
<% end %>
</div>
<% if idx < account_group.accounts.size - 1 %>

View file

@ -1,37 +1,55 @@
<%# locals: (series:, period:) %>
<%# locals: (balance_sheet:, period:, **args) %>
<div class="flex justify-between gap-4 px-4">
<div class="space-y-2">
<div id="net-worth-chart">
<% series = balance_sheet.net_worth_series(period: period) %>
<div class="flex justify-between gap-4 px-4">
<div class="space-y-2">
<p class="text-sm text-secondary font-medium"><%= t(".title") %></p>
<p class="text-primary -space-x-0.5 text-3xl font-medium">
<%= series.current.format %>
</p>
<% if series.trend.nil? %>
<p class="text-sm text-secondary"><%= t(".data_not_available") %></p>
<% else %>
<%= render partial: "shared/trend_change", locals: { trend: series.trend, comparison_label: period.comparison_label } %>
<% end %>
</div>
</div>
<div class="space-y-2">
<p class="text-sm text-secondary font-medium"><%= t(".title") %></p>
<%= form_with url: root_path, method: :get, data: { controller: "auto-submit-form" } do |form| %>
<%= form.select :period,
<% if balance_sheet.syncing? %>
<div class="flex flex-col gap-2">
<div class="bg-loader rounded-md h-7 w-20"></div>
<div class="bg-loader rounded-md h-5 w-32"></div>
</div>
<% else %>
<p class="text-primary -space-x-0.5 text-3xl font-medium">
<%= series.current.format %>
</p>
<% if series.trend.nil? %>
<p class="text-sm text-secondary"><%= t(".data_not_available") %></p>
<% else %>
<%= render partial: "shared/trend_change", locals: { trend: series.trend, comparison_label: period.comparison_label } %>
<% end %>
<% end %>
</div>
</div>
<%= form_with url: root_path, method: :get, data: { controller: "auto-submit-form" } do |form| %>
<%= form.select :period,
Period.as_options,
{ selected: period.key },
data: { "auto-submit-form-target": "auto" },
class: "bg-container border border-secondary font-medium rounded-lg px-3 py-2 text-sm pr-7 cursor-pointer text-primary focus:outline-hidden focus:ring-0" %>
<% end %>
</div>
<% end %>
</div>
<% if series.any? %>
<div
<% if balance_sheet.syncing? %>
<div class="w-full flex items-center justify-center p-4 h-52">
<div class="bg-loader rounded-md h-full w-full"></div>
</div>
<% else %>
<% if series.any? %>
<div
id="netWorthChart"
class="w-full flex-1 min-h-52"
data-controller="time-series-chart"
data-time-series-chart-data-value="<%= series.to_json %>"></div>
<% else %>
<div class="w-full h-full flex items-center justify-center">
<p class="text-secondary text-sm"><%= t(".data_not_available") %></p>
</div>
<% end %>
<% else %>
<div class="w-full h-full flex items-center justify-center">
<p class="text-secondary text-sm"><%= t(".data_not_available") %></p>
</div>
<% end %>
<% end %>
</div>