mirror of
https://github.com/lukaszraczylo/git-velocity.git
synced 2026-06-17 03:38:28 +00:00
7ba4d438dd
* feat(scoring): add tests bonus and fix average calculations - [x] Add CommitsWithTests metric to track commits with test file changes - [x] Add TestsBonus to score breakdown (15 points per commit with tests) - [x] Fix AvgTimeToMerge calculation to use count of PRs with valid data - [x] Fix AvgReviewTime calculation to use count of reviews with valid data - [x] Fix AvgPRSize calculation to only include merged PRs - [x] Add trackActivityDay helper to deduplicate activity tracking code - [x] Track activity days for PR creation, reviews, and issue comments - [x] Separate issue close tracking from issue open tracking - [x] Update early bird window from 5am-9am to 6am-9am - [x] Add time-based multipliers to velocity timeline scoring - [x] Update GraphQL query to fetch OPEN, MERGED, CLOSED PRs - [x] Fix PR filtering logic to handle all PR states correctly - [x] Improve watch handlers in Vue components to prevent double-loading - [x] Fix formatDuration to handle zero and negative values - [x] Update scoring documentation to include Tests component * refactor: use standard library and consolidate constants - [x] Replace custom contains function with slices.Contains - [x] Remove duplicate contains function implementations - [x] Extract magic numbers to named constants in formatters - [x] Create constants composable for app-wide values - [x] Add ESLint configuration with browser globals - [x] Add lint npm scripts to package.json - [x] Reorder Vue template attributes for consistency - [x] Remove unused variable in AchievementProgress - [x] Add pnpm lock file
69 lines
1.5 KiB
JavaScript
69 lines
1.5 KiB
JavaScript
// Number formatting thresholds
|
|
const ONE_MILLION = 1_000_000
|
|
const ONE_THOUSAND = 1_000
|
|
|
|
// Time conversion constants
|
|
const MINUTES_PER_HOUR = 60
|
|
const HOURS_PER_DAY = 24
|
|
|
|
/**
|
|
* Format a number with K/M suffixes for large values
|
|
*/
|
|
export function formatNumber(n) {
|
|
if (n === null || n === undefined) return '0'
|
|
if (n >= ONE_MILLION) {
|
|
return (n / ONE_MILLION).toFixed(1) + 'M'
|
|
}
|
|
if (n >= ONE_THOUSAND) {
|
|
return (n / ONE_THOUSAND).toFixed(1) + 'K'
|
|
}
|
|
return String(n)
|
|
}
|
|
|
|
/**
|
|
* Format hours as a human-readable duration
|
|
*/
|
|
export function formatDuration(hours) {
|
|
if (hours === null || hours === undefined || hours <= 0) return '-'
|
|
if (hours < 1) {
|
|
return Math.round(hours * MINUTES_PER_HOUR) + 'm'
|
|
}
|
|
if (hours < HOURS_PER_DAY) {
|
|
return hours.toFixed(1) + 'h'
|
|
}
|
|
return (hours / HOURS_PER_DAY).toFixed(1) + 'd'
|
|
}
|
|
|
|
/**
|
|
* Format a date string or Date object
|
|
*/
|
|
export function formatDate(dateInput) {
|
|
if (!dateInput) return ''
|
|
const date = new Date(dateInput)
|
|
return date.toLocaleDateString('en-US', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric'
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Format a number as a percentage
|
|
*/
|
|
export function formatPercent(value) {
|
|
if (value === null || value === undefined) return '0%'
|
|
return value.toFixed(1) + '%'
|
|
}
|
|
|
|
/**
|
|
* Convert a string to a URL-friendly slug
|
|
*/
|
|
export function slugify(str) {
|
|
if (!str) return ''
|
|
return str
|
|
.toLowerCase()
|
|
.replace(/\s+/g, '-')
|
|
.replace(/_/g, '-')
|
|
.replace(/[^a-z0-9-]/g, '')
|
|
}
|