diff --git a/cmd/prcost/main.go b/cmd/prcost/main.go index 4dccafd..ab4bb3f 100644 --- a/cmd/prcost/main.go +++ b/cmd/prcost/main.go @@ -35,7 +35,8 @@ func main() { days := flag.Int("days", 60, "Number of days to look back for PR modifications") // Modeling flags - targetMergeTime := flag.Duration("target-merge-time", 90*time.Minute, "Target merge time for efficiency modeling (default: 90 minutes / 1.5 hours)") + targetMergeTime := flag.Duration("target-merge-time", 90*time.Minute, + "Target merge time for efficiency modeling (default: 90 minutes / 1.5 hours)") flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s [options] \n", os.Args[0]) diff --git a/cmd/prcost/repository.go b/cmd/prcost/repository.go index 9dff09f..522aa42 100644 --- a/cmd/prcost/repository.go +++ b/cmd/prcost/repository.go @@ -14,8 +14,6 @@ import ( // analyzeRepository performs repository-wide cost analysis by sampling PRs. // Uses library functions from pkg/github and pkg/cost for fetching, sampling, // and extrapolation - all functionality is available to external clients. -// -//nolint:revive // argument-limit: acceptable for entry point function func analyzeRepository(ctx context.Context, owner, repo string, sampleSize, days int, cfg cost.Config, token, dataSource string) error { // Calculate since date since := time.Now().AddDate(0, 0, -days) @@ -113,8 +111,6 @@ func analyzeRepository(ctx context.Context, owner, repo string, sampleSize, days // analyzeOrganization performs organization-wide cost analysis by sampling PRs across all repos. // Uses library functions from pkg/github and pkg/cost for fetching, sampling, // and extrapolation - all functionality is available to external clients. -// -//nolint:revive // argument-limit: acceptable for entry point function func analyzeOrganization(ctx context.Context, org string, sampleSize, days int, cfg cost.Config, token, dataSource string) error { slog.Info("Fetching PR list from organization") diff --git a/internal/server/server.go b/internal/server/server.go index 2c72566..49947e8 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -313,7 +313,7 @@ func (s *Server) SetDataSource(source string) { s.logger.InfoContext(ctx, "Data source configured", "source", source) } -// SetR2RCallout enables or disables the Ready-to-Review promotional callout. +// SetR2RCallout enables or disables the Ready to Review promotional callout. func (s *Server) SetR2RCallout(enabled bool) { s.r2rCallout = enabled } diff --git a/internal/server/static/README.md b/internal/server/static/README.md index 5df3e6a..982a1e7 100644 --- a/internal/server/static/README.md +++ b/internal/server/static/README.md @@ -6,7 +6,7 @@ This directory contains static assets for the prcost web UI, including JavaScrip Key functions are extracted into separate `.js` files for testing purposes: -- `formatR2RCallout.js` - Renders the Ready-to-Review savings callout +- `formatR2RCallout.js` - Renders the Ready to Review savings callout - `formatR2RCallout.test.js` - Tests for the callout rendering ### Running Tests @@ -24,7 +24,7 @@ make test The JavaScript tests verify: - Correct rendering of the savings callout HTML - Proper formatting of dollar amounts ($50K, $2.5M, etc.) -- Presence of key messaging ("Pro-Tip:", "Ready-to-Review", etc.) +- Presence of key messaging ("Pro-Tip:", "Ready to Review", etc.) - Correct behavior for fast PRs (no callout for ≤1 hour) - HTML structure and styling diff --git a/internal/server/static/formatR2RCallout.js b/internal/server/static/formatR2RCallout.js index 125fde3..76ad6db 100644 --- a/internal/server/static/formatR2RCallout.js +++ b/internal/server/static/formatR2RCallout.js @@ -26,8 +26,8 @@ function formatR2RCallout(avgOpenHours, r2rSavings, currentEfficiency, modeledEf let html = '
'; html += 'Pro-Tip: Save ' + savingsText + '/yr in lost development effort by reducing merge times to <' + targetText + ' with '; - html += 'Ready-to-Review. '; - html += 'Free for OSS, cheap for everyone else.'; + html += 'Ready to Review. '; + html += 'Free for open-source repositories, $6/user/org for private repos.'; html += '
'; return html; } diff --git a/internal/server/static/formatR2RCallout.test.js b/internal/server/static/formatR2RCallout.test.js index cf92647..7df0034 100644 --- a/internal/server/static/formatR2RCallout.test.js +++ b/internal/server/static/formatR2RCallout.test.js @@ -38,17 +38,18 @@ test('Contains "Pro-Tip:" text', () => { assert(result.includes('Pro-Tip:'), 'Should contain "Pro-Tip:"'); }); -// Test 4: Should contain "Ready-to-Review" link -test('Contains "Ready-to-Review" link', () => { +// Test 4: Should contain "Ready to Review" link +test('Contains "Ready to Review" link', () => { const result = formatR2RCallout(10, 50000, 60, 70); - assert(result.includes('Ready-to-Review'), 'Should contain "Ready-to-Review"'); - assert(result.includes('href="https://codegroove.dev/"'), 'Should link to codegroove.dev'); + assert(result.includes('Ready to Review'), 'Should contain "Ready to Review"'); + assert(result.includes('href="https://codegroove.dev/products/ready-to-review/"'), 'Should link to Ready to Review page'); }); // Test 5: Should contain OSS pricing message test('Contains OSS pricing message', () => { const result = formatR2RCallout(10, 50000, 60, 70); - assert(result.includes('Free for OSS, cheap for everyone else'), 'Should contain OSS pricing message'); + assert(result.includes('Free for open-source repositories'), 'Should contain OSS pricing message'); + assert(result.includes('$6/user/org for private repos'), 'Should contain private repo pricing'); }); // Test 6: Should format savings in thousands (K) diff --git a/internal/server/static/index.html b/internal/server/static/index.html index ce8ca69..0e9dd30 100644 --- a/internal/server/static/index.html +++ b/internal/server/static/index.html @@ -1421,7 +1421,7 @@

Why calculate PR costs?

html += `${efficiencyPct.toFixed(1)}%`; html += ''; html += `
${message}
`; - html += '
Expected costs minus delay costs
'; + html += '
Time spent coding vs. waiting
'; html += ''; // Close efficiency-box // Merge Velocity box @@ -1452,9 +1452,9 @@

Why calculate PR costs?

return html; } - function formatR2RCallout(avgOpenHours, r2rSavings, currentEfficiency, modeledEfficiency) { - // Only show if average merge velocity is > 1 hour - if (avgOpenHours <= 1) { + function formatR2RCallout(avgOpenHours, r2rSavings, currentEfficiency, modeledEfficiency, targetMergeHours = 1.5) { + // Only show if average merge velocity is > target + if (avgOpenHours <= targetMergeHours) { return ''; } @@ -1474,10 +1474,13 @@

Why calculate PR costs?

throughputText = ' (+' + efficiencyDelta.toFixed(1) + '% throughput)'; } + // Format target merge time + let targetText = targetMergeHours.toFixed(1) + 'h'; + let html = '
'; - html += 'Pro-Tip: Save ' + savingsText + '/yr in lost development effort by reducing merge times to <1h with '; - html += 'Ready-to-Review. '; - html += 'Free for OSS, cheap for everyone else.'; + html += 'Pro-Tip: Save ' + savingsText + '/yr in lost development effort by reducing merge times to <' + targetText + ' with '; + html += 'Ready to Review. '; + html += 'Free for open-source repositories, $6/user/org for private repos.'; html += '
'; return html; }