Skip to content

Atom-Stats

INFO

Atom-Stats是一个项目数据可视化分析平台,帮助开发团队深入了解项目健康度、协作效率和开发趋势。

项目概述

GitHub仓库: https://github.com/FightingTrip/atom-stats

在线预览: https://atom-stats.vercel.app

Atom-Stats通过收集、分析和可视化GitHub项目数据,为开发团队提供全面的项目健康度评估和数据洞察。无论是个人开发者想要了解自己的项目状况,还是团队负责人需要评估团队协作效率,Atom-Stats都能提供直观、有价值的数据分析。

核心功能

仪表盘与概览

  • 项目健康度评分: 基于活跃度、代码质量、响应时间等指标的综合评分
  • 核心指标卡片: 直观展示PR合并率、问题解决率、代码提交频率等关键指标
  • 趋势分析: 项目活跃度、贡献者数量、代码量等关键指标的历史趋势
  • 团队概览: 团队组成、核心贡献者和贡献分布分析

代码贡献分析

  • 提交热力图: 直观展示代码提交时间分布
  • 代码变更统计: 按文件类型、目录结构分析代码增减情况
  • 贡献者排行: 基于提交次数、代码行数等维度的贡献者排名
  • 代码审查效率: PR审查时长、评论数量、修改频率等指标分析

问题与PR分析

  • 问题生命周期: 跟踪问题从创建到关闭的完整流程
  • 解决效率: 平均解决时间、首次响应时间等效率指标
  • 分类统计: 按标签、优先级、模块等维度统计问题分布
  • PR合并趋势: PR数量、合并率、反馈速度等指标的历史变化

团队协作分析

  • 协作网络图: 可视化团队成员间的协作关系
  • 代码评审矩阵: 展示团队成员间的代码评审频率和质量
  • 知识分布: 分析团队在不同模块和文件上的专业知识分布
  • 工作负载: 评估团队成员的工作负载平衡情况

技术架构

前端技术栈

  • 框架: Vue.js 3 + TypeScript
  • 状态管理: Pinia
  • UI框架: Naive UI
  • 数据可视化: ECharts + D3.js
  • HTTP客户端: Axios + VueQuery
  • 路由管理: Vue Router

后端技术栈

  • 服务框架: Node.js + Fastify
  • 数据库: MongoDB + Redis
  • 认证: Auth0 + JWT
  • API集成: GitHub API + GitLab API
  • 数据处理: Apache Spark (大数据分析)
  • 缓存: Redis + Node-Cache

部署与DevOps

  • 服务托管: Vercel (前端) + Heroku (后端)
  • CI/CD: GitHub Actions
  • 监控: Sentry + New Relic
  • 数据备份: MongoDB Atlas
  • 安全扫描: SonarQube + Snyk

系统架构图

mermaid
graph TD
    Client[用户浏览器] --> Frontend[前端应用]
    Frontend --> API[API网关]
    API --> AuthService[认证服务]
    API --> DataCollector[数据收集器]
    API --> AnalyticsService[分析服务]
    API --> VisualizationService[可视化服务]
    
    DataCollector --> GitHubAPI[GitHub API]
    DataCollector --> GitLabAPI[GitLab API]
    DataCollector --> JiraAPI[Jira API]
    
    DataCollector --> DataProcessor[数据处理器]
    DataProcessor --> DataStorage[数据存储]
    
    AnalyticsService --> DataStorage
    VisualizationService --> AnalyticsService
    
    subgraph 数据存储
        MongoDB[(MongoDB)]
        Redis[(Redis)]
        TimeSeries[(时序数据库)]
    end
    
    DataStorage --> MongoDB
    DataStorage --> Redis
    DataStorage --> TimeSeries

项目进展

已完成功能

  • [x] GitHub项目数据集成
  • [x] 基础仪表盘与数据可视化
  • [x] 代码提交分析
  • [x] 问题跟踪分析
  • [x] 团队贡献统计
  • [x] 多项目对比

正在开发

  • [ ] GitLab项目支持
  • [ ] 高级团队协作网络分析
  • [ ] 预测分析与趋势预测
  • [ ] 自定义报表生成
  • [ ] API集成与webhook支持

未来规划

  • 集成更多代码托管平台 (Bitbucket, Gitee等)
  • 添加智能推荐系统,提供改进建议
  • 开发项目健康度预警系统
  • 支持更复杂的多维数据分析
  • 提供可嵌入的数据组件,用于团队站点

技术亮点

高性能数据处理管道

我们设计了高性能的数据处理管道,能够处理大量的Git提交和项目数据:

typescript
// dataProcessor.ts
export class GitDataProcessor {
  private readonly batchSize = 100;
  
  constructor(
    private readonly dataSource: DataSource,
    private readonly storage: DataStorage,
    private readonly logger: Logger
  ) {}
  
  async processRepository(repoUrl: string): Promise<ProcessResult> {
    this.logger.info(`开始处理仓库数据: ${repoUrl}`);
    
    // 1. 获取基本仓库信息
    const repoInfo = await this.dataSource.getRepositoryInfo(repoUrl);
    
    // 2. 并行获取并处理不同类型的数据
    const [commits, issues, pullRequests] = await Promise.all([
      this.processCommits(repoUrl),
      this.processIssues(repoUrl),
      this.processPullRequests(repoUrl)
    ]);
    
    // 3. 计算派生指标
    const metrics = this.calculateMetrics({
      commits,
      issues,
      pullRequests,
      repoInfo
    });
    
    // 4. 存储处理后的数据
    await this.storage.saveRepositoryData(repoUrl, {
      repoInfo,
      commits,
      issues,
      pullRequests,
      metrics,
      lastUpdated: new Date()
    });
    
    this.logger.info(`仓库数据处理完成: ${repoUrl}`);
    
    return {
      success: true,
      repoUrl,
      metrics,
      lastUpdated: new Date()
    };
  }
  
