Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/prcost/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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] <PR_URL>\n", os.Args[0])
Expand Down
4 changes: 0 additions & 4 deletions cmd/prcost/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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")

Expand Down
2 changes: 1 addition & 1 deletion internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions internal/server/static/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
4 changes: 2 additions & 2 deletions internal/server/static/formatR2RCallout.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ function formatR2RCallout(avgOpenHours, r2rSavings, currentEfficiency, modeledEf

let html = '<div style="margin: 24px 0; padding: 12px 20px; background: linear-gradient(135deg, #e6f9f0 0%, #ffffff 100%); border: 1px solid #00c853; border-radius: 8px; font-size: 14px; color: #1d1d1f; line-height: 1.6;">';
html += 'Pro-Tip: Save <strong>' + savingsText + '/yr</strong> in lost development effort by reducing merge times to &lt;' + targetText + ' with ';
html += '<a href="https://codegroove.dev/" target="_blank" rel="noopener" style="color: #00c853; font-weight: 600; text-decoration: none;">Ready-to-Review</a>. ';
html += 'Free for OSS, cheap for everyone else.';
html += '<a href="https://codegroove.dev/products/ready-to-review/" target="_blank" rel="noopener" style="color: #00c853; font-weight: 600; text-decoration: none;">Ready to Review</a>. ';
html += 'Free for open-source repositories, $6/user/org for private repos.';
html += '</div>';
return html;
}
Expand Down
11 changes: 6 additions & 5 deletions internal/server/static/formatR2RCallout.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
17 changes: 10 additions & 7 deletions internal/server/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1421,7 +1421,7 @@ <h3>Why calculate PR costs?</h3>
html += `<span style="font-size: 28px; font-weight: 700; color: #1d1d1f;">${efficiencyPct.toFixed(1)}%</span>`;
html += '</div>';
html += `<div class="efficiency-message">${message}</div>`;
html += '<div style="font-size: 11px; color: #86868b; margin-top: 4px;">Expected costs minus delay costs</div>';
html += '<div style="font-size: 11px; color: #86868b; margin-top: 4px;">Time spent coding vs. waiting</div>';
html += '</div>'; // Close efficiency-box

// Merge Velocity box
Expand Down Expand Up @@ -1452,9 +1452,9 @@ <h3>Why calculate PR costs?</h3>
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 '';
}

Expand All @@ -1474,10 +1474,13 @@ <h3>Why calculate PR costs?</h3>
throughputText = ' (+' + efficiencyDelta.toFixed(1) + '% throughput)';
}

// Format target merge time
let targetText = targetMergeHours.toFixed(1) + 'h';

let html = '<div style="margin: 24px 0; padding: 12px 20px; background: linear-gradient(135deg, #e6f9f0 0%, #ffffff 100%); border: 1px solid #00c853; border-radius: 8px; font-size: 14px; color: #1d1d1f; line-height: 1.6;">';
html += 'Pro-Tip: Save <strong>' + savingsText + '/yr</strong> in lost development effort by reducing merge times to &lt;1h with ';
html += '<a href="https://codegroove.dev/" target="_blank" rel="noopener" style="color: #00c853; font-weight: 600; text-decoration: none;">Ready-to-Review</a>. ';
html += 'Free for OSS, cheap for everyone else.';
html += 'Pro-Tip: Save <strong>' + savingsText + '/yr</strong> in lost development effort by reducing merge times to &lt;' + targetText + ' with ';
html += '<a href="https://codegroove.dev/products/ready-to-review/" target="_blank" rel="noopener" style="color: #00c853; font-weight: 600; text-decoration: none;">Ready to Review</a>. ';
html += 'Free for open-source repositories, $6/user/org for private repos.';
html += '</div>';
return html;
}
Expand Down
Loading