Skip to content

Commit 39f1ad1

Browse files
authored
feat: match dashboard cost formatting and guardrail messages (#4)
* feat: match dashboard cost formatting and guardrail messages Update formatCost to use the dashboard's autoDecimals behavior: values >= $1 are rounded to whole numbers, values < $1 show 2 decimal places. Rewrite formatGuardrailMessage to match the dashboard's buildTriggerReason, including scope, threshold explanations, and user message. This requires the new config fields added to GuardrailResult in go-proto. * update go.mod
1 parent 7581a6e commit 39f1ad1

12 files changed

Lines changed: 112 additions & 64 deletions

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.25.5
44

55
require (
66
github.com/google/go-cmp v0.7.0
7-
github.com/infracost/go-proto v0.11.0
7+
github.com/infracost/go-proto v0.12.0
88
github.com/infracost/proto v1.29.0
99
github.com/shurcooL/githubv4 v0.0.0-20260209031235-2402fdf4a9ed
1010
golang.org/x/oauth2 v0.36.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
2424
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
2525
github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
2626
github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
27-
github.com/infracost/go-proto v0.11.0 h1:Q6nRZDxLJUsdVI0JDgnAb8NL/2JvhmgklZ2aYIeQmtI=
28-
github.com/infracost/go-proto v0.11.0/go.mod h1:VtzyuTe3h6GzrgHOQkVxjiP6Wpn4iUbr1HDaszcaV3k=
27+
github.com/infracost/go-proto v0.12.0 h1:A+4/yEkqp0IWiCgOcgF4Auw1l92LWGUzd8sZhGXWeDA=
28+
github.com/infracost/go-proto v0.12.0/go.mod h1:VtzyuTe3h6GzrgHOQkVxjiP6Wpn4iUbr1HDaszcaV3k=
2929
github.com/infracost/proto v1.29.0 h1:+SaqDjOwGMpKlitplVS9GdK3TnL4kat0Iyfbxkvzudk=
3030
github.com/infracost/proto v1.29.0/go.mod h1:Z8vPWBWblwJlw+/ksO+BtsXwf9NiOcSTWx0WRWNbfUA=
3131
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=

pkg/vcs/comment/costs.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,16 @@ func formatPercentChange(oldCost, newCost *rat.Rat) string {
202202
return fmt.Sprintf("%s%s%%", percentSym, p.StringFixed(0))
203203
}
204204

205-
// formatCost formats a cost value with currency symbol.
205+
// formatCost formats a cost value with currency symbol, matching the
206+
// dashboard's autoDecimals behavior: values with absolute value >= 1 (or zero)
207+
// are rounded to whole numbers, smaller values show 2 decimal places.
208+
// See: dashboard/api/src/utils/format.ts CurrencyFormatter.formatCost
206209
func formatCost(d *rat.Rat, currency string) string {
207-
if d == nil {
208-
return currencySymbol(currency) + "0.00"
210+
if d == nil || d.IsZero() {
211+
return currencySymbol(currency) + "0"
212+
}
213+
if d.Abs().GreaterThanOrEqual(rat.New(1)) {
214+
return currencySymbol(currency) + d.StringFixed(0)
209215
}
210216
return currencySymbol(currency) + d.StringFixed(2)
211217
}

pkg/vcs/comment/governance.go

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/infracost/go-proto/pkg/event"
99
"github.com/infracost/go-proto/pkg/rat"
10+
protoevent "github.com/infracost/proto/gen/go/infracost/parser/event"
1011
"github.com/infracost/proto/gen/go/infracost/provider"
1112
)
1213

@@ -234,7 +235,7 @@ func (data *Data) processGuardrailResults(inputs *Inputs) {
234235
entry := GovernanceEntry{
235236
Title: title,
236237
Blocking: result.BlockPR,
237-
Message: formatGuardrailMessage(result),
238+
Message: formatGuardrailMessage(result, data.Currency),
238239
}
239240

240241
table.Entries = append(table.Entries, entry)
@@ -438,19 +439,57 @@ func formatTagIssues(resource event.TagPolicyResultResource) []string {
438439
return issues
439440
}
440441

441-
// formatGuardrailMessage builds the trigger description for a guardrail result.
442-
func formatGuardrailMessage(result event.GuardrailResult) string {
442+
// formatGuardrailMessage builds the trigger description for a guardrail result,
443+
// matching the dashboard's buildTriggerReason logic.
444+
// See: dashboard/api/src/services/guardrails.ts buildTriggerReason (~line 1008)
445+
func formatGuardrailMessage(result event.GuardrailResult, currency string) string {
443446
var parts []string
444447

445-
if result.Increase != nil && result.Increase.GreaterThanZero() {
446-
parts = append(parts, fmt.Sprintf("Cost increase: %s/mo", result.Increase.StringFixed(2)))
448+
if result.Scope == protoevent.Guardrail_PROJECT {
449+
parts = append(parts, "At least one project exceeded per-project threshold.")
447450
}
448-
if result.PercentIncrease != nil && result.PercentIncrease.GreaterThanZero() {
449-
parts = append(parts, fmt.Sprintf("(%s%%)", result.PercentIncrease.StringFixed(0)))
451+
452+
hasIncrease := result.IncreaseThreshold != nil
453+
hasPercent := result.IncreasePercentThreshold != nil
454+
455+
if hasIncrease {
456+
cost := formatCost(result.Increase, currency)
457+
if hasPercent {
458+
parts = append(parts, fmt.Sprintf(
459+
"Cost increased by %s (%s%%), threshold was %s and %s%%.",
460+
cost,
461+
result.PercentIncrease.StringFixed(0),
462+
formatCost(result.IncreaseThreshold, currency),
463+
result.IncreasePercentThreshold.StringFixed(0),
464+
))
465+
} else {
466+
parts = append(parts, fmt.Sprintf(
467+
"Cost increased by %s, threshold was %s.",
468+
cost,
469+
formatCost(result.IncreaseThreshold, currency),
470+
))
471+
}
472+
} else if hasPercent {
473+
if result.PercentIncrease != nil && !result.PercentIncrease.IsZero() {
474+
parts = append(parts, fmt.Sprintf(
475+
"Cost increased by %s (%s%%), threshold was %s%%.",
476+
formatCost(result.Increase, currency),
477+
result.PercentIncrease.StringFixed(0),
478+
result.IncreasePercentThreshold.StringFixed(0),
479+
))
480+
}
481+
}
482+
483+
if result.TotalThreshold != nil {
484+
parts = append(parts, fmt.Sprintf(
485+
"New monthly cost was %s, threshold was %s.",
486+
formatCost(result.TotalMonthlyCost, currency),
487+
formatCost(result.TotalThreshold, currency),
488+
))
450489
}
451490

452-
if len(result.TriggeringProjectNames) > 0 {
453-
parts = append(parts, fmt.Sprintf("in %s", formatProjectNamesLabel(result.TriggeringProjectNames)))
491+
if result.Message != "" {
492+
parts = append(parts, result.Message)
454493
}
455494

456495
return strings.Join(parts, " ")

pkg/vcs/comment/template.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,16 +146,16 @@ func formatCostChangeSentence(data *Data) string {
146146
currency := data.Currency
147147

148148
if past == nil {
149-
return fmt.Sprintf("Monthly estimate increased by %s%s 📈",
150-
currencySymbol(currency), total.StringFixed(2))
149+
return fmt.Sprintf("Monthly estimate increased by %s 📈",
150+
formatCost(total, currency))
151151
}
152152

153153
if past.Equals(total) {
154154
return "Monthly estimate generated"
155155
}
156156

157157
diff := total.Sub(past).Abs()
158-
change := currencySymbol(currency) + diff.StringFixed(2)
158+
change := formatCost(diff, currency)
159159

160160
if past.GreaterThan(total) {
161161
return fmt.Sprintf("Monthly estimate decreased by %s 📉", change)

pkg/vcs/comment/template_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/infracost/go-proto/pkg/event"
1212
"github.com/infracost/go-proto/pkg/rat"
1313
parserpb "github.com/infracost/proto/gen/go/infracost/parser"
14+
protoevent "github.com/infracost/proto/gen/go/infracost/parser/event"
1415
"github.com/infracost/proto/gen/go/infracost/provider"
1516
)
1617

@@ -304,6 +305,8 @@ func TestRender(t *testing.T) {
304305
Increase: rat.New(400),
305306
PercentIncrease: rat.New(400),
306307
TriggeringProjectNames: []string{"my-project"},
308+
Scope: protoevent.Guardrail_PROJECT,
309+
IncreaseThreshold: rat.New(100),
307310
},
308311
},
309312
Projects: []ProjectResult{

pkg/vcs/comment/testdata/cost_increase.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<h3>💰 Infracost report</h3>
22
This pull request is aligned with your company's FinOps policies and the Well-Architected Framework.
33
<details >
4-
<summary><b>Monthly estimate increased by $50.00 📈</b> </summary>
4+
<summary><b>Monthly estimate increased by $50 📈</b> </summary>
55
<br/>
66

77
<table>
@@ -15,10 +15,10 @@ This pull request is aligned with your company's FinOps policies and the Well-Ar
1515
<tbody>
1616
<tr>
1717
<td>my-project</td>
18-
<td align="right">+$45.00</td>
19-
<td align="right">+$5.00</td>
20-
<td align="right">+$50.00 (+25%)</td>
21-
<td align="right">$250.00</td>
18+
<td align="right">+$45</td>
19+
<td align="right">+$5</td>
20+
<td align="right">+$50 (+25%)</td>
21+
<td align="right">$250</td>
2222
</tr>
2323
</tbody>
2424
</table>
@@ -35,13 +35,13 @@ Key: * usage cost, ~ changed, + added, - removed
3535
Project: my-project
3636
3737
~ aws_instance.web
38-
+$50.00 ($200.00 → $250.00)
38+
+$50 ($200 → $250)
3939
4040
~ Linux/UNIX usage (on-demand, m5.xlarge)
41-
+$50.00 ($70.00 → $140.00)
41+
+$50 ($70 → $140)
4242
4343
Monthly cost change for my-project
44-
Amount: +$50.00 ($200.00 → $250.00)
44+
Amount: +$50 ($200 → $250)
4545
Percent: +25%
4646
4747
──────────────────────────────────

pkg/vcs/comment/testdata/environmental_metrics.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Graviton instances are more energy efficient.
2323

2424
</table>
2525
<details >
26-
<summary><b>Monthly estimate increased by $100.00 📈</b> (🌱 emit 500.0 kg CO₂e - that's more than 3.3 flights between London & Paris)</summary>
26+
<summary><b>Monthly estimate increased by $100 📈</b> (🌱 emit 500.0 kg CO₂e - that's more than 3.3 flights between London & Paris)</summary>
2727
<br/>
2828

2929
<table>
@@ -37,10 +37,10 @@ Graviton instances are more energy efficient.
3737
<tbody>
3838
<tr>
3939
<td>my-project</td>
40-
<td align="right">+$100.00</td>
40+
<td align="right">+$100</td>
4141
<td align="right">-</td>
42-
<td align="right">+$100.00 (+50%)</td>
43-
<td align="right">$300.00</td>
42+
<td align="right">+$100 (+50%)</td>
43+
<td align="right">$300</td>
4444
</tr>
4545
</tbody>
4646
</table>
@@ -59,10 +59,10 @@ Key: * usage cost, ~ changed, + added, - removed
5959
Project: my-project
6060
6161
~ aws_instance.web
62-
+$100.00 ($200.00 → $300.00)
62+
+$100 ($200 → $300)
6363
6464
Monthly cost change for my-project
65-
Amount: +$100.00 ($200.00 → $300.00)
65+
Amount: +$100 ($200 → $300)
6666
Percent: +50%
6767
6868
──────────────────────────────────

pkg/vcs/comment/testdata/guardrail_blocks.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
</td></tr>
1111
<tr><td></td><td>
1212

13-
Cost increase: 400.00/mo (400%) in project `my-project`
13+
At least one project exceeded per-project threshold. Cost increased by $400, threshold was $100.
1414
</td></tr>
1515

1616
</table>
1717
<details open>
18-
<summary><b>Monthly estimate increased by $400.00 📈</b> </summary>
18+
<summary><b>Monthly estimate increased by $400 📈</b> </summary>
1919
<br/>
2020

2121
<table>
@@ -29,10 +29,10 @@ Cost increase: 400.00/mo (400%) in project `my-project`
2929
<tbody>
3030
<tr>
3131
<td>my-project</td>
32-
<td align="right">+$400.00</td>
32+
<td align="right">+$400</td>
3333
<td align="right">-</td>
34-
<td align="right">+$400.00 (+400%)</td>
35-
<td align="right">$500.00</td>
34+
<td align="right">+$400 (+400%)</td>
35+
<td align="right">$500</td>
3636
</tr>
3737
</tbody>
3838
</table>
@@ -49,10 +49,10 @@ Key: * usage cost, ~ changed, + added, - removed
4949
Project: my-project
5050
5151
~ aws_instance.big
52-
+$400.00 ($100.00 → $500.00)
52+
+$400 ($100 → $500)
5353
5454
Monthly cost change for my-project
55-
Amount: +$400.00 ($100.00 → $500.00)
55+
Amount: +$400 ($100 → $500)
5656
Percent: +400%
5757
5858
──────────────────────────────────

pkg/vcs/comment/testdata/project_errors.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<hr/>
1313
This pull request is aligned with your company's FinOps policies and the Well-Architected Framework.
1414
<details >
15-
<summary><b>Monthly estimate increased by $0.00 📈</b> </summary>
15+
<summary><b>Monthly estimate increased by $0 📈</b> </summary>
1616
<br/>
1717
<details>
1818
<summary>Estimate details (includes details of skipped projects due to errors)</summary>

0 commit comments

Comments
 (0)