  private async processCommits(repoUrl: string): Promise<CommitData[]> {
    let page = 1;
    let allCommits: CommitData[] = [];
    let batch: CommitData[];
    
    // 分批获取提交数据,避免内存溢出
    do {
      batch = await this.dataSource.getCommits(repoUrl, {
        page,
        perPage: this.batchSize
      });
      
      // 并行处理每个提交的详细信息
      const processedBatch = await Promise.all(
        batch.map(commit => this.enrichCommitData(commit))
      );
      
      allCommits = [...allCommits, ...processedBatch];
      page++;
    } while (batch.length === this.batchSize);
    
    return allCommits;
  }
  
  private async enrichCommitData(commit: CommitData): Promise<CommitData> {
    // 获取提交的文件变更详情
    const changes = await this.dataSource.getCommitChanges(commit.id);
    
    // 分析代码变更
    const codeAnalysis = this.analyzeCodeChanges(changes);
    
    return {
      ...commit,
      changes,
      analysis: codeAnalysis
    };
  }
  
  // 其他数据处理方法...
}

高级可视化组件

我们开发了复杂的可视化组件,如这个团队协作网络图:

typescript
// CollaborationNetwork.vue
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue';
import * as d3 from 'd3';
import { useCollaborationData } from '@/composables/useCollaborationData';

const props = defineProps<{
  repositoryId: string;
  timeRange: [Date, Date];
}>();

const svgRef = ref<SVGElement | null>(null);
const { data, loading, error } = useCollaborationData(
  props.repositoryId, 
  props.timeRange
);

// 图表配置
const width = 800;
const height = 600;
const nodeRadius = 10;

// 创建力导向图
function createForceGraph() {
  if (!svgRef.value || !data.value) return;
  
  const svg = d3.select(svgRef.value);
  svg.selectAll("*").remove();
  
  const { nodes, links } = data.value;
  
  // 创建力导向模拟
  const simulation = d3.forceSimulation(nodes)
    .force("link", d3.forceLink(links).id(d => d.id).distance(100))
    .force("charge", d3.forceManyBody().strength(-300))
    .force("center", d3.forceCenter(width / 2, height / 2))
    .force("collide", d3.forceCollide().radius(nodeRadius * 2));
  
  // 绘制链接
  const link = svg.append("g")
    .selectAll("line")
    .data(links)
    .enter()
    .append("line")
    .attr("stroke-width", d => Math.sqrt(d.value))
    .attr("stroke", "#999")
    .attr("stroke-opacity", 0.6);
  
  // 创建节点容器
  const nodeGroup = svg.append("g")
    .selectAll("g")
    .data(nodes)
    .enter()
    .append("g")
    .call(d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended));
  
  // 绘制节点
  nodeGroup.append("circle")
    .attr("r", d => nodeRadius + d.contributions / 10)
    .attr("fill", d => d.type === 'contributor' ? '#36a2eb' : '#ff6384')
    .attr("stroke", "#fff")
    .attr("stroke-width", 1.5);
  
  // 添加节点标签
  nodeGroup.append("text")
    .attr("dx", 12)
    .attr("dy", ".35em")
    .text(d => d.name)
    .style("font-size", "12px");
  
  // 更新模拟
  simulation.on("tick", () => {
    link
      .attr("x1", d => d.source.x)
      .attr("y1", d => d.source.y)
      .attr("x2", d => d.target.x)
      .attr("y2", d => d.target.y);
    
    nodeGroup.attr("transform", d => `translate(${d.x}, ${d.y})`);
  });
  
  // 拖拽行为
  function dragstarted(event) {
    if (!event.active) simulation.alphaTarget(0.3).restart();
    event.subject.fx = event.subject.x;
    event.subject.fy = event.subject.y;
  }
  
  function dragged(event) {
    event.subject.fx = event.x;
    event.subject.fy = event.y;
  }
  
  function dragended(event) {
    if (!event.active) simulation.alphaTarget(0);
    event.subject.fx = null;
    event.subject.fy = null;
  }
}

onMounted(() => {
  if (data.value) {
    createForceGraph();
  }
});

watch(
  [() => data.value, () => props.timeRange],
  () => {
    if (data.value) {
      createForceGraph();
    }
  }
);
</script>

<template>
  <div class="collaboration-network">
    <div v-if="loading" class="loading">
      加载数据中...
    </div>
    <div v-else-if="error" class="error">
      {{ error.message }}
    </div>
    <svg ref="svgRef" :width="width" :height="height"></svg>
  </div>
</template>

参与贡献

Atom-Stats是一个开源项目,我们欢迎各种形式的贡献:

联系方式

如果您对项目有任何问题或建议,欢迎通过以下方式联系我们:


帮助我们改进

使用过程中有任何建议或遇到问题,都可以通过GitHub Issues反馈给我们。您的反馈是我们不断改进的动力!

One Day We Will All Be Famous!