Repository navigation
feat(analytics): return committed spend and usage in the monthly trends series - #1563
Conversation
…ds series monthly_savings_summary has carried total_commitment and total_usage since 000067 (with NULL-propagating rollups since 000143), but QueryMonthlyTotals neither selected nor scanned them. So GET /api/analytics/trends could not give the Trends view its committed-spend and usage series. - MonthlySummary gains TotalCommitment / TotalUsage (*float64, omitted from JSON when nil). nil means unknown and is never turned into 0. A known 0 stays 0. - QueryMonthlyTotals selects and scans both columns. - openapi.yaml documents total_commitment / total_usage on TrendsMonthlySummary. Refs LeanerCloud#30 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the git-secrets account-ID detector Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 59 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
govulncheck in the Docker image build now fails on these two advisories, both fixed in x/net v0.62.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE Blocking:
Non-blocking:
Attacked:
Body-stale:
Reviewer: rG1. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths. |
…sage as recurring fees Committed spend is the sum of the two; a no-upfront portfolio has total_commitment 0. Raised in review of LeanerCloud#1563. Refs LeanerCloud#30 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE Blocking:
Non-blocking:
Attacked:
Body-stale:
Reviewer: rH1. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths. |
|
Local verification at |
|
Merged by IAmJSD's agent at IAmJSD's request (IAmJSD is acting owner while the maintainers are away), under the bar IAmJSD set: 1 approving independent review(s) on |
Summary
The Trends view (#1317) is meant to show committed spend and usage over time.
GET /api/analytics/trendscould not supply either, because itsmonthlyseries did not carry them. This PR addstotal_commitment(amortized upfront) andtotal_usage(recurring fees) to each monthly row; committed spend is their sum, and keeps an unknown value unknown rather than turning it into 0.Refs #30
Root cause
monthly_savings_summaryhas hadtotal_commitmentandtotal_usagecolumns since migration 000067. Since migration 000143 its rollups also return NULL when any snapshot in the bucket has no figure. ButQueryMonthlyTotals(internal/analytics/postgres_analytics.go) selected onlytotal_savings,avg_coverageandsnapshot_count, andanalytics.MonthlySummaryhad no fields for the other two columns.Fix
internal/analytics/interfaces.go:MonthlySummarygetsTotalCommitmentandTotalUsage(*float64,json:"…,omitempty"). nil means unknown and is never read as 0. A known 0 is a non-nil pointer to 0, so it is still sent. Omitting an unknown value matches how the same struct already handlesavg_coverage.internal/analytics/postgres_analytics.go:QueryMonthlyTotalsselects and scanstotal_commitment, total_usage. There is noCOALESCE, so the view's NULL reaches the caller unchanged.internal/api/openapi.yaml:TrendsMonthlySummarydocuments both fields. Each is a monthly run-rate (the bucket's total at each collection, averaged over the month, the same way astotal_savings). Each is omitted when unknown and returned as 0 when it is a known zero.TrendsResponsereturns[]analytics.MonthlySummarydirectly, so the handler needs no change. The collector test mock (mockAnalyticsStore.QueryMonthlyTotalsincollector_test.go) also needs no change: the interface signature is the same, and the mock builds noMonthlySummaryvalues.There is no migration and no new write path. The query is a plain read of a materialized view, so concurrent callers are not a concern here: refreshes already run
CONCURRENTLYagainst the unique index and are unchanged.Verification
All evidence below is local: unit tests against pgxmock and testify mocks, and integration tests against real Postgres (testcontainers). Go 1.26.9, run with
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true.New and updated tests:
Integration, new:
TestPostgresAnalyticsStore_QueryMonthlyTotals_UnknownStaysUnknownseeds three buckets:It refreshes the views and checks that rds commitment is 400 and rds usage is nil (not 250), that ec2 commitment is nil (not 70) and ec2 usage is 100, and that elasticache returns a non-nil 0 for both.
Integration, extended:
TestPostgresAnalyticsStore_QueryMonthlyTotalsnow also checks commitment 1000 and usage 800.Unit:
TestPostgresAnalyticsStore_QueryMonthlyTotals_WithRows(known, NULL and zero rows),TestQueryMonthlyTotals,TestQueryMonthlyTotalsRowsErr(mock columns updated),TestMonthlySummary(unknown is omitted, a known 0 is sent), andTestHandler_getAnalyticsTrends_MonthlyCommitmentAndUsage(the wire shape of/api/analytics/trends).Fail-before / pass-after:
interfaces.goandpostgres_analytics.goreset toorigin/main, the new tests do not compile (TotalCommitment undefined).TestPostgresAnalyticsStore_QueryMonthlyTotals_WithRows,TestQueryMonthlyTotalsandTestQueryMonthlyTotalsRowsErr;TestPostgresAnalyticsStore_QueryMonthlyTotalsandTestPostgresAnalyticsStore_QueryMonthlyTotals_UnknownStaysUnknown.COALESCE(…, 0)failsTestPostgresAnalyticsStore_QueryMonthlyTotals_UnknownStaysUnknown.omitempty, so an unknown is sent asnull, failsTestMonthlySummaryandTestHandler_getAnalyticsTrends_MonthlyCommitmentAndUsage.Not run here: the other integration tests that read
QueryMonthlyTotals(internal/database/postgres/migrations/000078_monthly_summary_nested_rollup_test.go,postgres_analytics_account_filter_db_test.go). They are left to the combined-tree run.Combined-tree verification. This branch was tested together with the other queued branches on a local, never-pushed tree:
queue/platform@400d256b= origin/main7b86e386plus these heads, merged in this order:Linux, Go 1.26.9,
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true; Docker Postgres via testcontainers. The full suite ran onf7c48020, which differs from this tree only by test-only commits on fix/issue-30, fix/issue-713 and fix/issue-263 that replace placeholder account-number literals with named constants:go build ./...,go vet ./...,go vet -tags integration ./...and the fullgo test -race -short -count=1 ./....--build-tags integration.gocyclo -over 10is silent on the changed non-test files.-tags integration -count=1):internal/analytics,internal/api,internal/auth,internal/config,internal/database/postgres/migrations,internal/purchase,internal/schedulerandinternal/serverall pass. Migrations 000149, 000150 and 000151 apply in order on this tree.approval-details,settings-accounts,api) pass (221 tests).accountkey); fixed with named constants. Re-run on this tree: git-secrets, using the repo'sscripts/setup-git-secrets.shpatterns in a fresh repo with an isolated HOME, found nothing in the changed files, and a planted fake key was flagged.scripts/test-git-secrets-allowlist.sh: 183 PASS, 0 FAIL..tftest.hclfiles were not run locally (CI runs them). Nothing was applied to any cloud.Out of scope
coverage_percentage = NULL, because purchase_history has no on-demand baseline, soavg_coveragestays omitted.by_provider/by_service(QueryByProvider/QueryByService) still return no commitment or usage. The issue's breakdowns only need savings and coverage. Adding the other two would mean a new NULL-propagating aggregate oversavings_snapshots, which is better done as its own change if the UI wants it.Frontend follow-up for the UI owner
monthly[i]row now hastotal_commitment(upfront payments amortized per month) andtotal_usage(recurring monthly fees), both monthly run-rates in USD. Committed spend istotal_commitment + total_usage; a no-upfront portfolio hastotal_commitment0. If either is missing, show committed spend as unknown rather than summing a partial value. Plot it per month next to realized savings.$0, and never sum it as 0 when combining rows across accounts or services. If any row in a month lacks the field, that month's combined total is unknown too.0is a real zero and should be shown as$0.Maintainers: please mirror the issue's triage labels onto this PR:
priority/p2,triaged,severity/low,urgency/this-quarter,impact/many,effort/m,type/feat.🤖 Generated with Claude Code