mirror of
https://github.com/we-promise/sure.git
synced 2026-05-30 07:49:01 +00:00
chore(goals): drop dead V1 hooks + surface chart errors
Loose ends from the V1 → V2 refactor that the architecture commit didn't sweep. - Demo generator (B14): the `goal_spec[:contributions]` arrays + the `wedding_contribs` / `house_contribs` builders still shipped in the file, but the seeding loop that consumed them was deleted alongside `GoalContribution`. Dead data. Strip both the per-goal arrays and the two locals. Goal balance/pace in the demo family now derives from the linked depository accounts' own seeded entries elsewhere in the generator. - Goal stepper controller (B16): the `static targets` declaration still listed `initialContributionAmount` and `initialContributionAccountSelect`, and `refreshAccountSelect` + its two callsites still ran every time a linked-account checkbox flipped. The HTML targets disappeared with the V2 stepper rebuild, so `has*Target` guards short-circuited and the method was a no-op — but it was still dispatched on every change. Drop the targets, the method, and the two callsites. - Chart series rescue (B25): `Goal#balance_series_values` and `FundingAccountsBreakdownComponent#sparkline_map` both swallowed `StandardError` with a `Rails.logger.warn(…)`. The chart then degraded to "target line only" silently. Promote the log to `error` level and forward to Sentry when present (matching the pattern in `Account::Syncer`, `Sync`, `PlaidItem`). Fallback to empty result still preserved so the surface degrades instead of 500-ing.
This commit is contained in:
@@ -105,7 +105,8 @@ class Goals::FundingAccountsBreakdownComponent < ApplicationComponent
|
||||
result
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.warn("Sparkline map for goal #{goal.id} failed: #{e.message}")
|
||||
Rails.logger.error("Sparkline map for goal #{goal.id} failed: #{e.class}: #{e.message}")
|
||||
Sentry.capture_exception(e) if defined?(Sentry)
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,8 +3,8 @@ import { Controller } from "@hotwired/stimulus";
|
||||
// 2-step modal stepper for creating a goal.
|
||||
//
|
||||
// Single <form> with two panels. Step 1 collects identity (name, amount,
|
||||
// date, color, notes, linked accounts). Step 2 reviews + optional initial
|
||||
// contribution. All state lives in the DOM — no half-records, single POST.
|
||||
// date, color, notes, linked accounts). Step 2 reviews and submits. All
|
||||
// state lives in the DOM — no half-records, single POST.
|
||||
export default class extends Controller {
|
||||
static targets = [
|
||||
"step1Panel",
|
||||
@@ -22,8 +22,6 @@ export default class extends Controller {
|
||||
"amountError",
|
||||
"accountsError",
|
||||
"linkedAccountCheckbox",
|
||||
"initialContributionAmount",
|
||||
"initialContributionAccountSelect",
|
||||
"reviewName",
|
||||
"reviewSummary",
|
||||
"reviewSuggested",
|
||||
@@ -85,7 +83,6 @@ export default class extends Controller {
|
||||
this.step1PanelTarget.classList.add("hidden");
|
||||
this.step2PanelTarget.classList.remove("hidden");
|
||||
this.updateStepperState();
|
||||
this.refreshAccountSelect();
|
||||
this.updateReview();
|
||||
this.updateFooter();
|
||||
}
|
||||
@@ -99,7 +96,6 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
linkedAccountChanged() {
|
||||
this.refreshAccountSelect();
|
||||
this.refreshSubmitState();
|
||||
this.updateReview();
|
||||
if (this.linkedAccountCheckboxTargets.some((cb) => cb.checked) && this.hasAccountsErrorTarget) {
|
||||
@@ -169,26 +165,6 @@ export default class extends Controller {
|
||||
this.footerRightButtonTarget.classList.toggle("opacity-50", !anyChecked && this.currentStep === 1);
|
||||
}
|
||||
|
||||
refreshAccountSelect() {
|
||||
if (!this.hasInitialContributionAccountSelectTarget) return;
|
||||
const select = this.initialContributionAccountSelectTarget;
|
||||
const previous = select.value;
|
||||
while (select.options.length > 1) select.remove(1);
|
||||
|
||||
this.linkedAccountCheckboxTargets
|
||||
.filter((cb) => cb.checked)
|
||||
.forEach((cb) => {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = cb.value;
|
||||
opt.textContent = cb.dataset.accountName || cb.value;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
|
||||
if ([...select.options].some((o) => o.value === previous)) {
|
||||
select.value = previous;
|
||||
}
|
||||
}
|
||||
|
||||
updateStepperState() {
|
||||
if (this.hasStep1CircleTarget) {
|
||||
this.step1CircleTarget.classList.toggle("bg-inverse", this.currentStep === 1);
|
||||
|
||||
@@ -1285,102 +1285,57 @@ class Demo::Generator
|
||||
currency = depository_accounts.first.currency
|
||||
eligible = depository_accounts.select { |a| a.currency == currency }
|
||||
primary = eligible.first
|
||||
secondary = eligible[1] || primary
|
||||
|
||||
# Build "Wedding fund" on_track contributions: target 12 months out,
|
||||
# $200/mo required, demo contributes $220/mo for last 6 months → on
|
||||
# pace.
|
||||
wedding_contribs = (0..5).map do |i|
|
||||
{ amount: 220, source: i.zero? ? "initial" : "manual", days_ago: 30 * (6 - i), account: primary }
|
||||
end
|
||||
|
||||
# "House downpayment" gets a fuller contribution history so the
|
||||
# scrollable list has real density.
|
||||
house_contribs = [
|
||||
{ amount: 5_000, source: "initial", days_ago: 365, account: primary },
|
||||
{ amount: 750, source: "manual", days_ago: 330, account: primary },
|
||||
{ amount: 750, source: "manual", days_ago: 300, account: secondary },
|
||||
{ amount: 750, source: "manual", days_ago: 270, account: primary },
|
||||
{ amount: 750, source: "manual", days_ago: 240, account: primary },
|
||||
{ amount: 750, source: "manual", days_ago: 210, account: secondary },
|
||||
{ amount: 750, source: "manual", days_ago: 180, account: primary },
|
||||
{ amount: 750, source: "manual", days_ago: 150, account: primary },
|
||||
{ amount: 750, source: "manual", days_ago: 120, account: secondary },
|
||||
{ amount: 750, source: "manual", days_ago: 90, account: primary },
|
||||
{ amount: 750, source: "manual", days_ago: 60, account: primary },
|
||||
{ amount: 750, source: "manual", days_ago: 30, account: secondary }
|
||||
]
|
||||
|
||||
# V2 goals derive balance + pace from the linked depository accounts
|
||||
# directly; the demo's contribution arrays were V1 ledger seed data
|
||||
# and have nothing to consume them now. Account-level transaction
|
||||
# seeding (paychecks, etc.) elsewhere in this generator already
|
||||
# populates the goal pace/balance.
|
||||
goals = [
|
||||
{
|
||||
name: "Vacation in Italy",
|
||||
target: 5_000,
|
||||
target_date: 4.months.from_now.to_date,
|
||||
accounts: eligible.first(2),
|
||||
contributions: [
|
||||
{ amount: 500, source: "initial", days_ago: 90, account: primary },
|
||||
{ amount: 250, source: "manual", days_ago: 60, account: primary },
|
||||
{ amount: 250, source: "manual", days_ago: 30, account: secondary }
|
||||
]
|
||||
accounts: eligible.first(2)
|
||||
},
|
||||
{
|
||||
name: "Wedding fund",
|
||||
target: 2_400,
|
||||
target_date: 6.months.from_now.to_date,
|
||||
accounts: eligible.first(2),
|
||||
contributions: wedding_contribs
|
||||
accounts: eligible.first(2)
|
||||
},
|
||||
{
|
||||
name: "Emergency fund",
|
||||
target: 10_000,
|
||||
target_date: nil,
|
||||
accounts: [ primary ],
|
||||
contributions: [
|
||||
{ amount: 1_000, source: "initial", days_ago: 180, account: primary },
|
||||
{ amount: 250, source: "manual", days_ago: 60, account: primary },
|
||||
{ amount: 250, source: "manual", days_ago: 30, account: primary }
|
||||
]
|
||||
accounts: [ primary ]
|
||||
},
|
||||
{
|
||||
name: "House downpayment",
|
||||
target: 50_000,
|
||||
target_date: 24.months.from_now.to_date,
|
||||
accounts: eligible.first(2),
|
||||
contributions: house_contribs
|
||||
accounts: eligible.first(2)
|
||||
},
|
||||
{
|
||||
name: "Sabbatical",
|
||||
target: 15_000,
|
||||
target_date: 18.months.from_now.to_date,
|
||||
state: "paused",
|
||||
accounts: [ primary ],
|
||||
contributions: [
|
||||
{ amount: 1_500, source: "initial", days_ago: 200, account: primary },
|
||||
{ amount: 500, source: "manual", days_ago: 150, account: primary }
|
||||
]
|
||||
accounts: [ primary ]
|
||||
},
|
||||
{
|
||||
name: "Old laptop fund",
|
||||
target: 1_500,
|
||||
target_date: 12.months.ago.to_date,
|
||||
state: "archived",
|
||||
accounts: [ primary ],
|
||||
contributions: [
|
||||
{ amount: 400, source: "initial", days_ago: 540, account: primary }
|
||||
]
|
||||
accounts: [ primary ]
|
||||
},
|
||||
{
|
||||
name: "Paid-off car",
|
||||
target: 8_000,
|
||||
target_date: 6.months.ago.to_date,
|
||||
state: "completed",
|
||||
accounts: [ primary ],
|
||||
contributions: [
|
||||
{ amount: 2_000, source: "initial", days_ago: 730, account: primary },
|
||||
{ amount: 2_000, source: "manual", days_ago: 600, account: primary },
|
||||
{ amount: 2_000, source: "manual", days_ago: 450, account: primary },
|
||||
{ amount: 2_000, source: "manual", days_ago: 300, account: primary }
|
||||
]
|
||||
accounts: [ primary ]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -297,7 +297,10 @@ class Goal < ApplicationRecord
|
||||
period: Period.last_90_days
|
||||
).balance_series.values
|
||||
rescue StandardError => e
|
||||
Rails.logger.warn("Goal##{id} balance series failed: #{e.message}")
|
||||
# Degrade gracefully (chart drops to target-line-only) but surface
|
||||
# the failure — silent fallbacks here masked real Builder bugs.
|
||||
Rails.logger.error("Goal##{id} balance series failed: #{e.class}: #{e.message}")
|
||||
Sentry.capture_exception(e) if defined?(Sentry)
|
||||
[]
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user