mirror of
https://github.com/lukaszraczylo/git-velocity.git
synced 2026-06-05 22:43:56 +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
240 lines
10 KiB
Go
240 lines
10 KiB
Go
package models
|
|
|
|
import "time"
|
|
|
|
// Period represents a time period for metrics aggregation
|
|
type Period struct {
|
|
Start time.Time `json:"start"`
|
|
End time.Time `json:"end"`
|
|
Granularity string `json:"granularity"` // daily, weekly, monthly, custom
|
|
Label string `json:"label"` // e.g., "Week 42", "December 2024", "Q1 2024"
|
|
}
|
|
|
|
// ContributorMetrics holds aggregated metrics for a single contributor
|
|
type ContributorMetrics struct {
|
|
Login string `json:"login"`
|
|
Name string `json:"name"`
|
|
AvatarURL string `json:"avatar_url"`
|
|
Period Period `json:"period"`
|
|
|
|
// Commit metrics
|
|
CommitCount int `json:"commit_count"`
|
|
CommitsWithTests int `json:"commits_with_tests"` // Commits that include test files
|
|
LinesAdded int `json:"lines_added"`
|
|
LinesDeleted int `json:"lines_deleted"`
|
|
FilesChanged int `json:"files_changed"`
|
|
|
|
// Meaningful line counts (excludes comments and whitespace)
|
|
MeaningfulLinesAdded int `json:"meaningful_lines_added"`
|
|
MeaningfulLinesDeleted int `json:"meaningful_lines_deleted"`
|
|
|
|
// Comment and documentation line counts
|
|
CommentLinesAdded int `json:"comment_lines_added"`
|
|
CommentLinesDeleted int `json:"comment_lines_deleted"`
|
|
|
|
// PR metrics
|
|
PRsOpened int `json:"prs_opened"`
|
|
PRsMerged int `json:"prs_merged"`
|
|
PRsClosed int `json:"prs_closed"`
|
|
AvgPRSize float64 `json:"avg_pr_size"`
|
|
AvgTimeToMerge float64 `json:"avg_time_to_merge_hours"`
|
|
LargestPRSize int `json:"largest_pr_size"` // Biggest single PR by lines changed
|
|
SmallPRCount int `json:"small_pr_count"` // PRs under 100 lines (good practice)
|
|
PerfectPRs int `json:"perfect_prs"` // PRs merged without changes requested
|
|
|
|
// Review metrics
|
|
ReviewsGiven int `json:"reviews_given"`
|
|
ReviewComments int `json:"review_comments"`
|
|
ApprovalsGiven int `json:"approvals_given"`
|
|
ChangesRequested int `json:"changes_requested"`
|
|
AvgReviewTime float64 `json:"avg_review_time_hours"`
|
|
|
|
// Issue metrics
|
|
IssuesOpened int `json:"issues_opened"`
|
|
IssuesClosed int `json:"issues_closed"`
|
|
IssueComments int `json:"issue_comments"`
|
|
IssueReferencesInCommits int `json:"issue_references_in_commits"` // Commits referencing issues (fixes #123, etc.)
|
|
|
|
// Activity patterns
|
|
ActiveDays int `json:"active_days"` // Unique days with activity
|
|
CurrentStreak int `json:"current_streak"` // Current consecutive days
|
|
LongestStreak int `json:"longest_streak"` // Longest consecutive days
|
|
WorkWeekStreak int `json:"work_week_streak"` // Longest consecutive weekdays (Mon-Fri, weekends don't break streak)
|
|
EarlyBirdCount int `json:"early_bird_count"` // Commits before 9am
|
|
NightOwlCount int `json:"night_owl_count"` // Commits after 9pm
|
|
MidnightCount int `json:"midnight_count"` // Commits between midnight and 4am
|
|
WeekendWarrior int `json:"weekend_warrior"` // Weekend commits
|
|
OutOfHoursCount int `json:"out_of_hours_count"` // Commits outside 9am-5pm (legacy, kept for achievements)
|
|
|
|
// Time-based commit counts for multiplier scoring
|
|
RegularHoursCount int `json:"regular_hours_count"` // Commits 9am-5pm (x1 multiplier)
|
|
EveningCount int `json:"evening_count"` // Commits 5pm-9pm (x2 multiplier)
|
|
LateNightCount int `json:"late_night_count"` // Commits 9pm-midnight (x2.5 multiplier)
|
|
OvernightCount int `json:"overnight_count"` // Commits midnight-6am (x5 multiplier)
|
|
EarlyMorningCount int `json:"early_morning_count"` // Commits 6am-9am (x2 multiplier)
|
|
|
|
// Repository participation
|
|
RepositoriesContributed []string `json:"repositories_contributed,omitempty"`
|
|
UniqueReviewees int `json:"unique_reviewees"`
|
|
|
|
// Scoring
|
|
Score Score `json:"score"`
|
|
Achievements []string `json:"achievements"` // Achievement IDs
|
|
}
|
|
|
|
// Score holds the calculated score and breakdown
|
|
type Score struct {
|
|
Total int `json:"total"`
|
|
Breakdown ScoreBreakdown `json:"breakdown"`
|
|
Rank int `json:"rank"`
|
|
PercentileRank float64 `json:"percentile_rank"`
|
|
}
|
|
|
|
// ScoreBreakdown shows how the score was calculated
|
|
type ScoreBreakdown struct {
|
|
Commits int `json:"commits"`
|
|
PRs int `json:"prs"`
|
|
Reviews int `json:"reviews"`
|
|
Comments int `json:"comments"` // PR review comments (not code comments)
|
|
Issues int `json:"issues"` // Issue-related points (opened, closed, comments, references)
|
|
ResponseBonus int `json:"response_bonus"`
|
|
LineChanges int `json:"line_changes"`
|
|
TestsBonus int `json:"tests_bonus"` // Bonus for commits that include test files
|
|
OutOfHours int `json:"out_of_hours"` // Bonus for out-of-hours commits
|
|
}
|
|
|
|
// RepositoryMetrics holds aggregated metrics for a single repository
|
|
type RepositoryMetrics struct {
|
|
Owner string `json:"owner"`
|
|
Name string `json:"name"`
|
|
FullName string `json:"full_name"` // owner/name
|
|
Period Period `json:"period"`
|
|
Contributors []ContributorMetrics `json:"contributors"`
|
|
TotalCommits int `json:"total_commits"`
|
|
TotalPRs int `json:"total_prs"`
|
|
TotalReviews int `json:"total_reviews"`
|
|
ActiveContributors int `json:"active_contributors"`
|
|
TotalLinesAdded int `json:"total_lines_added"`
|
|
TotalLinesDeleted int `json:"total_lines_deleted"`
|
|
|
|
// Meaningful line counts (excludes comments and whitespace)
|
|
TotalMeaningfulLinesAdded int `json:"total_meaningful_lines_added"`
|
|
TotalMeaningfulLinesDeleted int `json:"total_meaningful_lines_deleted"`
|
|
}
|
|
|
|
// TeamMetrics holds aggregated metrics for a team
|
|
type TeamMetrics struct {
|
|
Name string `json:"name"`
|
|
Color string `json:"color"`
|
|
Members []string `json:"members"`
|
|
Period Period `json:"period"`
|
|
AggregatedMetrics ContributorMetrics `json:"aggregated_metrics"`
|
|
MemberMetrics []ContributorMetrics `json:"member_metrics"`
|
|
TotalScore int `json:"total_score"`
|
|
AvgScore float64 `json:"avg_score"`
|
|
}
|
|
|
|
// GlobalMetrics holds metrics aggregated across all repositories
|
|
type GlobalMetrics struct {
|
|
Period Period `json:"period"`
|
|
Repositories []RepositoryMetrics `json:"repositories"`
|
|
Contributors []ContributorMetrics `json:"contributors"` // Aggregated across all repos
|
|
Teams []TeamMetrics `json:"teams"`
|
|
Leaderboard []LeaderboardEntry `json:"leaderboard"`
|
|
TopAchievers map[string]string `json:"top_achievers"` // category -> login
|
|
|
|
// Summary stats
|
|
TotalContributors int `json:"total_contributors"`
|
|
TotalCommits int `json:"total_commits"`
|
|
TotalPRs int `json:"total_prs"`
|
|
TotalReviews int `json:"total_reviews"`
|
|
TotalLinesAdded int `json:"total_lines_added"`
|
|
TotalLinesDeleted int `json:"total_lines_deleted"`
|
|
|
|
// Meaningful line counts (excludes comments and whitespace)
|
|
TotalMeaningfulLinesAdded int `json:"total_meaningful_lines_added"`
|
|
TotalMeaningfulLinesDeleted int `json:"total_meaningful_lines_deleted"`
|
|
|
|
// Velocity timeline (weekly granularity)
|
|
VelocityTimeline *VelocityTimeline `json:"velocity_timeline,omitempty"`
|
|
}
|
|
|
|
// VelocityTimeline holds weekly velocity data for trend visualization
|
|
type VelocityTimeline struct {
|
|
Labels []string `json:"labels"` // Week labels (e.g., "Dec 2", "Dec 9")
|
|
Series []VelocityTimelineSeries `json:"series"` // Data series (commits, PRs, reviews, score)
|
|
}
|
|
|
|
// VelocityTimelineSeries represents a single data series in the velocity timeline
|
|
type VelocityTimelineSeries struct {
|
|
Name string `json:"name"` // Series name (e.g., "Commits", "PRs", "Score")
|
|
Color string `json:"color"` // Series color
|
|
Data []float64 `json:"data"` // Values for each week
|
|
}
|
|
|
|
// LeaderboardEntry represents a single entry in the leaderboard
|
|
type LeaderboardEntry struct {
|
|
Rank int `json:"rank"`
|
|
Login string `json:"login"`
|
|
Name string `json:"name"`
|
|
AvatarURL string `json:"avatar_url"`
|
|
Score int `json:"score"`
|
|
Team string `json:"team,omitempty"`
|
|
TopCategory string `json:"top_category,omitempty"` // What they're best at
|
|
Achievements []string `json:"achievements,omitempty"` // Achievement IDs earned
|
|
}
|
|
|
|
// TimeSeriesPoint represents a single data point in a time series
|
|
type TimeSeriesPoint struct {
|
|
Date time.Time `json:"date"`
|
|
Label string `json:"label"`
|
|
Value float64 `json:"value"`
|
|
}
|
|
|
|
// TimeSeries represents a series of data points over time
|
|
type TimeSeries struct {
|
|
Name string `json:"name"`
|
|
Color string `json:"color,omitempty"`
|
|
Points []TimeSeriesPoint `json:"points"`
|
|
}
|
|
|
|
// ChartData holds data formatted for charts
|
|
type ChartData struct {
|
|
Title string `json:"title"`
|
|
Description string `json:"description,omitempty"`
|
|
Type string `json:"type"` // line, bar, pie, doughnut
|
|
Labels []string `json:"labels"`
|
|
Series []TimeSeries `json:"series"`
|
|
}
|
|
|
|
// DashboardData holds all data needed for the dashboard
|
|
type DashboardData struct {
|
|
GeneratedAt time.Time `json:"generated_at"`
|
|
Period Period `json:"period"`
|
|
GlobalMetrics GlobalMetrics `json:"global_metrics"`
|
|
Charts []ChartData `json:"charts"`
|
|
Achievements []Achievement `json:"achievements"`
|
|
Configuration DashboardConfig `json:"configuration"`
|
|
}
|
|
|
|
// DashboardConfig holds UI configuration
|
|
type DashboardConfig struct {
|
|
Title string `json:"title"`
|
|
Description string `json:"description,omitempty"`
|
|
Repositories []string `json:"repositories"`
|
|
Teams []string `json:"teams,omitempty"`
|
|
Granularities []string `json:"granularities"`
|
|
ScoringEnabled bool `json:"scoring_enabled"`
|
|
ShowAchievements bool `json:"show_achievements"`
|
|
}
|
|
|
|
// Achievement represents an earned achievement badge
|
|
type Achievement struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Icon string `json:"icon"`
|
|
EarnedBy string `json:"earned_by"` // Login of user who earned it
|
|
EarnedAt string `json:"earned_at"` // When it was earned (period label)
|
|
}
|