mirror of
https://github.com/lukaszraczylo/git-velocity.git
synced 2026-06-18 03: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
161 lines
6.0 KiB
Vue
161 lines
6.0 KiB
Vue
<script setup>
|
|
import { inject, computed, ref } from 'vue'
|
|
import { RouterLink } from 'vue-router'
|
|
import Card from '../components/Card.vue'
|
|
import StatCard from '../components/StatCard.vue'
|
|
import ContributorCard from '../components/ContributorCard.vue'
|
|
import RepoCard from '../components/RepoCard.vue'
|
|
import TeamCard from '../components/TeamCard.vue'
|
|
import SectionHeader from '../components/SectionHeader.vue'
|
|
import VelocityChart from '../components/VelocityChart.vue'
|
|
import { formatNumber, formatDate } from '../composables/formatters'
|
|
|
|
const globalData = inject('globalData')
|
|
|
|
const metrics = computed(() => globalData.value || {})
|
|
const leaderboard = computed(() => metrics.value.leaderboard?.slice(0, 3) || [])
|
|
const repositories = computed(() => metrics.value.repositories || [])
|
|
const teams = computed(() => metrics.value.teams || [])
|
|
const velocityTimeline = computed(() => metrics.value.velocity_timeline)
|
|
|
|
const showScoreInChart = ref(false)
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<!-- Hero Section -->
|
|
<header class="py-10 sm:py-16 px-4">
|
|
<div class="container mx-auto text-center animate-[fadeInUp_0.6s_ease-out]">
|
|
<h1 class="text-3xl sm:text-4xl md:text-6xl font-bold mb-3 sm:mb-4">
|
|
<span class="bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent">Git Velocity</span>
|
|
</h1>
|
|
<p class="text-base sm:text-xl text-gray-300 max-w-2xl mx-auto px-2">
|
|
Celebrate your team's achievements and contributions with beautiful insights.
|
|
</p>
|
|
<!-- Period and Generation Info -->
|
|
<div class="flex flex-col items-center space-y-2 mt-4 text-sm text-gray-400">
|
|
<p v-if="metrics.period?.start || metrics.period?.end">
|
|
<i class="fas fa-calendar-alt mr-1 text-primary-500"></i>
|
|
<span class="font-medium">Period:</span>
|
|
<span v-if="metrics.period.start">{{ formatDate(metrics.period.start) }}</span>
|
|
<span v-if="metrics.period.start && metrics.period.end"> — </span>
|
|
<span v-if="metrics.period.end">{{ formatDate(metrics.period.end) }}</span>
|
|
</p>
|
|
<p v-if="metrics.generated_at">
|
|
<i class="fas fa-clock mr-1"></i>
|
|
Generated on {{ formatDate(metrics.generated_at) }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Velocity Timeline Chart -->
|
|
<section v-if="velocityTimeline" class="py-6 sm:py-8 px-4">
|
|
<div class="container mx-auto">
|
|
<Card>
|
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 mb-4 sm:mb-6">
|
|
<SectionHeader title="Velocity Timeline" icon="fas fa-chart-line" icon-color="text-primary-500" />
|
|
<label class="flex items-center space-x-2 text-sm text-gray-400 cursor-pointer">
|
|
<input
|
|
v-model="showScoreInChart"
|
|
type="checkbox"
|
|
class="rounded border-gray-600 text-primary-500 focus:ring-primary-500"
|
|
/>
|
|
<span>Show Score</span>
|
|
</label>
|
|
</div>
|
|
<div class="h-[200px] sm:h-[280px] md:h-[320px]">
|
|
<VelocityChart :timeline="velocityTimeline" :show-score="showScoreInChart" height="100%" />
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Stats Overview -->
|
|
<section class="py-8 px-4">
|
|
<div class="container mx-auto">
|
|
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
|
|
<StatCard
|
|
:value="metrics.total_contributors || 0"
|
|
label="Contributors"
|
|
delay="0s"
|
|
/>
|
|
<StatCard
|
|
:value="metrics.total_commits || 0"
|
|
label="Commits"
|
|
delay="0.1s"
|
|
/>
|
|
<StatCard
|
|
:value="metrics.total_prs || 0"
|
|
label="Pull Requests"
|
|
delay="0.2s"
|
|
/>
|
|
<StatCard
|
|
:value="metrics.total_reviews || 0"
|
|
label="Reviews"
|
|
delay="0.3s"
|
|
/>
|
|
<StatCard
|
|
:value="'+' + formatNumber(metrics.total_lines_added || 0)"
|
|
label="Lines Added"
|
|
value-class="text-green-500"
|
|
delay="0.4s"
|
|
/>
|
|
<StatCard
|
|
:value="'-' + formatNumber(metrics.total_lines_deleted || 0)"
|
|
label="Lines Deleted"
|
|
value-class="text-red-500"
|
|
delay="0.5s"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Top Contributors -->
|
|
<section class="py-8 px-4">
|
|
<div class="container mx-auto">
|
|
<SectionHeader title="Top Contributors" icon="fas fa-trophy" icon-color="text-yellow-500" />
|
|
|
|
<div class="grid md:grid-cols-3 gap-6">
|
|
<ContributorCard
|
|
v-for="(entry, index) in leaderboard"
|
|
:key="entry.login"
|
|
:contributor="entry"
|
|
:rank="index + 1"
|
|
featured
|
|
/>
|
|
</div>
|
|
|
|
<div class="mt-6 text-center">
|
|
<RouterLink to="/leaderboard" class="inline-flex items-center px-6 py-3 bg-gradient-to-r from-primary-500 to-accent-500 text-white font-medium rounded-lg shadow-lg hover:from-primary-600 hover:to-accent-600 transition-all">
|
|
View Full Leaderboard
|
|
<i class="fas fa-arrow-right ml-2"></i>
|
|
</RouterLink>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Repositories -->
|
|
<section class="py-8 px-4">
|
|
<div class="container mx-auto">
|
|
<SectionHeader title="Repositories" icon="fas fa-code-branch" icon-color="text-accent-500" />
|
|
|
|
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<RepoCard v-for="repo in repositories" :key="`${repo.owner}/${repo.name}`" :repo="repo" />
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Teams -->
|
|
<section v-if="teams.length" class="py-8 px-4">
|
|
<div class="container mx-auto">
|
|
<SectionHeader title="Teams" icon="fas fa-users" icon-color="text-blue-500" />
|
|
|
|
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<TeamCard v-for="team in teams" :key="team.name" :team="team" />
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</template>
|